diff options
Diffstat (limited to 'src/qreg-commands.c')
| -rw-r--r-- | src/qreg-commands.c | 235 |
1 files changed, 124 insertions, 111 deletions
diff --git a/src/qreg-commands.c b/src/qreg-commands.c index a4019a0..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 @@ -39,8 +39,8 @@ teco_state_expectqreg_initial(teco_machine_main_t *ctx, GError **error) teco_state_t *current = ctx->parent.current; /* - * NOTE: We have to allocate a new instance always since `expectqreg` - * is part of an union. + * NOTE: This could theoretically be allocated once in + * teco_machine_main_init(), but we'd have to set the type here anyway. */ ctx->expectqreg = teco_machine_qregspec_new(current->expectqreg.type, ctx->qreg_table_locals, ctx->parent.must_undo); @@ -54,8 +54,8 @@ teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr, GError **err { teco_state_t *current = ctx->parent.current; - teco_qreg_t *qreg; - teco_qreg_table_t *table; + teco_qreg_t *qreg = NULL; + teco_qreg_table_t *table = NULL; switch (teco_machine_qregspec_input(ctx->expectqreg, chr, ctx->flags.mode == TECO_MODE_NORMAL ? &qreg : NULL, &table, error)) { @@ -69,7 +69,7 @@ teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr, GError **err /* * NOTE: ctx->expectqreg is preserved since we may want to query it from follow-up - * states. This means, it must usually be stored manually in got_register_cb() via: + * states. This means, it must usually be reset manually in got_register_cb() via: * teco_state_expectqreg_reset(ctx); */ return current->expectqreg.got_register_cb(ctx, qreg, table, error); @@ -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, @@ -99,25 +101,37 @@ teco_state_popqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, { teco_state_expectqreg_reset(ctx); - return ctx->flags.mode == TECO_MODE_NORMAL && - !teco_qreg_stack_pop(qreg, error) ? NULL : &teco_state_start; + if (ctx->flags.mode > TECO_MODE_NORMAL) + return &teco_state_start; + + if (!teco_machine_main_eval_colon(ctx)) + return !teco_qreg_stack_pop(qreg, error) ? NULL : &teco_state_start; + teco_expressions_push(teco_bool(teco_qreg_stack_pop(qreg, NULL))); + return &teco_state_start; } -/*$ "]" "]q" pop +/*$ "]" "]q" ":]q" pop * ]q -- Restore Q-Register + * :]q -> Success|Failure * * Restore Q-Register <q> by replacing its contents * with the contents of the register saved on top of * the Q-Register push-down stack. * The stack entry is popped. * + * When colon-modified, \fB]\fP returns a success boolean + * (-1) if there was a register to pop. + * If the stack was empty, a failure boolean (0) is returned + * instead of throwing an error. + * * In interactive mode, the original contents of <q> * are not immediately reclaimed but are kept in memory * to support rubbing out the command. * 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 * @@ -131,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; @@ -146,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 { @@ -176,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, @@ -188,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; @@ -201,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; } @@ -220,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) @@ -273,9 +294,7 @@ teco_state_queryqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, if (teco_expressions_args() > 0) { /* Query character from Q-Register string */ - teco_int_t pos; - if (!teco_expressions_pop_num_calc(&pos, 0, error)) - return NULL; + teco_int_t pos = teco_expressions_pop_num(0); if (pos < 0) { teco_error_range_set(error, "Q"); return NULL; @@ -297,24 +316,24 @@ teco_state_queryqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, return &teco_state_start; } -/*$ Q Qq query +/*$ "Q" "Qq" ":Qq" query * Qq -> n -- Query Q-Register existence, its integer or string characters * -Qq -> -n - * <position>Qq -> character + * <position>Qq -> code * :Qq -> -1 | size * * Without any arguments, get and return the integer-part of * Q-Register <q>. * - * With one argument, return the <character> code at <position> + * With one argument, return the character <code> at <position> * from the string-part of Q-Register <q>. * Positions are handled like buffer positions \(em they * begin at 0 up to the length of the string minus 1. - * An error is thrown for invalid positions. + * -1 is returned for invalid positions. * If <q> 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 UTF-8 byte sequences are returned as -3. * Both non-colon-modified forms of Q require register <q> * to be defined and fail otherwise. * @@ -337,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 * @@ -351,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; @@ -378,43 +399,46 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx, return NULL; g_autofree gchar *buffer = NULL; + const gchar *start; gsize len = 0; if (codepage == SC_CP_UTF8) { - /* the glib docs wrongly claim that one character can take 6 bytes */ - buffer = g_malloc(4*args); + /* 4 bytes should be enough for UTF-8, but we better follow the documentation */ + start = buffer = g_malloc(args*6); + for (gint i = args; i > 0; i--) { - teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, 0, error)) - return NULL; - if (v < 0 || !g_unichar_validate(v)) { + teco_int_t chr = teco_expressions_peek_num(i-1); + if (chr < 0 || !g_unichar_validate(chr)) { teco_error_codepoint_set(error, "^U"); return NULL; } - len += g_unichar_to_utf8(v, buffer+len); + len += g_unichar_to_utf8(chr, buffer+len); } + /* we pop only now since we had to peek in reverse order */ + for (gint i = 0; i < args; i++) + teco_expressions_pop_num(0); } else { buffer = g_malloc(args); - for (gint i = args; i > 0; i--) { - teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, 0, error)) - return NULL; - if (v < 0 || v > 0xFF) { + + for (gint i = 0; i < args; i++) { + teco_int_t chr = teco_expressions_pop_num(0); + if (chr < 0 || chr > 0xFF) { teco_error_codepoint_set(error, "^U"); return NULL; } - buffer[len++] = v; + buffer[args-(++len)] = chr; } + start = buffer+args-len; } if (colon_modified) { /* append to register */ - if (!qreg->vtable->append_string(qreg, buffer, len, error)) + if (!qreg->vtable->append_string(qreg, start, len, error)) return NULL; } else { /* set register */ if (!qreg->vtable->undo_set_string(qreg, error) || - !qreg->vtable->set_string(qreg, buffer, len, + !qreg->vtable->set_string(qreg, start, len, codepage, error)) return NULL; } @@ -422,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; } @@ -435,7 +459,7 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx, return &teco_state_start; } -/*$ ^Uq +/*$ "^Uq" ":^Uq" "set string" append * [c1,c2,...]^Uq[string]$ -- Set or append to Q-Register string without string building * [c1,c2,...]:^Uq[string]$ * @@ -462,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 * @@ -476,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 @@ -499,13 +525,7 @@ 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 +/*$ "EU" "EUq" ":EUq" * [c1,c2,...]EUq[string]$ -- Set or append to Q-Register string with string building characters * [c1,c2,...]:EUq[string]$ * @@ -517,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 * @@ -534,9 +555,12 @@ teco_state_getqregstring_got_register(teco_machine_main_t *ctx, teco_qreg_t *qre if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error)) return NULL; - 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 + str.len; - teco_undo_guint(teco_ranges_count) = 1; + if (teco_machine_main_eval_colon(ctx)) { + teco_interface_msg_literal(TECO_MSG_USER, str.data, str.len); + return &teco_state_start; + } + + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); if (str.len > 0) { teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); @@ -548,17 +572,28 @@ teco_state_getqregstring_got_register(teco_machine_main_t *ctx, teco_qreg_t *qre undo__teco_interface_ssm(SCI_UNDO, 0, 0); } + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos + str.len); + teco_undo_guint(teco_ranges_count) = 1; + return &teco_state_start; } -/*$ G Gq get - * Gq -- Insert Q-Register string +/*$ G Gq get paste + * Gq -- Insert or print Q-Register string + * :Gq * * Inserts the string of Q-Register <q> into the buffer * at its current position. + * If colon-modified prints the string as a message + * (i.e. to the terminal and/or in the message area) instead + * of modifying the current buffer. + * * 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, @@ -590,7 +625,7 @@ teco_state_setqreginteger_got_register(teco_machine_main_t *ctx, teco_qreg_t *qr return &teco_state_start; } -/*$ U Uq +/*$ "U" "Uq" ":Uq" set * nUq -- Set Q-Register integer * -Uq * [n]:Uq -> Success|Failure @@ -607,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 * @@ -641,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 * @@ -674,7 +711,7 @@ teco_state_macro_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, return &teco_state_start; } -/*$ M Mq eval +/*$ "M" "Mq" ":Mq" call eval macro * Mq -- Execute macro * :Mq * @@ -700,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_macrofile_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 */ @@ -725,9 +764,9 @@ teco_state_macrofile_done(teco_machine_main_t *ctx, const teco_string_t *str, GE return &teco_state_start; } -/*$ EM - * EMfile$ -- Execute macro from file - * :EMfile$ +/*$ "EI" ":EI" indirect include + * EIfile$ -- Execute from indirect command file + * :EIfile$ * * Read the file with name <file> into memory and execute its contents * as a macro. @@ -738,7 +777,9 @@ teco_state_macrofile_done(teco_machine_main_t *ctx, const teco_string_t *str, GE * 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_macrofile); +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, @@ -757,39 +798,10 @@ teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - gssize from, len; /* in bytes */ + gsize from, len; - if (!teco_expressions_eval(FALSE, error)) + if (!teco_get_range_args("X", &from, &len, error)) return NULL; - if (teco_expressions_args() <= 1) { - teco_int_t line; - - from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error)) - return NULL; - line += teco_interface_ssm(SCI_LINEFROMPOSITION, from, 0); - - if (!teco_validate_line(line)) { - teco_error_range_set(error, "X"); - return NULL; - } - - 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, "X"); - return NULL; - } - } /* * NOTE: This does not use SCI_GETRANGEPOINTER+SCI_GETGAPPOSITION @@ -837,7 +849,7 @@ teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, return &teco_state_start; } -/*$ X Xq +/*$ "X" "Xq" ":Xq" "@Xq" ":@Xq" copy extract * [lines]Xq -- Copy into or append or cut to Q-Register * -Xq * from,toXq @@ -870,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 ); |
