summaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
authorBarrett Ruth <62671086+barrettruth@users.noreply.github.com>2026-04-21 21:24:49 -0400
committerGitHub <noreply@github.com>2026-04-22 01:24:49 +0000
commit8efe4f9ac1b721267f886d6d577766cdbd2f7920 (patch)
tree2a3259ec181fb3fba2ed02e20932c39b373a2648 /src
parentead1478b69ec838383b822bd82768a2e235dfd9d (diff)
fix(incsearch): support `c_CTRL-{G,T}` with an offset (#39097)
vim-patch:9.2.0374: c_CTRL-{G,T} does not handle offset Problem: c_CTRL-{G,T} does not handle offset, when cycling between matches Solution: Refactor parsing logic into parse_search_pattern_offset() and handle offsets, note: highlighting does not handle offsets yet (Barrett Ruth). fixes: vim/vim#19991 closes: vim/vim#19998 https://github.com/vim/vim/commit/c62342e5cfc339a87c1eb40ef34b2b31070d72a6
Diffstat (limited to 'src')
-rw-r--r--src/nvim/ex_getln.c64
-rw-r--r--src/nvim/search.c143
2 files changed, 124 insertions, 83 deletions
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 2278ba2494..6cc697cc6e 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1595,31 +1595,19 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s
ui_flush();
pos_T t;
- char *pat;
+ char *pat = ccline.cmdbuff + skiplen;
+ char *dircp = NULL;
+ char *searchstr = pat;
+ char *strcopy = NULL;
+ size_t searchstrlen = (size_t)patlen;
+ SearchOffset offset;
int search_flags = SEARCH_NOOF;
+ size_t patlen_s = (size_t)(ccline.cmdlen - skiplen);
- if (search_delim == ccline.cmdbuff[skiplen]) {
- pat = last_search_pattern();
- if (pat == NULL) {
- restore_last_search_pattern();
- return FAIL;
- }
- skiplen = 0;
- patlen = (int)last_search_pattern_len();
- } else {
- pat = ccline.cmdbuff + skiplen;
- }
-
- bool bslsh = false;
// do not search for the search end delimiter,
// unless it is part of the pattern
- if (patlen > 2 && firstc == pat[patlen - 1]) {
- patlen--;
- if (pat[patlen - 1] == '\\') {
- pat[patlen - 1] = (char)(uint8_t)firstc;
- bslsh = true;
- }
- }
+ parse_search_pattern_offset(&pat, &patlen_s, search_delim, SEARCH_OPT, &strcopy, &searchstr,
+ &searchstrlen, &dircp, &offset);
if (next_match) {
t = s->match_end;
@@ -1636,20 +1624,39 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s
search_flags += SEARCH_KEEP;
}
emsg_off++;
- char save = pat[patlen];
- pat[patlen] = NUL;
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
- pat, (size_t)patlen, count, search_flags,
+ searchstr, searchstrlen, count, search_flags,
RE_SEARCH, NULL);
emsg_off--;
- pat[patlen] = save;
- if (bslsh) {
- pat[patlen - 1] = '\\';
+ if (dircp != NULL) {
+ *dircp = (char)search_delim;
}
ui_busy_stop();
if (found) {
- s->search_start = s->match_start;
+ pos_T match_start = s->match_start;
+ pos_T match_end = s->match_end;
+ int64_t off = offset.off;
+
+ s->search_start = match_start;
+ if (!offset.line && (offset.end || off != 0)) {
+ if (offset.end) {
+ s->search_start = match_end;
+ decl(&s->search_start);
+ }
+ while (off > 0) {
+ if (incl(&s->search_start) == -1) {
+ break;
+ }
+ off--;
+ }
+ while (off < 0) {
+ if (decl(&s->search_start) == -1) {
+ break;
+ }
+ off++;
+ }
+ }
s->match_end = t;
s->match_start = t;
if (!next_match && firstc != '?') {
@@ -1690,6 +1697,7 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s
} else {
vim_beep(kOptBoFlagError);
}
+ xfree(strcopy);
restore_last_search_pattern();
return FAIL;
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 555f6e6d94..89555afb41 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -1024,6 +1024,91 @@ static int first_submatch(regmmatch_T *rp)
return submatch;
}
+/// Parse a search pattern followed by an optional offset (e.g. "pat/e+1").
+/// On entry "*pat" points at the start of the pattern and "*patlen" is its
+/// length. Updates the in/out parameters:
+/// *pat / *patlen - moved past the pattern and offset
+/// *strcopy - allocated copy if "\?" or "\/" was unescaped
+/// (caller must vim_free() it)
+/// *searchstr and *searchstrlen - pointer/length of the search pattern only
+/// *dircp - location of the trailing delimiter that was
+/// replaced with NUL (or NULL); caller may restore
+/// it
+/// *offset - parsed offset (line/end/off)
+///
+/// Returns the length of the parsed pattern + offset (used by get_address()
+/// to know how much of the command line was consumed).
+int parse_search_pattern_offset(char **pat, size_t *patlen, int search_delim, int options,
+ char **strcopy, char **searchstr, size_t *searchstrlen,
+ char **dircp, SearchOffset *offset)
+{
+ if (*pat == NULL || **pat == NUL) {
+ return 0;
+ }
+
+ int cmdlen = 0;
+ char *p;
+ char *ps = *strcopy;
+
+ *searchstr = *pat;
+ *searchstrlen = *patlen;
+ *dircp = NULL;
+
+ // Find end of regular expression.
+ // If there is a matching '/' or '?', toss it.
+ p = skip_regexp_ex(*pat, search_delim, magic_isset(), strcopy, NULL, NULL);
+ if (*strcopy != ps) {
+ size_t len = strlen(*strcopy);
+ // made a copy of "pat" to change "\?" to "?"
+ cmdlen += (int)(*patlen - len);
+ *pat = *strcopy;
+ *patlen = len;
+ *searchstr = *strcopy;
+ *searchstrlen = len;
+ }
+ if (*p == search_delim) {
+ *searchstrlen = (size_t)(p - *pat);
+ *dircp = p; // remember where we put the NUL
+ *p++ = NUL;
+ }
+
+ offset->line = false;
+ offset->end = false;
+ offset->off = 0;
+ // Check for a line offset or a character offset.
+ // For get_address (echo off) we don't check for a character
+ // offset, because it is meaningless and the 's' could be a
+ // substitute command.
+ if (*p == '+' || *p == '-' || ascii_isdigit(*p)) {
+ offset->line = true;
+ } else if ((options & SEARCH_OPT) && (*p == 'e' || *p == 's' || *p == 'b')) {
+ if (*p == 'e') { // end
+ offset->end = true;
+ }
+ p++;
+ }
+ if (ascii_isdigit(*p) || *p == '+' || *p == '-') { // got an offset
+ if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) {
+ offset->off = atol(p);
+ } else if (*p == '-') { // single '-'
+ offset->off = -1;
+ } else { // single '+'
+ offset->off = 1;
+ }
+ p++;
+ while (ascii_isdigit(*p)) { // skip number
+ p++;
+ }
+ }
+
+ // compute length of search command for get_address()
+ cmdlen += (int)(p - *pat);
+ *patlen -= (size_t)(p - *pat);
+ *pat = p; // put pat after search command
+
+ return cmdlen;
+}
+
/// Highest level string search function.
/// Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
///
@@ -1059,7 +1144,6 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
int64_t c;
char *dircp;
char *strcopy = NULL;
- char *ps;
char *msgbuf = NULL;
size_t msgbuflen = 0;
bool has_offset = false;
@@ -1134,60 +1218,9 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
}
if (pat != NULL && *pat != NUL) { // look for (new) offset
- // Find end of regular expression.
- // If there is a matching '/' or '?', toss it.
- ps = strcopy;
- p = skip_regexp_ex(pat, search_delim, magic_isset(), &strcopy, NULL, NULL);
- if (strcopy != ps) {
- size_t len = strlen(strcopy);
- // made a copy of "pat" to change "\?" to "?"
- searchcmdlen += (int)(patlen - len);
- pat = strcopy;
- patlen = len;
- searchstr = strcopy;
- searchstrlen = len;
- }
- if (*p == search_delim) {
- searchstrlen = (size_t)(p - pat);
- dircp = p; // remember where we put the NUL
- *p++ = NUL;
- }
- spats[0].off.line = false;
- spats[0].off.end = false;
- spats[0].off.off = 0;
- // Check for a line offset or a character offset.
- // For get_address (echo off) we don't check for a character
- // offset, because it is meaningless and the 's' could be a
- // substitute command.
- if (*p == '+' || *p == '-' || ascii_isdigit(*p)) {
- spats[0].off.line = true;
- } else if ((options & SEARCH_OPT)
- && (*p == 'e' || *p == 's' || *p == 'b')) {
- if (*p == 'e') { // end
- spats[0].off.end = true;
- }
- p++;
- }
- if (ascii_isdigit(*p) || *p == '+' || *p == '-') { // got an offset
- // 'nr' or '+nr' or '-nr'
- if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) {
- spats[0].off.off = atol(p);
- } else if (*p == '-') { // single '-'
- spats[0].off.off = -1;
- } else { // single '+'
- spats[0].off.off = 1;
- }
- p++;
- while (ascii_isdigit(*p)) { // skip number
- p++;
- }
- }
-
- // compute length of search command for get_address()
- searchcmdlen += (int)(p - pat);
-
- patlen -= (size_t)(p - pat);
- pat = p; // put pat after search command
+ searchcmdlen += parse_search_pattern_offset(&pat, &patlen, search_delim, options,
+ &strcopy, &searchstr, &searchstrlen, &dircp,
+ &spats[0].off);
}
bool show_search_stats = false;