diff options
Diffstat (limited to 'src/view.c')
| -rw-r--r-- | src/view.c | 180 |
1 files changed, 145 insertions, 35 deletions
@@ -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 |
