diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 034c04f97a..e71f0d635f 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -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 instead of triggering completion, type 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 diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index f533995878..340839f469 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -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' diff --git a/src/cmdexpand.c b/src/cmdexpand.c index d5730ab6b3..75efe1c623 100644 --- a/src/cmdexpand.c +++ b/src/cmdexpand.c @@ -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) diff --git a/src/option.h b/src/option.h index 5590e5635b..91810c5611 100644 --- a/src/option.h +++ b/src/option.h @@ -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 diff --git a/src/optionstr.c b/src/optionstr.c index dcbb30b837..eb0b9d313a 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -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 diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim index a68d3e1233..47adb2b08f 100644 --- a/src/testdir/test_cmdline.vim +++ b/src/testdir/test_cmdline.vim @@ -4472,6 +4472,7 @@ func Test_search_complete() " Match case correctly %d call setline(1, ["foobar", "Foobar", "fooBAr", "FooBARR"]) + call feedkeys("gg/f\\", 'tx') call assert_equal(['fooBAr', 'foobar'], g:compl_info.matches) call feedkeys("gg/Fo\\", 'tx') @@ -4480,6 +4481,7 @@ func Test_search_complete() call assert_equal({}, g:compl_info) call feedkeys("gg/\\cFo\\", 'tx') call assert_equal(['\cFoobar', '\cFooBAr', '\cFooBARR'], g:compl_info.matches) + set ignorecase call feedkeys("gg/f\\", '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\\", 'tx') call assert_equal(['\CfooBAr', '\Cfoobar'], g:compl_info.matches) + set smartcase call feedkeys("gg/f\\", '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\\", 'tx') call assert_equal({}, g:compl_info) + call feedkeys("gg/foob\\", 'tx') + call assert_equal(['foobar', 'foobarr'], g:compl_info.matches) call feedkeys("gg/\\Cfo\\", 'tx') call assert_equal(['\CfooBAr', '\Cfoobar'], g:compl_info.matches) call feedkeys("gg/\\cFo\\", 'tx') call assert_equal(['\cFoobar', '\cFooBAr', '\cFooBARR'], g:compl_info.matches) + set wildoptions+=exacttext ignorecase& smartcase& + call feedkeys("gg/F\\", 'tx') + call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches) + call feedkeys("gg/foob\\", 'tx') + call assert_equal([], g:compl_info.matches) + call feedkeys("gg/r\\n.\\", 'tx') + call assert_equal(['r\nFoobar', 'r\nfooBAr', 'r\nFooBARR'], g:compl_info.matches) + + set ignorecase + call feedkeys("gg/F\\", 'tx') + call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches) + call feedkeys("gg/R\\n.\\", 'tx') + call assert_equal(['r\nFoobar', 'r\nfooBAr', 'r\nFooBARR'], g:compl_info.matches) + + set smartcase + call feedkeys("gg/f\\", 'tx') + call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches) + call feedkeys("gg/foob\\", 'tx') + call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches) + call feedkeys("gg/R\\n.\\", 'tx') + call assert_equal({}, g:compl_info) + call feedkeys("gg/r\\n.*\\n\\", '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() diff --git a/src/version.c b/src/version.c index bf9424f8b0..5a7225b7ce 100644 --- a/src/version.c +++ b/src/version.c @@ -719,6 +719,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1526, /**/ 1525, /**/