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:  #19991
closes: #19998

Signed-off-by: Barrett Ruth <br.barrettruth@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Barrett Ruth
2026-04-20 16:05:43 +00:00
committed by Christian Brabandt
parent 49e8630a28
commit c62342e5cf
6 changed files with 183 additions and 102 deletions
+5 -4
View File
@@ -1,4 +1,4 @@
*cmdline.txt* For Vim version 9.2. Last change: 2026 Mar 17
*cmdline.txt* For Vim version 9.2. Last change: 2026 Apr 20
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -466,14 +466,15 @@ CTRL-L A match is done on the pattern in front of the cursor. If
*c_CTRL-G* */_CTRL-G*
CTRL-G When 'incsearch' is set, entering a search pattern for "/" or
"?" and the current match is displayed then CTRL-G will move
to the next match (does not take |search-offset| into account)
to the next match. The |search-offset| is applied when <CR>
is pressed, but does not affect the match highlighting.
Use CTRL-T to move to the previous match. Hint: on a regular
keyboard G is below T.
*c_CTRL-T* */_CTRL-T*
CTRL-T When 'incsearch' is set, entering a search pattern for "/" or
"?" and the current match is displayed then CTRL-T will move
to the previous match (does not take |search-offset| into
account).
to the previous match. The |search-offset| is applied when
<CR> is pressed, but does not affect the match highlighting.
Use CTRL-G to move to the next match. Hint: on a regular
keyboard T is above G.
+48 -37
View File
@@ -614,14 +614,18 @@ may_adjust_incsearch_highlighting(
incsearch_state_T *is_state,
int c)
{
int skiplen, patlen;
pos_T t;
char_u *pat;
int search_flags = SEARCH_NOOF;
int i;
int save;
int bslsh = FALSE;
int search_delim;
int skiplen, patlen;
pos_T t;
char_u *pat;
char_u *dircp = NULL;
char_u *searchstr;
char_u *strcopy = NULL;
size_t searchstrlen;
size_t patlen_s;
soffset_T offset;
int search_flags = SEARCH_NOOF;
int i;
int search_delim;
// Parsing range may already set the last search pattern.
// NOTE: must call restore_last_search_pattern() before returning!
@@ -639,31 +643,16 @@ may_adjust_incsearch_highlighting(
return FAIL;
}
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;
pat = ccline.cmdbuff + skiplen;
searchstr = pat;
searchstrlen = (size_t)patlen;
patlen_s = (size_t)(ccline.cmdlen - skiplen);
// 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] = firstc;
bslsh = TRUE;
}
}
(void)parse_search_pattern_offset(&pat, &patlen_s, search_delim,
SEARCH_OPT, &strcopy, &searchstr,
&searchstrlen, &dircp, &offset);
cursor_off();
out_flush();
@@ -681,18 +670,39 @@ may_adjust_incsearch_highlighting(
if (!p_hls)
search_flags += SEARCH_KEEP;
++emsg_off;
save = pat[patlen];
pat[patlen] = NUL;
i = searchit(curwin, curbuf, &t, NULL,
c == Ctrl_G ? FORWARD : BACKWARD,
pat, patlen, count, search_flags, RE_SEARCH, NULL);
searchstr, searchstrlen, count, search_flags, RE_SEARCH, NULL);
--emsg_off;
pat[patlen] = save;
if (bslsh)
pat[patlen - 1] = '\\';
if (dircp != NULL)
*dircp = search_delim;
if (i)
{
is_state->search_start = is_state->match_start;
pos_T match_start = is_state->match_start;
pos_T match_end = is_state->match_end;
long off = offset.off;
is_state->search_start = match_start;
if (!offset.line && (offset.end || off != 0))
{
if (offset.end)
{
is_state->search_start = match_end;
(void)decl(&is_state->search_start);
}
while (off > 0)
{
if (incl(&is_state->search_start) == -1)
break;
--off;
}
while (off < 0)
{
if (decl(&is_state->search_start) == -1)
break;
++off;
}
}
is_state->match_end = t;
is_state->match_start = t;
if (c == Ctrl_T && firstc != '?')
@@ -733,6 +743,7 @@ may_adjust_incsearch_highlighting(
}
else
vim_beep(BO_ERROR);
vim_free(strcopy);
restore_last_search_pattern();
return FAIL;
}
+1
View File
@@ -24,6 +24,7 @@ void set_last_search_pat(char_u *s, int idx, int magic, int setlast);
void last_pat_prog(regmmatch_T *regmatch);
int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, int dir, char_u *pat, size_t patlen, long count, int options, int pat_use, searchit_arg_T *extra_arg);
void set_search_direction(int cdir);
int parse_search_pattern_offset(char_u **pat, size_t *patlen, int search_delim, int options, char_u **strcopy, char_u **searchstr, size_t *searchstrlen, char_u **dircp, soffset_T *offset);
int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, size_t patlen, long count, int options, searchit_arg_T *sia);
int search_for_exact_line(buf_T *buf, pos_T *pos, int dir, char_u *pat);
int searchc(cmdarg_T *cap, int t_cmd);
+106 -61
View File
@@ -1215,6 +1215,109 @@ first_submatch(regmmatch_T *rp)
}
#endif
/*
* 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_u **pat,
size_t *patlen,
int search_delim,
int options,
char_u **strcopy,
char_u **searchstr,
size_t *searchstrlen,
char_u **dircp,
soffset_T *offset)
{
int cmdlen = 0;
char_u *p;
char_u *ps;
if (*pat == NULL || **pat == NUL)
return 0;
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 = 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 == '-' || VIM_ISDIGIT(*p))
offset->line = TRUE;
else if ((options & SEARCH_OPT)
&& (*p == 'e' || *p == 's' || *p == 'b'))
{
if (*p == 'e') // end
offset->end = SEARCH_END;
++p;
}
if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-') // got an offset
{
// 'nr' or '+nr' or '-nr'
if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1)))
offset->off = atol((char *)p);
else if (*p == '-') // single '-'
offset->off = -1;
else // single '+'
offset->off = 1;
++p;
while (VIM_ISDIGIT(*p)) // skip number
++p;
}
// compute length of search command for get_address()
cmdlen += (int)(p - *pat);
*patlen -= 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'
@@ -1257,7 +1360,6 @@ do_search(
long c;
char_u *dircp;
char_u *strcopy = NULL;
char_u *ps;
int show_search_stats;
char_u *msgbuf = NULL;
size_t msgbuflen = 0;
@@ -1369,66 +1471,9 @@ do_search(
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 = 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 == '-' || VIM_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 = SEARCH_END;
++p;
}
if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-') // got an offset
{
// 'nr' or '+nr' or '-nr'
if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1)))
spats[0].off.off = atol((char *)p);
else if (*p == '-') // single '-'
spats[0].off.off = -1;
else // single '+'
spats[0].off.off = 1;
++p;
while (VIM_ISDIGIT(*p)) // skip number
++p;
}
// compute length of search command for get_address()
searchcmdlen += (int)(p - pat);
patlen -= 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);
}
show_search_stats = FALSE;
+21
View File
@@ -700,6 +700,27 @@ func Test_search_cmdline7()
call feedkeys("//e\<c-g>\<cr>", 'tx')
call assert_equal('1 bbvimb', getline('.'))
call assert_equal(4, col('.'))
call cursor(1, 1)
call feedkeys("//+1\<c-g>\<cr>", 'tx')
call assert_equal(' 2 bbvimb', getline('.'))
call assert_equal([0, 2, 1, 0], getpos('.'))
call setline(1, 'blah blah blah')
call feedkeys("gg0/blah/e\<C-G>\<CR>", 'tx')
call assert_equal([0, 1, 9, 0], getpos('.'))
call feedkeys("gg0/blah/e\<C-G>\<C-G>\<CR>", 'tx')
call assert_equal([0, 1, 14, 0], getpos('.'))
call feedkeys("gg0/blah/e\<C-G>\<C-G>\<C-T>\<CR>", 'tx')
call assert_equal([0, 1, 9, 0], getpos('.'))
call cursor(1, col('$'))
call feedkeys("?blah?e\<C-G>\<CR>", 'tx')
call assert_equal([0, 1, 9, 0], getpos('.'))
call feedkeys("gg0/blah/e+1\<C-G>\<CR>", 'tx')
call assert_equal([0, 1, 10, 0], getpos('.'))
call feedkeys("gg0/blah/e-2\<C-G>\<CR>", 'tx')
call assert_equal([0, 1, 7, 0], getpos('.'))
call setline(1, 'a/b a/b a/b')
call feedkeys("gg0/a\\/b/e\<C-G>\<CR>", 'tx')
call assert_equal([0, 1, 7, 0], getpos('.'))
set noincsearch
call test_override("char_avail", 0)
+2
View File
@@ -734,6 +734,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
374,
/**/
373,
/**/