Merge pull request #34852 from zeertzjq/vim-9.1.1526

vim-patch:9.1.{1526,1528}
This commit is contained in:
zeertzjq
2025-07-09 08:55:08 +08:00
committed by GitHub
5 changed files with 126 additions and 31 deletions

View File

@ -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

View File

@ -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

View File

@ -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++) {

View File

@ -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

View File

@ -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)