From aaa1d51a4c85fcc627e88ef7cf5292d9c5f5f840 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Sun, 8 Jun 2025 20:54:29 +0200 Subject: ^S/^Y calculates the glyph offsets earlier * Previously, deleting text after a text match or insertion could result in wrong ^S/^Y results. In particular, the amount of characters deleted by at the end of a buffer couldn't be queried. * This also fixes the M#rf (reflow paragraph) macro. --- TODO | 1 - src/core-commands.c | 29 ++++++++++++++++++----------- src/core-commands.h | 4 ++-- src/glob.c | 15 +++++++++------ src/qreg-commands.c | 5 +++-- src/search.c | 33 +++++++++++++++++++++++++++------ src/spawn.c | 5 +++-- tests/testsuite.at | 4 +++- 8 files changed, 65 insertions(+), 31 deletions(-) diff --git a/TODO b/TODO index ffaf95a..ab0702e 100644 --- a/TODO +++ b/TODO @@ -8,7 +8,6 @@ Tasks: https://github.com/Bill-Gray/PDCursesMod/issues/330 Known Bugs: - * returns the deleted length-1, which also breaks <^S>. * Gtk: The control characters in tutorial.woman are still styled with the variable-width font since its rendered in STYLE_CONTROLCHAR (36), which is reset in woman.tes. diff --git a/src/core-commands.c b/src/core-commands.c index 4f3ed94..b8918ef 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -225,8 +225,13 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) 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); @@ -1465,8 +1470,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 is only allowed to be 0 or missing. * * For instance, \(lq^YXq\(rq copies the entire matched pattern or text @@ -1489,8 +1494,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 @@ -1527,8 +1532,7 @@ teco_state_control_last_length(teco_machine_main_t *ctx, GError **error) 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 @@ -2671,7 +2675,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; /* @@ -2753,9 +2758,11 @@ 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) { - 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; } diff --git a/src/core-commands.h b/src/core-commands.h index 432ec38..9f62da8 100644 --- a/src/core-commands.h +++ b/src/core-commands.h @@ -44,8 +44,8 @@ TECO_DECLARE_STATE(teco_state_ctlc_control); TECO_DECLARE_STATE(teco_state_ecommand); typedef struct { - gsize from; /*< start position in bytes */ - gsize to; /*< end position in bytes */ + teco_int_t from; /*< start position in glyphs */ + teco_int_t to; /*< end position in glyphs */ } teco_range_t; extern guint teco_ranges_count; diff --git a/src/glob.c b/src/glob.c index 5e013e4..c7013b1 100644 --- a/src/glob.c +++ b/src/glob.c @@ -515,8 +515,9 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str if (!colon_modified) { gsize len = strlen(filename); - 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 + 1; + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos + len + 1); teco_undo_guint(teco_ranges_count) = 1; /* @@ -550,16 +551,15 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str g_auto(teco_globber_t) globber; teco_globber_init(&globber, pattern_str.data, file_flags); - 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; - teco_undo_guint(teco_ranges_count) = 1; + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0); gchar *globbed_filename; while ((globbed_filename = teco_globber_next(&globber))) { gsize len = strlen(globbed_filename); - teco_ranges[0].to += len+1; + pos += len+1; /* * FIXME: Filenames may contain linefeeds. @@ -573,6 +573,9 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str } teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); + + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + teco_undo_guint(teco_ranges_count) = 1; } if (colon_modified) { diff --git a/src/qreg-commands.c b/src/qreg-commands.c index 09c3549..0457e92 100644 --- a/src/qreg-commands.c +++ b/src/qreg-commands.c @@ -534,8 +534,9 @@ 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; + sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 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; if (str.len > 0) { diff --git a/src/search.c b/src/search.c index b02e178..d5387da 100644 --- a/src/search.c +++ b/src/search.c @@ -548,6 +548,16 @@ TECO_DEFINE_UNDO_OBJECT_OWN(ranges, teco_range_t *, g_free); #define teco_undo_ranges_own(VAR) \ (*teco_undo_object_ranges_push(&(VAR))) +/** + * Extract the ranges of the given GMatchInfo. + * + * @param match_info The result of g_regex_match(). + * @param offset The beginning of the match operation in bytes. + * Match results will be relative to this offset. + * @param count Where to store the number of ranges (subpatterns). + * @returns Ranges (subpatterns) in absolute byte positions. + * They \b must still be converted to glyph positions afterwards. + */ static teco_range_t * teco_get_ranges(const GMatchInfo *match_info, gsize offset, guint *count) { @@ -661,7 +671,7 @@ teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) matched[i].ranges = NULL; } - for (int i = 0; i < matched_num; i++) + for (gint i = 0; i < matched_num; i++) g_free(matched[i].ranges); } @@ -671,7 +681,16 @@ teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) teco_undo_guint(teco_ranges_count) = num_ranges; g_assert(teco_ranges_count > 0); - teco_interface_ssm(SCI_SETSEL, matched_ranges[0].from, matched_ranges[0].to); + teco_interface_ssm(SCI_SETSEL, teco_ranges[0].from, teco_ranges[0].to); + + /* + * teco_get_ranges() returned byte positions, + * while everything else expects glyph offsets. + */ + for (guint i = 0; i < teco_ranges_count; i++) { + teco_ranges[i].from = teco_interface_bytes2glyphs(teco_ranges[i].from); + teco_ranges[i].to = teco_interface_bytes2glyphs(teco_ranges[i].to); + } } return TRUE; @@ -1083,13 +1102,15 @@ teco_state_search_kill_done(teco_machine_main_t *ctx, const teco_string_t *str, if (teco_search_parameters.dot < dot) { /* kill forwards */ sptr_t anchor = teco_interface_ssm(SCI_GETANCHOR, 0, 0); - gsize len = anchor - teco_search_parameters.dot; + teco_int_t len_glyphs = teco_interface_bytes2glyphs(anchor) - + teco_interface_bytes2glyphs(teco_search_parameters.dot); if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_GOTOPOS, dot, 0); teco_interface_ssm(SCI_GOTOPOS, anchor, 0); - teco_interface_ssm(SCI_DELETERANGE, teco_search_parameters.dot, len); + teco_interface_ssm(SCI_DELETERANGE, teco_search_parameters.dot, + anchor - teco_search_parameters.dot); /* NOTE: An undo action is not always created. */ if (teco_current_doc_must_undo() && @@ -1098,8 +1119,8 @@ teco_state_search_kill_done(teco_machine_main_t *ctx, const teco_string_t *str, /* fix up ranges (^Y) */ for (guint i = 0; i < teco_ranges_count; i++) { - teco_ranges[i].from -= len; - teco_ranges[i].to -= len; + teco_ranges[i].from -= len_glyphs; + teco_ranges[i].to -= len_glyphs; } } else { /* kill backwards */ diff --git a/src/spawn.c b/src/spawn.c index d51dbb1..187de27 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -420,8 +420,9 @@ teco_state_execute_done(teco_machine_main_t *ctx, const teco_string_t *str, GErr teco_interface_ssm(SCI_DELETERANGE, teco_spawn_ctx.from, teco_spawn_ctx.to - teco_spawn_ctx.from); - teco_undo_gsize(teco_ranges[0].from) = teco_spawn_ctx.from; - teco_undo_gsize(teco_ranges[0].to) = 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(teco_spawn_ctx.from); + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); teco_undo_guint(teco_ranges_count) = 1; } teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0); diff --git a/tests/testsuite.at b/tests/testsuite.at index 8155ce4..96d4978 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -211,8 +211,10 @@ TE_CHECK([[@I/XYZ/J @EB/foo/ @I/XZY/J @:FN/Z/0/"F(0/0)' Q*-2"N(0/0)' AT_CLEANUP AT_SETUP([Search and insertion ranges]) -TE_CHECK([[@I/XXYYZZ/^SC ."N(0/0)' C @S/YY/^YU1U0 Q0-2"N(0/0)' Q1-4"N(0/0)']], 0, ignore, ignore) +# When deleting characters, the result of ^S/^Y must not change. +TE_CHECK([[@I/XXYYZZ/^SC ."N(0/0)' C @S/YY/ HK ^YU1U0 Q0-2"N(0/0)' Q1-4"N(0/0)']], 0, ignore, ignore) TE_CHECK([[@I/XXYYZZ/J @S/XX^E[^EMY]/ 1^YXa :Qa-2"N(0/0)']], 0, ignore, ignore) +TE_CHECK([[@I/XXYYZZ/J @FD/^EMZ/ ^S+2"N(0/0)']], 0, ignore, ignore) AT_CLEANUP AT_SETUP([Editing local registers in macro calls]) -- cgit v1.2.3