diff options
Diffstat (limited to 'src')
70 files changed, 1006 insertions, 718 deletions
diff --git a/src/cmdline.c b/src/cmdline.c index e1a4628..b944b5e 100644 --- a/src/cmdline.c +++ b/src/cmdline.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 @@ -99,6 +99,14 @@ teco_cmdline_init(void) teco_cmdline_ssm(SCI_SETTABDRAWMODE, SCTD_CONTROLCHAR, 0); /* + * FIXME: This works around a problem where the caret is not + * scrolled in multi-line mode after it wraps at the end of the + * line. You cannot scroll the command-line with the mouse, so the + * user won't see any difference. + */ + teco_cmdline_ssm(SCI_SETENDATLASTLINE, FALSE, 0); + + /* * FIXME: Something resets the margin text, so we have to set it last. */ teco_cmdline_ssm(SCI_MARGINSETTEXT, 0, (sptr_t)"*"); @@ -128,7 +136,7 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0); if (len <= macro_len - effective_len && - !teco_string_cmp(&src, macro + effective_len, len)) { + !teco_string_cmp(src, macro + effective_len, len)) { /* extend effective command line from rubbed out part */ teco_cmdline_ssm(SCI_GOTOPOS, effective_len+len, 0); } else { @@ -162,19 +170,39 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) * Result of command line replacement (}): * Exchange command lines */ + g_clear_error(&tmp_error); + teco_qreg_t *cmdline_reg = teco_qreg_table_find(&teco_qreg_table_globals, "\e", 1); g_auto(teco_string_t) new_cmdline = {NULL, 0}; if (!cmdline_reg->vtable->get_string(cmdline_reg, &new_cmdline.data, &new_cmdline.len, - NULL, error)) + NULL, &tmp_error)) { + teco_error_add_frame_toplevel(); + teco_error_display_short(tmp_error); + g_propagate_error(error, g_steal_pointer(&tmp_error)); + return FALSE; + } + + /* + * SciTECO code must always be UTF-8, but you can smuggle arbitrary bytes + * into the "\e" register. + * This would be cumbersome to test for in teco_state_start_cmdline_pop(). + */ + if (!teco_string_validate_utf8(new_cmdline)) { + g_set_error_literal(&tmp_error, TECO_ERROR, TECO_ERROR_CODEPOINT, + "Invalid UTF-8 byte sequence in command-line replacement"); + teco_error_add_frame_toplevel(); + teco_error_display_short(tmp_error); + g_propagate_error(error, g_steal_pointer(&tmp_error)); return FALSE; + } /* * Search for first differing character in old and * new command line. This avoids unnecessary rubouts * and insertions when the command line is updated. */ - teco_cmdline.pc = teco_string_diff(&new_cmdline, macro, effective_len); + teco_cmdline.pc = teco_string_diff(new_cmdline, macro, effective_len); teco_undo_pop(teco_cmdline.pc); @@ -229,7 +257,7 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) } } - /* error is handled in teco_cmdline_keypress_c() */ + /* error is handled in teco_cmdline_keypress() */ g_propagate_error(error, g_steal_pointer(&tmp_error)); return FALSE; } @@ -275,10 +303,9 @@ teco_cmdline_rubin(GError **error) gboolean teco_cmdline_keypress(const gchar *data, gsize len, GError **error) { - const teco_string_t str = {(gchar *)data, len}; teco_machine_t *machine = &teco_cmdline.machine.parent; - if (!teco_string_validate_utf8(&str)) { + if (!teco_string_validate_utf8((teco_string_t){(gchar *)data, len})) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT, "Invalid UTF-8 sequence"); return FALSE; @@ -694,14 +721,14 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * /* reinsert word chars */ while (ctx->parent.current == current && teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && - teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) + teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) if (!teco_cmdline_rubin(error)) return FALSE; /* reinsert non-word chars */ while (ctx->parent.current == current && teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len && - !teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) + !teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)])) if (!teco_cmdline_rubin(error)) return FALSE; @@ -715,7 +742,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * * a result string even in parse-only mode. */ if (ctx->result && ctx->result->len > 0) { - gboolean is_wordchar = teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]); + gboolean is_wordchar = teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]); teco_cmdline_rubout(); if (ctx->parent.current != current) { /* rub out string building command */ @@ -731,13 +758,13 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * */ if (!is_wordchar) { while (ctx->result->len > 0 && - !teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) + !teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) teco_cmdline_rubout(); } /* rubout word chars */ while (ctx->result->len > 0 && - teco_string_contains(&wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) + teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1])) teco_cmdline_rubout(); return TRUE; @@ -792,7 +819,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * return TRUE; } - const gchar *filename = teco_string_last_occurrence(ctx->result, + const gchar *filename = teco_string_last_occurrence(*ctx->result, TECO_DEFAULT_BREAK_CHARS); g_auto(teco_string_t) new_chars, new_chars_escaped; gboolean unambiguous = teco_file_auto_complete(filename, G_FILE_TEST_EXISTS, &new_chars); @@ -823,11 +850,11 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t * } gboolean -teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, const teco_string_t *str, GError **error) +teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, teco_string_t str, GError **error) { g_auto(teco_string_t) str_escaped; - teco_machine_stringbuilding_escape(ctx, str->data, str->len, &str_escaped); - if (!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1])) + teco_machine_stringbuilding_escape(ctx, str.data, str.len, &str_escaped); + if (!str.len || !G_IS_DIR_SEPARATOR(str.data[str.len-1])) teco_string_append_c(&str_escaped, ' '); return teco_cmdline_insert(str_escaped.data, str_escaped.len, error); } @@ -872,7 +899,7 @@ teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_ } gboolean -teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine; teco_state_t *stringbuilding_current = stringbuilding_ctx->parent.current; @@ -981,7 +1008,7 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t return TRUE; } - if (teco_string_contains(&ctx->expectstring.string, '\0')) + if (teco_string_contains(ctx->expectstring.string, '\0')) /* null-byte not allowed in file names */ return TRUE; @@ -1000,13 +1027,13 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t } gboolean -teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine; g_auto(teco_string_t) str_escaped; - teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped); - if ((!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1])) && + teco_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped); + if ((!str.len || !G_IS_DIR_SEPARATOR(str.data[str.len-1])) && ctx->expectstring.nesting == 1) teco_string_append_wc(&str_escaped, ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char); @@ -1037,7 +1064,7 @@ teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t return TRUE; } - if (teco_string_contains(&ctx->expectstring.string, '\0')) + if (teco_string_contains(ctx->expectstring.string, '\0')) /* null-byte not allowed in file names */ return TRUE; @@ -1068,14 +1095,14 @@ teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t } gboolean -teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine; - g_autofree gchar *pattern_escaped = teco_globber_escape_pattern(str->data); + g_autofree gchar *pattern_escaped = teco_globber_escape_pattern(str.data); g_auto(teco_string_t) str_escaped; teco_machine_stringbuilding_escape(stringbuilding_ctx, pattern_escaped, strlen(pattern_escaped), &str_escaped); - if ((!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1])) && + if ((!str.len || !G_IS_DIR_SEPARATOR(str.data[str.len-1])) && ctx->expectstring.nesting == 1) teco_string_append_wc(&str_escaped, ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char); @@ -1106,7 +1133,7 @@ teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t * return TRUE; } - if (teco_string_contains(&ctx->expectstring.string, '\0')) + if (teco_string_contains(ctx->expectstring.string, '\0')) /* null-byte not allowed in file names */ return TRUE; @@ -1126,7 +1153,7 @@ teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t * } gboolean -teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine; @@ -1134,7 +1161,7 @@ teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_stri * FIXME: We might terminate the command in case of leaf directories. */ g_auto(teco_string_t) str_escaped; - teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped); + teco_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped); return teco_cmdline_insert(str_escaped.data, str_escaped.len, error); } @@ -1151,7 +1178,7 @@ teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t } gboolean -teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { g_assert(ctx->expectqreg != NULL); /* @@ -1202,9 +1229,9 @@ teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_ } gboolean -teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, GError **error) +teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, teco_string_t str, GError **error) { - return teco_cmdline_insert(str->data, str->len, error); + return teco_cmdline_insert(str.data, str.len, error); } gboolean @@ -1248,12 +1275,12 @@ teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_m } gboolean -teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, GError **error) +teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = teco_machine_qregspec_get_stringbuilding(ctx); g_auto(teco_string_t) str_escaped; - teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped); + teco_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped); teco_string_append_c(&str_escaped, ']'); return teco_cmdline_insert(str_escaped.data, str_escaped.len, error); } @@ -1288,7 +1315,7 @@ teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa return TRUE; } - const gchar *filename = teco_string_last_occurrence(&ctx->expectstring.string, + const gchar *filename = teco_string_last_occurrence(ctx->expectstring.string, TECO_DEFAULT_BREAK_CHARS); g_auto(teco_string_t) new_chars, new_chars_escaped; gboolean unambiguous = teco_file_auto_complete(filename, G_FILE_TEST_EXISTS, &new_chars); @@ -1327,7 +1354,7 @@ teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_mac return TRUE; } - const gchar *symbol = teco_string_last_occurrence(&ctx->expectstring.string, ","); + const gchar *symbol = teco_string_last_occurrence(ctx->expectstring.string, ","); teco_symbol_list_t *list = symbol == ctx->expectstring.string.data ? &teco_symbol_list_scintilla : &teco_symbol_list_scilexer; @@ -1349,12 +1376,12 @@ teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_mac } gboolean -teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine; g_auto(teco_string_t) str_escaped; - teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped); + teco_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped); teco_string_append_c(&str_escaped, ','); return teco_cmdline_insert(str_escaped.data, str_escaped.len, error); } @@ -1384,7 +1411,7 @@ teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren } teco_string_t label = ctx->expectstring.string; - gint i = teco_string_rindex(&label, ','); + gint i = teco_string_rindex(label, ','); if (i >= 0) { label.data += i+1; label.len -= i+1; @@ -1407,12 +1434,12 @@ teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren } gboolean -teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_goto_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine; g_auto(teco_string_t) str_escaped; - teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped); + teco_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped); /* * FIXME: This does not escape `,`. Cannot be escaped via ^Q currently? */ @@ -1444,7 +1471,7 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren return TRUE; } - if (teco_string_contains(&ctx->expectstring.string, '\0')) + if (teco_string_contains(ctx->expectstring.string, '\0')) /* help term must not contain null-byte */ return TRUE; @@ -1463,12 +1490,12 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren } gboolean -teco_state_help_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_help_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine; g_auto(teco_string_t) str_escaped; - teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped); + teco_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped); if (ctx->expectstring.nesting == 1) teco_string_append_wc(&str_escaped, ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char); @@ -1504,5 +1531,6 @@ teco_state_save_cmdline_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg * Q-Register <q>. */ TECO_DEFINE_STATE_EXPECTQREG(teco_state_save_cmdline, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_save_cmdline_got_register ); diff --git a/src/cmdline.h b/src/cmdline.h index 9123358..f6d0345 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -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 @@ -120,4 +120,4 @@ void teco_cmdline_cleanup(void); * Command states */ -TECO_DECLARE_STATE(teco_state_save_cmdline); +extern teco_state_t teco_state_save_cmdline; diff --git a/src/core-commands.c b/src/core-commands.c index e756eab..cd9a8fa 100644 --- a/src/core-commands.c +++ b/src/core-commands.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 @@ -643,7 +643,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) ['"'] = {&teco_state_condcommand}, ['E'] = {&teco_state_ecommand, .modifier_at = TRUE, .modifier_colon = 2}, - ['I'] = {&teco_state_insert_plain, + ['I'] = {&teco_state_insert, .modifier_at = TRUE}, ['?'] = {&teco_state_help, .modifier_at = TRUE}, @@ -915,7 +915,9 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) teco_ascii_toupper(chr), error); } -TECO_DEFINE_STATE_START(teco_state_start); +TECO_DEFINE_STATE_START(teco_state_start, + .input_cb = (teco_state_input_cb_t)teco_state_start_input +); /*$ "F<" ":F<" * F< -- Go to loop start or jump to beginning of macro @@ -1080,7 +1082,9 @@ teco_state_fcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error teco_ascii_toupper(chr), error); } -TECO_DEFINE_STATE_COMMAND(teco_state_fcommand); +TECO_DEFINE_STATE_COMMAND(teco_state_fcommand, + .input_cb = (teco_state_input_cb_t)teco_state_fcommand_input +); static void teco_undo_change_dir_action(gchar **dir, gboolean run) @@ -1104,12 +1108,12 @@ teco_undo_change_dir_to_current(void) } static teco_state_t * -teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_changedir_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - g_autofree gchar *dir = teco_file_expand_path(str->data); + g_autofree gchar *dir = teco_file_expand_path(str.data); if (!*dir) { teco_qreg_t *qreg = teco_qreg_table_find(&teco_qreg_table_globals, "$HOME", 5); g_assert(qreg != NULL); @@ -1120,7 +1124,7 @@ teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GE /* * Null-characters must not occur in file names. */ - if (teco_string_contains(&home, '\0')) { + if (teco_string_contains(home, '\0')) { teco_string_clear(&home); g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Null-character not allowed in filenames"); @@ -1172,7 +1176,9 @@ teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GE * String-building characters are enabled on this * command and directories can be tab-completed. */ -TECO_DEFINE_STATE_EXPECTDIR(teco_state_changedir); +TECO_DEFINE_STATE_EXPECTDIR(teco_state_changedir, + .expectstring.done_cb = teco_state_changedir_done +); static teco_state_t * teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error) @@ -1287,7 +1293,8 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er } TECO_DEFINE_STATE_COMMAND(teco_state_condcommand, - .style = SCE_SCITECO_OPERATOR + .style = SCE_SCITECO_OPERATOR, + .input_cb = (teco_state_input_cb_t)teco_state_condcommand_input ); /*$ ^_ negate @@ -1701,7 +1708,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) teco_ascii_toupper(chr), error); } -TECO_DEFINE_STATE_COMMAND(teco_state_control); +TECO_DEFINE_STATE_COMMAND(teco_state_control, + .input_cb = (teco_state_input_cb_t)teco_state_control_input +); static teco_state_t * teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error) @@ -1724,7 +1733,9 @@ teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error) * Note that this command can be typed CTRL+Caret or * Caret-Caret. */ -TECO_DEFINE_STATE(teco_state_ascii); +TECO_DEFINE_STATE(teco_state_ascii, + .input_cb = (teco_state_input_cb_t)teco_state_ascii_input +); /*$ ^[^[ ^[$ $$ ^C terminate return * [a1,a2,...]$$ -- Terminate command line or return from macro @@ -1785,7 +1796,7 @@ teco_return(teco_machine_main_t *ctx, GError **error) * command line with `}` after command-line termination. */ if (G_UNLIKELY(ctx == &teco_cmdline.machine && - teco_qreg_current && !teco_string_cmp(&teco_qreg_current->head.name, "\e", 1))) { + teco_qreg_current && !teco_string_cmp(teco_qreg_current->head.name, "\e", 1))) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Not allowed to terminate command-line while " "editing command-line replacement register"); @@ -1858,6 +1869,7 @@ teco_state_escape_end_of_macro(teco_machine_t *ctx, GError **error) } TECO_DEFINE_STATE_START(teco_state_escape, + .input_cb = (teco_state_input_cb_t)teco_state_escape_input, .end_of_macro_cb = teco_state_escape_end_of_macro ); @@ -1892,7 +1904,8 @@ teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error) } TECO_DEFINE_STATE_START(teco_state_ctlc, - .initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial + .initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial, + .input_cb = (teco_state_input_cb_t)teco_state_ctlc_input ); static teco_state_t * @@ -1947,7 +1960,9 @@ teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **e * This state is necessary, so that you can type ^C^C exclusively with carets. * Otherwise it would be very cumbersome to cause exits with ASCII characters only. */ -TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control); +TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control, + .input_cb = (teco_state_input_cb_t)teco_state_ctlc_control_input +); /*$ ED flags * flags ED -- Set and get ED-flags @@ -1968,7 +1983,7 @@ TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control); * Without any argument ED returns the current flags. * * Currently, the following flags are used by \*(ST: - * .IP 2: 5 + * .IP 2: 6 * Reflects whether program termination has been requested * by successfully performing the \fBEX\fP command. * This flag can also be used to cancel the effect of any @@ -2843,7 +2858,9 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error teco_ascii_toupper(chr), error); } -TECO_DEFINE_STATE_COMMAND(teco_state_ecommand); +TECO_DEFINE_STATE_COMMAND(teco_state_ecommand, + .input_cb = (teco_state_input_cb_t)teco_state_ecommand_input +); gboolean teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) @@ -2913,14 +2930,14 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) } gboolean -teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str, +teco_state_insert_process(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error) { g_assert(new_chars > 0); teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); teco_interface_ssm(SCI_ADDTEXT, new_chars, - (sptr_t)(str->data + str->len - new_chars)); + (sptr_t)(str.data + str.len - new_chars)); teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); teco_ring_dirtify(); @@ -2931,7 +2948,7 @@ teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str, } teco_state_t * -teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -2962,7 +2979,7 @@ teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro * may be better, since it has string building characters * disabled. */ -TECO_DEFINE_STATE_INSERT(teco_state_insert_plain); +TECO_DEFINE_STATE_INSERT(teco_state_insert); static gboolean teco_state_insert_indent_initial(teco_machine_main_t *ctx, GError **error) diff --git a/src/core-commands.h b/src/core-commands.h index 4cc8747..254c4a7 100644 --- a/src/core-commands.h +++ b/src/core-commands.h @@ -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 @@ -55,15 +55,15 @@ gboolean teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_mach * FIXME: Most of these states can probably be private/static * as they are only referenced from teco_state_start. */ -TECO_DECLARE_STATE(teco_state_fcommand); +extern teco_state_t teco_state_fcommand; void teco_undo_change_dir_to_current(void); -TECO_DECLARE_STATE(teco_state_changedir); +extern teco_state_t teco_state_changedir; -TECO_DECLARE_STATE(teco_state_condcommand); -TECO_DECLARE_STATE(teco_state_control); -TECO_DECLARE_STATE(teco_state_ascii); -TECO_DECLARE_STATE(teco_state_ecommand); +extern teco_state_t teco_state_condcommand; +extern teco_state_t teco_state_control; +extern teco_state_t teco_state_ascii; +extern teco_state_t teco_state_ecommand; typedef struct { teco_int_t from; /*< start position in glyphs */ @@ -74,9 +74,9 @@ extern guint teco_ranges_count; extern teco_range_t *teco_ranges; gboolean teco_state_insert_initial(teco_machine_main_t *ctx, GError **error); -gboolean teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str, +gboolean teco_state_insert_process(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error); -teco_state_t *teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error); +teco_state_t *teco_state_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **error); /* in cmdline.c */ gboolean teco_state_insert_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, @@ -88,23 +88,18 @@ gboolean teco_state_insert_process_edit_cmd(teco_machine_main_t *ctx, teco_machi * @ingroup states * * @note Also serves as a base class of the replace-insertion commands. - * @fixme Generating the done_cb could be avoided if there simply were a default. */ #define TECO_DEFINE_STATE_INSERT(NAME, ...) \ - static teco_state_t * \ - NAME##_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) \ - { \ - return teco_state_insert_done(ctx, str, error); \ - } \ TECO_DEFINE_STATE_EXPECTSTRING(NAME, \ .initial_cb = (teco_state_initial_cb_t)teco_state_insert_initial, \ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_insert_process_edit_cmd, \ .expectstring.process_cb = teco_state_insert_process, \ + .expectstring.done_cb = teco_state_insert_done, \ ##__VA_ARGS__ \ ) -TECO_DECLARE_STATE(teco_state_insert_plain); -TECO_DECLARE_STATE(teco_state_insert_indent); +extern teco_state_t teco_state_insert; +extern teco_state_t teco_state_insert_indent; /** * @class TECO_DEFINE_STATE_START @@ -124,8 +119,8 @@ TECO_DECLARE_STATE(teco_state_insert_indent); teco_state_t *teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error); -TECO_DECLARE_STATE(teco_state_start); -TECO_DECLARE_STATE(teco_state_control); -TECO_DECLARE_STATE(teco_state_escape); -TECO_DECLARE_STATE(teco_state_ctlc); -TECO_DECLARE_STATE(teco_state_ctlc_control); +extern teco_state_t teco_state_start; +extern teco_state_t teco_state_control; +extern teco_state_t teco_state_escape; +extern teco_state_t teco_state_ctlc; +extern teco_state_t teco_state_ctlc_control; @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/src/error.c b/src/error.c index 89f0f72..716b60b 100644 --- a/src/error.c +++ b/src/error.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 diff --git a/src/error.h b/src/error.h index 2a12733..67de4aa 100644 --- a/src/error.h +++ b/src/error.h @@ -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 diff --git a/src/expressions.c b/src/expressions.c index 2fbb659..9561c46 100644 --- a/src/expressions.c +++ b/src/expressions.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 diff --git a/src/expressions.h b/src/expressions.h index 631c867..3ef0faf 100644 --- a/src/expressions.h +++ b/src/expressions.h @@ -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 diff --git a/src/file-utils.c b/src/file-utils.c index db06ff3..7c37b27 100644 --- a/src/file-utils.c +++ b/src/file-utils.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 @@ -360,7 +360,7 @@ teco_file_expand_path(const gchar *path) */ g_auto(teco_string_t) home = {NULL, 0}; if (!qreg->vtable->get_string(qreg, &home.data, &home.len, NULL, NULL) || - teco_string_contains(&home, '\0')) + teco_string_contains(home, '\0')) return g_strdup(path); g_assert(home.data != NULL); @@ -426,7 +426,7 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_ while ((cur_basename.data = (gchar *)g_dir_read_name(dir))) { cur_basename.len = strlen(cur_basename.data); - if (string_diff(&cur_basename, basename, basename_len) != basename_len) + if (string_diff(cur_basename, basename, basename_len) != basename_len) /* basename is not a prefix of cur_basename */ continue; @@ -460,7 +460,7 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_ other_file.data = (gchar *)g_slist_next(files)->data + filename_len; other_file.len = strlen(other_file.data); - gsize len = string_diff(&other_file, cur_filename + filename_len, + gsize len = string_diff(other_file, cur_filename + filename_len, strlen(cur_filename) - filename_len); if (len < prefix_len) prefix_len = len; diff --git a/src/file-utils.h b/src/file-utils.h index 9a2f8d6..11d6650 100644 --- a/src/file-utils.h +++ b/src/file-utils.h @@ -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 @@ -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 @@ -38,10 +38,7 @@ #include "undo.h" #include "glob.h" -/* - * FIXME: This state could be static. - */ -TECO_DECLARE_STATE(teco_state_glob_filename); +static teco_state_t teco_state_glob_filename; /** @memberof teco_globber_t */ void @@ -308,13 +305,13 @@ teco_globber_compile_pattern(const gchar *pattern) */ static teco_state_t * -teco_state_glob_pattern_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_glob_pattern_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_glob_filename; - if (str->len > 0) { - g_autofree gchar *filename = teco_file_expand_path(str->data); + if (str.len > 0) { + g_autofree gchar *filename = teco_file_expand_path(str.data); teco_qreg_t *glob_reg = teco_qreg_table_find(&teco_qreg_table_globals, "_", 1); g_assert(glob_reg != NULL); @@ -454,11 +451,12 @@ teco_state_glob_pattern_done(teco_machine_main_t *ctx, const teco_string_t *str, * have to edit that register anyway. */ TECO_DEFINE_STATE_EXPECTGLOB(teco_state_glob_pattern, - .expectstring.last = FALSE + .expectstring.last = FALSE, + .expectstring.done_cb = teco_state_glob_pattern_done ); static teco_state_t * -teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_glob_filename_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -497,16 +495,16 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str if (!glob_reg->vtable->get_string(glob_reg, &pattern_str.data, &pattern_str.len, NULL, error)) return NULL; - if (teco_string_contains(&pattern_str, '\0')) { + if (teco_string_contains(pattern_str, '\0')) { teco_error_qregcontainsnull_set(error, "_", 1, FALSE); return NULL; } - if (str->len > 0) { + if (str.len > 0) { /* * Match pattern against provided file name */ - g_autofree gchar *filename = teco_file_expand_path(str->data); + g_autofree gchar *filename = teco_file_expand_path(str.data); g_autoptr(GRegex) pattern = teco_globber_compile_pattern(pattern_str.data); if (g_regex_match(pattern, filename, 0, NULL) && @@ -593,4 +591,6 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str return &teco_state_start; } -TECO_DEFINE_STATE_EXPECTFILE(teco_state_glob_filename); +static TECO_DEFINE_STATE_EXPECTFILE(teco_state_glob_filename, + .expectstring.done_cb = teco_state_glob_filename_done +); @@ -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 @@ -47,8 +47,9 @@ gchar *teco_globber_escape_pattern(const gchar *pattern); GRegex *teco_globber_compile_pattern(const gchar *pattern); /* in cmdline.c */ -gboolean teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error); +gboolean teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, + gunichar key, GError **error); +gboolean teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /** * @interface TECO_DEFINE_STATE_EXPECTGLOB @@ -68,4 +69,4 @@ gboolean teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const * Command states */ -TECO_DECLARE_STATE(teco_state_glob_pattern); +extern teco_state_t teco_state_glob_pattern; diff --git a/src/goto-commands.c b/src/goto-commands.c index 05d495f..a9ff3c2 100644 --- a/src/goto-commands.c +++ b/src/goto-commands.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 @@ -35,8 +35,8 @@ #include "goto.h" #include "goto-commands.h" -TECO_DECLARE_STATE(teco_state_blockcomment); -TECO_DECLARE_STATE(teco_state_eolcomment); +static teco_state_t teco_state_blockcomment; +static teco_state_t teco_state_eolcomment; /** * In TECO_MODE_PARSE_ONLY_GOTO mode, we remain in parse-only mode @@ -75,7 +75,7 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error) teco_goto_table_undo_remove(&ctx->goto_table, ctx->goto_label.data, ctx->goto_label.len); if (teco_goto_skip_label.len > 0 && - !teco_string_cmp(&ctx->goto_label, teco_goto_skip_label.data, teco_goto_skip_label.len)) { + !teco_string_cmp(ctx->goto_label, teco_goto_skip_label.data, teco_goto_skip_label.len)) { teco_undo_string_own(teco_goto_skip_label); memset(&teco_goto_skip_label, 0, sizeof(teco_goto_skip_label)); teco_undo_gssize(teco_goto_backup_pc) = -1; @@ -113,16 +113,17 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error) } TECO_DEFINE_STATE(teco_state_label, - .style = SCE_SCITECO_LABEL + .style = SCE_SCITECO_LABEL, + .input_cb = (teco_state_input_cb_t)teco_state_label_input ); static teco_state_t * -teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_goto_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - if (!str->len) { + if (!str.len) { /* you can still write @O/,/, though... */ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "No labels given for <O>"); @@ -140,9 +141,9 @@ teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError */ teco_string_t label = {NULL, 0}; while (value >= 0) { - label.data = label.data ? label.data+label.len+1 : str->data; - const gchar *p = label.data ? memchr(label.data, ',', str->len - (label.data - str->data)) : NULL; - label.len = p ? p - label.data : str->len - (label.data - str->data); + label.data = label.data ? label.data+label.len+1 : str.data; + const gchar *p = label.data ? memchr(label.data, ',', str.len - (label.data - str.data)) : NULL; + label.len = p ? p - label.data : str.len - (label.data - str.data); value--; @@ -177,7 +178,7 @@ teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError /* in cmdline.c */ gboolean teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error); -gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, +gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /*$ "O" ":O" goto @@ -221,7 +222,8 @@ gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_ */ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_goto_process_edit_cmd, - .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_goto_insert_completion + .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_goto_insert_completion, + .expectstring.done_cb = teco_state_goto_done ); /** @@ -242,28 +244,34 @@ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto, ) static teco_state_t * -teco_state_blockcomment_star_input(teco_machine_main_t *ctx, gunichar chr, GError **error) +teco_state_blockcomment_star_input(teco_machine_t *ctx, gunichar chr, GError **error) { return chr == '!' ? &teco_state_start : &teco_state_blockcomment; } -TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment_star); +static TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment_star, + .input_cb = teco_state_blockcomment_star_input +); static teco_state_t * -teco_state_blockcomment_input(teco_machine_main_t *ctx, gunichar chr, GError **error) +teco_state_blockcomment_input(teco_machine_t *ctx, gunichar chr, GError **error) { return chr == '*' ? &teco_state_blockcomment_star : &teco_state_blockcomment; } -TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment); +static TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment, + .input_cb = teco_state_blockcomment_input +); /* * `!!` line comments are inspired by TECO-64. */ static teco_state_t * -teco_state_eolcomment_input(teco_machine_main_t *ctx, gunichar chr, GError **error) +teco_state_eolcomment_input(teco_machine_t *ctx, gunichar chr, GError **error) { return chr == '\n' ? &teco_state_start : &teco_state_eolcomment; } -TECO_DEFINE_STATE_COMMENT(teco_state_eolcomment); +static TECO_DEFINE_STATE_COMMENT(teco_state_eolcomment, + .input_cb = teco_state_eolcomment_input +); diff --git a/src/goto-commands.h b/src/goto-commands.h index 45c68e9..3b44168 100644 --- a/src/goto-commands.h +++ b/src/goto-commands.h @@ -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 @@ -24,5 +24,5 @@ extern teco_string_t teco_goto_skip_label; extern gssize teco_goto_backup_pc; -TECO_DECLARE_STATE(teco_state_label); -TECO_DECLARE_STATE(teco_state_goto); +extern teco_state_t teco_state_label; +extern teco_state_t teco_state_goto; @@ -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 @@ -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 @@ -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 @@ -271,7 +271,7 @@ teco_state_help_initial(teco_machine_main_t *ctx, GError **error) } static teco_state_t * -teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_help_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -281,7 +281,7 @@ teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError "Help topic must not contain null-byte"); return NULL; } - const gchar *topic_name = str->data ? : ""; + const gchar *topic_name = str.data ? : ""; teco_help_topic_t *topic = teco_help_find(topic_name); if (!topic) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, @@ -316,7 +316,7 @@ teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError /* in cmdline.c */ gboolean teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error); -gboolean teco_state_help_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, +gboolean teco_state_help_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /*$ "?" help @@ -388,5 +388,6 @@ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_help, .initial_cb = (teco_state_initial_cb_t)teco_state_help_initial, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_help_process_edit_cmd, .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_help_insert_completion, - .expectstring.string_building = FALSE + .expectstring.string_building = FALSE, + .expectstring.done_cb = teco_state_help_done ); @@ -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 @@ -27,4 +27,4 @@ gboolean teco_help_auto_complete(const gchar *topic_name, teco_string_t *insert) * Command states */ -TECO_DECLARE_STATE(teco_state_help); +extern teco_state_t teco_state_help; diff --git a/src/interface-curses/curses-icons.c b/src/interface-curses/curses-icons.c index 7c021d6..0e14655 100644 --- a/src/interface-curses/curses-icons.c +++ b/src/interface-curses/curses-icons.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 @@ -362,6 +362,10 @@ teco_curses_icon_cmp(const void *a, const void *b) gunichar teco_curses_icons_lookup_file(const gchar *filename) { + if (!filename || !*filename) + /* "(Unnamed)" file */ + return 0xf1036; /* ó±€¶ */ + g_autofree gchar *basename = g_path_get_basename(filename); const teco_curses_icon_t *icon; diff --git a/src/interface-curses/curses-icons.h b/src/interface-curses/curses-icons.h index fce9d75..a12fe88 100644 --- a/src/interface-curses/curses-icons.h +++ b/src/interface-curses/curses-icons.h @@ -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 diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c index c51a99b..83d4665 100644 --- a/src/interface-curses/curses-info-popup.c +++ b/src/interface-curses/curses-info-popup.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 @@ -19,6 +19,8 @@ #include "config.h" #endif +#include <string.h> + #include <glib.h> #include <curses.h> @@ -38,6 +40,7 @@ typedef struct { teco_stailq_entry_t entry; teco_popup_entry_type_t type; + /** entry name or empty string for the "(Unnamed)" buffer */ teco_string_t name; gboolean highlight; } teco_popup_entry_t; @@ -70,7 +73,7 @@ teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_ } static void -teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) +teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair) { int pad_lines; /**! pad height */ gint pad_cols; /**! entry columns */ @@ -99,11 +102,11 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) ctx->pad = newpad(pad_lines, COLS - 2); /* - * NOTE: attr could contain A_REVERSE on monochrome terminals, + * NOTE: attr could contain WA_REVERSE on monochrome terminals, * so we use foreground attributes instead of background attributes. - * This way, we can cancel out the A_REVERSE if necessary. + * This way, we can cancel out the WA_REVERSE if necessary. */ - wattrset(ctx->pad, attr); + wattr_set(ctx->pad, attr, pair, NULL); teco_curses_clrtobot(ctx->pad); /* @@ -122,25 +125,32 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) if (entry->highlight) wattron(ctx->pad, A_BOLD); + teco_string_t name = entry->name; + if (!name.len) { + name.data = TECO_UNNAMED_FILE; + name.len = strlen(name.data); + } + switch (entry->type) { case TECO_POPUP_FILE: - g_assert(!teco_string_contains(&entry->name, '\0')); + g_assert(!teco_string_contains(name, '\0')); if (teco_ed & TECO_ED_ICONS) { + /* "(Unnamed)" buffer is looked up as "" */ teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_file(entry->name.data)); waddch(ctx->pad, ' '); } - teco_curses_format_filename(ctx->pad, entry->name.data, -1); + teco_curses_format_filename(ctx->pad, name.data, -1); break; case TECO_POPUP_DIRECTORY: - g_assert(!teco_string_contains(&entry->name, '\0')); + g_assert(!teco_string_contains(name, '\0')); if (teco_ed & TECO_ED_ICONS) { teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_dir(entry->name.data)); waddch(ctx->pad, ' '); } - teco_curses_format_filename(ctx->pad, entry->name.data, -1); + teco_curses_format_filename(ctx->pad, name.data, -1); break; default: - teco_curses_format_str(ctx->pad, entry->name.data, entry->name.len, -1); + teco_curses_format_str(ctx->pad, name.data, name.len, -1); break; } @@ -151,7 +161,7 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) } void -teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) +teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair) { if (!ctx->length) /* nothing to display */ @@ -161,7 +171,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) delwin(ctx->window); if (!ctx->pad) - teco_curses_info_popup_init_pad(ctx, attr); + teco_curses_info_popup_init_pad(ctx, attr, pair); gint pad_lines = getmaxy(ctx->pad); /* @@ -173,15 +183,17 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) /* window covers message, scintilla and info windows */ ctx->window = newwin(popup_lines, 0, LINES - teco_cmdline.height - popup_lines, 0); - wattrset(ctx->window, attr); + wattr_set(ctx->window, attr, pair, NULL); - wborder(ctx->window, - ACS_VLINE, - ACS_VLINE, /* may be overwritten with scrollbar */ - ACS_HLINE, - ' ', /* no bottom line */ - ACS_ULCORNER, ACS_URCORNER, - ACS_VLINE, ACS_VLINE); + /* + * NOTE: wborder() is broken for large pair numbers, at least on ncurses. + */ + waddch(ctx->window, ACS_ULCORNER); + whline(ctx->window, ACS_HLINE, COLS - 2); + mvwaddch(ctx->window, 0, COLS - 1, ACS_URCORNER); + mvwvline(ctx->window, 1, 0, ACS_VLINE, getmaxy(ctx->window)-1); + /* may be overwritten with scrollbar */ + mvwvline(ctx->window, 1, COLS - 1, ACS_VLINE, getmaxy(ctx->window)-1); copywin(ctx->pad, ctx->window, ctx->pad_first_line, 0, @@ -204,7 +216,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) * Instead, simply draw reverse blanks. */ wmove(ctx->window, bar_y, COLS-1); - wattrset(ctx->window, attr ^ A_REVERSE); + wattr_set(ctx->window, attr ^ WA_REVERSE, pair, NULL); wvline(ctx->window, ' ', bar_height); } diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h index d845b29..898ba70 100644 --- a/src/interface-curses/curses-info-popup.h +++ b/src/interface-curses/curses-info-popup.h @@ -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 @@ -49,7 +49,7 @@ teco_curses_info_popup_init(teco_curses_info_popup_t *ctx) void teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_t type, const gchar *name, gsize name_len, gboolean highlight); -void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr); +void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair); const teco_string_t *teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x); void teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx); void teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta); diff --git a/src/interface-curses/curses-utils.c b/src/interface-curses/curses-utils.c index f94b6dc..3b25d56 100644 --- a/src/interface-curses/curses-utils.c +++ b/src/interface-curses/curses-utils.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 @@ -53,9 +53,9 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) /* * The entire background might be in reverse, especially * on monochrome terminals. - * In those cases, we have to __remove__ the A_REVERSE flag. + * In those cases, we have to __remove__ the WA_REVERSE flag. */ - attr_t attrs = A_NORMAL; + attr_t attrs = WA_NORMAL; short pair = 0; wattr_get(win, &attrs, &pair, NULL); @@ -81,28 +81,28 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) chars_added++; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddch(win, '$'); break; case '\r': chars_added += 2; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddstr(win, "CR"); break; case '\n': chars_added += 2; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddstr(win, "LF"); break; case '\t': chars_added += 3; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddstr(win, "TAB"); break; default: @@ -110,7 +110,7 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) chars_added += 2; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddch(win, '^'); waddch(win, TECO_CTL_ECHO(*str)); } else { @@ -126,7 +126,7 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) waddnstr(win, str, clen); } } - /* restore original state of A_REVERSE */ + /* restore original state of WA_REVERSE */ wattr_set(win, attrs, pair, NULL); str += clen; diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h index 18cdd3d..c6d9d8d 100644 --- a/src/interface-curses/curses-utils.h +++ b/src/interface-curses/curses-utils.h @@ -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 @@ -20,19 +20,19 @@ #include <curses.h> +/** what is displayed for unnamed buffers in the info line and popups */ +#define TECO_UNNAMED_FILE "(Unnamed)" + guint teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width); guint teco_curses_format_filename(WINDOW *win, const gchar *filename, gint max_width); -/** - * Add Unicode character to window. - * This is just like wadd_wch(), but does not require wide-char APIs. - */ +/** Add Unicode character to window. */ static inline void teco_curses_add_wc(WINDOW *win, gunichar chr) { - gchar buf[6]; - waddnstr(win, buf, g_unichar_to_utf8(chr, buf)); + wchar_t wc = chr; + waddnwstr(win, &wc, 1); } /** diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index 073ff86..45821f9 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.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 @@ -138,8 +138,6 @@ static gint teco_xterm_version(void) G_GNUC_UNUSED; static gint teco_interface_blocking_getch(void); -#define UNNAMED_FILE "(Unnamed)" - /** * Get bright variant of one of the 8 standard * curses colors. @@ -202,6 +200,7 @@ static struct { TECO_INFO_TYPE_BUFFER = 0, TECO_INFO_TYPE_QREG } info_type; + /* current document's name or empty string for "(Unnamed)" buffer */ teco_string_t info_current; gboolean info_dirty; @@ -233,53 +232,50 @@ static struct { * * Scinterm no longer initializes all color pairs for all combinations of * the builtin foreground and background colors. - * Since curses guarantees only 256 color pairs, we cannot do that either. - * Instead we allocate color pairs beginnig at 128 on demand - * (similar to what Scinterm does). + * Since curses does not guarantee any number of color pairs, we cannot do that either. + * Instead we allocate color pairs in the first half of + * color pair space on demand, while the second half is reserved to Scinterm. * + * @param attr attributes to modify (for supporting monochrome terminals) * @param fg curses foreground color * @param bg curses background color * @return curses color pair number */ static gshort -teco_color_pair(gshort fg, gshort bg) +teco_color_pair(attr_t *attr, gshort fg, gshort bg) { - static gshort last_pair = 127; + static guint next_pair = 1; + + /* + * Basic support for monochrome terminals: + * Every background, that is not black is assumed to be a + * dark-on-bright area, rendered in reverse. + * This will at least work with the terminal.tes and contrast.tes + * color schemes. + */ + if (!has_colors()) { + if (bg != COLOR_BLACK) + *attr |= WA_REVERSE; + return 0; + } G_STATIC_ASSERT(sizeof(gshort)*2 <= sizeof(guint)); - gpointer key = GUINT_TO_POINTER(((guint)fg << 16) | bg); + gpointer key = GUINT_TO_POINTER(((guint)fg << 8*sizeof(bg)) | bg); gpointer value = g_hash_table_lookup(teco_interface.pair_table, key); if (G_LIKELY(value != NULL)) return GPOINTER_TO_UINT(value); - init_pair(++last_pair, fg, bg); - g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(last_pair)); - return last_pair; + if (G_UNLIKELY(next_pair >= COLOR_PAIRS || next_pair > G_MAXSHORT)) + return 0; + init_pair(next_pair, fg, bg); + g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(next_pair)); + return next_pair++; } -/** - * Curses attribute for the color combination - * according to the color pairs initialized by - * Scinterm. - * This is equivalent to Scinterm's internal term_color_attr(). - * - * @param fg foreground color - * @param bg background color - * @return curses attribute - */ -static inline attr_t -teco_color_attr(gshort fg, gshort bg) +static inline gint +teco_wattr_set(WINDOW *win, attr_t attr, gshort fg, gshort bg) { - if (has_colors()) - return COLOR_PAIR(teco_color_pair(fg, bg)); - - /* - * Basic support for monochrome terminals: - * Every background, that is not black is assumed to be a - * dark-on-bright area, rendered in reverse. - * This will at least work with the terminal.tes - * color scheme. - */ - return bg != COLOR_BLACK ? A_REVERSE : 0; + gshort pair = teco_color_pair(&attr, fg, bg); + return wattr_set(win, attr, pair, NULL); } /** @@ -393,6 +389,13 @@ teco_view_noutrefresh(teco_view_t *ctx) scintilla_noutrefresh(ctx); } +static inline void +teco_view_update_cursor(teco_view_t *ctx) +{ + if (teco_view_ssm(ctx, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES) + scintilla_update_cursor(ctx); +} + static inline WINDOW * teco_view_get_window(teco_view_t *ctx) { @@ -426,7 +429,7 @@ static void teco_interface_set_window_title(const gchar *title); static void teco_interface_draw_info(void); void -teco_interface_init(void) +teco_interface_init(gint argc, gchar **argv) { for (guint i = 0; i < G_N_ELEMENTS(teco_interface.color_table); i++) teco_interface.color_table[i] = -1; @@ -437,20 +440,16 @@ teco_interface_init(void) teco_curses_info_popup_init(&teco_interface.popup); - /* - * Make sure we have a string for the info line - * even if teco_interface_info_update() is never called. - */ - teco_string_init(&teco_interface.info_current, PACKAGE_NAME, strlen(PACKAGE_NAME)); - teco_cmdline_init(); /* * The default INDIC_STRIKE wouldn't be visible. - * Instead we use INDIC_STRAIGHTBOX, which will be rendered as underlined if - * the alpha is 0. + * Instead we use INDIC_SQUIGGLE, which is rendered as WA_UNDERLINE. */ - teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_STRAIGHTBOX); - teco_cmdline_ssm(SCI_INDICSETALPHA, INDICATOR_RUBBEDOUT, 0); + teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_SQUIGGLE); + /* + * Enable hardware cursor by default. + */ + teco_cmdline_ssm(SCI_SETCARETSTYLE, CARETSTYLE_CURSES, 0); /* * On all platforms except Curses/XTerm, it's @@ -747,6 +746,12 @@ teco_interface_init_interactive(GError **error) teco_interface.pair_table = g_hash_table_new(g_direct_hash, g_direct_equal); start_color(); + /* + * Scinterm uses shorts for color pairs, so G_MAXSHORT is the maximum. + * Some Curses implementations (NetBSD, PDCurses) may support less, + * but set COLOR_PAIRS accordingly. + */ + scintilla_set_color_offsets(0, MIN(COLOR_PAIRS, G_MAXSHORT)/2); /* * On UNIX terminals, the escape key is usually @@ -790,8 +795,6 @@ teco_interface_init_interactive(GError **error) cbreak(); noecho(); - /* Scintilla draws its own cursor */ - curs_set(0); /* * This has also been observed to reduce flickering * in teco_interface_refresh(). @@ -818,7 +821,7 @@ teco_interface_init_interactive(GError **error) * must always be TRUE so we receive KEY_RESIZE. */ keypad(teco_interface.input_pad, TRUE); - nodelay(teco_interface.input_pad, TRUE); + wtimeout(teco_interface.input_pad, 0); teco_interface.input_queue = g_queue_new(); @@ -939,7 +942,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) teco_interface_stdio_msg(type, str, len); #endif - short fg, bg; + gshort fg, bg; fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); @@ -961,7 +964,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) } wmove(teco_interface.msg_window, 0, 0); - wattrset(teco_interface.msg_window, teco_color_attr(fg, bg)); + teco_wattr_set(teco_interface.msg_window, 0, fg, bg); teco_curses_format_str(teco_interface.msg_window, str, len, -1); teco_curses_clrtobot(teco_interface.msg_window); } @@ -972,11 +975,11 @@ teco_interface_msg_clear(void) if (!teco_interface.input_pad) /* batch mode */ return; - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); wmove(teco_interface.msg_window, 0, 0); - wattrset(teco_interface.msg_window, teco_color_attr(fg, bg)); + teco_wattr_set(teco_interface.msg_window, 0, fg, bg); teco_curses_clrtobot(teco_interface.msg_window); } @@ -990,10 +993,11 @@ teco_interface_getch(gboolean widechar) /* * Signal that we accept input by drawing a real cursor in the message bar. + * FIXME: curs_set(2) would be better, but isn't always visible. */ wmove(teco_interface.msg_window, 0, 0); - curs_set(1); wrefresh(teco_interface.msg_window); + curs_set(1); gchar buf[4]; gint i = 0; @@ -1020,7 +1024,6 @@ teco_interface_getch(gboolean widechar) i = 0; } while (cp < 0); - curs_set(0); return cp; } @@ -1150,36 +1153,40 @@ teco_interface_draw_info(void) * the current buffer's STYLE_DEFAULT. * The same style is used for MSG_USER messages. */ - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); wmove(teco_interface.info_window, 0, 0); - wattrset(teco_interface.info_window, teco_color_attr(fg, bg)); + teco_wattr_set(teco_interface.info_window, 0, fg, bg); const gchar *info_type_str; waddstr(teco_interface.info_window, PACKAGE_NAME " "); + teco_string_t info_current = teco_interface.info_current; + if (!info_current.len) { + info_current.data = TECO_UNNAMED_FILE; + info_current.len = strlen(info_current.data); + } + switch (teco_interface.info_type) { case TECO_INFO_TYPE_QREG: info_type_str = PACKAGE_NAME " - <QRegister> "; teco_curses_add_wc(teco_interface.info_window, teco_ed & TECO_ED_ICONS ? TECO_CURSES_ICONS_QREG : '-'); waddstr(teco_interface.info_window, " <QRegister> "); - /* same formatting as in command lines */ teco_curses_format_str(teco_interface.info_window, - teco_interface.info_current.data, - teco_interface.info_current.len, -1); + info_current.data, info_current.len, -1); break; case TECO_INFO_TYPE_BUFFER: info_type_str = PACKAGE_NAME " - <Buffer> "; - g_assert(!teco_string_contains(&teco_interface.info_current, '\0')); + g_assert(!teco_string_contains(info_current, '\0')); + /* "(Unnamed)" buffer has to be looked up as "" */ teco_curses_add_wc(teco_interface.info_window, teco_ed & TECO_ED_ICONS ? teco_curses_icons_lookup_file(teco_interface.info_current.data) : '-'); waddstr(teco_interface.info_window, " <Buffer> "); - teco_curses_format_filename(teco_interface.info_window, - teco_interface.info_current.data, + teco_curses_format_filename(teco_interface.info_window, info_current.data, getmaxx(teco_interface.info_window) - getcurx(teco_interface.info_window) - 1); waddch(teco_interface.info_window, teco_interface.info_dirty ? '*' : ' '); @@ -1195,8 +1202,7 @@ teco_interface_draw_info(void) * Make sure the title will consist only of printable characters. */ g_autofree gchar *info_current_printable; - info_current_printable = teco_string_echo(teco_interface.info_current.data, - teco_interface.info_current.len); + info_current_printable = teco_string_echo(info_current.data, info_current.len); g_autofree gchar *title = g_strconcat(info_type_str, info_current_printable, teco_interface.info_dirty ? "*" : "", NULL); teco_interface_set_window_title(title); @@ -1216,10 +1222,9 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg) void teco_interface_info_update_buffer(const teco_buffer_t *buffer) { - const gchar *filename = buffer->filename ? : UNNAMED_FILE; - teco_string_clear(&teco_interface.info_current); - teco_string_init(&teco_interface.info_current, filename, strlen(filename)); + teco_string_init(&teco_interface.info_current, buffer->filename, + buffer->filename ? strlen(buffer->filename) : 0); teco_interface.info_dirty = buffer->state > TECO_BUFFER_CLEAN; teco_interface.info_type = TECO_INFO_TYPE_BUFFER; /* NOTE: drawn in teco_interface_event_loop_iter() */ @@ -1233,7 +1238,20 @@ teco_interface_info_update_buffer(const teco_buffer_t *buffer) * default clipboard ("~") as we do not know whether * it corresponds to the X11 PRIMARY, SECONDARY or * CLIPBOARD selections. + * + * On XCurses we must not (and don't have to) probe + * the clipboard as it would be before Xinitscr(). */ +#ifdef XCURSES + +static void +teco_interface_init_clipboard(void) +{ + teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new("")); +} + +#else /* XCURSES */ + static void teco_interface_init_clipboard(void) { @@ -1258,6 +1276,8 @@ teco_interface_init_clipboard(void) teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new("")); } +#endif /* !XCURSES */ + gboolean teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error) { @@ -1429,7 +1449,7 @@ teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, G * We restore all changed Curses settings before returning * to be on the safe side. */ - halfdelay(1); /* 100ms timeout */ + wtimeout(teco_interface.input_pad, 100); /* don't interpret escape sequences */ keypad(teco_interface.input_pad, FALSE); @@ -1496,7 +1516,7 @@ teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, G cleanup: keypad(teco_interface.input_pad, TRUE); - nodelay(teco_interface.input_pad, TRUE); + wtimeout(teco_interface.input_pad, 0); return ret; } @@ -1547,7 +1567,7 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, g_auto(teco_string_t) command; if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error)) return FALSE; - if (teco_string_contains(&command, '\0')) { + if (teco_string_contains(command, '\0')) { teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE); return FALSE; } @@ -1608,7 +1628,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError g_auto(teco_string_t) command; if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error)) return FALSE; - if (teco_string_contains(&command, '\0')) { + if (teco_string_contains(command, '\0')) { teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE); return FALSE; } @@ -1706,11 +1726,13 @@ teco_interface_popup_show(gsize prefix_len) /* batch mode */ return; - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); + gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); teco_interface.popup_prefix_len = prefix_len; - teco_curses_info_popup_show(&teco_interface.popup, teco_color_attr(fg, bg)); + attr_t attr = 0; + gshort pair = teco_color_pair(&attr, fg, bg); + teco_curses_info_popup_show(&teco_interface.popup, attr, pair); } void @@ -1828,7 +1850,31 @@ teco_interface_refresh(gboolean force) wnoutrefresh(teco_interface.msg_window); teco_view_noutrefresh(teco_cmdline.view); teco_curses_info_popup_noutrefresh(&teco_interface.popup); + /* + * If hardware cursors (CARETSTYLE_CURSES) are enabled on the + * command-line view, make sure that the cursor is left + * in the correct position. + * + * FIXME: This shouldn't be necessary if we refreshed the command-line + * view last. Also, if we wanted to support the hardware cursor + * in the main view as well, we'd have to handle a possibly + * overlappig info popup. + */ + teco_view_update_cursor(teco_cmdline.view); doupdate(); + + /* + * Scinterm enables/disables the hardware cursor, + * but only if CARETSTYLE_CURSES is used. + * Disabling the cursor repeatedly ensures you can change + * the caret style interactively. + * It also makes sure the cursor is reset after + * teco_interface_getch(). + * Also, window resizes sometimes enable the hardware cursor on + * PDCurses/wincon (FIXME). + */ + if (!(teco_view_ssm(teco_cmdline.view, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES)) + curs_set(0); } #if NCURSES_MOUSE_VERSION >= 2 @@ -1865,10 +1911,13 @@ teco_interface_process_mevent(MEVENT *event, GError **error) event->y, event->x); if (insert && machine->current->insert_completion_cb) { - /* successfully clicked popup item */ + /* + * Successfully clicked popup item. + * `insert` is the empty string for the "(Unnamed)" buffer. + */ const teco_string_t insert_suffix = {insert->data + teco_interface.popup_prefix_len, insert->len - teco_interface.popup_prefix_len}; - if (!machine->current->insert_completion_cb(machine, &insert_suffix, error)) + if (!machine->current->insert_completion_cb(machine, insert_suffix, error)) return FALSE; teco_interface_popup_clear(); @@ -1883,9 +1932,11 @@ teco_interface_process_mevent(MEVENT *event, GError **error) else if (event->bstate & BUTTON_NUM(5)) teco_curses_info_popup_scroll(&teco_interface.popup, +2); - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); - teco_curses_info_popup_show(&teco_interface.popup, teco_color_attr(fg, bg)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); + gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + attr_t attr = 0; + gshort pair = teco_color_pair(&attr, fg, bg); + teco_curses_info_popup_show(&teco_interface.popup, attr, pair); return TRUE; } @@ -1968,6 +2019,20 @@ teco_interface_process_mevent(MEVENT *event, GError **error) return teco_cmdline_keymacro("MOUSE", -1, error); } +#ifdef __PDCURSES__ + +static gboolean +teco_interface_getmouse(GError **error) +{ + MEVENT event; + + /* in contrast to ncurses, there is no separate mouse event queue */ + return getmouse(&event) != OK || + teco_interface_process_mevent(&event, error); +} + +#else /* __PDCURSES__ */ + static gboolean teco_interface_getmouse(GError **error) { @@ -1980,6 +2045,8 @@ teco_interface_getmouse(GError **error) return TRUE; } +#endif /* !__PDCURSES__ */ + #endif /* NCURSES_MOUSE_VERSION >= 2 */ static gint @@ -2001,7 +2068,8 @@ teco_interface_blocking_getch(void) gboolean new_mousekey = (teco_ed & TECO_ED_MOUSEKEY) != 0; if (new_mousekey != old_mousekey) { old_mousekey = new_mousekey; - mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED); + mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED) | + BUTTON_SHIFT | BUTTON_CTRL | BUTTON_ALT; #ifdef __PDCURSES__ /* * On PDCurses it's crucial NOT to mask for BUTTONX_CLICKED. @@ -2016,7 +2084,6 @@ teco_interface_blocking_getch(void) /* no special <CTRL/C> handling */ raw(); - nodelay(teco_interface.input_pad, FALSE); /* * Make sure we return when it's time to create recovery dumps. @@ -2027,6 +2094,8 @@ teco_interface_blocking_getch(void) gdouble elapsed = g_timer_elapsed(teco_interface.recovery_timer, NULL); wtimeout(teco_interface.input_pad, MAX((gdouble)teco_ring_recovery_interval - elapsed, 0)*1000); + } else { + wtimeout(teco_interface.input_pad, -1); } /* @@ -2038,7 +2107,7 @@ teco_interface_blocking_getch(void) teco_memory_start_limiting(); /* allow asynchronous interruptions on <CTRL/C> */ teco_interrupted = FALSE; - nodelay(teco_interface.input_pad, TRUE); + wtimeout(teco_interface.input_pad, 0); #if defined(CURSES_TTY) || defined(PDCURSES_WINCON) || defined(NCURSES_WIN32) noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */ cbreak(); @@ -2083,11 +2152,6 @@ teco_interface_event_loop_iter(void) return; #ifdef KEY_RESIZE case KEY_RESIZE: - /* - * At least on PDCurses/Wincon, the hardware cursor is sometimes - * reactivated. - */ - curs_set(0); teco_interface_resize_all_windows(); break; #endif diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c index aaa0a65..f2c8dc8 100644 --- a/src/interface-gtk/gtk-info-popup.c +++ b/src/interface-gtk/gtk-info-popup.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 @@ -30,6 +30,8 @@ #include "gtk-label.h" #include "gtk-info-popup.h" +#define TECO_UNNAMED_FILE "(Unnamed)" + /* * FIXME: This is redundant with curses-info-popup.c. */ @@ -37,6 +39,7 @@ typedef struct { teco_stailq_entry_t entry; teco_popup_entry_type_t type; + /** entry name or empty string for the "(Unnamed)" buffer */ teco_string_t name; gboolean highlight; } teco_popup_entry_t; @@ -109,10 +112,10 @@ teco_gtk_info_popup_activated_cb(GtkFlowBox *box, GtkFlowBoxChild *child, gpoint GList *entry; for (entry = child_list; entry != NULL && !TECO_IS_GTK_LABEL(entry->data); entry = g_list_next(entry)); g_assert(entry != NULL); - const teco_string_t *str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data)); + teco_string_t str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data)); g_signal_emit(popup, teco_gtk_info_popup_clicked_signal, 0, - str->data, (gulong)str->len); + str.data, (gulong)str.len); } static void @@ -249,6 +252,10 @@ teco_gtk_info_popup_new(void) GIcon * teco_gtk_info_popup_get_icon_for_path(const gchar *path, const gchar *fallback_name) { + if (!path || !*path) + /* "(Unnamed)" file */ + return g_icon_new_for_string(fallback_name, NULL); + GIcon *icon = NULL; g_autoptr(GFile) file = g_file_new_for_path(path); @@ -299,7 +306,7 @@ teco_gtk_info_popup_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type, static void teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type, - const gchar *name, gssize len, gboolean highlight) + const gchar *name, gsize len, gboolean highlight) { g_return_if_fail(self != NULL); g_return_if_fail(TECO_IS_GTK_INFO_POPUP(self)); @@ -318,12 +325,8 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ const gchar *fallback = type == TECO_POPUP_FILE ? "text-x-generic" : "folder"; - /* - * `name` is not guaranteed to be null-terminated. - */ - g_autofree gchar *path = len < 0 ? g_strdup(name) : g_strndup(name, len); - - g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(path, fallback); + /* name comes from a teco_string_t and is guaranteed to be null-terminated */ + g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(name, fallback); if (icon) { gint width, height; gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); @@ -335,7 +338,7 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ } } - GtkWidget *label = teco_gtk_label_new(name, len); + GtkWidget *label = teco_gtk_label_new(name, len, TECO_UNNAMED_FILE); /* * Gtk v3.20 changed the CSS element names. * Adding a style class eases writing a portable fallback.css. diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h index ad79b84..3ce8e1f 100644 --- a/src/interface-gtk/gtk-info-popup.h +++ b/src/interface-gtk/gtk-info-popup.h @@ -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 diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c index e4b2823..6e05045 100644 --- a/src/interface-gtk/gtk-label.c +++ b/src/interface-gtk/gtk-label.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 @@ -32,7 +32,7 @@ #include "gtk-label.h" -#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16)) +#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16)) struct _TecoGtkLabel { GtkLabel parent_instance; @@ -40,7 +40,10 @@ struct _TecoGtkLabel { PangoColor fg, bg; guint16 fg_alpha, bg_alpha; + /** text backing the label or empty string for fallback */ teco_string_t string; + /** fallback string to render if `string` is empty or NULL */ + const gchar *fallback; }; G_DEFINE_TYPE(TecoGtkLabel, teco_gtk_label, GTK_TYPE_LABEL) @@ -122,11 +125,21 @@ teco_gtk_label_class_init(TecoGtkLabelClass *klass) static void teco_gtk_label_init(TecoGtkLabel *self) {} +/** + * Create new TECO label widget. + * + * @param str String to render (can be NULL) + * @param len Length of str or negative value for null-terminated strings. + * @param fallback Null-terminated fallback string to render + * instead of empty strings. + * Must be a string constant with global lifetime or NULL. + */ GtkWidget * -teco_gtk_label_new(const gchar *str, gssize len) +teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback) { TecoGtkLabel *widget = TECO_GTK_LABEL(g_object_new(TECO_TYPE_GTK_LABEL, NULL)); + widget->fallback = fallback; teco_gtk_label_set_text(widget, str, len); return GTK_WIDGET(widget); @@ -251,27 +264,30 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len) teco_string_clear(&self->string); teco_string_init(&self->string, str, len < 0 ? strlen(str) : len); + teco_string_t string = self->string; + if (!string.len && self->fallback) { + string.data = (gchar *)self->fallback; + string.len = strlen(string.data); + } + g_autofree gchar *plaintext = NULL; + PangoAttrList *attribs = NULL; - if (self->string.len > 0) { - PangoAttrList *attribs = NULL; + teco_gtk_label_parse_string(string.data, string.len, + &self->fg, self->fg_alpha, + &self->bg, self->bg_alpha, + &attribs, &plaintext); - teco_gtk_label_parse_string(self->string.data, self->string.len, - &self->fg, self->fg_alpha, - &self->bg, self->bg_alpha, - &attribs, &plaintext); - - gtk_label_set_attributes(GTK_LABEL(self), attribs); - pango_attr_list_unref(attribs); - } + gtk_label_set_attributes(GTK_LABEL(self), attribs); + pango_attr_list_unref(attribs); gtk_label_set_text(GTK_LABEL(self), plaintext); } -const teco_string_t * +teco_string_t teco_gtk_label_get_text(TecoGtkLabel *self) { - return &self->string; + return self->string; } /** diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h index 3cd4cb9..a84608a 100644 --- a/src/interface-gtk/gtk-label.h +++ b/src/interface-gtk/gtk-label.h @@ -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 @@ -24,10 +24,10 @@ #define TECO_TYPE_GTK_LABEL teco_gtk_label_get_type() G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel) -GtkWidget *teco_gtk_label_new(const gchar *str, gssize len); +GtkWidget *teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback); void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len); -const teco_string_t *teco_gtk_label_get_text(TecoGtkLabel *self); +teco_string_t teco_gtk_label_get_text(TecoGtkLabel *self); void teco_gtk_label_parse_string(const gchar *str, gssize len, PangoColor *fg, guint16 fg_alpha, diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index d7fc4c4..08ccf5d 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.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 @@ -19,14 +19,17 @@ #include "config.h" #endif +#include <stdlib.h> #include <string.h> #include <signal.h> +#include <unistd.h> #include <glib.h> #include <glib/gprintf.h> #include <glib/gstdio.h> #ifdef G_OS_UNIX +#include <unistd.h> #include <glib-unix.h> #endif @@ -78,7 +81,7 @@ static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny * static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED; static gchar teco_interface_get_ansi_key(GdkEventKey *event); -#define UNNAMED_FILE "(Unnamed)" +#define TECO_UNNAMED_FILE "(Unnamed)" #define USER_CSS_FILE ".teco_css" @@ -102,9 +105,10 @@ static struct { TECO_INFO_TYPE_BUFFER_DIRTY, TECO_INFO_TYPE_QREG } info_type; + /* current document's name or empty string for "(Unnamed)" buffer */ teco_string_t info_current; - gboolean no_csd; + gboolean no_csd, detach; gint xembed_id; GtkWidget *info_bar_widget; @@ -129,8 +133,48 @@ static struct { } teco_interface; void -teco_interface_init(void) +teco_interface_init(gint argc, gchar **argv) { +#ifdef G_OS_UNIX + if (teco_interface.detach && !g_getenv("__SCITECO_DETACHED")) { + /* + * NOTE: There is also daemon() on BSD/Linux, + * but the following should be more portable. + */ + pid_t pid = fork(); + g_assert(pid >= 0); + if (pid != 0) + /* parent process */ + exit(EXIT_SUCCESS); + + setsid(); + + if (isatty(0)) { + G_GNUC_UNUSED FILE *stdin_new = g_freopen("/dev/null", "r", stdin); + g_assert(stdin_new != NULL); + } + if (isatty(1)) { + G_GNUC_UNUSED FILE *stdout_new = g_freopen("/dev/null", "a+", stdout); + g_assert(stdout_new != NULL); + } + if (isatty(2)) { + G_GNUC_UNUSED FILE *stderr_new = g_freopen("/dev/null", "a+", stderr); + g_assert(stderr_new != NULL); + } + + /* + * gtk_get_option_group() already initialized GTK and even though the + * display is not yet opened, it's unsafe to continue. + * Instead, we re-exec already in the child process. + * We cannot easily remove --detach from argv, but still guard against + * recursive forks by using an environment variable. + */ + g_setenv("__SCITECO_DETACHED", "1", TRUE); + execv(argv[0], argv); + g_assert_not_reached(); + } +#endif + /* * gtk_init() is not necessary when using gtk_get_option_group(), * but this will open the default display. @@ -179,7 +223,7 @@ teco_interface_init(void) */ teco_interface.info_bar_widget = gtk_header_bar_new(); gtk_widget_set_name(teco_interface.info_bar_widget, "sciteco-info-bar"); - teco_interface.info_name_widget = teco_gtk_label_new("", 0); + teco_interface.info_name_widget = teco_gtk_label_new(NULL, 0, NULL); gtk_widget_set_valign(teco_interface.info_name_widget, GTK_ALIGN_CENTER); /* eases writing portable fallback.css that avoids CSS element names */ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget), @@ -265,7 +309,7 @@ teco_interface_init(void) gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar"); GtkWidget *message_bar_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(teco_interface.message_bar_widget)); - teco_interface.message_widget = teco_gtk_label_new(NULL, 0); + teco_interface.message_widget = teco_gtk_label_new(NULL, 0, NULL); /* eases writing portable fallback.css that avoids CSS element names */ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.message_widget), "label"); @@ -340,6 +384,11 @@ teco_interface_get_options(void) G_OPTION_ARG_INT, &teco_interface.xembed_id, "Embed into an existing X11 Window.", "ID"}, #endif +#ifdef G_OS_UNIX + {"detach", 'd', G_OPTION_FLAG_IN_MAIN, + G_OPTION_ARG_NONE, &teco_interface.detach, + "Detach from controlling terminal (daemonize).", NULL}, +#endif {NULL} }; @@ -501,8 +550,13 @@ teco_interface_refresh_info(void) gtk_style_context_remove_class(style, "dirty"); g_auto(teco_string_t) info_current_temp; - teco_string_init(&info_current_temp, - teco_interface.info_current.data, teco_interface.info_current.len); + + if (!teco_interface.info_current.len) + teco_string_init(&info_current_temp, TECO_UNNAMED_FILE, strlen(TECO_UNNAMED_FILE)); + else + teco_string_init(&info_current_temp, + teco_interface.info_current.data, teco_interface.info_current.len); + if (teco_interface.info_type == TECO_INFO_TYPE_BUFFER_DIRTY) teco_string_append_c(&info_current_temp, '*'); teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.info_name_widget), @@ -570,10 +624,9 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg) void teco_interface_info_update_buffer(const teco_buffer_t *buffer) { - const gchar *filename = buffer->filename ? : UNNAMED_FILE; - teco_string_clear(&teco_interface.info_current); - teco_string_init(&teco_interface.info_current, filename, strlen(filename)); + teco_string_init(&teco_interface.info_current, buffer->filename, + buffer->filename ? strlen(buffer->filename) : 0); teco_interface.info_type = buffer->state > TECO_BUFFER_CLEAN ? TECO_INFO_TYPE_BUFFER_DIRTY : TECO_INFO_TYPE_BUFFER; } @@ -1129,7 +1182,7 @@ teco_interface_event_loop(GError **error) if (!scitecoconfig_reg->vtable->get_string(scitecoconfig_reg, &scitecoconfig.data, &scitecoconfig.len, NULL, error)) return FALSE; - if (teco_string_contains(&scitecoconfig, '\0')) { + if (teco_string_contains(scitecoconfig, '\0')) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Null-character not allowed in filenames"); return FALSE; @@ -1472,7 +1525,9 @@ static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpointer user_data) { g_assert(len >= teco_interface.popup_prefix_len); - const teco_string_t insert = {str+teco_interface.popup_prefix_len, len-teco_interface.popup_prefix_len}; + /* str is an empty string for the "(Unnamed)" buffer */ + const teco_string_t insert = {str+teco_interface.popup_prefix_len, + len-teco_interface.popup_prefix_len}; teco_machine_t *machine = &teco_cmdline.machine.parent; const teco_view_t *last_view = teco_interface_current_view; @@ -1482,7 +1537,7 @@ teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpoint * A auto completion should never result in program termination. */ if (machine->current->insert_completion_cb && - !machine->current->insert_completion_cb(machine, &insert, NULL)) + !machine->current->insert_completion_cb(machine, insert, NULL)) return; teco_interface_popup_clear(); teco_cmdline_update(); diff --git a/src/interface-gtk/view.c b/src/interface-gtk/view.c index 44e3988..3a18f33 100644 --- a/src/interface-gtk/view.c +++ b/src/interface-gtk/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 diff --git a/src/interface.c b/src/interface.c index aa7e00e..2343a16 100644 --- a/src/interface.c +++ b/src/interface.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 diff --git a/src/interface.h b/src/interface.h index 8ab66ef..54f807b 100644 --- a/src/interface.h +++ b/src/interface.h @@ -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 @@ -51,7 +51,7 @@ extern teco_view_t *teco_interface_current_view; /** @pure */ -void teco_interface_init(void); +void teco_interface_init(gint argc, gchar **argv); /** @pure */ GOptionGroup *teco_interface_get_options(void); @@ -135,7 +135,17 @@ typedef enum { TECO_POPUP_DIRECTORY } teco_popup_entry_type_t; -/** @pure */ +/** + * Add entry to popup. + * + * @param type Entry type + * @param name + * Name string of the entry or NULL for "(Unnamed)". + * It is not necessarily null-terminated. + * @param name_len Length of string in name + * @param highlight Whether to highlight the entry + * @pure + */ void teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize name_len, gboolean highlight); /** @pure */ diff --git a/src/lexer.c b/src/lexer.c index 787fe89..25ea8f2 100644 --- a/src/lexer.c +++ b/src/lexer.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 diff --git a/src/lexer.h b/src/lexer.h index e91cdd1..e7073c2 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -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 @@ -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 @@ -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 @@ -54,7 +54,7 @@ /* * Define this to pause the program at the beginning - * of main() (Windows only). + * of main(). * This is a useful hack on Windows, where gdbserver * sometimes refuses to start SciTECO but attaches * to a running process just fine. @@ -382,8 +382,7 @@ main(int argc, char **argv) #endif #ifdef DEBUG_PAUSE - /* Windows debugging hack (see above) */ - system("pause"); + getchar(); #endif signal(SIGINT, teco_sigint_handler); @@ -445,7 +444,7 @@ main(int argc, char **argv) */ teco_qreg_table_init(&teco_qreg_table_globals, TRUE); - teco_interface_init(); + teco_interface_init(argc, argv); /* * QRegister view must be initialized only now diff --git a/src/memory.c b/src/memory.c index 264c235..d8de483 100644 --- a/src/memory.c +++ b/src/memory.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 diff --git a/src/memory.h b/src/memory.h index ae7b506..9826073 100644 --- a/src/memory.h +++ b/src/memory.h @@ -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 diff --git a/src/move-commands.c b/src/move-commands.c index cf5d6b0..45afc4e 100644 --- a/src/move-commands.c +++ b/src/move-commands.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 @@ -276,7 +276,7 @@ teco_find_words(gsize *pos, teco_int_t n, gboolean end_of_word) /* * FIXME: Is this safe or do we have to look up Unicode code points? */ - if ((!teco_string_contains(&wchars, *p)) == skip_word) { + if ((!teco_string_contains(wchars, *p)) == skip_word) { if (skip_word == end_of_word) break; skip_word = !skip_word; @@ -314,7 +314,7 @@ teco_find_words(gsize *pos, teco_int_t n, gboolean end_of_word) /* * FIXME: Is this safe or do we have to look up Unicode code points? */ - if ((!teco_string_contains(&wchars, p[-1])) == skip_word) { + if ((!teco_string_contains(wchars, p[-1])) == skip_word) { if (skip_word != end_of_word) break; skip_word = !skip_word; diff --git a/src/move-commands.h b/src/move-commands.h index 1f32151..cc92961 100644 --- a/src/move-commands.h +++ b/src/move-commands.h @@ -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 diff --git a/src/parser.c b/src/parser.c index a5e6e4f..747249d 100644 --- a/src/parser.c +++ b/src/parser.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 @@ -162,9 +162,7 @@ gboolean teco_execute_macro(const gchar *macro, gsize macro_len, teco_qreg_table_t *qreg_table_locals, GError **error) { - const teco_string_t str = {(gchar *)macro, macro_len}; - - if (!teco_string_validate_utf8(&str)) { + if (!teco_string_validate_utf8((teco_string_t){(gchar *)macro, macro_len})) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT, "Invalid UTF-8 byte sequence in macro"); return FALSE; @@ -476,27 +474,24 @@ teco_machine_stringbuilding_append_c(teco_machine_stringbuilding_t *ctx, teco_in return TRUE; } -/* - * FIXME: All teco_state_stringbuilding_* states could be static? - */ static teco_state_t *teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error); -TECO_DECLARE_STATE(teco_state_stringbuilding_ctl); +static teco_state_t teco_state_stringbuilding_ctl; static teco_state_t *teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error); -TECO_DECLARE_STATE(teco_state_stringbuilding_escaped); +static teco_state_t teco_state_stringbuilding_escaped; -TECO_DECLARE_STATE(teco_state_stringbuilding_lower); -TECO_DECLARE_STATE(teco_state_stringbuilding_upper); +static teco_state_t teco_state_stringbuilding_lower; +static teco_state_t teco_state_stringbuilding_upper; -TECO_DECLARE_STATE(teco_state_stringbuilding_ctle); -TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_num); -TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_u); -TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_code); -TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_q); -TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_quote); -TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_n); +static teco_state_t teco_state_stringbuilding_ctle; +static teco_state_t teco_state_stringbuilding_ctle_num; +static teco_state_t teco_state_stringbuilding_ctle_u; +static teco_state_t teco_state_stringbuilding_ctle_code; +static teco_state_t teco_state_stringbuilding_ctle_q; +static teco_state_t teco_state_stringbuilding_ctle_quote; +static teco_state_t teco_state_stringbuilding_ctle_n; static teco_state_t * teco_state_stringbuilding_start_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -524,14 +519,15 @@ teco_state_stringbuilding_start_input(teco_machine_stringbuilding_t *ctx, gunich /* in cmdline.c */ gboolean teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, const teco_string_t *str, GError **error); - -TECO_DEFINE_STATE(teco_state_stringbuilding_start, - .is_start = TRUE, - .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) - teco_state_stringbuilding_start_process_edit_cmd, - .insert_completion_cb = (teco_state_insert_completion_cb_t) - teco_state_stringbuilding_insert_completion +gboolean teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, teco_string_t str, GError **error); + +static TECO_DEFINE_STATE(teco_state_stringbuilding_start, + .is_start = TRUE, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_start_input, + .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) + teco_state_stringbuilding_start_process_edit_cmd, + .insert_completion_cb = (teco_state_insert_completion_cb_t) + teco_state_stringbuilding_insert_completion ); static teco_state_t * @@ -581,7 +577,9 @@ teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctl); +static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctl, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctl_input, +); static teco_state_t * teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -613,7 +611,8 @@ teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, guni gboolean teco_state_stringbuilding_escaped_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -TECO_DEFINE_STATE(teco_state_stringbuilding_escaped, +static TECO_DEFINE_STATE(teco_state_stringbuilding_escaped, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_escaped_input, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) teco_state_stringbuilding_escaped_process_edit_cmd ); @@ -639,7 +638,9 @@ teco_state_stringbuilding_lower_ctl_input(teco_machine_stringbuilding_t *ctx, gu return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_lower_ctl); +static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_lower_ctl, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_lower_ctl_input +); static teco_state_t * teco_state_stringbuilding_lower_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -657,7 +658,9 @@ teco_state_stringbuilding_lower_input(teco_machine_stringbuilding_t *ctx, gunich return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE(teco_state_stringbuilding_lower); +static TECO_DEFINE_STATE(teco_state_stringbuilding_lower, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_lower_input +); static teco_state_t * teco_state_stringbuilding_upper_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -680,7 +683,9 @@ teco_state_stringbuilding_upper_ctl_input(teco_machine_stringbuilding_t *ctx, gu return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_upper_ctl); +static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_upper_ctl, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_upper_ctl_input +); static teco_state_t * teco_state_stringbuilding_upper_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -698,7 +703,9 @@ teco_state_stringbuilding_upper_input(teco_machine_stringbuilding_t *ctx, gunich return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE(teco_state_stringbuilding_upper); +static TECO_DEFINE_STATE(teco_state_stringbuilding_upper, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_upper_input +); static teco_state_t * teco_state_stringbuilding_ctle_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -731,7 +738,9 @@ teco_state_stringbuilding_ctle_input(teco_machine_stringbuilding_t *ctx, gunicha return next; } -TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctle); +static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctle, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_input +); /* in cmdline.c */ gboolean teco_state_stringbuilding_qreg_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx, @@ -782,7 +791,9 @@ teco_state_stringbuilding_ctle_num_input(teco_machine_stringbuilding_t *ctx, gun return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_num); +static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_num, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_num_input +); static teco_state_t * teco_state_stringbuilding_ctle_u_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -817,7 +828,9 @@ teco_state_stringbuilding_ctle_u_input(teco_machine_stringbuilding_t *ctx, gunic return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_u); +static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_u, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_u_input +); static teco_state_t * teco_state_stringbuilding_ctle_code_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -869,7 +882,9 @@ teco_state_stringbuilding_ctle_code_input(teco_machine_stringbuilding_t *ctx, gu return &teco_state_stringbuilding_ctle_code; } -TECO_DEFINE_STATE(teco_state_stringbuilding_ctle_code); +static TECO_DEFINE_STATE(teco_state_stringbuilding_ctle_code, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_code_input +); static teco_state_t * teco_state_stringbuilding_ctle_q_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -897,7 +912,9 @@ teco_state_stringbuilding_ctle_q_input(teco_machine_stringbuilding_t *ctx, gunic return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_q); +static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_q, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_q_input +); static teco_state_t * teco_state_stringbuilding_ctle_quote_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -929,7 +946,7 @@ teco_state_stringbuilding_ctle_quote_input(teco_machine_stringbuilding_t *ctx, g * in command line arguments anyway. * Otherwise, we'd have to implement our own POSIX shell escape function. */ - if (teco_string_contains(&str, '\0')) { + if (teco_string_contains(str, '\0')) { teco_error_qregcontainsnull_set(error, qreg->head.name.data, qreg->head.name.len, table != &teco_qreg_table_globals); return NULL; @@ -940,7 +957,9 @@ teco_state_stringbuilding_ctle_quote_input(teco_machine_stringbuilding_t *ctx, g return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_quote); +static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_quote, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_quote_input +); static teco_state_t * teco_state_stringbuilding_ctle_n_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) @@ -965,7 +984,7 @@ teco_state_stringbuilding_ctle_n_input(teco_machine_stringbuilding_t *ctx, gunic g_auto(teco_string_t) str = {NULL, 0}; if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error)) return NULL; - if (teco_string_contains(&str, '\0')) { + if (teco_string_contains(str, '\0')) { teco_error_qregcontainsnull_set(error, qreg->head.name.data, qreg->head.name.len, table != &teco_qreg_table_globals); return NULL; @@ -977,7 +996,9 @@ teco_state_stringbuilding_ctle_n_input(teco_machine_stringbuilding_t *ctx, gunic return &teco_state_stringbuilding_start; } -TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_n); +static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_n, + .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_n_input +); void teco_machine_stringbuilding_init(teco_machine_stringbuilding_t *ctx, gunichar escape_char, @@ -1115,11 +1136,11 @@ teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar chr, GError **e * so they may do their main activity in process_cb(). */ if (ctx->expectstring.insert_len && current->expectstring.process_cb && - !current->expectstring.process_cb(ctx, &ctx->expectstring.string, + !current->expectstring.process_cb(ctx, ctx->expectstring.string, ctx->expectstring.insert_len, error)) return NULL; - teco_state_t *next = current->expectstring.done_cb(ctx, &ctx->expectstring.string, error); + teco_state_t *next = current->expectstring.done_cb(ctx, ctx->expectstring.string, error); if (ctx->parent.must_undo) teco_undo_string_own(ctx->expectstring.string); @@ -1194,7 +1215,7 @@ teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **error) /* never calls process_cb() in parse-only mode */ if (ctx->expectstring.insert_len && current->expectstring.process_cb && - !current->expectstring.process_cb(ctx, &ctx->expectstring.string, + !current->expectstring.process_cb(ctx, ctx->expectstring.string, ctx->expectstring.insert_len, error)) return FALSE; @@ -1206,10 +1227,10 @@ teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **error) } gboolean -teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_string_t *str, +teco_state_expectfile_process(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error) { - g_assert(str->data != NULL); + g_assert(str.data != NULL); /* * Null-chars must not occur in filename/path strings and at some point @@ -1218,7 +1239,7 @@ teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_string_t *str * Doing it here ensures that teco_file_expand_path() can be safely called * from the done_cb(). */ - if (memchr(str->data + str->len - new_chars, '\0', new_chars)) { + if (memchr(str.data + str.len - new_chars, '\0', new_chars)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Null-character not allowed in filenames"); return FALSE; diff --git a/src/parser.h b/src/parser.h index 2308925..0c389cc 100644 --- a/src/parser.h +++ b/src/parser.h @@ -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 @@ -85,7 +85,7 @@ typedef const struct { * * Can be NULL if no interactive feedback is required. */ - gboolean (*process_cb)(teco_machine_main_t *ctx, const teco_string_t *str, + gboolean (*process_cb)(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error); /** @@ -93,7 +93,7 @@ typedef const struct { * Commands that don't give interactive feedback can use this callback * to perform their main processing. */ - teco_state_t *(*done_cb)(teco_machine_main_t *ctx, const teco_string_t *str, GError **error); + teco_state_t *(*done_cb)(teco_machine_main_t *ctx, teco_string_t str, GError **error); } teco_state_expectstring_t; typedef const struct { @@ -110,7 +110,7 @@ typedef gboolean (*teco_state_refresh_cb_t)(teco_machine_t *ctx, GError **error) typedef gboolean (*teco_state_end_of_macro_cb_t)(teco_machine_t *ctx, GError **error); typedef gboolean (*teco_state_process_edit_cmd_cb_t)(teco_machine_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -typedef gboolean (*teco_state_insert_completion_cb_t)(teco_machine_t *ctx, const teco_string_t *str, GError **error); +typedef gboolean (*teco_state_insert_completion_cb_t)(teco_machine_t *ctx, teco_string_t str, GError **error); typedef enum { TECO_KEYMACRO_MASK_START = (1 << 0), @@ -248,15 +248,18 @@ gboolean teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent * @implements teco_state_t * @ingroup states * - * @todo Should we eliminate required callbacks, this could be turned into a - * struct initializer TECO_INIT_STATE() and TECO_DECLARE_STATE() would become pointless. - * This would also ease declaring static states. + * Base class of all states. + * + * Since states are constant, you can append static assertions for required callbacks + * and other conditions. + * You should use TECO_ASSERT_SAFE(), but it won't be checked on all supported compilers. + * You should not put anything in front of the definition, though, so you + * can write `static TECO_DEFINE_STATE(...)`. */ #define TECO_DEFINE_STATE(NAME, ...) \ /** @ingroup states */ \ teco_state_t NAME = { \ .initial_cb = NULL, /* do nothing */ \ - .input_cb = (teco_state_input_cb_t)NAME##_input, /* always required */ \ .refresh_cb = NULL, /* do nothing */ \ .end_of_macro_cb = teco_state_end_of_macro, \ .process_edit_cmd_cb = teco_state_process_edit_cmd, \ @@ -265,11 +268,8 @@ gboolean teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent .keymacro_mask = TECO_KEYMACRO_MASK_DEFAULT, \ .style = SCE_SCITECO_DEFAULT, \ ##__VA_ARGS__ \ - } - -/** @ingroup states */ -#define TECO_DECLARE_STATE(NAME) \ - extern teco_state_t NAME + }; \ + TECO_ASSERT_SAFE(NAME.input_cb != NULL) /* in cmdline.c */ gboolean teco_state_caseinsensitive_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error); @@ -599,7 +599,7 @@ gboolean teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **erro /* in cmdline.c */ gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, +gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /** @@ -610,18 +610,11 @@ gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, con * Super-class for states accepting string arguments * Opaquely cares about alternative-escape characters, * string building commands and accumulation into a string - * - * @note Generating the input_cb could be avoided if there were a default - * implementation. */ #define TECO_DEFINE_STATE_EXPECTSTRING(NAME, ...) \ - static teco_state_t * \ - NAME##_input(teco_machine_main_t *ctx, gunichar chr, GError **error) \ - { \ - return teco_state_expectstring_input(ctx, chr, error); \ - } \ TECO_DEFINE_STATE(NAME, \ .initial_cb = (teco_state_initial_cb_t)teco_state_expectstring_initial, \ + .input_cb = (teco_state_input_cb_t)teco_state_expectstring_input, \ .refresh_cb = (teco_state_refresh_cb_t)teco_state_expectstring_refresh, \ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \ teco_state_expectstring_process_edit_cmd, \ @@ -631,17 +624,17 @@ gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, con .style = SCE_SCITECO_STRING, \ .expectstring.string_building = TRUE, \ .expectstring.last = TRUE, \ - .expectstring.process_cb = NULL, /* do nothing */ \ - .expectstring.done_cb = NAME##_done, /* always required */ \ + .expectstring.process_cb = NULL, /* do nothing */ \ ##__VA_ARGS__ \ - ) + ); \ + TECO_ASSERT_SAFE(NAME.expectstring.done_cb != NULL) -gboolean teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_string_t *str, +gboolean teco_state_expectfile_process(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error); /* in cmdline.c */ gboolean teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error); +gboolean teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /** * @interface TECO_DEFINE_STATE_EXPECTFILE @@ -660,7 +653,7 @@ gboolean teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const /* in cmdline.c */ gboolean teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error); +gboolean teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /** * @interface TECO_DEFINE_STATE_EXPECTDIR diff --git a/src/qreg-commands.c b/src/qreg-commands.c index 83a45b1..4ede403 100644 --- a/src/qreg-commands.c +++ b/src/qreg-commands.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 @@ -91,7 +91,9 @@ teco_state_pushqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, * Save Q-Register <q> contents on the global Q-Register push-down * stack. */ -TECO_DEFINE_STATE_EXPECTQREG(teco_state_pushqreg); +TECO_DEFINE_STATE_EXPECTQREG(teco_state_pushqreg, + .expectqreg.got_register_cb = teco_state_pushqreg_got_register +); static teco_state_t * teco_state_popqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, @@ -128,7 +130,8 @@ teco_state_popqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, * Memory is reclaimed on command-line termination. */ TECO_DEFINE_STATE_EXPECTQREG(teco_state_popqreg, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_popqreg_got_register ); static teco_state_t * @@ -142,11 +145,12 @@ teco_state_eqcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, } TECO_DEFINE_STATE_EXPECTQREG(teco_state_eqcommand, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_eqcommand_got_register ); static teco_state_t * -teco_state_loadqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_loadqreg_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_qreg_t *qreg; teco_qreg_table_t *table; @@ -157,9 +161,9 @@ teco_state_loadqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - if (str->len > 0) { + if (str.len > 0) { /* Load file into Q-Register */ - g_autofree gchar *filename = teco_file_expand_path(str->data); + g_autofree gchar *filename = teco_file_expand_path(str.data); if (!qreg->vtable->load(qreg, filename, error)) return NULL; } else { @@ -187,7 +191,9 @@ teco_state_loadqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr * Undefined Q-Registers will be defined. * The command fails if <file> could not be read. */ -TECO_DEFINE_STATE_EXPECTFILE(teco_state_loadqreg); +TECO_DEFINE_STATE_EXPECTFILE(teco_state_loadqreg, + .expectstring.done_cb = teco_state_loadqreg_done +); static teco_state_t * teco_state_epctcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, @@ -199,10 +205,12 @@ teco_state_epctcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, return &teco_state_saveqreg; } -TECO_DEFINE_STATE_EXPECTQREG(teco_state_epctcommand); +TECO_DEFINE_STATE_EXPECTQREG(teco_state_epctcommand, + .expectqreg.got_register_cb = teco_state_epctcommand_got_register +); static teco_state_t * -teco_state_saveqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_saveqreg_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { teco_qreg_t *qreg; @@ -212,7 +220,7 @@ teco_state_saveqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - g_autofree gchar *filename = teco_file_expand_path(str->data); + g_autofree gchar *filename = teco_file_expand_path(str.data); return qreg->vtable->save(qreg, filename, error) ? &teco_state_start : NULL; } @@ -231,7 +239,9 @@ teco_state_saveqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr * File names may also be tab-completed and string building * characters are enabled by default. */ -TECO_DEFINE_STATE_EXPECTFILE(teco_state_saveqreg); +TECO_DEFINE_STATE_EXPECTFILE(teco_state_saveqreg, + .expectstring.done_cb = teco_state_saveqreg_done +); static gboolean teco_state_queryqreg_initial(teco_machine_main_t *ctx, GError **error) @@ -346,7 +356,8 @@ teco_state_queryqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, * boolean. */ TECO_DEFINE_STATE_EXPECTQREG(teco_state_queryqreg, - .initial_cb = (teco_state_initial_cb_t)teco_state_queryqreg_initial + .initial_cb = (teco_state_initial_cb_t)teco_state_queryqreg_initial, + .expectqreg.got_register_cb = teco_state_queryqreg_got_register ); static teco_state_t * @@ -360,12 +371,13 @@ teco_state_ctlucommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, } TECO_DEFINE_STATE_EXPECTQREG(teco_state_ctlucommand, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_ctlucommand_got_register ); static teco_state_t * teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx, - const teco_string_t *str, GError **error) + teco_string_t str, GError **error) { teco_qreg_t *qreg; @@ -434,12 +446,12 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx, if (args > 0 || colon_modified) { /* append to register */ - if (!qreg->vtable->append_string(qreg, str->data, str->len, error)) + if (!qreg->vtable->append_string(qreg, str.data, str.len, error)) return NULL; } else { /* set register */ if (!qreg->vtable->undo_set_string(qreg, error) || - !qreg->vtable->set_string(qreg, str->data, str->len, + !qreg->vtable->set_string(qreg, str.data, str.len, teco_default_codepage(), error)) return NULL; } @@ -474,7 +486,8 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx, * is desired. */ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_setqregstring_nobuilding, - .expectstring.string_building = FALSE + .expectstring.string_building = FALSE, + .expectstring.done_cb = teco_state_setqregstring_nobuilding_done ); static teco_state_t * @@ -488,7 +501,8 @@ teco_state_eucommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, } TECO_DEFINE_STATE_EXPECTQREG(teco_state_eucommand, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_eucommand_got_register ); static gboolean @@ -511,12 +525,6 @@ teco_state_setqregstring_building_initial(teco_machine_main_t *ctx, GError **err return TRUE; } -static teco_state_t * -teco_state_setqregstring_building_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) -{ - return teco_state_setqregstring_nobuilding_done(ctx, str, error); -} - /*$ "EU" "EUq" ":EUq" * [c1,c2,...]EUq[string]$ -- Set or append to Q-Register string with string building characters * [c1,c2,...]:EUq[string]$ @@ -529,7 +537,8 @@ teco_state_setqregstring_building_done(teco_machine_main_t *ctx, const teco_stri */ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_setqregstring_building, .initial_cb = (teco_state_initial_cb_t)teco_state_setqregstring_building_initial, - .expectstring.string_building = TRUE + .expectstring.string_building = TRUE, + .expectstring.done_cb = teco_state_setqregstring_nobuilding_done ); static teco_state_t * @@ -582,7 +591,9 @@ teco_state_getqregstring_got_register(teco_machine_main_t *ctx, teco_qreg_t *qre * * Specifying an undefined <q> yields an error. */ -TECO_DEFINE_STATE_EXPECTQREG(teco_state_getqregstring); +TECO_DEFINE_STATE_EXPECTQREG(teco_state_getqregstring, + .expectqreg.got_register_cb = teco_state_getqregstring_got_register +); static teco_state_t * teco_state_setqreginteger_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, @@ -631,7 +642,8 @@ teco_state_setqreginteger_got_register(teco_machine_main_t *ctx, teco_qreg_t *qr * The register is defined if it does not exist. */ TECO_DEFINE_STATE_EXPECTQREG(teco_state_setqreginteger, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_setqreginteger_got_register ); static teco_state_t * @@ -665,7 +677,8 @@ teco_state_increaseqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg * <q> will be defined if it does not exist. */ TECO_DEFINE_STATE_EXPECTQREG(teco_state_increaseqreg, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_increaseqreg_got_register ); static teco_state_t * @@ -724,15 +737,17 @@ teco_state_macro_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, * (as reported by \fBEE\fP), its contents must be and are checked to be in * valid UTF-8. */ -TECO_DEFINE_STATE_EXPECTQREG(teco_state_macro); +TECO_DEFINE_STATE_EXPECTQREG(teco_state_macro, + .expectqreg.got_register_cb = teco_state_macro_got_register +); static teco_state_t * -teco_state_indirect_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_indirect_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - g_autofree gchar *filename = teco_file_expand_path(str->data); + g_autofree gchar *filename = teco_file_expand_path(str.data); if (teco_machine_main_eval_colon(ctx) > 0) { /* don't create new local Q-Registers if colon modifier is given */ @@ -762,7 +777,9 @@ teco_state_indirect_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr * As all \*(ST code, the contents of <file> must be in valid UTF-8 * even if operating in the \(lqdefault ANSI\(rq mode as configured by \fBED\fP. */ -TECO_DEFINE_STATE_EXPECTFILE(teco_state_indirect); +TECO_DEFINE_STATE_EXPECTFILE(teco_state_indirect, + .expectstring.done_cb = teco_state_indirect_done +); static teco_state_t * teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, @@ -865,5 +882,6 @@ teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, * Register <q> will be created if it is undefined. */ TECO_DEFINE_STATE_EXPECTQREG(teco_state_copytoqreg, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_copytoqreg_got_register ); diff --git a/src/qreg-commands.h b/src/qreg-commands.h index e224797..51f792b 100644 --- a/src/qreg-commands.h +++ b/src/qreg-commands.h @@ -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 @@ -40,7 +40,7 @@ teco_state_t *teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr /* in cmdline.c */ gboolean teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, +gboolean teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /** @@ -51,48 +51,44 @@ gboolean teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const * Super class for states accepting Q-Register specifications. */ #define TECO_DEFINE_STATE_EXPECTQREG(NAME, ...) \ - static teco_state_t * \ - NAME##_input(teco_machine_main_t *ctx, gunichar chr, GError **error) \ - { \ - return teco_state_expectqreg_input(ctx, chr, error); \ - } \ TECO_DEFINE_STATE(NAME, \ .initial_cb = (teco_state_initial_cb_t)teco_state_expectqreg_initial, \ + .input_cb = (teco_state_input_cb_t)teco_state_expectqreg_input, \ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \ teco_state_expectqreg_process_edit_cmd, \ .insert_completion_cb = (teco_state_insert_completion_cb_t) \ teco_state_expectqreg_insert_completion, \ .style = SCE_SCITECO_QREG, \ .expectqreg.type = TECO_QREG_REQUIRED, \ - .expectqreg.got_register_cb = NAME##_got_register, /* always required */ \ ##__VA_ARGS__ \ - ) + ); \ + TECO_ASSERT_SAFE(NAME.expectqreg.got_register_cb != NULL) /* * FIXME: Some of these states are referenced only in qreg-commands.c, * so they should be moved there? */ -TECO_DECLARE_STATE(teco_state_pushqreg); -TECO_DECLARE_STATE(teco_state_popqreg); +extern teco_state_t teco_state_pushqreg; +extern teco_state_t teco_state_popqreg; -TECO_DECLARE_STATE(teco_state_eqcommand); -TECO_DECLARE_STATE(teco_state_loadqreg); +extern teco_state_t teco_state_eqcommand; +extern teco_state_t teco_state_loadqreg; -TECO_DECLARE_STATE(teco_state_epctcommand); -TECO_DECLARE_STATE(teco_state_saveqreg); +extern teco_state_t teco_state_epctcommand; +extern teco_state_t teco_state_saveqreg; -TECO_DECLARE_STATE(teco_state_queryqreg); +extern teco_state_t teco_state_queryqreg; -TECO_DECLARE_STATE(teco_state_ctlucommand); -TECO_DECLARE_STATE(teco_state_setqregstring_nobuilding); -TECO_DECLARE_STATE(teco_state_eucommand); -TECO_DECLARE_STATE(teco_state_setqregstring_building); +extern teco_state_t teco_state_ctlucommand; +extern teco_state_t teco_state_setqregstring_nobuilding; +extern teco_state_t teco_state_eucommand; +extern teco_state_t teco_state_setqregstring_building; -TECO_DECLARE_STATE(teco_state_getqregstring); -TECO_DECLARE_STATE(teco_state_setqreginteger); -TECO_DECLARE_STATE(teco_state_increaseqreg); +extern teco_state_t teco_state_getqregstring; +extern teco_state_t teco_state_setqreginteger; +extern teco_state_t teco_state_increaseqreg; -TECO_DECLARE_STATE(teco_state_macro); -TECO_DECLARE_STATE(teco_state_indirect); +extern teco_state_t teco_state_macro; +extern teco_state_t teco_state_indirect; -TECO_DECLARE_STATE(teco_state_copytoqreg); +extern teco_state_t teco_state_copytoqreg; @@ -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 @@ -732,7 +732,7 @@ teco_qreg_workingdir_set_string(teco_qreg_t *qreg, const gchar *str, gsize len, g_auto(teco_string_t) dir; teco_string_init(&dir, str, len); - if (teco_string_contains(&dir, '\0')) { + if (teco_string_contains(dir, '\0')) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Directory contains null-character"); return FALSE; @@ -1107,7 +1107,7 @@ teco_qreg_table_get_environ(teco_qreg_table_t *table, GError **error) for (teco_qreg_t *cur = first; cur && cur->head.name.data[0] == '$'; cur = (teco_qreg_t *)teco_rb3str_get_next(&cur->head)) { - const teco_string_t *name = &cur->head.name; + const teco_string_t name = cur->head.name; /* * Ignore the "$" register (not an environment @@ -1115,7 +1115,7 @@ teco_qreg_table_get_environ(teco_qreg_table_t *table, GError **error) * name contains "=" or null (not allowed in environment * variable names). */ - if (name->len == 1 || + if (name.len == 1 || teco_string_contains(name, '=') || teco_string_contains(name, '\0')) continue; @@ -1124,16 +1124,16 @@ teco_qreg_table_get_environ(teco_qreg_table_t *table, GError **error) g_strfreev(envp); return NULL; } - if (teco_string_contains(&value, '\0')) { + if (teco_string_contains(value, '\0')) { g_strfreev(envp); g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Environment register \"%s\" must not contain null characters", - name->data); + name.data); return NULL; } /* more efficient than g_environ_setenv() */ - *p++ = g_strconcat(name->data+1, "=", value.data, NULL); + *p++ = g_strconcat(name.data+1, "=", value.data, NULL); } *p = NULL; @@ -1377,15 +1377,12 @@ TECO_DEFINE_UNDO_SCALAR(teco_machine_qregspec_flags_t); #define teco_undo_qregspec_flags(VAR) \ (*teco_undo_object_teco_machine_qregspec_flags_t_push(&(VAR))) -/* - * FIXME: All teco_state_qregspec_* states could be static? - */ -TECO_DECLARE_STATE(teco_state_qregspec_start); -TECO_DECLARE_STATE(teco_state_qregspec_start_global); -TECO_DECLARE_STATE(teco_state_qregspec_caret); -TECO_DECLARE_STATE(teco_state_qregspec_firstchar); -TECO_DECLARE_STATE(teco_state_qregspec_secondchar); -TECO_DECLARE_STATE(teco_state_qregspec_string); +static teco_state_t teco_state_qregspec_start; +static teco_state_t teco_state_qregspec_start_global; +static teco_state_t teco_state_qregspec_caret; +static teco_state_t teco_state_qregspec_firstchar; +static teco_state_t teco_state_qregspec_secondchar; +static teco_state_t teco_state_qregspec_string; static teco_state_t *teco_state_qregspec_start_global_input(teco_machine_qregspec_t *ctx, gunichar chr, GError **error); @@ -1442,11 +1439,12 @@ teco_state_qregspec_start_input(teco_machine_qregspec_t *ctx, gunichar chr, GErr /* in cmdline.c */ gboolean teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, +gboolean teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, teco_string_t str, GError **error); -TECO_DEFINE_STATE(teco_state_qregspec_start, +static TECO_DEFINE_STATE(teco_state_qregspec_start, .is_start = TRUE, + .input_cb = (teco_state_input_cb_t)teco_state_qregspec_start_input, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd, .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_qregspec_insert_completion ); @@ -1482,7 +1480,8 @@ teco_state_qregspec_start_global_input(teco_machine_qregspec_t *ctx, gunichar ch * Alternatively, we'd have to introduce a teco_machine_qregspec_t::status attribute. * Or even better, why not use special pointers like ((teco_state_t *)"teco_state_qregspec_done")? */ -TECO_DEFINE_STATE(teco_state_qregspec_start_global, +static TECO_DEFINE_STATE(teco_state_qregspec_start_global, + .input_cb = (teco_state_input_cb_t)teco_state_qregspec_start_global_input, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd ); @@ -1503,7 +1502,9 @@ teco_state_qregspec_caret_input(teco_machine_qregspec_t *ctx, gunichar chr, GErr return teco_state_qregspec_done(ctx, error); } -TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_qregspec_caret); +static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_qregspec_caret, + .input_cb = (teco_state_input_cb_t)teco_state_qregspec_caret_input +); static teco_state_t * teco_state_qregspec_firstchar_input(teco_machine_qregspec_t *ctx, gunichar chr, GError **error) @@ -1519,7 +1520,8 @@ teco_state_qregspec_firstchar_input(teco_machine_qregspec_t *ctx, gunichar chr, return &teco_state_qregspec_secondchar; } -TECO_DEFINE_STATE(teco_state_qregspec_firstchar, +static TECO_DEFINE_STATE(teco_state_qregspec_firstchar, + .input_cb = (teco_state_input_cb_t)teco_state_qregspec_firstchar_input, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd ); @@ -1537,7 +1539,8 @@ teco_state_qregspec_secondchar_input(teco_machine_qregspec_t *ctx, gunichar chr, return teco_state_qregspec_done(ctx, error); } -TECO_DEFINE_STATE(teco_state_qregspec_secondchar, +static TECO_DEFINE_STATE(teco_state_qregspec_secondchar, + .input_cb = (teco_state_input_cb_t)teco_state_qregspec_secondchar_input, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd ); @@ -1584,10 +1587,11 @@ teco_state_qregspec_string_input(teco_machine_qregspec_t *ctx, gunichar chr, GEr /* in cmdline.c */ gboolean teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, +gboolean teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, teco_string_t str, GError **error); -TECO_DEFINE_STATE(teco_state_qregspec_string, +static TECO_DEFINE_STATE(teco_state_qregspec_string, + .input_cb = (teco_state_input_cb_t)teco_state_qregspec_string_input, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_string_process_edit_cmd, .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_qregspec_string_insert_completion ); @@ -1690,11 +1694,7 @@ teco_machine_qregspec_auto_complete(teco_machine_qregspec_t *ctx, teco_string_t /* two-letter Q-Reg */ restrict_len = 2; - /* - * FIXME: This is not quite right as it will propose even - * lower case single or two-letter Q-Register names. - */ - return teco_rb3str_auto_complete(&ctx->result_table->tree, !restrict_len, + return teco_rb3str_auto_complete(&ctx->result_table->tree, TRUE, ctx->name.data, ctx->name.len, restrict_len, insert) && ctx->nesting == 1; } @@ -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 diff --git a/src/rb3str.c b/src/rb3str.c index 276b624..f4c16fa 100644 --- a/src/rb3str.c +++ b/src/rb3str.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 @@ -34,13 +34,13 @@ static gint teco_rb3str_cmp(const teco_rb3str_head_t *head, const teco_string_t *data) { - return teco_string_cmp(&head->key, data->data, data->len); + return teco_string_cmp(head->key, data->data, data->len); } static gint teco_rb3str_casecmp(const teco_rb3str_head_t *head, const teco_string_t *data) { - return teco_string_casecmp(&head->key, data->data, data->len); + return teco_string_casecmp(head->key, data->data, data->len); } /** @memberof teco_rb3str_tree_t */ @@ -113,7 +113,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive, guint prefixed_entries = 0; for (teco_rb3str_head_t *cur = teco_rb3str_nfind(tree, case_sensitive, str, str_len); - cur && cur->key.len >= str_len && diff(&cur->key, str, str_len) == str_len; + cur && cur->key.len >= str_len && diff(cur->key, str, str_len) == str_len; cur = teco_rb3str_get_next(cur)) { if (restrict_len && g_utf8_strlen(cur->key.data, cur->key.len) != restrict_len) continue; @@ -122,7 +122,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive, first = cur; prefix_len = cur->key.len - str_len; } else { - gsize len = diff(&cur->key, first->key.data, first->key.len) - str_len; + gsize len = diff(cur->key, first->key.data, first->key.len) - str_len; if (len < prefix_len) prefix_len = len; } @@ -134,7 +134,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive, teco_string_init(insert, first->key.data + str_len, prefix_len); } else if (prefixed_entries > 1) { for (teco_rb3str_head_t *cur = first; - cur && cur->key.len >= str_len && diff(&cur->key, str, str_len) == str_len; + cur && cur->key.len >= str_len && diff(cur->key, str, str_len) == str_len; cur = teco_rb3str_get_next(cur)) { if (restrict_len && g_utf8_strlen(cur->key.data, cur->key.len) != restrict_len) continue; diff --git a/src/rb3str.h b/src/rb3str.h index 466cf90..00d1791 100644 --- a/src/rb3str.h +++ b/src/rb3str.h @@ -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 @@ -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 @@ -118,11 +118,11 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error) */ if (ctx == teco_ring_current && !teco_qreg_current) undo__teco_interface_info_update_buffer(ctx); - if (ctx->state == TECO_BUFFER_DIRTY_DUMPED) { + if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP) { g_autofree gchar *filename_recovery = teco_buffer_get_recovery(ctx); g_unlink(filename_recovery); /* on rubout, we do not restore the recovery file */ - ctx->state = TECO_BUFFER_DIRTY; + ctx->state = TECO_BUFFER_DIRTY_NO_DUMP; } teco_undo_guint(ctx->state) = TECO_BUFFER_CLEAN; @@ -143,7 +143,7 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error) static inline void teco_buffer_free(teco_buffer_t *ctx) { - if (ctx->state == TECO_BUFFER_DIRTY_DUMPED) { + if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP) { g_autofree gchar *filename_recovery = teco_buffer_get_recovery(ctx); g_unlink(filename_recovery); } @@ -244,7 +244,7 @@ teco_ring_find_by_id(teco_int_t id) static void teco_ring_undirtify(void) { - if (teco_ring_current->state == TECO_BUFFER_DIRTY_DUMPED) { + if (teco_ring_current->state > TECO_BUFFER_DIRTY_NO_DUMP) { g_autofree gchar *filename_recovery = teco_buffer_get_recovery(teco_ring_current); g_unlink(filename_recovery); } @@ -261,17 +261,18 @@ teco_ring_dirtify(void) if (teco_qreg_current) return; - teco_buffer_state_t old_state = teco_ring_current->state; - teco_ring_current->state = TECO_BUFFER_DIRTY; - switch (old_state) { + switch ((teco_buffer_state_t)teco_ring_current->state) { case TECO_BUFFER_CLEAN: + teco_ring_current->state = TECO_BUFFER_DIRTY_NO_DUMP; teco_interface_info_update(teco_ring_current); undo__teco_ring_undirtify(); break; - case TECO_BUFFER_DIRTY: + case TECO_BUFFER_DIRTY_NO_DUMP: + case TECO_BUFFER_DIRTY_OUTDATED_DUMP: break; - case TECO_BUFFER_DIRTY_DUMPED: - /* set to TECO_BUFFER_DIRTY on rubout */ + case TECO_BUFFER_DIRTY_RECENT_DUMP: + teco_ring_current->state = TECO_BUFFER_DIRTY_OUTDATED_DUMP; + /* set to TECO_BUFFER_DIRTY_OUTDATED_DUMP on rubout */ teco_undo_guint(teco_ring_current->state); break; } @@ -327,12 +328,16 @@ teco_ring_dump_recovery(void) for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { teco_buffer_t *buffer = (teco_buffer_t *)cur; + /* already dumped buffers don't have to be written again */ + if (buffer->state != TECO_BUFFER_DIRTY_NO_DUMP && + buffer->state != TECO_BUFFER_DIRTY_OUTDATED_DUMP) + continue; + /* * Dirty unnamed buffers cannot be backed up. - * Already backed-up buffers don't have to be written again. - * FIXME: Perhaps they should be under ~/UNNAMED~? + * FIXME: Perhaps they should be dumped under ~/#UNNAMED#? */ - if (buffer->state != TECO_BUFFER_DIRTY || !buffer->filename) + if (!buffer->filename) continue; g_autofree gchar *filename_recovery = teco_buffer_get_recovery(buffer); @@ -357,7 +362,7 @@ teco_ring_dump_recovery(void) if (!teco_view_save_to_channel(buffer->view, channel, NULL)) continue; - buffer->state = TECO_BUFFER_DIRTY_DUMPED; + buffer->state = TECO_BUFFER_DIRTY_RECENT_DUMP; } } @@ -517,11 +522,10 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error) ctx->flags.allow_filename = TRUE; if (id == 0) { - for (teco_buffer_t *cur = teco_ring_first(); cur; cur = teco_buffer_next(cur)) { - const gchar *filename = cur->filename ? : "(Unnamed)"; - teco_interface_popup_add(TECO_POPUP_FILE, filename, - strlen(filename), cur == teco_ring_current); - } + for (teco_buffer_t *cur = teco_ring_first(); cur; cur = teco_buffer_next(cur)) + teco_interface_popup_add(TECO_POPUP_FILE, cur->filename, + cur->filename ? strlen(cur->filename) : 0, + cur == teco_ring_current); teco_interface_popup_show(0); } else if (id > 0) { @@ -535,7 +539,7 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error) } gboolean -teco_state_edit_file_process(teco_machine_main_t *ctx, const teco_string_t *str, +teco_state_edit_file_process(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error) { g_assert(new_chars > 0); @@ -551,18 +555,18 @@ teco_state_edit_file_process(teco_machine_main_t *ctx, const teco_string_t *str, } static teco_state_t * -teco_state_edit_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_edit_file_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; if (!ctx->flags.allow_filename) { - /* process_cb() already throws error if str->len > 0 */ - g_assert(str->len == 0); + /* process_cb() already throws error if str.len > 0 */ + g_assert(str.len == 0); return &teco_state_start; } - g_autofree gchar *filename = teco_file_expand_path(str->data); + g_autofree gchar *filename = teco_file_expand_path(str.data); if (teco_globber_is_pattern(filename)) { g_auto(teco_globber_t) globber; teco_globber_init(&globber, filename, G_FILE_TEST_IS_REGULAR); @@ -640,11 +644,12 @@ teco_state_edit_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE */ TECO_DEFINE_STATE_EXPECTGLOB(teco_state_edit_file, .initial_cb = (teco_state_initial_cb_t)teco_state_edit_file_initial, - .expectstring.process_cb = teco_state_edit_file_process + .expectstring.process_cb = teco_state_edit_file_process, + .expectstring.done_cb = teco_state_edit_file_done ); static teco_state_t * -teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_save_file_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -652,7 +657,7 @@ teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE if (!teco_expressions_eval(FALSE, error)) return NULL; - g_autofree gchar *filename = teco_file_expand_path(str->data); + g_autofree gchar *filename = teco_file_expand_path(str.data); /* * This is like implying teco_ring_get_id(teco_ring_current) @@ -715,22 +720,29 @@ teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE * File names may also be tab-completed and string building * characters are enabled by default. */ -TECO_DEFINE_STATE_EXPECTFILE(teco_state_save_file); +TECO_DEFINE_STATE_EXPECTFILE(teco_state_save_file, + .expectstring.done_cb = teco_state_save_file_done +); static teco_state_t * -teco_state_read_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_read_file_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); - g_autofree gchar *filename = teco_file_expand_path(str->data); + g_autofree gchar *filename = teco_file_expand_path(str.data); /* FIXME: Add wrapper to interface.h? */ if (!teco_view_load(teco_interface_current_view, filename, FALSE, error)) return NULL; - if (teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0) != pos) { + pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + teco_undo_guint(teco_ranges_count) = 1; + + if (teco_ranges[0].from != teco_ranges[0].to) { teco_ring_dirtify(); if (teco_current_doc_must_undo()) @@ -749,7 +761,9 @@ teco_state_read_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE /* * NOTE: Video TECO allows glob patterns as an argument. */ -TECO_DEFINE_STATE_EXPECTFILE(teco_state_read_file); +TECO_DEFINE_STATE_EXPECTFILE(teco_state_read_file, + .expectstring.done_cb = teco_state_read_file_done +); /*$ "EF" ":EF" close * [n]EF -- Remove buffer from ring @@ -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 @@ -26,11 +26,14 @@ #include "list.h" typedef enum { + /** buffer is freshly opened or saved */ TECO_BUFFER_CLEAN = 0, - /** buffer modified */ - TECO_BUFFER_DIRTY, - /** buffer modified and recovery file already written */ - TECO_BUFFER_DIRTY_DUMPED + /** buffer modified, but a recovery file does not yet exist */ + TECO_BUFFER_DIRTY_NO_DUMP, + /** buffer modified, recovery file outdated */ + TECO_BUFFER_DIRTY_OUTDATED_DUMP, + /** buffer modified and recovery file is up to date */ + TECO_BUFFER_DIRTY_RECENT_DUMP } teco_buffer_state_t; typedef struct teco_buffer_t { @@ -114,9 +117,9 @@ void teco_ring_cleanup(void); * Command states */ -TECO_DECLARE_STATE(teco_state_edit_file); -TECO_DECLARE_STATE(teco_state_save_file); -TECO_DECLARE_STATE(teco_state_read_file); +extern teco_state_t teco_state_edit_file; +extern teco_state_t teco_state_save_file; +extern teco_state_t teco_state_read_file; void teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error); diff --git a/src/sciteco.h b/src/sciteco.h index 388d6c4..16dba69 100644 --- a/src/sciteco.h +++ b/src/sciteco.h @@ -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 @@ -117,6 +117,16 @@ extern volatile sig_atomic_t teco_interrupted; */ G_DEFINE_AUTOPTR_CLEANUP_FUNC(FILE, fclose); +/** + * A "safe" compile-time assertion, which also passes if the expression is not constant. + * + * Can be useful since different compilers have different ideas about what's a constant expression. + * In particular GCC does not treat `static const` objects as constant (in the way it qualifies + * for _Static_assert()), while constexpr is only available since C23. + */ +#define TECO_ASSERT_SAFE(EXPR) \ + G_STATIC_ASSERT(!__builtin_constant_p(EXPR) || (EXPR)) + /* * BEWARE DRAGONS! */ diff --git a/src/search.c b/src/search.c index 5ef2179..856d079 100644 --- a/src/search.c +++ b/src/search.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 @@ -537,7 +537,7 @@ teco_pattern2regexp(teco_string_t *pattern, teco_machine_qregspec_t *qreg_machin if (state == TECO_SEARCH_STATE_ALT) teco_string_append_c(&re, ')'); - g_assert(!teco_string_contains(&re, '\0')); + g_assert(!teco_string_contains(re, '\0')); return g_steal_pointer(&re.data) ? : g_strdup(""); } @@ -696,7 +696,7 @@ teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) } static gboolean -teco_state_search_process(teco_machine_main_t *ctx, const teco_string_t *str, gsize new_chars, GError **error) +teco_state_search_process(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error) { /* FIXME: Should G_REGEX_OPTIMIZE be added under certain circumstances? */ GRegexCompileFlags flags = G_REGEX_MULTILINE | G_REGEX_DOTALL; @@ -741,10 +741,9 @@ teco_state_search_process(teco_machine_main_t *ctx, const teco_string_t *str, gs qreg_machine = teco_machine_qregspec_new(TECO_QREG_REQUIRED, ctx->qreg_table_locals, FALSE); g_autoptr(GRegex) re = NULL; - teco_string_t pattern = *str; g_autofree gchar *re_pattern; /* NOTE: teco_pattern2regexp() modifies str pointer */ - re_pattern = teco_pattern2regexp(&pattern, qreg_machine, + re_pattern = teco_pattern2regexp(&str, qreg_machine, ctx->expectstring.machine.codepage, FALSE, error); if (!re_pattern) return FALSE; @@ -829,7 +828,7 @@ failure: } static teco_state_t * -teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_search_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -837,14 +836,14 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro teco_qreg_t *search_reg = teco_qreg_table_find(&teco_qreg_table_globals, "_", 1); g_assert(search_reg != NULL); - if (str->len > 0) { + if (str.len > 0) { /* workaround: preserve selection (also on rubout) */ gint anchor = teco_interface_ssm(SCI_GETANCHOR, 0, 0); if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_SETANCHOR, anchor, 0); if (!search_reg->vtable->undo_set_string(search_reg, error) || - !search_reg->vtable->set_string(search_reg, str->data, str->len, + !search_reg->vtable->set_string(search_reg, str.data, str.len, teco_default_codepage(), error)) return NULL; @@ -853,7 +852,7 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro g_auto(teco_string_t) search_str = {NULL, 0}; if (!search_reg->vtable->get_string(search_reg, &search_str.data, &search_str.len, NULL, error) || - !teco_state_search_process(ctx, &search_str, search_str.len, error)) + !teco_state_search_process(ctx, search_str, search_str.len, error)) return NULL; } @@ -882,7 +881,6 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro TECO_DEFINE_STATE_EXPECTSTRING(NAME, \ .initial_cb = (teco_state_initial_cb_t)teco_state_search_initial, \ .expectstring.process_cb = teco_state_search_process, \ - .expectstring.done_cb = NAME##_done, \ ##__VA_ARGS__ \ ) @@ -956,7 +954,9 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro * Changing the <pattern> results in the search being reperformed * from the beginning. */ -TECO_DEFINE_STATE_SEARCH(teco_state_search); +TECO_DEFINE_STATE_SEARCH(teco_state_search, + .expectstring.done_cb = teco_state_search_done +); static gboolean teco_state_search_all_initial(teco_machine_main_t *ctx, GError **error) @@ -1015,7 +1015,7 @@ teco_state_search_all_initial(teco_machine_main_t *ctx, GError **error) } static teco_state_t * -teco_state_search_all_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_search_all_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -1074,11 +1074,12 @@ teco_state_search_all_done(teco_machine_main_t *ctx, const teco_string_t *str, G * This is probably not very useful in practice, so it's not documented. */ TECO_DEFINE_STATE_SEARCH(teco_state_search_all, - .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial + .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial, + .expectstring.done_cb = teco_state_search_all_done ); static teco_state_t * -teco_state_search_kill_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_search_kill_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -1157,10 +1158,12 @@ teco_state_search_kill_done(teco_machine_main_t *ctx, const teco_string_t *str, /* * ::FK is possible but doesn't make much sense, so it's undocumented. */ -TECO_DEFINE_STATE_SEARCH(teco_state_search_kill); +TECO_DEFINE_STATE_SEARCH(teco_state_search_kill, + .expectstring.done_cb = teco_state_search_kill_done +); static teco_state_t * -teco_state_search_delete_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_search_delete_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -1200,37 +1203,50 @@ teco_state_search_delete_done(teco_machine_main_t *ctx, const teco_string_t *str * Searches for <pattern> just like the regular search command * (\fBS\fP) but when found deletes the entire occurrence. */ -TECO_DEFINE_STATE_SEARCH(teco_state_search_delete); +TECO_DEFINE_STATE_SEARCH(teco_state_search_delete, + .expectstring.done_cb = teco_state_search_delete_done +); static gboolean teco_state_replace_insert_initial(teco_machine_main_t *ctx, GError **error) { - if (ctx->flags.mode == TECO_MODE_NORMAL) - teco_machine_stringbuilding_set_codepage(&ctx->expectstring.machine, - teco_interface_get_codepage()); + if (ctx->flags.mode > TECO_MODE_NORMAL) + return TRUE; + + /* + * Overwrites teco_ranges set by the preceding search. + * FIXME: Wastes undo tokens in teco_do_search(). + * Perhaps make this configurable in the state. + */ + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + teco_undo_guint(teco_ranges_count) = 1; + + /* + * Current document's encoding determines the behaviour of + * string building constructs. + */ + teco_machine_stringbuilding_set_codepage(&ctx->expectstring.machine, + teco_interface_get_codepage()); return TRUE; } -/* - * FIXME: Could be static - */ -TECO_DEFINE_STATE_INSERT(teco_state_replace_insert, +static TECO_DEFINE_STATE_INSERT(teco_state_replace_insert, .initial_cb = (teco_state_initial_cb_t)teco_state_replace_insert_initial ); static teco_state_t * -teco_state_replace_ignore_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_replace_ignore_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { return &teco_state_start; } -/* - * FIXME: Could be static - */ -TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_ignore); +static TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_ignore, + .expectstring.done_cb = teco_state_replace_ignore_done +); static teco_state_t * -teco_state_replace_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_replace_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_replace_ignore; @@ -1270,16 +1286,12 @@ teco_state_replace_done(teco_machine_main_t *ctx, const teco_string_t *str, GErr * immediately and interactively. */ TECO_DEFINE_STATE_SEARCH(teco_state_replace, - .expectstring.last = FALSE + .expectstring.last = FALSE, + .expectstring.done_cb = teco_state_replace_done ); -/* - * FIXME: TECO_DEFINE_STATE_INSERT() already defines a done_cb(), - * so we had to name this differently. - * Perhaps it simply shouldn't define it. - */ static teco_state_t * -teco_state_replace_default_insert_done_overwrite(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_replace_default_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -1287,55 +1299,53 @@ teco_state_replace_default_insert_done_overwrite(teco_machine_main_t *ctx, const teco_qreg_t *replace_reg = teco_qreg_table_find(&teco_qreg_table_globals, "-", 1); g_assert(replace_reg != NULL); - if (str->len > 0) { + if (str.len > 0) { if (!replace_reg->vtable->undo_set_string(replace_reg, error) || - !replace_reg->vtable->set_string(replace_reg, str->data, str->len, + !replace_reg->vtable->set_string(replace_reg, str.data, str.len, teco_default_codepage(), error)) return NULL; } else { g_auto(teco_string_t) replace_str = {NULL, 0}; if (!replace_reg->vtable->get_string(replace_reg, &replace_str.data, &replace_str.len, NULL, error) || - (replace_str.len > 0 && !teco_state_insert_process(ctx, &replace_str, replace_str.len, error))) + (replace_str.len > 0 && !teco_state_insert_process(ctx, replace_str, replace_str.len, error))) return NULL; } + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); return &teco_state_start; } -/* - * FIXME: Could be static - */ -TECO_DEFINE_STATE_INSERT(teco_state_replace_default_insert, - .initial_cb = NULL, - .expectstring.done_cb = teco_state_replace_default_insert_done_overwrite +static TECO_DEFINE_STATE_INSERT(teco_state_replace_default_insert, + .initial_cb = (teco_state_initial_cb_t)teco_state_replace_insert_initial, + .expectstring.done_cb = teco_state_replace_default_insert_done ); static teco_state_t * -teco_state_replace_default_ignore_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_replace_default_ignore_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL || - !str->len) + !str.len) return &teco_state_start; teco_qreg_t *replace_reg = teco_qreg_table_find(&teco_qreg_table_globals, "-", 1); g_assert(replace_reg != NULL); if (!replace_reg->vtable->undo_set_string(replace_reg, error) || - !replace_reg->vtable->set_string(replace_reg, str->data, str->len, + !replace_reg->vtable->set_string(replace_reg, str.data, str.len, teco_default_codepage(), error)) return NULL; return &teco_state_start; } -/* - * FIXME: Could be static - */ -TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_default_ignore); +static TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_default_ignore, + .expectstring.done_cb = teco_state_replace_default_ignore_done +); static teco_state_t * -teco_state_replace_default_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_replace_default_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_replace_default_ignore; @@ -1374,11 +1384,12 @@ teco_state_replace_default_done(teco_machine_main_t *ctx, const teco_string_t *s * register is implied instead. */ TECO_DEFINE_STATE_SEARCH(teco_state_replace_default, - .expectstring.last = FALSE + .expectstring.last = FALSE, + .expectstring.done_cb = teco_state_replace_default_done ); static teco_state_t * -teco_state_replace_default_all_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_replace_default_all_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_replace_default_ignore; @@ -1417,6 +1428,7 @@ teco_state_replace_default_all_done(teco_machine_main_t *ctx, const teco_string_ * <to>. */ TECO_DEFINE_STATE_SEARCH(teco_state_replace_default_all, + .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial, .expectstring.last = FALSE, - .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial + .expectstring.done_cb = teco_state_replace_default_all_done ); diff --git a/src/search.h b/src/search.h index 222ca6c..9bd62f7 100644 --- a/src/search.h +++ b/src/search.h @@ -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 @@ -22,10 +22,10 @@ void teco_state_control_search_mode(teco_machine_main_t *ctx, GError **error); -TECO_DECLARE_STATE(teco_state_search); -TECO_DECLARE_STATE(teco_state_search_all); -TECO_DECLARE_STATE(teco_state_search_kill); -TECO_DECLARE_STATE(teco_state_search_delete); -TECO_DECLARE_STATE(teco_state_replace); -TECO_DECLARE_STATE(teco_state_replace_default); -TECO_DECLARE_STATE(teco_state_replace_default_all); +extern teco_state_t teco_state_search; +extern teco_state_t teco_state_search_all; +extern teco_state_t teco_state_search_kill; +extern teco_state_t teco_state_search_delete; +extern teco_state_t teco_state_replace; +extern teco_state_t teco_state_replace_default; +extern teco_state_t teco_state_replace_default_all; diff --git a/src/spawn.c b/src/spawn.c index 73f389e..61718fd 100644 --- a/src/spawn.c +++ b/src/spawn.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 @@ -129,7 +129,7 @@ teco_parse_shell_command_line(const gchar *cmdline, GError **error) teco_string_t comspec; if (!reg->vtable->get_string(reg, &comspec.data, &comspec.len, NULL, error)) return NULL; - if (teco_string_contains(&comspec, '\0')) { + if (teco_string_contains(comspec, '\0')) { teco_string_clear(&comspec); teco_error_qregcontainsnull_set(error, "$COMSPEC", 8, FALSE); return NULL; @@ -150,7 +150,7 @@ teco_parse_shell_command_line(const gchar *cmdline, GError **error) teco_string_t shell; if (!reg->vtable->get_string(reg, &shell.data, &shell.len, NULL, error)) return NULL; - if (teco_string_contains(&shell, '\0')) { + if (teco_string_contains(shell, '\0')) { teco_string_clear(&shell); teco_error_qregcontainsnull_set(error, "$SHELL", 6, FALSE); return NULL; @@ -242,7 +242,7 @@ teco_state_execute_initial(teco_machine_main_t *ctx, GError **error) } static teco_state_t * -teco_state_execute_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_execute_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { /* * NOTE: With G_SPAWN_LEAVE_DESCRIPTORS_OPEN and without G_SPAWN_SEARCH_PATH_FROM_ENVP, @@ -285,13 +285,13 @@ teco_state_execute_done(teco_machine_main_t *ctx, const teco_string_t *str, GErr } #endif - if (!str->len || teco_string_contains(str, '\0')) { + if (!str.len || teco_string_contains(str, '\0')) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Command line must not be empty or contain null-bytes"); goto gerror; } - argv = teco_parse_shell_command_line(str->data, error); + argv = teco_parse_shell_command_line(str.data, error); if (!argv) goto gerror; @@ -605,7 +605,8 @@ gboolean teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_mach */ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_execute, .initial_cb = (teco_state_initial_cb_t)teco_state_execute_initial, - .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_execute_process_edit_cmd + .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_execute_process_edit_cmd, + .expectstring.done_cb = teco_state_execute_done ); static teco_state_t * @@ -647,7 +648,8 @@ teco_state_egcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, * The register <q> is defined if it does not already exist. */ TECO_DEFINE_STATE_EXPECTQREG(teco_state_egcommand, - .expectqreg.type = TECO_QREG_OPTIONAL_INIT + .expectqreg.type = TECO_QREG_OPTIONAL_INIT, + .expectqreg.got_register_cb = teco_state_egcommand_got_register ); /* diff --git a/src/spawn.h b/src/spawn.h index ef210e9..09764bd 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -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 @@ -18,5 +18,5 @@ #include "parser.h" -TECO_DECLARE_STATE(teco_state_execute); -TECO_DECLARE_STATE(teco_state_egcommand); +extern teco_state_t teco_state_execute; +extern teco_state_t teco_state_egcommand; diff --git a/src/stdio-commands.c b/src/stdio-commands.c index 565f442..abb6566 100644 --- a/src/stdio-commands.c +++ b/src/stdio-commands.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 @@ -118,7 +118,7 @@ teco_print(teco_machine_main_t *ctx, guint radix, GError **error) * then octal and finally in hexadecimal. * This won't happen e.g. in a loop that is closed on the command-line. */ -TECO_DECLARE_STATE(teco_state_print_octal); +static teco_state_t teco_state_print_octal; static gboolean teco_state_print_decimal_initial(teco_machine_main_t *ctx, GError **error) @@ -168,6 +168,7 @@ teco_state_print_decimal_end_of_macro(teco_machine_main_t *ctx, GError **error) TECO_DEFINE_STATE_START(teco_state_print_decimal, .initial_cb = (teco_state_initial_cb_t)teco_state_print_decimal_initial, + .input_cb = (teco_state_input_cb_t)teco_state_print_decimal_input, .end_of_macro_cb = (teco_state_end_of_macro_cb_t) teco_state_print_decimal_end_of_macro ); @@ -224,8 +225,9 @@ teco_state_print_octal_end_of_macro(teco_machine_main_t *ctx, GError **error) return TRUE; } -TECO_DEFINE_STATE_START(teco_state_print_octal, +static TECO_DEFINE_STATE_START(teco_state_print_octal, .initial_cb = (teco_state_initial_cb_t)teco_state_print_octal_initial, + .input_cb = (teco_state_input_cb_t)teco_state_print_octal_input, .end_of_macro_cb = (teco_state_end_of_macro_cb_t) teco_state_print_octal_end_of_macro ); @@ -251,9 +253,9 @@ teco_state_print_string_initial(teco_machine_main_t *ctx, GError **error) } static teco_state_t * -teco_state_print_string_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_print_string_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { - teco_interface_msg_literal(TECO_MSG_USER, str->data, str->len); + teco_interface_msg_literal(TECO_MSG_USER, str.data, str.len); return &teco_state_start; } @@ -282,7 +284,8 @@ teco_state_print_string_done(teco_machine_main_t *ctx, const teco_string_t *str, * ^EUq, ^E<...> and case folding. */ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_print_string, - .initial_cb = (teco_state_initial_cb_t)teco_state_print_string_initial + .initial_cb = (teco_state_initial_cb_t)teco_state_print_string_initial, + .expectstring.done_cb = teco_state_print_string_done ); /*$ T type typeout diff --git a/src/stdio-commands.h b/src/stdio-commands.h index 985d1ac..cf04b34 100644 --- a/src/stdio-commands.h +++ b/src/stdio-commands.h @@ -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 @@ -26,5 +26,5 @@ void teco_state_control_typeout(teco_machine_main_t *ctx, GError **error); /* * Command states */ -TECO_DECLARE_STATE(teco_state_print_decimal); -TECO_DECLARE_STATE(teco_state_print_string); +extern teco_state_t teco_state_print_decimal; +extern teco_state_t teco_state_print_string; diff --git a/src/string-utils.c b/src/string-utils.c index 10e34a8..d98b6b0 100644 --- a/src/string-utils.c +++ b/src/string-utils.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 @@ -87,8 +87,10 @@ teco_string_get_coord(const gchar *str, gsize off, guint *pos, guint *line, guin } /** - * Get the length of the prefix common to two strings. - * Works with UTF-8 and single-byte encodings. + * Get the length of the prefix common to two UTF-8 strings. + * + * The UTF-8 strings must be validated, which should be the case + * for help labels and short Q-Register names. * * @param a Left string. * @param b Right string. @@ -98,13 +100,13 @@ teco_string_get_coord(const gchar *str, gsize off, guint *pos, guint *line, guin * @memberof teco_string_t */ gsize -teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len) +teco_string_diff(teco_string_t a, const gchar *b, gsize b_len) { gsize len = 0; - while (len < a->len && len < b_len && - a->data[len] == b[len]) - len++; + while (len < a.len && len < b_len && + g_utf8_get_char(a.data+len) == g_utf8_get_char(b+len)) + len = g_utf8_next_char(b+len) - b; return len; } @@ -124,12 +126,12 @@ teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len) * @memberof teco_string_t */ gsize -teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len) +teco_string_casediff(teco_string_t a, const gchar *b, gsize b_len) { gsize len = 0; - while (len < a->len && len < b_len) { - gunichar a_chr = g_utf8_get_char(a->data+len); + while (len < a.len && len < b_len) { + gunichar a_chr = g_utf8_get_char(a.data+len); gunichar b_chr = g_utf8_get_char(b+len); if (g_unichar_tolower(a_chr) != g_unichar_tolower(b_chr)) break; @@ -141,36 +143,36 @@ teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len) /** @memberof teco_string_t */ gint -teco_string_cmp(const teco_string_t *a, const gchar *b, gsize b_len) +teco_string_cmp(teco_string_t a, const gchar *b, gsize b_len) { - for (guint i = 0; i < a->len; i++) { + for (guint i = 0; i < a.len; i++) { if (i == b_len) /* b is a prefix of a */ return 1; - gint ret = (gint)a->data[i] - (gint)b[i]; + gint ret = (gint)a.data[i] - (gint)b[i]; if (ret != 0) /* a and b have a common prefix of length i */ return ret; } - return a->len == b_len ? 0 : -1; + return a.len == b_len ? 0 : -1; } /** @memberof teco_string_t */ gint -teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len) +teco_string_casecmp(teco_string_t a, const gchar *b, gsize b_len) { - for (guint i = 0; i < a->len; i++) { + for (guint i = 0; i < a.len; i++) { if (i == b_len) /* b is a prefix of a */ return 1; - gint ret = (gint)g_ascii_tolower(a->data[i]) - (gint)g_ascii_tolower(b[i]); + gint ret = (gint)g_ascii_tolower(a.data[i]) - (gint)g_ascii_tolower(b[i]); if (ret != 0) /* a and b have a common prefix of length i */ return ret; } - return a->len == b_len ? 0 : -1; + return a.len == b_len ? 0 : -1; } /** @@ -184,22 +186,20 @@ teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len) * @memberof teco_string_t */ const gchar * -teco_string_last_occurrence(const teco_string_t *str, const gchar *chars) +teco_string_last_occurrence(teco_string_t str, const gchar *chars) { - teco_string_t ret = *str; - - if (!ret.len) + if (!str.len) return NULL; do { - gint i = teco_string_rindex(&ret, *chars); + gint i = teco_string_rindex(str, *chars); if (i >= 0) { - ret.data += i+1; - ret.len -= i+1; + str.data += i+1; + str.len -= i+1; } } while (*chars++); - return ret.data; + return str.data; } TECO_DEFINE_UNDO_CALL(teco_string_truncate, teco_string_t *, gsize); diff --git a/src/string-utils.h b/src/string-utils.h index 2491d07..a1eda4e 100644 --- a/src/string-utils.h +++ b/src/string-utils.h @@ -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 @@ -63,6 +63,9 @@ teco_strv_remove(gchar **strv, guint i) * to functions expecting traditional null-terminated C strings if you can * guarantee that it contains no null-character other than the trailing one. * + * Since string objects are just two words, they can and should be passed by + * value if the callee doesn't have to modify it. + * * @warning For consistency with C idioms the underlying character type is * `char`, which might be signed! * Accessing individual characters may yield signed integers and that sign @@ -102,7 +105,7 @@ teco_string_init(teco_string_t *target, const gchar *str, gsize len) static inline void teco_string_init_chunk(teco_string_t *target, const gchar *str, gssize len, GStringChunk *chunk) { - target->data = g_string_chunk_insert_len(chunk, str, len); + target->data = g_string_chunk_insert_len(chunk, str ? : "", len); target->len = len; } @@ -164,19 +167,19 @@ gchar *teco_string_echo(const gchar *str, gsize len); void teco_string_get_coord(const gchar *str, gsize off, guint *pos, guint *line, guint *column); -typedef gsize (*teco_string_diff_t)(const teco_string_t *a, const gchar *b, gsize b_len); -gsize teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len); -gsize teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len); +typedef gsize (*teco_string_diff_t)(teco_string_t a, const gchar *b, gsize b_len); +gsize teco_string_diff(teco_string_t a, const gchar *b, gsize b_len); +gsize teco_string_casediff(teco_string_t a, const gchar *b, gsize b_len); -typedef gint (*teco_string_cmp_t)(const teco_string_t *a, const gchar *b, gsize b_len); -gint teco_string_cmp(const teco_string_t *a, const gchar *b, gsize b_len); -gint teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len); +typedef gint (*teco_string_cmp_t)(teco_string_t a, const gchar *b, gsize b_len); +gint teco_string_cmp(teco_string_t a, const gchar *b, gsize b_len); +gint teco_string_casecmp(teco_string_t a, const gchar *b, gsize b_len); /** @memberof teco_string_t */ static inline gboolean -teco_string_contains(const teco_string_t *str, gchar chr) +teco_string_contains(teco_string_t str, gchar chr) { - return str->data && memchr(str->data, chr, str->len); + return str.data && memchr(str.data, chr, str.len); } /** @@ -188,26 +191,26 @@ teco_string_contains(const teco_string_t *str, gchar chr) * @memberof teco_string_t */ static inline gint -teco_string_rindex(const teco_string_t *str, gchar chr) +teco_string_rindex(teco_string_t str, gchar chr) { gint i; - for (i = str->len-1; i >= 0 && str->data[i] != chr; i--); + for (i = str.len-1; i >= 0 && str.data[i] != chr; i--); return i; } -const gchar *teco_string_last_occurrence(const teco_string_t *str, const gchar *chars); +const gchar *teco_string_last_occurrence(teco_string_t str, const gchar *chars); /** * Validate whether string consists exclusively of valid UTF-8, but accept null bytes. * @note there is g_utf8_validate_len() in Glib 2.60 */ static inline gboolean -teco_string_validate_utf8(const teco_string_t *str) +teco_string_validate_utf8(teco_string_t str) { - const gchar *p = str->data; - while (!g_utf8_validate(p, str->len - (p - str->data), &p) && !*p) + const gchar *p = str.data; + while (!g_utf8_validate(p, str.len - (p - str.data), &p) && !*p) p++; - return p - str->data == str->len; + return p - str.data == str.len; } /** @memberof teco_string_t */ diff --git a/src/symbols.c b/src/symbols.c index 9cf1c35..dd5856e 100644 --- a/src/symbols.c +++ b/src/symbols.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 @@ -139,7 +139,7 @@ teco_symbol_list_auto_complete(teco_symbol_list_t *ctx, const gchar *symbol, tec glist_str.data = (gchar *)glist->data + symbol_len; glist_str.len = strlen(glist_str.data); - gsize len = teco_string_casediff(&glist_str, (gchar *)entry->data + symbol_len, + gsize len = teco_string_casediff(glist_str, (gchar *)entry->data + symbol_len, strlen(entry->data) - symbol_len); if (!prefix_len || len < prefix_len) prefix_len = len; @@ -174,13 +174,10 @@ teco_scintilla_ssm(unsigned int iMessage, uptr_t wParam, sptr_t lParam) iMessage, wParam, lParam); } -/* - * FIXME: This state could be static. - */ -TECO_DECLARE_STATE(teco_state_scintilla_lparam); +static teco_state_t teco_state_scintilla_lparam; static gboolean -teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, const teco_string_t *str, GError **error) +teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, teco_string_t str, GError **error) { if (teco_string_contains(str, '\0')) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, @@ -188,7 +185,7 @@ teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, const teco_str return FALSE; } - g_auto(GStrv) symbols = g_strsplit(str->data, ",", -1); + g_auto(GStrv) symbols = g_strsplit(str.data, ",", -1); if (!symbols[0]) return TRUE; @@ -220,7 +217,7 @@ teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, const teco_str } static teco_state_t * -teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_scintilla_lparam; @@ -234,7 +231,7 @@ teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t teco_undo_scintilla_message(ctx->scintilla); memset(&ctx->scintilla, 0, sizeof(ctx->scintilla)); - if ((str->len > 0 && !teco_scintilla_parse_symbols(&ctx->scintilla, str, error)) || + if ((str.len > 0 && !teco_scintilla_parse_symbols(&ctx->scintilla, str, error)) || !teco_expressions_eval(FALSE, error)) return NULL; @@ -254,7 +251,7 @@ teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t /* in cmdline.c */ gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); -gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, +gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error); /*$ ES scintilla message @@ -355,17 +352,18 @@ gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_symbols, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_scintilla_symbols_process_edit_cmd, .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_scintilla_symbols_insert_completion, - .expectstring.last = FALSE + .expectstring.last = FALSE, + .expectstring.done_cb = teco_state_scintilla_symbols_done ); #ifdef HAVE_LEXILLA static gpointer -teco_create_lexer(const teco_string_t *str, GError **error) +teco_create_lexer(teco_string_t str, GError **error) { CreateLexerFn CreateLexerFn = CreateLexer; - const gchar *lexer = memchr(str->data ? : "", '\0', str->len); + const gchar *lexer = memchr(str.data ? : "", '\0', str.len); if (lexer) { /* external lexer */ lexer++; @@ -374,7 +372,7 @@ teco_create_lexer(const teco_string_t *str, GError **error) * NOTE: The same module can be opened multiple times. * They are internally reference counted. */ - GModule *module = g_module_open(str->data, G_MODULE_BIND_LAZY); + GModule *module = g_module_open(str.data, G_MODULE_BIND_LAZY); if (!module) { teco_error_module_set(error, "Error opening lexer module"); return NULL; @@ -396,7 +394,7 @@ teco_create_lexer(const teco_string_t *str, GError **error) * * FIXME: In Scintillua distributions, the lexers are usually contained in the * same directory as the prebuilt shared libraries. - * Perhaps we should default scintillua.lexers to the dirname in str->data? + * Perhaps we should default scintillua.lexers to the dirname in str.data? */ teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_SCINTILLUA_LEXERS", 26); if (reg) { @@ -408,7 +406,7 @@ teco_create_lexer(const teco_string_t *str, GError **error) } } else { /* Lexilla lexer */ - lexer = str->data ? : ""; + lexer = str.data ? : ""; } ILexer5 *ret = CreateLexerFn(lexer); @@ -424,9 +422,9 @@ teco_create_lexer(const teco_string_t *str, GError **error) #else /* !HAVE_LEXILLA */ static gpointer -teco_create_lexer(const teco_string_t *str, GError **error) +teco_create_lexer(teco_string_t str, GError **error) { - g_autofree gchar *str_printable = teco_string_echo(str->data, str->len); + g_autofree gchar *str_printable = teco_string_echo(str.data, str.len); g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Cannot load lexer \"%s\": Lexilla disabled", str_printable); return NULL; @@ -435,7 +433,7 @@ teco_create_lexer(const teco_string_t *str, GError **error) #endif static teco_state_t * -teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) +teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; @@ -468,13 +466,13 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t * } g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, - "Style name \"%s\" not found.", str->data ? : ""); + "Style name \"%s\" not found.", str.data ? : ""); return NULL; } else if (ctx->scintilla.iMessage == SCI_SETILEXER) { lParam = (sptr_t)teco_create_lexer(str, error); if (!lParam) return NULL; - } else if (str->len > 0) { + } else if (str.len > 0) { /* * Theoretically, Scintilla could use null bytes from the string specified. * This could only be the case for messages where the string length is @@ -484,13 +482,13 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t * * which unlocks useful messages like * SCI_SETREPRESENTATIONS and SCI_SETPROPERTY. */ - const gchar *p = memchr(str->data, '\0', str->len); + const gchar *p = memchr(str.data, '\0', str.len); if (p) { - ctx->scintilla.wParam = (uptr_t)str->data; - if (str->len > p - str->data + 1) + ctx->scintilla.wParam = (uptr_t)str.data; + if (str.len > p - str.data + 1) lParam = (sptr_t)(p+1); } else { - lParam = (sptr_t)str->data; + lParam = (sptr_t)str.data; } } @@ -512,4 +510,6 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t * return &teco_state_start; } -TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_lparam); +static TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_lparam, + .expectstring.done_cb = teco_state_scintilla_lparam_done +); diff --git a/src/symbols.h b/src/symbols.h index 1d0af12..c7db610 100644 --- a/src/symbols.h +++ b/src/symbols.h @@ -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 @@ -61,4 +61,4 @@ extern teco_symbol_list_t teco_symbol_list_scilexer; * Command states */ -TECO_DECLARE_STATE(teco_state_scintilla_symbols); +extern teco_state_t teco_state_scintilla_symbols; @@ -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 @@ -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 @@ -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 @@ -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 |
