diff options
Diffstat (limited to 'src/core-commands.c')
| -rw-r--r-- | src/core-commands.c | 834 |
1 files changed, 561 insertions, 273 deletions
diff --git a/src/core-commands.c b/src/core-commands.c index dbf86bd..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 @@ -18,7 +18,9 @@ #include "config.h" #endif +#include <time.h> #include <string.h> +#include <stdio.h> #include <glib.h> #include <glib/gstdio.h> @@ -42,31 +44,76 @@ #include "memory.h" #include "eol.h" #include "qreg.h" +#include "stdio-commands.h" #include "qreg-commands.h" #include "goto-commands.h" #include "move-commands.h" #include "core-commands.h" -gboolean teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, - gunichar key, GError **error); +static teco_state_t *teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error); +static teco_state_t *teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error); /** - * @class TECO_DEFINE_STATE_COMMAND - * @implements TECO_DEFINE_STATE_CASEINSENSITIVE - * @ingroup states - * - * Base state for everything that is the beginning of a one or two - * letter command. + * Translate buffer range arguments from the expression stack to + * a from-position and length in bytes. + * + * If only one argument is given, it is interpreted as a number of lines + * beginning with dot. + * If two arguments are given, it is interpreted as two buffer positions + * in glyphs. + * + * @param cmd Name of the command + * @param from_ret Where to store the from-position in bytes + * @param len_ret Where to store the length of the range in bytes + * @param error A GError + * @return FALSE if an error occurred + * + * @fixme There are still redundancies with teco_state_start_kill(). + * But it needs to discern between invalid ranges and other errors. */ -#define TECO_DEFINE_STATE_COMMAND(NAME, ...) \ - TECO_DEFINE_STATE_CASEINSENSITIVE(NAME, \ - .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \ - teco_state_command_process_edit_cmd, \ - .style = SCE_SCITECO_COMMAND, \ - ##__VA_ARGS__ \ - ) +gboolean +teco_get_range_args(const gchar *cmd, gsize *from_ret, gsize *len_ret, GError **error) +{ + gssize from, len; /* in bytes */ -static teco_state_t *teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error); + if (!teco_expressions_eval(FALSE, error)) + return FALSE; + + if (teco_expressions_args() <= 1) { + teco_int_t line; + + if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error)) + return FALSE; + + from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + line += teco_interface_ssm(SCI_LINEFROMPOSITION, from, 0); + + if (!teco_validate_line(line)) { + teco_error_range_set(error, cmd); + return FALSE; + } + + len = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0) - from; + + if (len < 0) { + from += len; + len *= -1; + } + } else { + gssize to = teco_interface_glyphs2bytes(teco_expressions_pop_num(0)); + from = teco_interface_glyphs2bytes(teco_expressions_pop_num(0)); + len = to - from; + + if (len < 0 || from < 0 || to < 0) { + teco_error_range_set(error, cmd); + return FALSE; + } + } + + *from_ret = from; + *len_ret = len; + return TRUE; +} /* * NOTE: This needs some extra code in teco_state_start_input(). @@ -213,19 +260,20 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) return; if (teco_expressions_args()) { - teco_int_t value; - - if (!teco_expressions_pop_num_calc(&value, 0, error)) - return; - gchar buffer[TECO_EXPRESSIONS_FORMAT_LEN]; - gchar *str = teco_expressions_format(buffer, value, + gchar *str = teco_expressions_format(buffer, + teco_expressions_pop_num(0), ctx->qreg_table_locals->radix); g_assert(*str != '\0'); gsize len = strlen(str); - teco_undo_gsize(teco_ranges[0].from) = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - teco_undo_gsize(teco_ranges[0].to) = teco_ranges[0].from + len; + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + /* + * We can assume that `len` is already in glyphs, + * i.e. formatted numbers will never use multi-byte/Unicode characters. + */ + teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + len; teco_undo_guint(teco_ranges_count) = 1; teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); @@ -276,8 +324,7 @@ static void teco_state_start_loop_open(teco_machine_main_t *ctx, GError **error) { teco_loop_context_t lctx; - if (!teco_expressions_eval(FALSE, error) || - !teco_expressions_pop_num_calc(&lctx.counter, -1, error)) + if (!teco_expressions_pop_num_calc(&lctx.counter, -1, error)) return; lctx.brace_level = teco_brace_level; lctx.pass_through = teco_machine_main_eval_colon(ctx) > 0; @@ -370,7 +417,7 @@ teco_state_start_loop_close(teco_machine_main_t *ctx, GError **error) } } -/*$ ";" break +/*$ ";" ":;" break * [bool]; -- Conditionally break from loop * [bool]:; * @@ -504,9 +551,11 @@ teco_state_start_cmdline_push(teco_machine_main_t *ctx, GError **error) !teco_qreg_table_edit_name(&teco_qreg_table_globals, "\e", 1, error)) return; + const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0); + teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); teco_interface_ssm(SCI_CLEARALL, 0, 0); - teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)teco_cmdline.str.data); + teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)macro); teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); /* @@ -536,34 +585,6 @@ teco_state_start_cmdline_pop(teco_machine_main_t *ctx, GError **error) g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CMDLINE, ""); } -/*$ "=" print - * <n>= -- Show value as message - * - * Shows integer <n> as a message in the message line and/or - * on the console. - * It is currently always formatted as a decimal integer and - * shown with the user-message severity. - * The command fails if <n> is not given. - */ -/** - * @todo perhaps care about current radix - * @todo colon-modifier to suppress line-break on console? - */ -static void -teco_state_start_print(teco_machine_main_t *ctx, GError **error) -{ - if (!teco_expressions_eval(FALSE, error)) - return; - if (!teco_expressions_args()) { - teco_error_argexpected_set(error, "="); - return; - } - teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) - return; - teco_interface_msg(TECO_MSG_USER, "%" TECO_INT_FORMAT, v); -} - /*$ A * [n]A -> code -- Get character code from buffer * -A -> code @@ -573,8 +594,6 @@ teco_state_start_print(teco_machine_main_t *ctx, GError **error) * This can be an ASCII <code> or Unicode codepoint * depending on Scintilla's encoding of the current * buffer. - * Invalid Unicode byte sequences are reported as - * -1 or -2. * * - If <n> is 0, return the <code> of the character * pointed to by dot. @@ -585,12 +604,11 @@ teco_state_start_print(teco_machine_main_t *ctx, GError **error) * - If <n> is omitted, the sign prefix is implied. * * If the position of the queried character is off-page, - * the command will yield an error. - * + * the command will return -1. * If the document is encoded as UTF-8 and there is - * an incomplete sequence at the requested position, - * -1 is returned. - * All other invalid Unicode sequences are returned as -2. + * an invalid byte sequence at the requested position, + * -2 is returned. + * Incomplete byte sequences are returned as -3. */ static void teco_state_start_get(teco_machine_main_t *ctx, GError **error) @@ -603,15 +621,11 @@ teco_state_start_get(teco_machine_main_t *ctx, GError **error) gssize get_pos = teco_interface_glyphs2bytes_relative(pos, v); sptr_t len = teco_interface_ssm(SCI_GETLENGTH, 0, 0); - if (get_pos < 0 || get_pos == len) { - teco_error_range_set(error, "A"); - return; - } - - teco_expressions_push(teco_interface_get_character(get_pos, len)); + teco_expressions_push(get_pos < 0 || get_pos == len + ? -1 : teco_interface_get_character(get_pos, len)); } -static teco_state_t * +teco_state_t * teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) { static teco_machine_main_transition_t transitions[] = { @@ -621,7 +635,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) ['$'] = {&teco_state_escape}, ['!'] = {&teco_state_label}, ['O'] = {&teco_state_goto, - .modifier_at = TRUE}, + .modifier_at = TRUE, .modifier_colon = 1}, ['^'] = {&teco_state_control, .modifier_at = TRUE, .modifier_colon = 2}, ['F'] = {&teco_state_fcommand, @@ -629,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_building, + ['I'] = {&teco_state_insert, .modifier_at = TRUE}, ['?'] = {&teco_state_help, .modifier_at = TRUE}, @@ -639,8 +653,10 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) .modifier_at = TRUE, .modifier_colon = 1}, ['['] = {&teco_state_pushqreg}, - [']'] = {&teco_state_popqreg}, - ['G'] = {&teco_state_getqregstring}, + [']'] = {&teco_state_popqreg, + .modifier_colon = 1}, + ['G'] = {&teco_state_getqregstring, + .modifier_colon = 1}, ['Q'] = {&teco_state_queryqreg, .modifier_colon = 1}, ['U'] = {&teco_state_setqreginteger, @@ -650,6 +666,8 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) .modifier_colon = 1}, ['X'] = {&teco_state_copytoqreg, .modifier_at = TRUE, .modifier_colon = 1}, + ['='] = {&teco_state_print_decimal, + .modifier_colon = 1}, /* * Arithmetics @@ -702,28 +720,25 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) .modifier_colon = 1}, ['D'] = {&teco_state_start, teco_state_start_delete_chars, .modifier_colon = 1}, - ['='] = {&teco_state_start, teco_state_start_print}, - ['A'] = {&teco_state_start, teco_state_start_get} + ['A'] = {&teco_state_start, teco_state_start_get}, + ['T'] = {&teco_state_start, teco_state_start_typeout} }; - switch (chr) { /* - * No-ops (same as TECO_NOOPS): + * Non-operational commands. * These are explicitly not handled in teco_state_control, * so that we can potentially reuse the upcaret notations like ^J. */ - case ' ': - case '\f': - case '\r': - case '\n': - case '\v': + if (teco_is_noop(chr)) { if (ctx->flags.modifier_at || (ctx->flags.mode == TECO_MODE_NORMAL && ctx->flags.modifier_colon)) { teco_error_modifier_set(error, chr); return NULL; } return &teco_state_start; + } + switch (chr) { /*$ 0 1 2 3 4 5 6 7 8 9 digit number * [n]0|1|2|3|4|5|6|7|8|9 -> n*Radix+X -- Append digit * @@ -758,8 +773,10 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) * for beginnings of command-lines? * It could also be used for a corresponding KEYMACRO mask. */ - if (teco_cmdline.effective_len == 1 && teco_cmdline.str.data[0] == '*') + if (teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) == 1 && + teco_cmdline_ssm(SCI_GETCHARAT, 0, 0) == '*') return &teco_state_save_cmdline; + /* treat as an operator */ break; case '<': @@ -898,20 +915,18 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) teco_ascii_toupper(chr), error); } -TECO_DEFINE_STATE_COMMAND(teco_state_start, - .end_of_macro_cb = NULL, /* Allowed at the end of a macro! */ - .is_start = TRUE, - .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE +TECO_DEFINE_STATE_START(teco_state_start, + .input_cb = (teco_state_input_cb_t)teco_state_start_input ); -/*$ F< +/*$ "F<" ":F<" * F< -- Go to loop start or jump to beginning of macro * :F< * * Immediately jumps to the current loop's start. * Also works from inside conditionals. * - * This command behaves exactly like \fB>\fP with regard to + * This command behaves exactly like \fB<\fP with regard to * colon-modifiers. * * Outside of loops \(em or in a macro without @@ -945,7 +960,7 @@ teco_state_fcommand_loop_start(teco_machine_main_t *ctx, GError **error) ctx->macro_pc = lctx->pc; } -/*$ F> continue +/*$ "F>" ":F>" continue * F> -- Go to loop end or return from macro * :F> * @@ -1043,6 +1058,8 @@ teco_state_fcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error .modifier_at = TRUE, .modifier_colon = 2}, ['R'] = {&teco_state_replace_default, .modifier_at = TRUE, .modifier_colon = 2}, + ['N'] = {&teco_state_replace_default_all, + .modifier_at = TRUE, .modifier_colon = 1}, ['G'] = {&teco_state_changedir, .modifier_at = TRUE}, @@ -1065,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) @@ -1089,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); @@ -1105,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"); @@ -1157,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) @@ -1185,8 +1206,7 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er teco_error_argexpected_set(error, "\""); return NULL; } - if (!teco_expressions_pop_num_calc(&value, 0, error)) - return NULL; + value = teco_expressions_pop_num(0); break; default: @@ -1273,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 @@ -1287,8 +1308,6 @@ TECO_DEFINE_STATE_COMMAND(teco_state_condcommand, static void teco_state_control_negate(teco_machine_main_t *ctx, GError **error) { - teco_int_t v; - if (!teco_expressions_eval(FALSE, error)) return; @@ -1296,9 +1315,8 @@ teco_state_control_negate(teco_machine_main_t *ctx, GError **error) teco_error_argexpected_set(error, "^_"); return; } - if (!teco_expressions_pop_num_calc(&v, 0, error)) - return; - teco_expressions_push(~v); + + teco_expressions_push(~teco_expressions_pop_num(0)); } static void @@ -1319,35 +1337,6 @@ teco_state_control_xor(teco_machine_main_t *ctx, GError **error) teco_expressions_push_calc(TECO_OP_XOR, error); } -/*$ ^C exit - * ^C -- Exit program immediately - * - * Lets the top-level macro return immediately - * regardless of the current macro invocation frame. - * This command is only allowed in batch mode, - * so it is not invoked accidentally when using - * the CTRL+C immediate editing command to - * interrupt long running operations. - * When using \fB^C\fP in a munged file, - * interactive mode is never started, so it behaves - * effectively just like \(lq-EX\fB$$\fP\(rq - * (when executed in the top-level macro at least). - * - * The \fBquit\fP hook is still executed. - */ -static void -teco_state_control_exit(teco_machine_main_t *ctx, GError **error) -{ - if (teco_undo_enabled) { - g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, - "<^C> not allowed in interactive mode"); - return; - } - - teco_quit_requested = TRUE; - g_set_error_literal(error, TECO_ERROR, TECO_ERROR_QUIT, ""); -} - /*$ ^O octal * ^O -- Set radix to 8 (octal) */ @@ -1401,14 +1390,13 @@ teco_state_control_radix(teco_machine_main_t *ctx, GError **error) return; teco_expressions_push(radix); } else { - if (!teco_expressions_pop_num_calc(&radix, 0, error) || - !qreg->vtable->undo_set_integer(qreg, error) || - !qreg->vtable->set_integer(qreg, radix, error)) + if (!qreg->vtable->undo_set_integer(qreg, error) || + !qreg->vtable->set_integer(qreg, teco_expressions_pop_num(0), error)) return; } } -/*$ ^E glyphs2bytes bytes2glyphs +/*$ "^E" ":^E" glyphs2bytes bytes2glyphs * glyphs^E -> bytes -- Translate between glyph and byte indexes * bytes:^E -> glyphs * ^E -> bytes @@ -1455,9 +1443,7 @@ teco_state_control_glyphs2bytes(teco_machine_main_t *ctx, GError **error) */ res = teco_interface_ssm(colon_modified ? SCI_GETLENGTH : SCI_GETCURRENTPOS, 0, 0); } else { - teco_int_t pos; - if (!teco_expressions_pop_num_calc(&pos, 0, error)) - return; + teco_int_t pos = teco_expressions_pop_num(0); if (colon_modified) { /* teco_interface_bytes2glyphs() does not check addresses */ res = 0 <= pos && pos <= teco_interface_ssm(SCI_GETLENGTH, 0, 0) @@ -1498,8 +1484,8 @@ teco_ranges_init(void) * The default value 0 specifies the entire matched pattern, * while higher numbers refer to \fB^E[\fI...\fB]\fR subpatterns. * \fB^Y\fP can also be used to return the buffer range of the - * last text insertion by any \*(ST command (\fBI\fP, \fBEI\fP, \fB^I\fP, \fBG\fIq\fR, - * \fB\\\fP, \fBEC\fP, \fBEN\fP, etc). + * last text insertion by any \*(ST command (\fBI\fP, \fB^I\fP, \fBG\fIq\fR, + * \fB\\\fP, \fBEC\fP, \fBEN\fP, search replacements, etc). * In this case <n> is only allowed to be 0 or missing. * * For instance, \(lq^YXq\(rq copies the entire matched pattern or text @@ -1522,8 +1508,8 @@ teco_state_control_last_range(teco_machine_main_t *ctx, GError **error) return; } - teco_expressions_push(teco_interface_bytes2glyphs(teco_ranges[n].from)); - teco_expressions_push(teco_interface_bytes2glyphs(teco_ranges[n].to)); + teco_expressions_push(teco_ranges[n].from); + teco_expressions_push(teco_ranges[n].to); } /*$ ^S @@ -1551,17 +1537,20 @@ teco_state_control_last_length(teco_machine_main_t *ctx, GError **error) /* * There is little use in supporting n^S for n != 0. * This is just for consistency with ^Y. + * + * We do not use teco_expressions_pop_num_calc(), + * so as not to reset the sign prefix. */ - if (teco_expressions_args() > 0 && - !teco_expressions_pop_num_calc(&n, 0, error)) + if (!teco_expressions_eval(FALSE, error)) return; + if (teco_expressions_args() > 0) + n = teco_expressions_pop_num(0); if (n < 0 || n >= teco_ranges_count) { - teco_error_subpattern_set(error, "^Y"); + teco_error_subpattern_set(error, "^S"); return; } - teco_expressions_push(teco_interface_bytes2glyphs(teco_ranges[n].from) - - teco_interface_bytes2glyphs(teco_ranges[n].to)); + teco_expressions_push(teco_ranges[n].from - teco_ranges[n].to); } static void TECO_DEBUG_CLEANUP @@ -1570,6 +1559,100 @@ teco_ranges_cleanup(void) g_free(teco_ranges); } +/*$ ^B date + * ^B -> (((year-1900)*16 + month)*32 + day) -- Retrieve date + * + * Returns the current date via the given equation. + */ +/* + * FIXME: Perhaps :^B should directly return the + * decoded year, month and day. + */ +static void +teco_state_control_date(teco_machine_main_t *ctx, GError **error) +{ + GDate date; + + g_date_clear(&date, 1); + g_date_set_time_t(&date, time(NULL)); + teco_expressions_push(((g_date_get_year(&date)-1900)*16 + g_date_get_month(&date))*32 + + g_date_get_day(&date)); +} + +/*$ "^H" ":^H" "::^H" time timestamp + * ^H -> seconds since midnight -- Retrieve time of day or timestamp + * :^H -> seconds + * ::^H -> timestamp + * + * By default returns the current time in seconds since midnight (UTC). + * + * If colon-modified it returns the number of <seconds> since the Epoch, + * 1970-01-01 00:00:00 +0000 (UTC). + * + * If modified by two colons it returns the system's monotonic time in microseconds, + * which can be used as a <timestamp>. + */ +static void +teco_state_control_time(teco_machine_main_t *ctx, GError **error) +{ + switch (teco_machine_main_eval_colon(ctx)) { + case 0: + teco_expressions_push(time(NULL) % (60*60*24)); + break; + case 1: + teco_expressions_push(time(NULL)); + break; + case 2: + /* + * NOTE: Might not be reliable if TECO_INTEGER==32. + */ + teco_expressions_push(g_get_monotonic_time()); + break; + default: + g_assert_not_reached(); + } +} + +/*$ ^W refresh sleep delay wait + * [n]^W -- Wait and refresh screen + * + * First sleep <n> milliseconds before refreshing the view, + * i.e. drawing it. + * By default it sleeps for 10ms. + * This can be added to loops to make progress visible + * in interactive mode. + * In batch mode this command is useful as a sleep command. + * Sleeps can of course be interrupted with CTRL+C. + * + * Since CTRL+W is an immediate editing command, you may + * have to type this command in upcaret mode. + * To enforce a complete screen redraw you can also + * press CTRL+L. + */ +static void +teco_state_control_refresh(teco_machine_main_t *ctx, GError **error) +{ + teco_int_t ms; + + if (!teco_expressions_pop_num_calc(&ms, 10, error)) + return; + + while (ms > 0 && !teco_interface_is_interrupted()) { + /* + * UNIX' usleep() would also be interrupted by + * SIGINT, but polling for interruptions is + * probably precise enough. + * We need this as a fallback anyway. + */ + g_usleep(MIN(ms*1000, TECO_POLL_INTERVAL)); + ms -= TECO_POLL_INTERVAL/1000; + } + + teco_interface_unfold(); + teco_interface_ssm(SCI_SCROLLCARET, 0, 0); + teco_interface_refresh(FALSE); +} + static teco_state_t * teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) { @@ -1583,6 +1666,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) .modifier_at = TRUE, .modifier_colon = 1}, ['^'] = {&teco_state_ascii}, ['['] = {&teco_state_escape}, + ['C'] = {&teco_state_ctlc}, + ['A'] = {&teco_state_print_string, + .modifier_at = TRUE, .modifier_colon = 1}, /* * Additional numeric operations @@ -1595,7 +1681,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) /* * Commands */ - ['C'] = {&teco_state_start, teco_state_control_exit}, + ['B'] = {&teco_state_start, teco_state_control_date}, + ['H'] = {&teco_state_start, teco_state_control_time, + .modifier_colon = 2}, ['O'] = {&teco_state_start, teco_state_control_octal}, ['D'] = {&teco_state_start, teco_state_control_decimal}, ['R'] = {&teco_state_start, teco_state_control_radix}, @@ -1605,7 +1693,10 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) .modifier_colon = 1}, ['X'] = {&teco_state_start, teco_state_control_search_mode}, ['Y'] = {&teco_state_start, teco_state_control_last_range}, - ['S'] = {&teco_state_start, teco_state_control_last_length} + ['S'] = {&teco_state_start, teco_state_control_last_length}, + ['T'] = {&teco_state_start, teco_state_control_typeout, + .modifier_colon = 1}, + ['W'] = {&teco_state_start, teco_state_control_refresh} }; /* @@ -1617,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) @@ -1640,7 +1733,82 @@ 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 + * [a1,a2,...]^[$ + * [a1,a2,...]^C + * + * Returns from the current macro invocation. + * This will pass control to the calling macro immediately + * and is thus faster than letting control reach the macro's end. + * Also, direct arguments to \fB$$\fP will be left on the expression + * stack when the macro returns. + * \fB$$\fP closes loops automatically and is thus safe to call + * from loop bodies. + * Furthermore, it has defined semantics when executed + * from within braced expressions: + * All braces opened in the current macro invocation will + * be closed and their values discarded. + * Only the direct arguments to \fB$$\fP will be kept. + * + * Returning from the top-level macro in batch mode + * will exit the program or start up interactive mode depending + * on whether program exit has been requested. + * If \fB$$\fP exits the program, any remaining numeric parameter + * is returned by the process as its exit status. + * By default, the success code is returned. + * \(lqEX\fB$$\fP\(rq is thus a common idiom to exit + * prematurely. + * + * In interactive mode, returning from the top-level macro + * (i.e. typing \fB$$\fP at the command line) has the + * effect of command line termination. + * The arguments to \fB$$\fP are currently not used + * when terminating a command line \(em the new command line + * will always start with a clean expression stack. + * + * \fB^C\fP cannot be typed directly on the command-line + * as it could be inserted accidentally after interrupting + * operations with CTRL+C. + * + * The first \fIescape\fP of \fB$$\fP may be typed either + * as an escape character (ASCII 27), in up-arrow mode + * (e.g. \fB^[$\fP) or as a dollar character \(em the + * second character must be either a real escape character + * or a dollar character. + */ +/* + * FIXME: Analogous to ^C^C, we could support ^[^[ typed with carets only + * at the expense of yet another parser state. + */ +static teco_state_t * +teco_return(teco_machine_main_t *ctx, GError **error) +{ + g_assert(ctx->flags.mode == TECO_MODE_NORMAL); + + /* + * This check is not crucial, but a return command would + * terminate the command line and it would be impossible to apply the new + * 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))) { + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "Not allowed to terminate command-line while " + "editing command-line replacement register"); + return NULL; + } + + ctx->parent.current = &teco_state_start; + if (!teco_expressions_eval(FALSE, error)) + return NULL; + teco_error_return_set(error, teco_expressions_args()); + return NULL; +} /* * The Escape state is special, as it implements @@ -1658,52 +1826,9 @@ TECO_DEFINE_STATE(teco_state_ascii); static teco_state_t * teco_state_escape_input(teco_machine_main_t *ctx, gunichar chr, GError **error) { - /*$ ^[^[ ^[$ $$ terminate return - * [a1,a2,...]$$ -- Terminate command line or return from macro - * [a1,a2,...]^[$ - * - * Returns from the current macro invocation. - * This will pass control to the calling macro immediately - * and is thus faster than letting control reach the macro's end. - * Also, direct arguments to \fB$$\fP will be left on the expression - * stack when the macro returns. - * \fB$$\fP closes loops automatically and is thus safe to call - * from loop bodies. - * Furthermore, it has defined semantics when executed - * from within braced expressions: - * All braces opened in the current macro invocation will - * be closed and their values discarded. - * Only the direct arguments to \fB$$\fP will be kept. - * - * Returning from the top-level macro in batch mode - * will exit the program or start up interactive mode depending - * on whether program exit has been requested. - * \(lqEX\fB$$\fP\(rq is thus a common idiom to exit - * prematurely. - * - * In interactive mode, returning from the top-level macro - * (i.e. typing \fB$$\fP at the command line) has the - * effect of command line termination. - * The arguments to \fB$$\fP are currently not used - * when terminating a command line \(em the new command line - * will always start with a clean expression stack. - * - * The first \fIescape\fP of \fB$$\fP may be typed either - * as an escape character (ASCII 27), in up-arrow mode - * (e.g. \fB^[$\fP) or as a dollar character \(em the - * second character must be either a real escape character - * or a dollar character. - */ - if (chr == '\e' || chr == '$') { - if (ctx->flags.mode > TECO_MODE_NORMAL) - return &teco_state_start; - - ctx->parent.current = &teco_state_start; - if (!teco_expressions_eval(FALSE, error)) - return NULL; - teco_error_return_set(error, teco_expressions_args()); - return NULL; - } + if (chr == '\e' || chr == '$') + return ctx->flags.mode > TECO_MODE_NORMAL + ? &teco_state_start : teco_return(ctx, error); /* * Alternatives: ^[, <CTRL/[>, <ESC>, $ (dollar) @@ -1743,14 +1868,100 @@ teco_state_escape_end_of_macro(teco_machine_t *ctx, GError **error) return teco_expressions_discard_args(error); } -TECO_DEFINE_STATE_COMMAND(teco_state_escape, - .end_of_macro_cb = teco_state_escape_end_of_macro, - /* - * The state should behave like teco_state_start - * when it comes to function key macro masking. +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 +); + +/* + * Just like ^[, ^C actually implements a lookahead, + * so a ^C itself does nothing. + * This does not break the user experience since ^C + * is disallowed to type at the command-line. + */ +static teco_state_t * +teco_state_ctlc_input(teco_machine_main_t *ctx, gunichar chr, GError **error) +{ + switch (chr) { + case TECO_CTL_KEY('C'): return teco_state_ctlc_control_input(ctx, 'C', error); + case '^': return &teco_state_ctlc_control; + } + + return ctx->flags.mode > TECO_MODE_NORMAL + ? teco_state_start_input(ctx, chr, error) : teco_return(ctx, error); +} + +static gboolean +teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error) +{ + if (G_UNLIKELY(ctx == &teco_cmdline.machine)) { + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "<^C> is not allowed to terminate command-lines"); + return FALSE; + } + + return TRUE; +} + +TECO_DEFINE_STATE_START(teco_state_ctlc, + .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 * +teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) +{ + /*$ ^C^C exit + * [n]^C^C -- Exit program immediately + * + * Lets the top-level macro return immediately + * regardless of the current macro invocation frame. + * This command is only allowed in batch mode, + * so it is not invoked accidentally when using + * the CTRL+C immediate editing command to + * interrupt long running operations. + * When using \fB^C^C\fP in a munged file, + * interactive mode is never started, so it behaves + * effectively just like \(lq-EX\fB$$\fP\(rq + * (when executed in the top-level macro at least). + * + * Any numeric parameter is returned by the process + * as its exit status. + * By default, the success code is returned. + * The \fBquit\fP hook is still executed. + * + * This command is currently disallowed in interactive mode. + * + * Note that both \(lq^C\(rq can be typed either + * as control codes (3) or with carets. */ - .is_start = TRUE, - .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE + if (chr == 'c' || chr == 'C') { + if (ctx->flags.mode > TECO_MODE_NORMAL) + return &teco_state_start; + + if (teco_undo_enabled) { + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "<^C^C> not allowed in interactive mode"); + return NULL; + } + + if (!teco_expressions_eval(FALSE, error)) + return NULL; + teco_ed |= TECO_ED_EXIT; + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_QUIT, ""); + return NULL; + } + + return ctx->flags.mode > TECO_MODE_NORMAL + ? teco_state_control_input(ctx, chr, error) : teco_return(ctx, error); +} + +/* + * 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, + .input_cb = (teco_state_input_cb_t)teco_state_ctlc_control_input ); /*$ ED flags @@ -1772,7 +1983,12 @@ TECO_DEFINE_STATE_COMMAND(teco_state_escape, * Without any argument ED returns the current flags. * * Currently, the following flags are used by \*(ST: - * .IP 4: 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 + * prior \fBEX\fP. + * .IP 4: * If enabled, prefer raw single-byte ANSI encoding * for all new buffers and registers. * This does not change the encoding of any existing @@ -1816,6 +2032,13 @@ TECO_DEFINE_STATE_COMMAND(teco_state_escape, * by the \(lqNerd Fonts\(rq project. * Changes to this flag in interactive mode may not become * effective immediately. + * .IP 1024: + * If set the default clipboard register \(lq~\(rq will refer + * to the primary clipboard (\(lq~P\(rq) instead of the + * clipboard selection (\(lq~C\(rq). + * .IP 2048: + * Enable/Disable redirection of Scintilla messages (\fBES\fP) + * to the command line's Scintilla view. * * The features controlled thus are discribed in other sections * of this manual. @@ -1862,7 +2085,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * The current user interface: 1 for Curses, 2 for GTK * (\fBread-only\fP) * .IP 1: - * The current numbfer of buffers: Also the numeric id + * The current number of buffers: Also the numeric id * of the last buffer in the ring. This is implied if * no argument is given, so \(lqEJ\(rq returns the number * of buffers in the ring. @@ -1938,20 +2161,35 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * The column after the last horizontal movement. * This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal * setting \fBSCI_CHOOSECARETX\fP. - * Unless most other settings, this is on purpose not restored on rubout, - * so it "survives" command line replacements. + * Unlike most other settings, this is on purpose not restored on rubout, + * so it \(lqsurvives\(rq command line replacements. + * .IP 5: + * Height of the command line view in lines (1 by default). + * Must not be smaller than 1. + * .IP 6: + * .SCITECO_TOPIC recovery + * Interval in seconds for the creation of recovery files + * or 0 if those dumps are disabled (the default is 300 seconds). + * When enabled all dirty buffers are dumped to files with hash + * signs around the original basename (\fB#\fIfilename\fB#\fR). + * They are removed automatically when no longer required, + * but may be left around when the \*(ST crashes or terminates + * unexpectedly. + * After changing the interval, the new value may become + * active only after the previous interval expires. + * Recovery files are not dumped in batch mode. * . * .IP -1: * Type of the last mouse event (\fBread-only\fP). * One of the following values will be returned: * .RS - * . IP 1: 4 + * . IP 0: 4 * Some button has been pressed - * . IP 2: + * . IP 1: * Some button has been released - * . IP 3: + * . IP 2: * Scroll up - * . IP 4: + * . IP 3: * Scroll down * .RE * .IP -2: @@ -1992,23 +2230,22 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) EJ_BUFFERS, EJ_MEMORY_LIMIT, EJ_INIT_COLOR, - EJ_CARETX + EJ_CARETX, + EJ_CMDLINE_HEIGHT, + EJ_RECOVERY_INTERVAL }; static teco_int_t caret_x = 0; teco_int_t property; - if (!teco_expressions_eval(FALSE, error) || - !teco_expressions_pop_num_calc(&property, teco_num_sign, error)) + if (!teco_expressions_pop_num_calc(&property, teco_num_sign, error)) return; if (teco_expressions_args() > 0) { /* * Set property */ - teco_int_t value, color; - if (!teco_expressions_pop_num_calc(&value, 0, error)) - return; + teco_int_t value = teco_expressions_pop_num(0); switch (property) { case EJ_MEMORY_LIMIT: @@ -2027,15 +2264,36 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) teco_error_argexpected_set(error, "EJ"); return; } - if (!teco_expressions_pop_num_calc(&color, 0, error)) - return; - teco_interface_init_color((guint)value, (guint32)color); + teco_interface_init_color((guint)value, + (guint32)teco_expressions_pop_num(0)); break; case EJ_CARETX: + /* DON'T undo on rubout */ caret_x = value; break; + case EJ_CMDLINE_HEIGHT: + if (value < 1 || value > G_MAXUINT) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Invalid command line height %" TECO_INT_FORMAT " " + "for <EJ>", value); + return; + } + teco_undo_guint(teco_cmdline.height) = value; + break; + + case EJ_RECOVERY_INTERVAL: + if (value < 0 || value > G_MAXUINT) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Invalid recovery interval %" TECO_INT_FORMAT "s " + "for <EJ>", value); + return; + } + teco_undo_guint(teco_ring_recovery_interval) = value; + /* FIXME: Perhaps signal the interface to reprogram timers */ + break; + default: g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Cannot set property %" TECO_INT_FORMAT " " @@ -2091,6 +2349,14 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) teco_expressions_push(caret_x); break; + case EJ_CMDLINE_HEIGHT: + teco_expressions_push(teco_cmdline.height); + break; + + case EJ_RECOVERY_INTERVAL: + teco_expressions_push(teco_ring_recovery_interval); + break; + default: g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Invalid property %" TECO_INT_FORMAT " " @@ -2099,7 +2365,7 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) } } -/*$ EL eol +/*$ "EL" ":EL" EOL * 0EL -- Set or get End of Line mode * 13,10:EL * 1EL @@ -2150,11 +2416,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error) teco_int_t eol_mode; if (teco_machine_main_eval_colon(ctx) > 0) { - teco_int_t v1, v2; - if (!teco_expressions_pop_num_calc(&v1, 0, error)) - return; - - switch (v1) { + switch (teco_expressions_pop_num(0)) { case '\r': eol_mode = SC_EOL_CR; break; @@ -2163,9 +2425,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error) eol_mode = SC_EOL_LF; break; } - if (!teco_expressions_pop_num_calc(&v2, 0, error)) - return; - if (v2 == '\r') { + if (teco_expressions_pop_num(0) == '\r') { eol_mode = SC_EOL_CRLF; break; } @@ -2176,8 +2436,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error) return; } } else { - if (!teco_expressions_pop_num_calc(&eol_mode, 0, error)) - return; + eol_mode = teco_expressions_pop_num(0); switch (eol_mode) { case SC_EOL_CRLF: case SC_EOL_CR: @@ -2195,6 +2454,13 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error) undo__teco_interface_ssm(SCI_SETEOLMODE, teco_interface_ssm(SCI_GETEOLMODE, 0, 0), 0); teco_interface_ssm(SCI_SETEOLMODE, eol_mode, 0); + + /* + * While the buffer contents were not changed, + * the result of saving the file may differ, + * so we still dirtify the buffer. + */ + teco_ring_dirtify(); } else if (teco_machine_main_eval_colon(ctx) > 0) { const gchar *eol_seq = teco_eol_get_seq(teco_interface_ssm(SCI_GETEOLMODE, 0, 0)); teco_expressions_push(eol_seq); @@ -2255,7 +2521,7 @@ teco_codepage2str(guint codepage) return NULL; } -/*$ EE encoding codepage charset +/*$ "EE" ":EE" encoding codepage charset * codepageEE -- Edit current document's encoding (codepage/charset) * EE -> codepage * codepage:EE @@ -2319,10 +2585,7 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error) /* * Set code page */ - teco_int_t new_cp; - if (!teco_expressions_pop_num_calc(&new_cp, 0, error)) - return; - + teco_int_t new_cp = teco_expressions_pop_num(0); if (old_cp == SC_CP_UTF8 && new_cp == SC_CP_UTF8) return; @@ -2449,7 +2712,26 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error) teco_interface_ssm(SCI_GOTOPOS, teco_interface_glyphs2bytes(dot_glyphs), 0); } -/*$ EX exit +/*$ EO version + * EO -> major*10000 + minor*100 + micro -- Get program version + * + * Return the version of \*(ST encoded into an integer. + */ +static void +teco_state_ecommand_version(teco_machine_main_t *ctx, GError **error) +{ + /* + * FIXME: This is inefficient and could be done at build-time. + * Or we could have PACKAGE_MAJOR_VERSION, PACKAGE_MINOR_VERSION etc. macros. + * But then, who cares? + */ + guint major, minor, micro; + G_GNUC_UNUSED gint rc = sscanf(PACKAGE_VERSION, "%u.%u.%u", &major, &minor, µ); + g_assert(rc == 3); + teco_expressions_push(major*10000 + minor*100 + micro); +} + +/*$ "EX" ":EX" exit quit * [bool]EX -- Exit program * -EX * :EX @@ -2486,6 +2768,10 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error) * \(lq:EX\fB$$\fP\(rq is nevertheless the usual interactive * command sequence to exit while saving all modified * buffers. + * + * The program termination request is also available in bit 2 + * of the \fBED\fP flags, so \(lqED&2\(rq can be used to + * check whether EX has been successfully called. */ /** @fixme what if changing file after EX? will currently still exit */ static void @@ -2498,14 +2784,22 @@ teco_state_ecommand_exit(teco_machine_main_t *ctx, GError **error) teco_int_t v; if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) return; - if (teco_is_failure(v) && teco_ring_is_any_dirty()) { - g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, - "Modified buffers exist"); + guint id; + if (teco_is_failure(v) && (id = teco_ring_get_first_dirty())) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Buffer with id %u is dirty", id); return; } } - teco_undo_gboolean(teco_quit_requested) = TRUE; + teco_undo_int(teco_ed) |= TECO_ED_EXIT; +} + +static void +teco_state_macrofile_deprecated(teco_machine_main_t *ctx, GError **error) +{ + teco_interface_msg(TECO_MSG_WARNING, + "<EM> command is deprecated - use <EI> instead"); } static teco_state_t * @@ -2523,9 +2817,10 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error .modifier_at = TRUE, .modifier_colon = 1}, ['G'] = {&teco_state_egcommand, .modifier_at = TRUE, .modifier_colon = 1}, - ['I'] = {&teco_state_insert_nobuilding, - .modifier_at = TRUE}, - ['M'] = {&teco_state_macrofile, + ['I'] = {&teco_state_indirect, + .modifier_at = TRUE, .modifier_colon = 1}, + /* DEPRECATED: can be repurposed */ + ['M'] = {&teco_state_indirect, teco_state_macrofile_deprecated, .modifier_at = TRUE, .modifier_colon = 1}, ['N'] = {&teco_state_glob_pattern, .modifier_at = TRUE, .modifier_colon = 1}, @@ -2537,6 +2832,8 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error .modifier_at = TRUE, .modifier_colon = 1}, ['W'] = {&teco_state_save_file, .modifier_at = TRUE}, + ['R'] = {&teco_state_read_file, + .modifier_at = TRUE}, /* * Commands @@ -2549,6 +2846,7 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error .modifier_colon = 1}, ['E'] = {&teco_state_start, teco_state_ecommand_encoding, .modifier_colon = 1}, + ['O'] = {&teco_state_start, teco_state_ecommand_version}, ['X'] = {&teco_state_start, teco_state_ecommand_exit, .modifier_colon = 1}, }; @@ -2560,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) @@ -2568,7 +2868,8 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) if (ctx->flags.mode > TECO_MODE_NORMAL) return TRUE; - teco_undo_gsize(teco_ranges[0].from) = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + 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; /* @@ -2622,22 +2923,21 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) undo__teco_interface_ssm(SCI_UNDO, 0, 0); /* This is done only now because it can _theoretically_ fail. */ - for (gint i = args; i > 0; i--) - if (!teco_expressions_pop_num_calc(NULL, 0, error)) - return FALSE; + for (gint i = 0; i < args; i++) + teco_expressions_pop_num(0); return TRUE; } 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(); @@ -2648,11 +2948,13 @@ 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) - teco_undo_gsize(teco_ranges[0].to) = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + 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].to) = teco_interface_bytes2glyphs(pos); return &teco_state_start; } @@ -2677,21 +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_building); - -/*$ EI - * [c1,c2,...]EI[text]$ -- Insert text without string building characters - * - * Inserts text at the current position in the current - * document. - * This command is identical to the \fBI\fP command, - * except that string building characters are \fBdisabled\fP. - * Therefore it may be beneficial when editing \*(ST - * macros. - */ -TECO_DEFINE_STATE_INSERT(teco_state_insert_nobuilding, - .expectstring.string_building = FALSE -); +TECO_DEFINE_STATE_INSERT(teco_state_insert); static gboolean teco_state_insert_indent_initial(teco_machine_main_t *ctx, GError **error) |
