aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/view.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/view.c')
-rw-r--r--src/view.c180
1 files changed, 145 insertions, 35 deletions
diff --git a/src/view.c b/src/view.c
index 71d74e2..a522d1c 100644
--- a/src/view.c
+++ b/src/view.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -145,11 +145,18 @@ teco_view_setup(teco_view_t *ctx)
TECO_DEFINE_UNDO_CALL(teco_view_ssm, teco_view_t *, unsigned int, uptr_t, sptr_t);
-/** @memberof teco_view_t */
+/**
+ * Configure typical TECO representations for control characters.
+ *
+ * You may have to SCI_SETVIEWEOL(TRUE) to see the CR and LF characters.
+ * In order to see the TAB character use SCI_SETTABDRAWMODE(SCTD_CONTROLCHAR).
+ *
+ * @memberof teco_view_t
+ */
void
teco_view_set_representations(teco_view_t *ctx)
{
- static const char *reps[] = {
+ static const gchar reps[][4] = {
"^@", "^A", "^B", "^C", "^D", "^E", "^F", "^G",
"^H", "TAB" /* ^I */, "LF" /* ^J */, "^K", "^L", "CR" /* ^M */, "^N", "^O",
"^P", "^Q", "^R", "^S", "^T", "^U", "^V", "^W",
@@ -198,16 +205,22 @@ teco_view_set_representations(teco_view_t *ctx)
*
* @param ctx The view to load.
* @param channel Channel to read from.
+ * @param clear Whether to completely replace document
+ * (leaving dot at the beginning of the document) or insert at dot
+ * (leaving dot at the end of the insertion).
* @param error A GError.
* @return FALSE in case of a GError.
*
* @memberof teco_view_t
*/
gboolean
-teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel, GError **error)
+teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel,
+ gboolean clear, GError **error)
{
gboolean ret = TRUE;
+ unsigned int message = SCI_ADDTEXT;
+
g_auto(teco_eol_reader_t) reader;
teco_eol_reader_init_gio(&reader, channel);
@@ -225,22 +238,27 @@ teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel, GError **erro
SC_LINECHARACTERINDEX_UTF32, 0);
teco_view_ssm(ctx, SCI_BEGINUNDOACTION, 0, 0);
- teco_view_ssm(ctx, SCI_CLEARALL, 0, 0);
+ if (clear) {
+ teco_view_ssm(ctx, SCI_CLEARALL, 0, 0);
- /*
- * Preallocate memory based on the file size.
- * May waste a few bytes if file contains DOS EOLs
- * and EOL translation is enabled, but is faster.
- * NOTE: g_io_channel_unix_get_fd() should report the correct fd
- * on Windows, too.
- */
- struct stat stat_buf = {.st_size = 0};
- if (!fstat(g_io_channel_unix_get_fd(channel), &stat_buf) &&
- stat_buf.st_size > 0) {
- ret = teco_memory_check(stat_buf.st_size, error);
- if (!ret)
- goto cleanup;
- teco_view_ssm(ctx, SCI_ALLOCATE, stat_buf.st_size, 0);
+ /*
+ * Preallocate memory based on the file size.
+ * May waste a few bytes if file contains DOS EOLs
+ * and EOL translation is enabled, but is faster.
+ * NOTE: g_io_channel_unix_get_fd() should report the correct fd
+ * on Windows, too.
+ */
+ struct stat stat_buf = {.st_size = 0};
+ if (!fstat(g_io_channel_unix_get_fd(channel), &stat_buf) &&
+ stat_buf.st_size > 0) {
+ ret = teco_memory_check(stat_buf.st_size, error);
+ if (!ret)
+ goto cleanup;
+ teco_view_ssm(ctx, SCI_ALLOCATE, stat_buf.st_size, 0);
+ }
+
+ /* keep dot at beginning of document */
+ message = SCI_APPENDTEXT;
}
for (;;) {
@@ -258,7 +276,7 @@ teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel, GError **erro
if (rc == G_IO_STATUS_EOF)
break;
- teco_view_ssm(ctx, SCI_APPENDTEXT, str.len, (sptr_t)str.data);
+ teco_view_ssm(ctx, message, str.len, (sptr_t)str.data);
/*
* Even if we checked initially, knowing the file size,
@@ -285,7 +303,7 @@ teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel, GError **erro
* If it is enabled but the stream does not contain any
* EOL characters, the platform default is still assumed.
*/
- if (reader.eol_style >= 0)
+ if (clear && reader.eol_style >= 0)
teco_view_ssm(ctx, SCI_SETEOLMODE, reader.eol_style, 0);
if (reader.eol_style_inconsistent)
@@ -303,12 +321,21 @@ cleanup:
}
/**
- * Load view's document from file.
+ * Load file into view's document.
+ *
+ * @param ctx The view to load.
+ * @param filename File name to read
+ * @param clear Whether to completely replace document
+ * (leaving dot at the beginning of the document) or insert at dot
+ * (leaving dot at the end of the insertion).
+ * @param error A GError.
+ * @return FALSE in case of a GError.
*
* @memberof teco_view_t
*/
gboolean
-teco_view_load_from_file(teco_view_t *ctx, const gchar *filename, GError **error)
+teco_view_load_from_file(teco_view_t *ctx, const gchar *filename,
+ gboolean clear, GError **error)
{
g_autoptr(GIOChannel) channel = g_io_channel_new_file(filename, "r", error);
if (!channel)
@@ -322,7 +349,7 @@ teco_view_load_from_file(teco_view_t *ctx, const gchar *filename, GError **error
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, FALSE);
- if (!teco_view_load_from_channel(ctx, channel, error)) {
+ if (!teco_view_load_from_channel(ctx, channel, clear, error)) {
g_prefix_error(error, "Error reading file \"%s\": ", filename);
return FALSE;
}
@@ -330,6 +357,44 @@ teco_view_load_from_file(teco_view_t *ctx, const gchar *filename, GError **error
return TRUE;
}
+/**
+ * Load stdin until EOF into view's document.
+ *
+ * @param ctx The view to load.
+ * @param clear Whether to completely replace document
+ * (leaving dot at the beginning of the document) or insert at dot
+ * (leaving dot at the end of the insertion).
+ * @param error A GError.
+ * @return FALSE in case of a GError.
+ *
+ * @memberof teco_view_t
+ */
+gboolean
+teco_view_load_from_stdin(teco_view_t *ctx, gboolean clear, GError **error)
+{
+#ifdef G_OS_WIN32
+ g_autoptr(GIOChannel) channel = g_io_channel_win32_new_fd(0);
+#else
+ g_autoptr(GIOChannel) channel = g_io_channel_unix_new(0);
+#endif
+ g_assert(channel != NULL);
+
+ /*
+ * The file loading algorithm does not need buffered
+ * streams, so disabling buffering should increase
+ * performance (slightly).
+ */
+ g_io_channel_set_encoding(channel, NULL, NULL);
+ g_io_channel_set_buffered(channel, FALSE);
+
+ if (!teco_view_load_from_channel(ctx, channel, clear, error)) {
+ g_prefix_error(error, "Error reading stdin: ");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
#if 0
/*
@@ -401,13 +466,10 @@ teco_undo_restore_savepoint_push(gchar *savepoint, const gchar *filename)
static void
teco_make_savepoint(const gchar *filename)
{
- gchar savepoint_basename[FILENAME_MAX];
-
g_autofree gchar *basename = g_path_get_basename(filename);
- g_snprintf(savepoint_basename, sizeof(savepoint_basename),
- ".teco-%d-%s~", savepoint_id, basename);
g_autofree gchar *dirname = g_path_get_dirname(filename);
- gchar *savepoint = g_build_filename(dirname, savepoint_basename, NULL);
+ gchar *savepoint = g_strdup_printf("%s%c.teco-%d-%s~", dirname, G_DIR_SEPARATOR,
+ savepoint_id, basename);
if (g_rename(filename, savepoint)) {
teco_interface_msg(TECO_MSG_WARNING,
@@ -446,6 +508,13 @@ teco_undo_remove_file_push(const gchar *filename)
strcpy(ctx, filename);
}
+/**
+ * Save the view's document to the given IO channel.
+ *
+ * @note This must not emit undo tokens as it is also used by teco_ring_dump_recovery().
+ *
+ * @memberof teco_view_t
+ */
gboolean
teco_view_save_to_channel(teco_view_t *ctx, GIOChannel *channel, GError **error)
{
@@ -485,16 +554,16 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
file_stat.st_gid = -1;
#endif
teco_file_attributes_t attributes = TECO_FILE_INVALID_ATTRIBUTES;
+ gboolean undo_remove_file = FALSE;
if (teco_undo_enabled) {
- if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+ undo_remove_file = !g_file_test(filename, G_FILE_TEST_IS_REGULAR);
+ if (!undo_remove_file) {
#ifdef G_OS_UNIX
g_stat(filename, &file_stat);
#endif
attributes = teco_file_get_attributes(filename);
teco_make_savepoint(filename);
- } else {
- teco_undo_remove_file_push(filename);
}
}
@@ -503,6 +572,18 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
if (!channel)
return FALSE;
+ if (undo_remove_file) {
+ /*
+ * The file is new, so has to be removed on undo.
+ * If `filename` is a symlink, it's crucial to resolve it now,
+ * since early canonicalization may have failed (for non-existent
+ * path segments).
+ * Now, `filename` is guaranteed to exist.
+ */
+ g_autofree gchar *filename_canon = teco_file_get_absolute_path(filename);
+ teco_undo_remove_file_push(filename_canon);
+ }
+
/*
* teco_view_save_to_channel() expects a buffered and blocking channel
*/
@@ -511,6 +592,7 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
if (!teco_view_save_to_channel(ctx, channel, error)) {
g_prefix_error(error, "Error writing file \"%s\": ", filename);
+ /* file might also be removed (in interactive mode) */
return FALSE;
}
@@ -534,6 +616,31 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
return TRUE;
}
+/** @memberof teco_view_t */
+gboolean
+teco_view_save_to_stdout(teco_view_t *ctx, GError **error)
+{
+#ifdef G_OS_WIN32
+ g_autoptr(GIOChannel) channel = g_io_channel_win32_new_fd(1);
+#else
+ g_autoptr(GIOChannel) channel = g_io_channel_unix_new(1);
+#endif
+ g_assert(channel != NULL);
+
+ /*
+ * teco_view_save_to_channel() expects a buffered and blocking channel
+ */
+ g_io_channel_set_encoding(channel, NULL, NULL);
+ g_io_channel_set_buffered(channel, TRUE);
+
+ if (!teco_view_save_to_channel(ctx, channel, error)) {
+ g_prefix_error(error, "Error writing to stdout: ");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
/**
* Convert a glyph index to a byte offset as used by Scintilla.
*
@@ -628,8 +735,8 @@ teco_view_glyphs2bytes_relative(teco_view_t *ctx, gsize pos, teco_int_t n)
* @param pos The glyph's byte position
* @param len The length of the document in bytes
* @return The requested codepoint.
- * In UTF-8 encoded documents, this might be -1 (incomplete sequence)
- * or -2 (invalid byte sequence).
+ * In UTF-8 encoded documents, this might be -2 (invalid byte sequence)
+ * or -3 (incomplete sequence).
*/
teco_int_t
teco_view_get_character(teco_view_t *ctx, gsize pos, gsize len)
@@ -653,12 +760,15 @@ teco_view_get_character(teco_view_t *ctx, gsize pos, gsize len)
* or repeatedly calling SCI_GETCHARAT.
*/
teco_view_ssm(ctx, SCI_GETTEXTRANGEFULL, 0, (sptr_t)&range);
+ if (!*buf)
+ return 0;
/*
* Make sure that the -1/-2 error values are preserved.
* The sign bit in UCS-4/UTF-32 is unused, so this will even
* suffice if TECO_INTEGER == 32.
*/
- return *buf ? (gint32)g_utf8_get_char_validated(buf, -1) : 0;
+ gint32 rc = g_utf8_get_char_validated(buf, -1);
+ return rc < 0 ? rc-1 : rc;
}
void