commit 96aef50624e03800a3ec35491a0bacab33427fd9
parent db7c2acbc6104e44b524e720c64623c3b63ed4b8
Author: zeertzjq <zeertzjq@outlook.com>
Date: Wed, 9 Jul 2025 08:55:08 +0800
Merge pull request #34852 from zeertzjq/vim-9.1.1526
vim-patch:9.1.{1526,1528}
Diffstat:
5 files changed, 126 insertions(+), 31 deletions(-)
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -7173,6 +7173,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'|.
*'wildcharm'* *'wcm'*
'wildcharm' 'wcm' number (default 0)
@@ -7319,6 +7320,20 @@ A jump table for the options with a short description can be found at |Q_op|.
global
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/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -7818,6 +7818,7 @@ vim.go.ww = vim.go.whichwrap
--- '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'`.
---
--- @type integer
vim.o.wildchar = 9
@@ -8012,6 +8013,20 @@ vim.go.wim = vim.go.wildmode
--- 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/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
@@ -249,13 +249,14 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
char *p;
if (xp->xp_numfiles == -1) {
- may_expand_pattern = options & WILD_MAY_EXPAND_PATTERN;
pre_incsearch_pos = xp->xp_pre_incsearch_pos;
if (ccline->input_fn && ccline->xp_context == EXPAND_COMMANDS) {
// Expand commands typed in input() function
set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, false);
} else {
+ may_expand_pattern = options & WILD_MAY_EXPAND_PATTERN;
set_expand_context(xp);
+ may_expand_pattern = false;
}
if (xp->xp_context == EXPAND_LUA) {
nlua_expand_pat(xp);
@@ -3893,6 +3894,8 @@ void f_cmdcomplete_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// matched text is returned in '*match_end'.
static int copy_substring_from_pos(pos_T *start, pos_T *end, char **match, pos_T *match_end)
{
+ bool exacttext = wop_flags & kOptWopFlagExacttext;
+
if (start->lnum > end->lnum
|| (start->lnum == end->lnum && start->col >= end->col)) {
return FAIL; // invalid range
@@ -3909,19 +3912,27 @@ static int copy_substring_from_pos(pos_T *start, pos_T *end, char **match, pos_T
int segment_len = is_single_line ? (int)(end->col - start->col)
: (int)strlen(start_ptr);
- ga_grow(&ga, segment_len + 1);
+ ga_grow(&ga, segment_len + 2);
ga_concat_len(&ga, start_ptr, (size_t)segment_len);
if (!is_single_line) {
- ga_append(&ga, '\n');
+ if (exacttext) {
+ ga_concat_len(&ga, "\\n", 2);
+ } else {
+ ga_append(&ga, '\n');
+ }
}
// Append full lines between start and end
if (!is_single_line) {
for (linenr_T lnum = start->lnum + 1; lnum < end->lnum; lnum++) {
char *line = ml_get(lnum);
- ga_grow(&ga, ml_get_len(lnum) + 1);
+ ga_grow(&ga, ml_get_len(lnum) + 2);
ga_concat(&ga, line);
- ga_append(&ga, '\n');
+ if (exacttext) {
+ ga_concat_len(&ga, "\\n", 2);
+ } else {
+ ga_append(&ga, '\n');
+ }
}
}
@@ -4004,6 +4015,7 @@ static char *concat_pattern_with_buffer_match(char *pat, int pat_len, pos_T *end
/// @param[out] numMatches number of matches
static int expand_pattern_in_buf(char *pat, Direction dir, char ***matches, int *numMatches)
{
+ bool exacttext = wop_flags & kOptWopFlagExacttext;
bool has_range = search_first_line != 0;
*matches = NULL;
@@ -4090,22 +4102,26 @@ static int expand_pattern_in_buf(char *pat, Direction dir, char ***matches, int
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);
+ if (exacttext) {
+ match = full_match;
+ } else {
+ // 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 (!is_regex_match(match, full_match)) {
- xfree(match);
- match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos, true);
+ // 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 (!is_regex_match(match, full_match)) {
xfree(match);
- xfree(full_match);
- continue;
+ match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos, true);
+ if (!is_regex_match(match, full_match)) {
+ xfree(match);
+ xfree(full_match);
+ continue;
+ }
}
+ xfree(full_match);
}
- xfree(full_match);
// Include this match if it is not a duplicate
for (int i = 0; i < ga.ga_len; i++) {
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -10117,6 +10117,7 @@ local options = {
< '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'|.
]=],
full_name = 'wildchar',
scope = { 'global' },
@@ -10311,12 +10312,26 @@ local options = {
{
abbreviation = 'wop',
defaults = 'pum,tagfile',
- values = { 'fuzzy', 'tagfile', 'pum' },
+ values = { 'fuzzy', 'tagfile', 'pum', 'exacttext' },
flags = true,
deny_duplicates = true,
desc = [=[
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/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim
@@ -4499,14 +4499,16 @@ 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')
call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
call feedkeys("gg/FO\<tab>\<f9>", 'tx')
- call assert_equal({}, g:compl_info)
+ 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)
@@ -4516,23 +4518,55 @@ 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)
call feedkeys("gg/Fo\<tab>\<f9>", 'tx')
call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
call feedkeys("gg/FO\<tab>\<f9>", 'tx')
- call assert_equal({}, g:compl_info)
+ call assert_equal({}, g:compl_info)
+
+ " Issue #17680 (getcompletion() does not support search completion)
+ let result = getcompletion('%s/', 'cmdline')
+ call assert_equal([], result)
+
+ 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 Ntest_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()
@@ -4601,44 +4635,44 @@ func Test_range_complete()
for trig in ["\<tab>", "\<c-z>"]
call feedkeys($":%s/a{trig}\<f9>", 'xt')
- call assert_equal(['ab', 'a', 'af'], g:compl_info.matches)
+ call assert_equal(['ab', 'a', 'af'], g:compl_info.matches)
" call feedkeys($":vim9cmd :%s/a{trig}\<f9>", 'xt')
call feedkeys($":verbose :%s/a{trig}\<f9>", 'xt')
- call assert_equal(['ab', 'a', 'af'], g:compl_info.matches)
+ call assert_equal(['ab', 'a', 'af'], g:compl_info.matches)
endfor
call feedkeys(":%s/\<c-z>\<f9>", 'xt')
- call assert_equal({}, g:compl_info)
+ call assert_equal({}, g:compl_info)
for cmd in ['s', 'g']
- call feedkeys(":1,2" . cmd . "/a\<c-z>\<f9>", 'xt')
- call assert_equal(['ab', 'a'], g:compl_info.matches)
+ call feedkeys($":1,2{cmd}/a\<c-z>\<f9>", 'xt')
+ call assert_equal(['ab', 'a'], g:compl_info.matches)
endfor
1
call feedkeys(":.,+2s/a\<c-z>\<f9>", 'xt')
- call assert_equal(['ab', 'a'], g:compl_info.matches)
+ call assert_equal(['ab', 'a'], g:compl_info.matches)
/f
call feedkeys(":1,s/b\<c-z>\<f9>", 'xt')
- call assert_equal(['b', 'ba'], g:compl_info.matches)
+ call assert_equal(['b', 'ba'], g:compl_info.matches)
/c
call feedkeys(":\\?,4s/a\<c-z>\<f9>", 'xt')
- call assert_equal(['a', 'af'], g:compl_info.matches)
+ call assert_equal(['a', 'af'], g:compl_info.matches)
%s/c/c/
call feedkeys(":1,\\&s/a\<c-z>\<f9>", 'xt')
- call assert_equal(['ab', 'a'], g:compl_info.matches)
+ call assert_equal(['ab', 'a'], g:compl_info.matches)
3
normal! ma
call feedkeys(":'a,$s/a\<c-z>\<f9>", 'xt')
- call assert_equal(['a', 'af'], g:compl_info.matches)
+ call assert_equal(['a', 'af'], g:compl_info.matches)
" Line number followed by a search pattern ([start]/pattern/[command])
call feedkeys("3/a\<c-z>\<f9>", 'xt')
- call assert_equal(['a', 'af', 'ab'], g:compl_info.matches)
+ call assert_equal(['a', 'af', 'ab'], g:compl_info.matches)
bw!
call Ntest_override("char_avail", 0)