aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmdline.c116
-rw-r--r--src/cmdline.h4
-rw-r--r--src/core-commands.c57
-rw-r--r--src/core-commands.h39
-rw-r--r--src/doc.c2
-rw-r--r--src/doc.h2
-rw-r--r--src/eol.c2
-rw-r--r--src/eol.h2
-rw-r--r--src/error.c2
-rw-r--r--src/error.h2
-rw-r--r--src/expressions.c2
-rw-r--r--src/expressions.h2
-rw-r--r--src/file-utils.c8
-rw-r--r--src/file-utils.h2
-rw-r--r--src/glob.c28
-rw-r--r--src/glob.h9
-rw-r--r--src/goto-commands.c44
-rw-r--r--src/goto-commands.h6
-rw-r--r--src/goto.c2
-rw-r--r--src/goto.h2
-rw-r--r--src/help.c11
-rw-r--r--src/help.h4
-rw-r--r--src/interface-curses/curses-icons.c6
-rw-r--r--src/interface-curses/curses-icons.h2
-rw-r--r--src/interface-curses/curses-info-popup.c54
-rw-r--r--src/interface-curses/curses-info-popup.h4
-rw-r--r--src/interface-curses/curses-utils.c18
-rw-r--r--src/interface-curses/curses-utils.h14
-rw-r--r--src/interface-curses/interface.c244
-rw-r--r--src/interface-gtk/gtk-info-popup.c25
-rw-r--r--src/interface-gtk/gtk-info-popup.h2
-rw-r--r--src/interface-gtk/gtk-label.c46
-rw-r--r--src/interface-gtk/gtk-label.h6
-rw-r--r--src/interface-gtk/interface.c83
-rw-r--r--src/interface-gtk/view.c2
-rw-r--r--src/interface.c2
-rw-r--r--src/interface.h16
-rw-r--r--src/lexer.c2
-rw-r--r--src/lexer.h2
-rw-r--r--src/list.h2
-rw-r--r--src/main.c9
-rw-r--r--src/memory.c2
-rw-r--r--src/memory.h2
-rw-r--r--src/move-commands.c6
-rw-r--r--src/move-commands.h2
-rw-r--r--src/parser.c115
-rw-r--r--src/parser.h49
-rw-r--r--src/qreg-commands.c86
-rw-r--r--src/qreg-commands.h48
-rw-r--r--src/qreg.c58
-rw-r--r--src/qreg.h2
-rw-r--r--src/rb3str.c12
-rw-r--r--src/rb3str.h2
-rw-r--r--src/ring.c80
-rw-r--r--src/ring.h19
-rw-r--r--src/sciteco.h12
-rw-r--r--src/search.c126
-rw-r--r--src/search.h16
-rw-r--r--src/spawn.c18
-rw-r--r--src/spawn.h6
-rw-r--r--src/stdio-commands.c15
-rw-r--r--src/stdio-commands.h6
-rw-r--r--src/string-utils.c52
-rw-r--r--src/string-utils.h37
-rw-r--r--src/symbols.c54
-rw-r--r--src/symbols.h4
-rw-r--r--src/undo.c2
-rw-r--r--src/undo.h2
-rw-r--r--src/view.c2
-rw-r--r--src/view.h2
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;
diff --git a/src/doc.c b/src/doc.c
index 1ec4d80..9bc9dc8 100644
--- a/src/doc.c
+++ b/src/doc.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/doc.h b/src/doc.h
index 99568ff..3218a70 100644
--- a/src/doc.h
+++ b/src/doc.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/eol.c b/src/eol.c
index e5cab2c..201e949 100644
--- a/src/eol.c
+++ b/src/eol.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/eol.h b/src/eol.h
index 0bcb28c..be5a4b1 100644
--- a/src/eol.h
+++ b/src/eol.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/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
diff --git a/src/glob.c b/src/glob.c
index 9621f1a..d15f601 100644
--- a/src/glob.c
+++ b/src/glob.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
@@ -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
+);
diff --git a/src/glob.h b/src/glob.h
index d11fbce..995437c 100644
--- a/src/glob.h
+++ b/src/glob.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
@@ -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;
diff --git a/src/goto.c b/src/goto.c
index 855c9f9..42eb964 100644
--- a/src/goto.c
+++ b/src/goto.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/goto.h b/src/goto.h
index 05c7598..c514692 100644
--- a/src/goto.h
+++ b/src/goto.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/help.c b/src/help.c
index 0bbbc54..03cc595 100644
--- a/src/help.c
+++ b/src/help.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
@@ -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
);
diff --git a/src/help.h b/src/help.h
index 3148e01..c2accc4 100644
--- a/src/help.h
+++ b/src/help.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
@@ -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
diff --git a/src/list.h b/src/list.h
index 2a7ab91..fc4b247 100644
--- a/src/list.h
+++ b/src/list.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/main.c b/src/main.c
index 620455a..8bc02f1 100644
--- a/src/main.c
+++ b/src/main.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
@@ -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;
diff --git a/src/qreg.c b/src/qreg.c
index 8fca9e2..8ef82c0 100644
--- a/src/qreg.c
+++ b/src/qreg.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
@@ -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;
}
diff --git a/src/qreg.h b/src/qreg.h
index 6daaff3..c5e9461 100644
--- a/src/qreg.h
+++ b/src/qreg.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/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
diff --git a/src/ring.c b/src/ring.c
index bc04e50..5eb16e6 100644
--- a/src/ring.c
+++ b/src/ring.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,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
diff --git a/src/ring.h b/src/ring.h
index e6b0658..e45434d 100644
--- a/src/ring.h
+++ b/src/ring.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,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;
diff --git a/src/undo.c b/src/undo.c
index 2048af3..18f90c5 100644
--- a/src/undo.c
+++ b/src/undo.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/undo.h b/src/undo.h
index c236970..476ca45 100644
--- a/src/undo.h
+++ b/src/undo.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/view.c b/src/view.c
index c41a4d4..a522d1c 100644
--- a/src/view.c
+++ b/src/view.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/view.h b/src/view.h
index 82a5c99..4c57eb8 100644
--- a/src/view.h
+++ b/src/view.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