]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1526: completion: search completion match may differ in case v9.1.1526
authorGirish Palya <girishji@gmail.com>
Tue, 8 Jul 2025 19:29:02 +0000 (21:29 +0200)
committerChristian Brabandt <cb@256bit.org>
Tue, 8 Jul 2025 19:29:02 +0000 (21:29 +0200)
Problem:  completion: search completion match may differ in case
          (techntools)
Solution: add "exacttext" to 'wildoptions' value (Girish Palya)

This flag does the following:

exacttext
      When this flag is present, search pattern completion
      (e.g., in |/|, |?|, |:s|, |:g|, |:v|, and |:vim|)
      shows exact buffer text as menu items, without
      preserving regex artifacts like position
      anchors (e.g., |/\<|). This provides more intuitive
      menu items that match the actual buffer text. However,
      searches may be less accurate since the pattern is not
      preserved exactly.
      By default, Vim preserves the typed pattern (with
      anchors) and appends the matched word. This preserves
      search correctness, especially when using regular
      expressions or with 'smartcase' enabled. However, the
      case of the appended matched word may not exactly
      match the case of the word in the buffer.

fixes: #17654
closes: #17667

Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/options.txt
runtime/doc/version9.txt
src/cmdexpand.c
src/option.h
src/optionstr.c
src/testdir/test_cmdline.vim
src/version.c

index 034c04f97a128ffe82ad912b7a070a0519bb0abc..e71f0d635fb838fabb64611e2ee597096d49989e 100644 (file)
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.1.  Last change: 2025 Jul 05
+*options.txt*  For Vim version 9.1.  Last change: 2025 Jul 08
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -9759,6 +9759,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 <      'wildchar' also enables completion in search pattern contexts such as
        |/|, |?|, |:s|, |:g|, |:v|, and |:vim|.  To insert a literal <Tab>
        instead of triggering completion, type <C-V><Tab> or "\t".
+       See also |'wildoptions'|.
        NOTE: This option is set to the Vi default value when 'compatible' is
        set and to the Vim default value when 'compatible' is reset.
 
@@ -9926,6 +9927,20 @@ A jump table for the options with a short description can be found at |Q_op|.
        A list of words that change how |cmdline-completion| is done.
 
        The following values are supported:
+         exacttext     When this flag is present, search pattern completion
+                       (e.g., in |/|, |?|, |:s|, |:g|, |:v|, and |:vim|)
+                       shows exact buffer text as menu items, without
+                       preserving regex artifacts like position
+                       anchors (e.g., |/\<|).  This provides more intuitive
+                       menu items that match the actual buffer text.
+                       However, searches may be less accurate since the
+                       pattern is not preserved exactly.
+                       By default, Vim preserves the typed pattern (with
+                       anchors) and appends the matched word.  This preserves
+                       search correctness, especially when using regular
+                       expressions or with 'smartcase' enabled.  However, the
+                       case of the appended matched word may not exactly
+                       match the case of the word in the buffer.
          fuzzy         Use |fuzzy-matching| to find completion matches. When
                        this value is specified, wildcard expansion will not
                        be used for completion.  The matches will be sorted by
index f53399587840a0b9b55ff46c3e1d77f8f4ad89cf..340839f4699d3113f29f6aceece030379b8164ac 100644 (file)
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2025 Jul 05
+*version9.txt*  For Vim version 9.1.  Last change: 2025 Jul 08
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41623,6 +41623,8 @@ Completion: ~
 - improved commandline completion for the |:hi| command
 - New option value for 'wildmode':
        "noselect"      - do not auto select an entry in the wildmenu
+       "exacttext"     - show exact matches in wildmenu with search
+                         completion
 - New flags for 'complete':
        "F{func}"       - complete using given function
        "F"             - complete using 'completefunc'
index d5730ab6b3c8dc6fe8cb3debb3c45386b84e63b2..75efe1c6236e88cf2c00328ea4273a4ecbe5222e 100644 (file)
@@ -4631,6 +4631,7 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
     int                segment_len;
     linenr_T   lnum;
     garray_T   ga;
+    int                exacttext = vim_strchr(p_wop, WOP_EXACTTEXT) != NULL;
 
     if (start->lnum > end->lnum
            || (start->lnum == end->lnum && start->col >= end->col))
@@ -4646,12 +4647,17 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
 
     segment_len = is_single_line ? (end->col - start->col)
                                : (int)STRLEN(start_ptr);
-    if (ga_grow(&ga, segment_len + 1) != OK)
+    if (ga_grow(&ga, segment_len + 2) != OK)
        return FAIL;
 
     ga_concat_len(&ga, start_ptr, segment_len);
     if (!is_single_line)
-       ga_append(&ga, '\n');
+    {
+       if (exacttext)
+           ga_concat_len(&ga, (char_u *)"\\n", 2);
+       else
+           ga_append(&ga, '\n');
+    }
 
     // Append full lines between start and end
     if (!is_single_line)
@@ -4659,10 +4665,13 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
        for (lnum = start->lnum + 1; lnum < end->lnum; lnum++)
        {
            line = ml_get(lnum);
-           if (ga_grow(&ga, ml_get_len(lnum) + 1) != OK)
+           if (ga_grow(&ga, ml_get_len(lnum) + 2) != OK)
                return FAIL;
            ga_concat(&ga, line);
-           ga_append(&ga, '\n');
+           if (exacttext)
+               ga_concat_len(&ga, (char_u *)"\\n", 2);
+           else
+               ga_append(&ga, '\n');
        }
     }
 
@@ -4783,6 +4792,7 @@ expand_pattern_in_buf(
     int                compl_started = FALSE;
     int                search_flags;
     char_u     *match, *full_match;
+    int                exacttext = vim_strchr(p_wop, WOP_EXACTTEXT) != NULL;
 
 #ifdef FEAT_SEARCH_EXTRA
     has_range = search_first_line != 0;
@@ -4871,26 +4881,31 @@ expand_pattern_in_buf(
                    &word_end_pos))
            break;
 
-       // Construct a new match from completed word appended to pattern itself
-       match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos,
-               FALSE);
-
-       // The regex pattern may include '\C' or '\c'. First, try matching the
-       // buffer word as-is. If it doesn't match, try again with the lowercase
-       // version of the word to handle smartcase behavior.
-       if (match == NULL || !is_regex_match(match, full_match))
+       if (exacttext)
+           match = full_match;
+       else
        {
-           vim_free(match);
-           match = concat_pattern_with_buffer_match(pat, pat_len,
-                   &end_match_pos, TRUE);
+           // Construct a new match from completed word appended to pattern itself
+           match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos,
+                   FALSE);
+
+           // The regex pattern may include '\C' or '\c'. First, try matching the
+           // buffer word as-is. If it doesn't match, try again with the lowercase
+           // version of the word to handle smartcase behavior.
            if (match == NULL || !is_regex_match(match, full_match))
            {
                vim_free(match);
-               vim_free(full_match);
-               continue;
+               match = concat_pattern_with_buffer_match(pat, pat_len,
+                       &end_match_pos, TRUE);
+               if (match == NULL || !is_regex_match(match, full_match))
+               {
+                   vim_free(match);
+                   vim_free(full_match);
+                   continue;
+               }
            }
+           vim_free(full_match);
        }
-       vim_free(full_match);
 
        // Include this match if it is not a duplicate
        for (int i = 0; i < ga.ga_len; ++i)
index 5590e5635bc31b8c09c8bc412c882aece76d80a6..91810c5611010a8e0a36dd5aa65b8812389ee864 100644 (file)
@@ -375,8 +375,9 @@ typedef enum {
 // flags for the 'wildoptions' option
 // each defined char should be unique over all values.
 #define WOP_FUZZY      'z'
-#define WOP_TAGFILE    't'
+#define WOP_TAGFILE    'g'
 #define WOP_PUM                'p'
+#define WOP_EXACTTEXT  'x'
 
 // arguments for can_bs()
 // each defined char should be unique over all values
index dcbb30b837eb2679c1dd40434fff79662b61fa50..eb0b9d313a736a198b39951a9ecc8a2413894712 100644 (file)
@@ -103,7 +103,7 @@ static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm"
 static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL};
 // Note: Keep this in sync with check_opt_wim()
 static char *(p_wim_values[]) = {"full", "longest", "list", "lastused", "noselect", NULL};
-static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", NULL};
+static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", "exacttext", NULL};
 #ifdef FEAT_WAK
 static char *(p_wak_values[]) = {"yes", "menu", "no", NULL};
 #endif
index a68d3e12332c405a589b66eaff5cc2634cce4ea6..47adb2b08f8e7e9b862f783b79d3f4b944f36117 100644 (file)
@@ -4472,6 +4472,7 @@ func Test_search_complete()
   " Match case correctly
   %d
   call setline(1, ["foobar", "Foobar", "fooBAr", "FooBARR"])
+
   call feedkeys("gg/f\<tab>\<f9>", 'tx')
   call assert_equal(['fooBAr', 'foobar'], g:compl_info.matches)
   call feedkeys("gg/Fo\<tab>\<f9>", 'tx')
@@ -4480,6 +4481,7 @@ func Test_search_complete()
   call assert_equal({},  g:compl_info)
   call feedkeys("gg/\\cFo\<tab>\<f9>", 'tx')
   call assert_equal(['\cFoobar', '\cFooBAr', '\cFooBARR'], g:compl_info.matches)
+
   set ignorecase
   call feedkeys("gg/f\<tab>\<f9>", 'tx')
   call assert_equal(['foobar', 'fooBAr', 'fooBARR'], g:compl_info.matches)
@@ -4489,6 +4491,7 @@ func Test_search_complete()
   call assert_equal(['FOobar', 'FOoBAr', 'FOoBARR'], g:compl_info.matches)
   call feedkeys("gg/\\Cfo\<tab>\<f9>", 'tx')
   call assert_equal(['\CfooBAr', '\Cfoobar'], g:compl_info.matches)
+
   set smartcase
   call feedkeys("gg/f\<tab>\<f9>", 'tx')
   call assert_equal(['foobar', 'fooBAr', 'foobarr'], g:compl_info.matches)
@@ -4496,16 +4499,42 @@ func Test_search_complete()
   call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
   call feedkeys("gg/FO\<tab>\<f9>", 'tx')
   call assert_equal({},  g:compl_info)
+  call feedkeys("gg/foob\<tab>\<f9>", 'tx')
+  call assert_equal(['foobar', 'foobarr'], g:compl_info.matches)
   call feedkeys("gg/\\Cfo\<tab>\<f9>", 'tx')
   call assert_equal(['\CfooBAr', '\Cfoobar'], g:compl_info.matches)
   call feedkeys("gg/\\cFo\<tab>\<f9>", 'tx')
   call assert_equal(['\cFoobar', '\cFooBAr', '\cFooBARR'], g:compl_info.matches)
 
+  set wildoptions+=exacttext ignorecase& smartcase&
+  call feedkeys("gg/F\<tab>\<f9>", 'tx')
+  call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
+  call feedkeys("gg/foob\<tab>\<f9>", 'tx')
+  call assert_equal([], g:compl_info.matches)
+  call feedkeys("gg/r\\n.\<tab>\<f9>", 'tx')
+  call assert_equal(['r\nFoobar', 'r\nfooBAr', 'r\nFooBARR'], g:compl_info.matches)
+
+  set ignorecase
+  call feedkeys("gg/F\<tab>\<f9>", 'tx')
+  call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches)
+  call feedkeys("gg/R\\n.\<tab>\<f9>", 'tx')
+  call assert_equal(['r\nFoobar', 'r\nfooBAr', 'r\nFooBARR'], g:compl_info.matches)
+
+  set smartcase
+  call feedkeys("gg/f\<tab>\<f9>", 'tx')
+  call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches)
+  call feedkeys("gg/foob\<tab>\<f9>", 'tx')
+  call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches)
+  call feedkeys("gg/R\\n.\<tab>\<f9>", 'tx')
+  call assert_equal({}, g:compl_info)
+  call feedkeys("gg/r\\n.*\\n\<tab>\<f9>", 'tx')
+  call assert_equal(['r\nFoobar\nfooBAr', 'r\nfooBAr\nFooBARR'], g:compl_info.matches)
+
   bw!
   call test_override("char_avail", 0)
   delfunc GetComplInfo
   unlet! g:compl_info
-  set wildcharm=0 incsearch& ignorecase& smartcase&
+  set wildcharm=0 incsearch& ignorecase& smartcase& wildoptions&
 endfunc
 
 func Test_search_wildmenu_screendump()
index bf9424f8b05b43e74540c141611018ed04bab4d6..5a7225b7cec4611f94ae3109112120d1faf9a594 100644 (file)
@@ -719,6 +719,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1526,
 /**/
     1525,
 /**/