vim-patch:9.2.0417: completion: no support for "noinsert" with 'wildmode' (#39516)

Problem:  completion: no support for "noinsert" with 'wildmode' and
          commandline completion
Solution: Add "noinsert" value to the 'wildmode' option, mirroring
          'completeopt' "noinsert" behaviour (glepnir).

fixes:  vim/vim#16551
closes: vim/vim#20080

af494af5ff
This commit is contained in:
glepnir
2026-04-30 19:13:47 +08:00
committed by GitHub
parent 6195624a3f
commit c79d5f5028
8 changed files with 139 additions and 23 deletions

View File

@@ -7423,8 +7423,12 @@ A jump table for the options with a short description can be found at |Q_op|.
applies to buffer name completion.
"noselect" If 'wildmenu' is enabled, show the menu but do not
preselect the first item.
If only one match exists, it is completed fully, unless "noselect" is
specified.
"noinsert" If 'wildmenu' is enabled, show the menu and preselect
the first match, but do not insert it in the
command line. If both "noinsert" and "noselect" are
present, "noselect" takes precedence.
If only one match exists, it is completed fully, unless "noselect" or
"noinsert" is specified.
Some useful combinations of colon-separated values:
"longest:full" Start with the longest common string and show

View File

@@ -8059,8 +8059,12 @@ vim.go.wmnu = vim.go.wildmenu
--- applies to buffer name completion.
--- "noselect" If 'wildmenu' is enabled, show the menu but do not
--- preselect the first item.
--- If only one match exists, it is completed fully, unless "noselect" is
--- specified.
--- "noinsert" If 'wildmenu' is enabled, show the menu and preselect
--- the first match, but do not insert it in the
--- command line. If both "noinsert" and "noselect" are
--- present, "noselect" takes precedence.
--- If only one match exists, it is completed fully, unless "noselect" or
--- "noinsert" is specified.
---
--- Some useful combinations of colon-separated values:
--- "longest:full" Start with the longest common string and show

View File

@@ -346,7 +346,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
cmdline_orig = cstrn_to_string(ccline->cmdbuff, (size_t)ccline->cmdlen);
}
if (p != NULL && !got_int && !(options & WILD_NOSELECT)) {
if (p != NULL && !got_int && !(options & (WILD_NOSELECT | WILD_NOINSERT))) {
size_t plen = strlen(p);
int difflen = (int)plen - (int)(xp->xp_pattern_len);
if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) {
@@ -373,7 +373,8 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
if (xp->xp_numfiles <= 0 && p == NULL) {
beep_flush();
} else if (xp->xp_numfiles == 1 && !(options & WILD_NOSELECT) && !wild_navigate) {
} else if (xp->xp_numfiles == 1 && !(options & (WILD_NOSELECT | WILD_NOINSERT))
&& !wild_navigate) {
// free expanded pattern
ExpandOne(xp, NULL, NULL, 0, WILD_FREE);
}
@@ -385,7 +386,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
/// Create completion popup menu with items from "matches".
static void cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches,
bool showtail, bool noselect)
bool showtail, bool cmdline_unchanged)
{
assert(numMatches >= 0);
// Add all the completion matches
@@ -403,7 +404,7 @@ static void cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches
}
// Compute the popup menu starting column
char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, noselect) : xp->xp_pattern;
char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, cmdline_unchanged) : xp->xp_pattern;
if (ui_has(kUICmdline) && cmdline_win == NULL) {
compl_startcol = (int)(endpos - ccline->cmdbuff);
} else {
@@ -1103,7 +1104,7 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in
/// Display completion matches.
/// Returns EXPAND_NOTHING when the character that triggered expansion should be
/// inserted as a normal character.
int showmatches(expand_T *xp, bool display_wildmenu, bool display_list, bool noselect)
int showmatches(expand_T *xp, bool display_wildmenu, bool display_list, int wim_flags_arg)
{
CmdlineInfo *const ccline = get_cmdline_info();
int numMatches;
@@ -1112,6 +1113,9 @@ int showmatches(expand_T *xp, bool display_wildmenu, bool display_list, bool nos
int lines;
int columns;
bool showtail;
bool noselect = (wim_flags_arg & kOptWimFlagNoselect);
bool noinsert = (wim_flags_arg & kOptWimFlagNoinsert);
bool cmdline_unchanged = noselect || noinsert;
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
@@ -1131,7 +1135,7 @@ int showmatches(expand_T *xp, bool display_wildmenu, bool display_list, bool nos
}
if (cmdline_compl_use_pum(display_wildmenu && !display_list)) {
cmdline_pum_create(ccline, xp, matches, numMatches, showtail, noselect);
cmdline_pum_create(ccline, xp, matches, numMatches, showtail, cmdline_unchanged);
compl_selected = noselect ? -1 : 0;
pum_clear();
cmdline_pum_display(true);

View File

@@ -43,6 +43,7 @@ enum {
WILD_NOSELECT = 0x4000,
WILD_MAY_EXPAND_PATTERN = 0x8000,
WILD_FUNC_TRIGGER = 0x10000, ///< called from wildtrigger()
WILD_NOINSERT = 0x20000,
};
#include "cmdexpand.h.generated.h"

View File

@@ -1153,6 +1153,7 @@ static int command_line_wildchar_complete(CommandLineState *s)
bool escape = s->firstc != '@';
bool redraw_if_menu_empty = s->c == K_WILD;
bool wim_noselect = p_wmnu && (wim_flags[0] & kOptWimFlagNoselect) != 0;
bool wim_noinsert = p_wmnu && (wim_flags[0] & kOptWimFlagNoinsert) != 0;
if (wim_flags[s->wim_index] & kOptWimFlagLastused) {
options |= WILD_BUFLASTUSED;
@@ -1162,7 +1163,7 @@ static int command_line_wildchar_complete(CommandLineState *s)
if (s->xpc.xp_numfiles > 1
&& !s->did_wild_list
&& (wim_flags[s->wim_index] & kOptWimFlagList)) {
showmatches(&s->xpc, false, true, wim_noselect);
showmatches(&s->xpc, false, true, p_wmnu ? wim_flags[s->wim_index] : 0);
redrawcmd();
s->did_wild_list = true;
}
@@ -1196,6 +1197,9 @@ static int command_line_wildchar_complete(CommandLineState *s)
if (wim_noselect || wim_list) {
options |= WILD_NOSELECT;
}
if (wim_noinsert) {
options |= WILD_NOINSERT;
}
res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, escape);
}
@@ -1214,20 +1218,22 @@ static int command_line_wildchar_complete(CommandLineState *s)
}
// Display matches
if (res == OK && s->xpc.xp_numfiles > (wim_noselect ? 0 : 1)) {
if (res == OK && s->xpc.xp_numfiles > ((wim_noselect || wim_noinsert) ? 0 : 1)) {
if (wim_longest) {
bool found_longest_prefix = (ccline.cmdpos != cmdpos_before);
if (wim_list || (p_wmnu && wim_full)) {
showmatches(&s->xpc, p_wmnu, wim_list, true);
showmatches(&s->xpc, p_wmnu, wim_list, kOptWimFlagNoselect);
} else if (!found_longest_prefix) {
bool wim_list_next = (wim_flags[1] & kOptWimFlagList);
bool wim_full_next = (wim_flags[1] & kOptWimFlagFull);
bool wim_noselect_next = (wim_flags[1] & kOptWimFlagNoselect);
if (wim_list_next || (p_wmnu && (wim_full_next || wim_noselect_next))) {
if (wim_full_next && !wim_noselect_next) {
bool wim_noinsert_next = (wim_flags[1] & kOptWimFlagNoinsert);
if (wim_list_next
|| (p_wmnu && (wim_full_next || wim_noselect_next || wim_noinsert_next))) {
if (wim_full_next && !wim_noselect_next && !wim_noinsert_next) {
nextwild(&s->xpc, WILD_NEXT, options, escape);
} else {
showmatches(&s->xpc, p_wmnu, wim_list_next, wim_noselect_next);
showmatches(&s->xpc, p_wmnu, wim_list_next, p_wmnu ? wim_flags[1] : 0);
}
if (wim_list_next) {
s->did_wild_list = true;
@@ -1235,8 +1241,8 @@ static int command_line_wildchar_complete(CommandLineState *s)
}
}
} else {
if (wim_list || (p_wmnu && (wim_full || wim_noselect))) {
showmatches(&s->xpc, p_wmnu, wim_list, wim_noselect);
if (wim_list || (p_wmnu && (wim_full || wim_noselect || wim_noinsert))) {
showmatches(&s->xpc, p_wmnu, wim_list, p_wmnu ? wim_flags[0] : 0);
} else {
vim_beep(kOptBoFlagWildmode);
}
@@ -1538,7 +1544,7 @@ static int command_line_execute(VimState *state, int key)
&& ((!s->did_wild_list && (wim_flags[s->wim_index] & kOptWimFlagList)) || p_wmnu)) {
// Trigger the popup menu when wildoptions=pum
showmatches(&s->xpc, p_wmnu, wim_flags[s->wim_index] & kOptWimFlagList,
wim_flags[0] & kOptWimFlagNoselect);
p_wmnu ? wim_flags[0] : 0);
}
nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
@@ -2113,7 +2119,7 @@ static int command_line_handle_key(CommandLineState *s)
}
case Ctrl_D:
if (showmatches(&s->xpc, false, true, wim_flags[0] & kOptWimFlagNoselect)
if (showmatches(&s->xpc, false, true, p_wmnu ? wim_flags[0] : 0)
== EXPAND_NOTHING) {
break; // Use ^D as normal char instead
}
@@ -3060,6 +3066,8 @@ int check_opt_wim(void)
new_wim_flags[idx] |= kOptWimFlagLastused;
} else if (i == 8 && strncmp(p, "noselect", 8) == 0) {
new_wim_flags[idx] |= kOptWimFlagNoselect;
} else if (i == 8 && strncmp(p, "noinsert", 8) == 0) {
new_wim_flags[idx] |= kOptWimFlagNoinsert;
} else {
return FAIL;
}

View File

@@ -10370,7 +10370,7 @@ local options = {
cb = 'did_set_wildmode',
defaults = 'full',
-- Keep this in sync with check_opt_wim().
values = { 'full', 'longest', 'list', 'lastused', 'noselect' },
values = { 'full', 'longest', 'list', 'lastused', 'noselect', 'noinsert' },
flags = true,
deny_duplicates = false,
desc = [=[
@@ -10396,8 +10396,12 @@ local options = {
applies to buffer name completion.
"noselect" If 'wildmenu' is enabled, show the menu but do not
preselect the first item.
If only one match exists, it is completed fully, unless "noselect" is
specified.
"noinsert" If 'wildmenu' is enabled, show the menu and preselect
the first match, but do not insert it in the
command line. If both "noinsert" and "noselect" are
present, "noselect" takes precedence.
If only one match exists, it is completed fully, unless "noselect" or
"noinsert" is specified.
Some useful combinations of colon-separated values:
"longest:full" Start with the longest common string and show

View File

@@ -378,6 +378,7 @@ let test_values = {
\ ['xxx']],
\ 'wildmode': [['', 'full', 'longest', 'list', 'lastused', 'list:full',
\ 'noselect', 'noselect,full', 'noselect:lastused,full',
\ 'noinsert', 'noinsert,full', 'noinsert:lastused,full',
\ 'full,longest', 'full,full,full,full'],
\ ['xxx', 'a4', 'full,full,full,full,full']],
\ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']],

View File

@@ -5321,4 +5321,94 @@ func Test_rulerformat_empty()
set rulerformat&
endfunc
func Test_wildmode_noinsert()
command! -nargs=1 -complete=custom,T MyCmd echo
func T(a, c, p)
return "oneA\noneB\noneC"
endfunc
set wildmenu wildoptions=pum wildmode=noinsert,full wildchar=<Tab>
call feedkeys(":MyCmd o\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
call feedkeys(":MyCmd o\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneB', @:)
call feedkeys(":MyCmd o\<Tab>\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneC', @:)
call feedkeys(":MyCmd o\<Tab>\<C-Y>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneA', @:)
" CTRL-P from highlighted first item returns to original text
call feedkeys(":MyCmd o\<Tab>\<C-P>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
" Another CTRL-P wraps to the last match
call feedkeys(":MyCmd o\<Tab>\<C-P>\<C-P>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneC', @:)
set wildoptions=
call feedkeys(":MyCmd o\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
call feedkeys(":MyCmd o\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneB', @:)
call feedkeys(":MyCmd o\<Tab>\<C-Y>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneA', @:)
call feedkeys(":MyCmd o\<Tab>\<C-E>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
" 'nowildmenu' should make 'noinsert' ineffective
set nowildmenu
call feedkeys(":MyCmd o\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneA', @:)
" 'noselect' takes precedence over 'noinsert'
set wildmenu wildoptions=pum wildmode=noselect:noinsert,full
call feedkeys(":MyCmd o\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
call feedkeys(":MyCmd o\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneA', @:)
call feedkeys(":MyCmd o\<Tab>\<C-Y>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
set wildmode=noinsert
call feedkeys(":MyCmd o\<Tab>\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
set wildmode=noinsert,full
call feedkeys(":MyCmd o\<Tab>\<C-N>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneB', @:)
call feedkeys(":MyCmd o\<Tab>\<C-E>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
" 'longest' takes precedence over 'noinsert'
set wildmode=noinsert:longest
call feedkeys(":MyCmd o\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd one', @:)
set wildmode&
call feedkeys(":set wildmode=noi\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"set wildmode=noinsert', @:)
set wildmode=noinsert:lastused,full
call assert_equal('noinsert:lastused,full', &wildmode)
call assert_fails('set wildmode=noinser', 'E474:')
" Single match with 'noinsert': item shown highlighted, C-Y commits
command! -nargs=1 -complete=custom,T1 MyCmd1 echo
func T1(a, c, p)
return "oneA"
endfunc
set wildmenu wildoptions=pum wildmode=noinsert,full
call feedkeys(":MyCmd1 o\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd1 o', @:)
call feedkeys(":MyCmd1 o\<Tab>\<C-Y>\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd1 oneA', @:)
delcommand MyCmd1
delfunc T1
set wildmenu& wildoptions& wildmode& wildchar&
delcommand MyCmd
delfunc T
endfunc
" vim: shiftwidth=2 sts=2 expandtab