]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0374: c_CTRL-{G,T} does not handle offset v9.2.0374
authorBarrett Ruth <br.barrettruth@gmail.com>
Mon, 20 Apr 2026 16:05:43 +0000 (16:05 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 20 Apr 2026 16:08:47 +0000 (16:08 +0000)
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>
runtime/doc/cmdline.txt
src/ex_getln.c
src/proto/search.pro
src/search.c
src/testdir/test_search.vim
src/version.c

index 4a0729d0ed809f80e127721327dcb5cc0c9b45fc..bf47c51999ff0367a86c973f675cda8888599037 100644 (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.
 
index f89bcecc50cb3c87c8090c8f1915c26c000f0193..0f69d0db230066b14e3e8079f13a3531de4b9791 100644 (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;
 }
index 445817762dab5d888c95996db1a2ba2792c333b6..3e700a6d8e4df53bd6b78f1bba627a317da1a12a 100644 (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);
index 9b51ef94a947bd6fb3fa8e14aae46d298897378e..03e66aaec8be8644af960931794025999a344870 100644 (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;
index 725d08b59a8be0f5aa6e88c9e8c2b0b4919230e6..0a50681751054a0437c740c8ab0afe2166ba3e20 100644 (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)
index 1328b4db3849565de1610087b456fb76144b583f..f36742a33adc90e148174bdeab5951c7a7cb2cfd 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    374,
 /**/
     373,
 /**/