aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/search.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/search.c')
-rw-r--r--src/search.c228
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
);