From c79d5f5028c542db3f6678409bad2ee5d4ac8e4b Mon Sep 17 00:00:00 2001 From: glepnir Date: Thu, 30 Apr 2026 19:13:47 +0800 Subject: [PATCH] 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 https://github.com/vim/vim/commit/af494af5ff188d976073cbc049249614edffa4bf --- runtime/doc/options.txt | 8 ++- runtime/lua/vim/_meta/options.gen.lua | 8 ++- src/nvim/cmdexpand.c | 16 +++-- src/nvim/cmdexpand.h | 1 + src/nvim/ex_getln.c | 28 ++++++--- src/nvim/options.lua | 10 ++- test/old/testdir/gen_opt_test.vim | 1 + test/old/testdir/test_cmdline.vim | 90 +++++++++++++++++++++++++++ 8 files changed, 139 insertions(+), 23 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 6000dc92f1..b017866583 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -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 diff --git a/runtime/lua/vim/_meta/options.gen.lua b/runtime/lua/vim/_meta/options.gen.lua index 5f68f31e82..d85dee2c7e 100644 --- a/runtime/lua/vim/_meta/options.gen.lua +++ b/runtime/lua/vim/_meta/options.gen.lua @@ -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 diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 9c30f516cf..620c66ba9e 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -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); diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h index 198b178f71..c8e77682bd 100644 --- a/src/nvim/cmdexpand.h +++ b/src/nvim/cmdexpand.h @@ -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" diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5f384c6ee4..fb95114aae 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -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; } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2651584a36..a53f8dc55b 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -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 diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 733d8099cc..fd772665e9 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -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']], diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index bc2827ddfc..aa9d66a5c3 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -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= + call feedkeys(":MyCmd o\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd oneB', @:) + call feedkeys(":MyCmd o\\\\\"\", 'xt') + call assert_equal('"MyCmd oneC', @:) + + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd oneA', @:) + + " CTRL-P from highlighted first item returns to original text + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + " Another CTRL-P wraps to the last match + call feedkeys(":MyCmd o\\\\\"\", 'xt') + call assert_equal('"MyCmd oneC', @:) + + set wildoptions= + call feedkeys(":MyCmd o\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd oneB', @:) + + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd oneA', @:) + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + + " 'nowildmenu' should make 'noinsert' ineffective + set nowildmenu + call feedkeys(":MyCmd o\\\"\", 'xt') + call assert_equal('"MyCmd oneA', @:) + + " 'noselect' takes precedence over 'noinsert' + set wildmenu wildoptions=pum wildmode=noselect:noinsert,full + call feedkeys(":MyCmd o\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd oneA', @:) + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + + set wildmode=noinsert + call feedkeys(":MyCmd o\\\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + + set wildmode=noinsert,full + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd oneB', @:) + call feedkeys(":MyCmd o\\\\"\", 'xt') + call assert_equal('"MyCmd o', @:) + + " 'longest' takes precedence over 'noinsert' + set wildmode=noinsert:longest + call feedkeys(":MyCmd o\\\"\", 'xt') + call assert_equal('"MyCmd one', @:) + + set wildmode& + call feedkeys(":set wildmode=noi\\\"\", '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\\\"\", 'xt') + call assert_equal('"MyCmd1 o', @:) + call feedkeys(":MyCmd1 o\\\\"\", '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