From: Barrett Ruth Date: Mon, 20 Apr 2026 16:05:43 +0000 (+0000) Subject: patch 9.2.0374: c_CTRL-{G,T} does not handle offset X-Git-Tag: v9.2.0374^0 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=c62342e5cfc339a87c1eb40ef34b2b31070d72a6;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 4a0729d0ed..bf47c51999 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -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 + 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 + 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. diff --git a/src/ex_getln.c b/src/ex_getln.c index f89bcecc50..0f69d0db23 100644 --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -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; } diff --git a/src/proto/search.pro b/src/proto/search.pro index 445817762d..3e700a6d8e 100644 --- a/src/proto/search.pro +++ b/src/proto/search.pro @@ -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); diff --git a/src/search.c b/src/search.c index 9b51ef94a9..03e66aaec8 100644 --- a/src/search.c +++ b/src/search.c @@ -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; diff --git a/src/testdir/test_search.vim b/src/testdir/test_search.vim index 725d08b59a..0a50681751 100644 --- a/src/testdir/test_search.vim +++ b/src/testdir/test_search.vim @@ -700,6 +700,27 @@ func Test_search_cmdline7() call feedkeys("//e\\", 'tx') call assert_equal('1 bbvimb', getline('.')) call assert_equal(4, col('.')) + call cursor(1, 1) + call feedkeys("//+1\\", '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\\", 'tx') + call assert_equal([0, 1, 9, 0], getpos('.')) + call feedkeys("gg0/blah/e\\\", 'tx') + call assert_equal([0, 1, 14, 0], getpos('.')) + call feedkeys("gg0/blah/e\\\\", 'tx') + call assert_equal([0, 1, 9, 0], getpos('.')) + call cursor(1, col('$')) + call feedkeys("?blah?e\\", 'tx') + call assert_equal([0, 1, 9, 0], getpos('.')) + call feedkeys("gg0/blah/e+1\\", 'tx') + call assert_equal([0, 1, 10, 0], getpos('.')) + call feedkeys("gg0/blah/e-2\\", 'tx') + call assert_equal([0, 1, 7, 0], getpos('.')) + call setline(1, 'a/b a/b a/b') + call feedkeys("gg0/a\\/b/e\\", 'tx') + call assert_equal([0, 1, 7, 0], getpos('.')) set noincsearch call test_override("char_avail", 0) diff --git a/src/version.c b/src/version.c index 1328b4db38..f36742a33a 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 374, /**/ 373, /**/