patch 9.2.0417: completion: no support for "noinsert" with 'wildmode'
Commit:
https://github.com/vim/vim/commit/af494af5ff188d976073cbc049249614edffa4bf
Author: glepnir <
gleph...@gmail.com>
Date: Wed Apr 29 18:35:55 2026 +0000
patch 9.2.0417: completion: no support for "noinsert" with 'wildmode'
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: #16551
closes: #20080
Signed-off-by: glepnir <
gleph...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index a0e450a43..e994d2901 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt* For Vim version 9.2. Last change: 2026 Apr 28
+*options.txt* For Vim version 9.2. Last change: 2026 Apr 29
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -10398,8 +10398,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/doc/version9.txt b/runtime/doc/version9.txt
index f3bea36e1..7dbc447a9 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt* For Vim version 9.2. Last change: 2026 Apr 28
+*version9.txt* For Vim version 9.2. Last change: 2026 Apr 29
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -52618,6 +52618,8 @@ Other ~
- Enable reflow support in the |:terminal|.
- Enabled scrolling for the tabpanel when the tab page list exceeds the screen
height. Also added the "scrollbar" sub-option to 'tabpanelopt'.
+- Added the "noinsert" value to the 'wildmode' option for symmetry with the
+ 'completeopt' option
Platform specific ~
-----------------
diff --git a/src/cmdexpand.c b/src/cmdexpand.c
index 9dfb6979c..58a59bef6 100644
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -346,7 +346,7 @@ nextwild(
cmdline_orig.length = 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;
@@ -380,7 +380,8 @@ nextwild(
if (xp->xp_numfiles <= 0 && p == NULL)
beep_flush();
- else if (xp->xp_numfiles == 1 && !(options & WILD_NOSELECT)
+ else if (xp->xp_numfiles == 1
+ && !(options & (WILD_NOSELECT | WILD_NOINSERT))
&& !wild_navigate)
// free expanded pattern
(void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE);
@@ -1295,7 +1296,11 @@ showmatches_oneline(
* inserted as a normal character.
*/
int
-showmatches(expand_T *xp, int display_wildmenu, int display_list, int noselect)
+showmatches(
+ expand_T *xp,
+ int display_wildmenu,
+ int display_list,
+ int wim_flags_arg)
{
cmdline_info_T *ccline = get_cmdline_info();
int numMatches;
@@ -1306,6 +1311,9 @@ showmatches(expand_T *xp, int display_wildmenu, int display_list, int noselect)
int columns;
int attr;
int showtail;
+ int noselect = (wim_flags_arg & WIM_NOSELECT);
+ int noinsert = (wim_flags_arg & WIM_NOINSERT);
+ int cmdline_unchanged = noselect || noinsert;
if (xp->xp_numfiles == -1)
{
@@ -1328,7 +1336,7 @@ showmatches(expand_T *xp, int display_wildmenu, int display_list, int noselect)
&& vim_strchr(p_wop, WOP_PUM) != NULL)
{
int retval = cmdline_pum_create(ccline, xp, matches, numMatches,
- showtail && !noselect);
+ showtail && !cmdline_unchanged);
if (retval == EXPAND_OK)
{
compl_selected = noselect ? -1 : 0;
diff --git a/src/ex_getln.c b/src/ex_getln.c
index 3cec79f80..1124f4586 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -958,6 +958,7 @@ cmdline_wildchar_complete(
int cmdpos_before;
int options = WILD_NO_BEEP;
int wim_noselect = p_wmnu && (wim_flags[0] & WIM_NOSELECT);
+ int wim_noinsert = p_wmnu && (wim_flags[0] & WIM_NOINSERT);
if (wim_flags[wim_index] & WIM_BUFLASTUSED)
options |= WILD_BUFLASTUSED;
@@ -968,7 +969,8 @@ cmdline_wildchar_complete(
&& !*did_wild_list
&& (wim_flags[wim_index] & WIM_LIST))
{
- (void)showmatches(xp, FALSE, TRUE, wim_noselect);
+ (void)showmatches(xp, FALSE, TRUE,
+ p_wmnu ? wim_flags[wim_index] : 0);
redrawcmd();
*did_wild_list = TRUE;
}
@@ -1006,6 +1008,8 @@ cmdline_wildchar_complete(
{
if (wim_noselect || wim_list)
options |= WILD_NOSELECT;
+ if (wim_noinsert)
+ options |= WILD_NOINSERT;
res = nextwild(xp, WILD_EXPAND_KEEP, options, escape);
}
@@ -1028,26 +1032,28 @@ cmdline_wildchar_complete(
}
// Display matches
- if (res == OK && xp->xp_numfiles > (wim_noselect ? 0 : 1))
+ if (res == OK && xp->xp_numfiles > ((wim_noselect || wim_noinsert) ? 0 : 1))
{
if (wim_longest)
{
int found_longest_prefix = (ccline.cmdpos != cmdpos_before);
if (wim_list || (p_wmnu && wim_full))
- (void)showmatches(xp, p_wmnu, wim_list, TRUE);
+ (void)showmatches(xp, p_wmnu, wim_list, WIM_NOSELECT);
else if (!found_longest_prefix)
{
int wim_list_next = (wim_flags[1] & WIM_LIST);
int wim_full_next = (wim_flags[1] & WIM_FULL);
int wim_noselect_next = (wim_flags[1] & WIM_NOSELECT);
+ int wim_noinsert_next = (wim_flags[1] & WIM_NOINSERT);
if (wim_list_next || (p_wmnu && (wim_full_next
- || wim_noselect_next)))
+ || wim_noselect_next || wim_noinsert_next)))
{
- if (wim_full_next && !wim_noselect_next)
+ if (wim_full_next && !wim_noselect_next && !wim_noinsert_next)
nextwild(xp, WILD_NEXT, options, escape);
else
(void)showmatches(xp, p_wmnu, wim_list_next,
- wim_noselect_next);
+ p_wmnu ? wim_flags[1] : 0);
+
if (wim_list_next)
*did_wild_list = TRUE;
}
@@ -1055,8 +1061,10 @@ cmdline_wildchar_complete(
}
else
{
- if (wim_list || (p_wmnu && (wim_full || wim_noselect)))
- (void)showmatches(xp, p_wmnu, wim_list, wim_noselect);
+ if (wim_list || (p_wmnu && (wim_full || wim_noselect
+ || wim_noinsert)))
+ (void)showmatches(xp, p_wmnu, wim_list,
+ p_wmnu ? wim_flags[0] : 0);
else
vim_beep(BO_WILD);
}
@@ -2181,7 +2189,7 @@ getcmdline_int(
{
// Trigger the popup menu when wildoptions=pum
showmatches(&xpc, p_wmnu, wim_flags[wim_index] & WIM_LIST,
- wim_flags[0] & WIM_NOSELECT);
+ p_wmnu ? wim_flags[0] : 0);
}
if (nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK
&& nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK)
@@ -2296,7 +2304,7 @@ getcmdline_int(
goto cmdline_not_changed;
case Ctrl_D:
- if (showmatches(&xpc, FALSE, TRUE, wim_flags[0] & WIM_NOSELECT)
+ if (showmatches(&xpc, FALSE, TRUE, p_wmnu ? wim_flags[0] : 0)
== EXPAND_NOTHING)
break; // Use ^D as normal char instead
@@ -2900,6 +2908,8 @@ check_opt_wim(void)
new_wim_flags[idx] |= WIM_BUFLASTUSED;
else if (i == 8 && STRNCMP(p, "noselect", 8) == 0)
new_wim_flags[idx] |= WIM_NOSELECT;
+ else if (i == 8 && STRNCMP(p, "noinsert", 8) == 0)
+ new_wim_flags[idx] |= WIM_NOINSERT;
else
return FAIL;
p += i;
diff --git a/src/option.h b/src/option.h
index c6607a626..cda49a4af 100644
--- a/src/option.h
+++ b/src/option.h
@@ -375,6 +375,7 @@ typedef enum {
#define WIM_LIST 0x04
#define WIM_BUFLASTUSED 0x08
#define WIM_NOSELECT 0x10
+#define WIM_NOINSERT 0x20
// flags for the 'wildoptions' option
// each defined char should be unique over all values.
diff --git a/src/optionstr.c b/src/optionstr.c
index 0390d2dac..128e5a946 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -116,7 +116,7 @@ static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm"
#endif
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_wim_values[]) = {"full", "longest", "list", "lastused", "noselect", "noinsert", NULL};
static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", "exacttext", NULL};
#ifdef FEAT_WAK
static char *(p_wak_values[]) = {"yes", "menu", "no", NULL};
diff --git a/src/proto/
cmdexpand.pro b/src/proto/
cmdexpand.pro
index 388523d6f..50383b98c 100644
--- a/src/proto/
cmdexpand.pro
+++ b/src/proto/
cmdexpand.pro
@@ -12,7 +12,7 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode
void ExpandInit(expand_T *xp);
void ExpandCleanup(expand_T *xp);
void clear_cmdline_orig(void);
-int showmatches(expand_T *xp, int display_wildmenu, int display_list, int noselect);
+int showmatches(expand_T *xp, int display_wildmenu, int display_list, int wim_flags_arg);
char_u *addstar(char_u *fname, int len, int context);
void set_expand_context(expand_T *xp);
void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline);
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index 8a170894c..80a023d2c 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -5416,4 +5416,94 @@ func Test_cmdline_complete_with_space()
call chdir(save_cwd)
endfunc
+func Test_wildmode_noinsert()
+ command! -nargs=1 -complete=custom,T MyCmd echo
+ func T(a, c, p)
+ return "oneA
oneB
oneC"
+ 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
diff --git a/src/testdir/util/gen_opt_test.vim b/src/testdir/util/gen_opt_test.vim
index 02f7fdf34..91ed2c80c 100644
--- a/src/testdir/util/gen_opt_test.vim
+++ b/src/testdir/util/gen_opt_test.vim
@@ -365,6 +365,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/src/version.c b/src/version.c
index e4607baba..86f3c4ffd 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 417,
/**/
416,
/**/
diff --git a/src/vim.h b/src/vim.h
index 143642d04..c0c811d1d 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -904,6 +904,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
#define WILD_NOSELECT 0x4000
#define WILD_MAY_EXPAND_PATTERN 0x8000
#define WILD_FUNC_TRIGGER 0x10000 // called from wildtrigger()
+#define WILD_NOINSERT 0x20000
// Flags for expand_wildcards()
#define EW_DIR 0x01 // include directory names