diff options
Diffstat (limited to 'src/search.c')
| -rw-r--r-- | src/search.c | 228 |
1 files changed, 153 insertions, 75 deletions
diff --git a/src/search.c b/src/search.c index 7fcf10e..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 @@ -56,7 +56,7 @@ TECO_DEFINE_UNDO_SCALAR(teco_search_parameters_t); */ static teco_search_parameters_t teco_search_parameters; -/*$ ^X search-mode +/*$ "^X" "search mode" * mode^X -- Set or get search mode flag * -^X * ^X -> mode @@ -111,8 +111,7 @@ teco_state_search_initial(teco_machine_main_t *ctx, GError **error) return FALSE; if (teco_expressions_args()) { /* TODO: optional count argument? */ - if (!teco_expressions_pop_num_calc(&v1, 0, error)) - return FALSE; + v1 = teco_expressions_pop_num(0); if (v1 <= v2) { teco_search_parameters.count = 1; teco_search_parameters.from = teco_interface_glyphs2bytes(v1); @@ -538,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(""); } @@ -548,6 +547,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 +670,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,14 +680,23 @@ 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; } 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; @@ -723,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; @@ -811,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; @@ -819,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; @@ -835,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; } @@ -864,11 +881,10 @@ 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__ \ ) -/*$ S search pattern compare +/*$ "S" ":S" "::S" search pattern compare * [n]S[pattern]$ -- Search for pattern * -S[pattern]$ * from,toS[pattern]$ @@ -938,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) @@ -958,8 +976,7 @@ teco_state_search_all_initial(teco_machine_main_t *ctx, GError **error) return FALSE; if (teco_expressions_args()) { /* TODO: optional count argument? */ - if (!teco_expressions_pop_num_calc(&v1, 0, error)) - return FALSE; + v1 = teco_expressions_pop_num(0); if (v1 <= v2) { teco_search_parameters.count = 1; teco_search_parameters.from_buffer = teco_ring_find(v1); @@ -998,17 +1015,20 @@ 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 && - (!teco_state_search_done(ctx, str, error) || - !teco_ed_hook(TECO_ED_HOOK_EDIT, error))) + if (ctx->flags.mode > TECO_MODE_NORMAL) + return &teco_state_start; + + const teco_buffer_t *curbuf = teco_ring_current; + if (!teco_state_search_done(ctx, str, error) || + (teco_ring_current != curbuf && !teco_ed_hook(TECO_ED_HOOK_EDIT, error))) return NULL; return &teco_state_start; } -/*$ N +/*$ "N" ":N" "search all" * [n]N[pattern]$ -- Search over buffer-boundaries * -N[pattern]$ * from,toN[pattern]$ @@ -1054,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; @@ -1080,13 +1101,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() && @@ -1095,8 +1118,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 */ @@ -1113,7 +1136,7 @@ teco_state_search_kill_done(teco_machine_main_t *ctx, const teco_string_t *str, return &teco_state_start; } -/*$ FK +/*$ "FK" ":FK" * FK[pattern]$ -- Delete up to occurrence of pattern * [n]FK[pattern]$ * -FK[pattern]$ @@ -1135,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; @@ -1164,7 +1189,7 @@ teco_state_search_delete_done(teco_machine_main_t *ctx, const teco_string_t *str return &teco_state_start; } -/*$ FD +/*$ "FD" ":FD" "::FD" * [n]FD[pattern]$ -- Delete occurrence of pattern * -FD[pattern]$ * from,toFD[pattern]$ @@ -1178,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; @@ -1225,7 +1263,7 @@ teco_state_replace_done(teco_machine_main_t *ctx, const teco_string_t *str, GErr : &teco_state_replace_ignore; } -/*$ FS +/*$ "FS" ":FS" "::FS" * [n]FS[pattern]$[string]$ -- Search and replace * -FS[pattern]$[string]$ * from,toFS[pattern]$[string]$ @@ -1248,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; @@ -1265,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; @@ -1330,7 +1362,7 @@ teco_state_replace_default_done(teco_machine_main_t *ctx, const teco_string_t *s : &teco_state_replace_default_ignore; } -/*$ FR +/*$ "FR" ":FR" "::FR" search-replace * [n]FR[pattern]$[string]$ -- Search and replace with default * -FR[pattern]$[string]$ * from,toFR[pattern]$[string]$ @@ -1352,5 +1384,51 @@ 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, teco_string_t str, GError **error) +{ + if (ctx->flags.mode > TECO_MODE_NORMAL) + return &teco_state_replace_default_ignore; + + const teco_buffer_t *curbuf = teco_ring_current; + teco_state_t *state = teco_state_replace_default_done(ctx, str, error); + if (!state || (curbuf != teco_ring_current && !teco_ed_hook(TECO_ED_HOOK_EDIT, error))) + return NULL; + + return state; +} + +/*$ "FN" ":FN" "::FN" "search-replace all" + * [n]FN[pattern]$[string]$ -- Search and replace with default over buffer-boundaries + * -FN[pattern]$[string]$ + * from,toFN[pattern]$[string]$ + * [n]:FN[pattern]$[string]$ -> Success|Failure + * -:FN[pattern]$[string]$ -> Success|Failure + * from,to:FN[pattern]$[string]$ -> Success|Failure + * [n]::FN[pattern]$[string]$ -> Success|Failure + * -::FN[pattern]$[string]$ -> Success|Failure + * from,to::FN[pattern]$[string]$ -> Success|Failure + * + * The \fBFN\fP command is similar to the \fBFR\fP command + * but will continue to search for occurrences of <pattern> when the + * end or beginning of the current buffer is reached. + * It searches for <pattern> just like the search over buffer-boundaries + * command (\fBN\fP) and replaces the occurrence with <string> + * similar to what \fBFR\fP does. + * If <string> is empty the string in the global replacement + * register is implied instead. + * + * \fBFN\fP also differs from \fBFR\fP in the interpretation of two arguments. + * Using two arguments the search will be bounded between the + * buffer with number <from>, up to the buffer with number + * <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, + .expectstring.done_cb = teco_state_replace_default_all_done ); |
