Skip to content

Commit 4c83dd2

Browse files
zeertzjqgirishjideathbeam
committed
vim-patch:9.1.1166: command-line auto-completion hard with wildmenu
Problem: command-line auto-completion hard with wildmenu Solution: implement "noselect" wildoption value (Girish Palya) When `noselect` is present in `wildmode` and 'wildmenu' is enabled, the completion menu appears without pre-selecting the first item. This change makes it easier to implement command-line auto-completion, where the menu dynamically appears as characters are typed, and `<Tab>` can be used to manually select an item. This can be achieved by leveraging the `CmdlineChanged` event to insert `wildchar(m)`, triggering completion menu. Without this change, auto-completion using the 'wildmenu' mechanism is not feasible, as it automatically inserts the first match, preventing dynamic selection. The following Vimscript snippet demonstrates how to configure auto-completion using `noselect`: ```vim vim9script set wim=noselect:lastused,full wop=pum wcm=<C-@> wmnu autocmd CmdlineChanged : timer_start(0, function(CmdComplete, [getcmdline()])) def CmdComplete(cur_cmdline: string, timer: number) var [cmdline, curpos] = [getcmdline(), getcmdpos()] if cur_cmdline ==# cmdline # Avoid completing each character in keymaps and pasted text && !pumvisible() && curpos == cmdline->len() + 1 if cmdline[curpos - 2] =~ '[\w*/:]' # Reduce noise by completing only selected characters feedkeys("\<C-@>", "ti") set eventignore+=CmdlineChanged # Suppress redundant completion attempts timer_start(0, (_) => { getcmdline()->substitute('\%x00$', '', '')->setcmdline() # Remove <C-@> if no completion items exist set eventignore-=CmdlineChanged }) endif endif enddef ``` fixes: vim/vim#16551 closes: vim/vim#16759 vim/vim@2bacc3e Cherry-pick Wildmode_Tests() change from patch 9.0.0418. Co-authored-by: Girish Palya <girishji@gmail.com> Co-authored-by: Tomas Slusny <slusnucky@gmail.com>
1 parent 948179c commit 4c83dd2

File tree

9 files changed

+105
-19
lines changed

9 files changed

+105
-19
lines changed

runtime/doc/news.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ OPTIONS
329329

330330
'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|.
331331
'completeopt' flag "preinsert" highlights text to be inserted.
332+
'wildmode' flag "noselect" shows 'wildmenu' without selecting an entry.
332333
'messagesopt' configures |:messages| and |hit-enter| prompt.
333334
'tabclose' controls which tab page to focus when closing a tab page.
334335
'eventignorewin' to persistently ignore events in a window.

runtime/doc/options.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7050,7 +7050,10 @@ A jump table for the options with a short description can be found at |Q_op|.
70507050
"lastused" When completing buffer names and more than one buffer
70517051
matches, sort buffers by time last used (other than
70527052
the current buffer).
7053-
When there is only a single match, it is fully completed in all cases.
7053+
"noselect" Do not pre-select first menu item and start 'wildmenu'
7054+
if it is enabled.
7055+
When there is only a single match, it is fully completed in all cases
7056+
except when "noselect" is present.
70547057

70557058
Examples of useful colon-separated values:
70567059
"longest:full" Like "longest", but also start 'wildmenu' if it is
@@ -7073,7 +7076,11 @@ A jump table for the options with a short description can be found at |Q_op|.
70737076
set wildmode=list,full
70747077
< List all matches without completing, then each full match >vim
70757078
set wildmode=longest,list
7076-
< Complete longest common string, then list alternatives.
7079+
< Complete longest common string, then list alternatives >vim
7080+
set wildmode=noselect:full
7081+
< Display 'wildmenu' without completing, then each full match >vim
7082+
set wildmode=noselect:lastused,full
7083+
< Same as above, but sort buffers by time last used.
70777084
More info here: |cmdline-completion|.
70787085

70797086
*'wildoptions'* *'wop'*

runtime/lua/vim/_meta/options.lua

Lines changed: 15 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nvim/cmdexpand.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
288288
p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
289289
}
290290
// Translate string into pattern and expand it.
291-
const int use_options = (options
291+
const int use_options = ((options & ~WILD_KEEP_SOLE_ITEM)
292292
| WILD_HOME_REPLACE
293293
| WILD_ADD_SLASH
294294
| WILD_SILENT
@@ -339,7 +339,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
339339

340340
if (xp->xp_numfiles <= 0 && p2 == NULL) {
341341
beep_flush();
342-
} else if (xp->xp_numfiles == 1) {
342+
} else if (xp->xp_numfiles == 1 && !(options & WILD_KEEP_SOLE_ITEM)) {
343343
// free expanded pattern
344344
ExpandOne(xp, NULL, NULL, 0, WILD_FREE);
345345
}

src/nvim/cmdexpand.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ enum {
4040
WILD_NOERROR = 0x800, ///< sets EW_NOERROR
4141
WILD_BUFLASTUSED = 0x1000,
4242
BUF_DIFF_FILTER = 0x2000,
43+
WILD_KEEP_SOLE_ITEM = 0x4000,
4344
};
4445

4546
#ifdef INCLUDE_GENERATED_DECLARATIONS

src/nvim/ex_getln.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,9 @@ static int command_line_wildchar_complete(CommandLineState *s)
10821082
if (wim_flags[s->wim_index] & kOptWimFlagLastused) {
10831083
options |= WILD_BUFLASTUSED;
10841084
}
1085+
if (wim_flags[0] & kOptWimFlagNoselect) {
1086+
options |= WILD_KEEP_SOLE_ITEM;
1087+
}
10851088
if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
10861089
// if 'wildmode' contains "list" may still need to list
10871090
if (s->xpc.xp_numfiles > 1
@@ -1124,19 +1127,20 @@ static int command_line_wildchar_complete(CommandLineState *s)
11241127
// when more than one match, and 'wildmode' first contains
11251128
// "list", or no change and 'wildmode' contains "longest,list",
11261129
// list all matches
1127-
if (res == OK && s->xpc.xp_numfiles > 1) {
1130+
if (res == OK
1131+
&& s->xpc.xp_numfiles > ((wim_flags[s->wim_index] & kOptWimFlagNoselect) ? 0 : 1)) {
11281132
// a "longest" that didn't do anything is skipped (but not
11291133
// "list:longest")
11301134
if (wim_flags[0] == kOptWimFlagLongest && ccline.cmdpos == j) {
11311135
s->wim_index = 1;
11321136
}
11331137
if ((wim_flags[s->wim_index] & kOptWimFlagList)
1134-
|| (p_wmnu && (wim_flags[s->wim_index] & kOptWimFlagFull) != 0)) {
1138+
|| (p_wmnu && (wim_flags[s->wim_index] & (kOptWimFlagFull|kOptWimFlagNoselect)) != 0)) {
11351139
if (!(wim_flags[0] & kOptWimFlagLongest)) {
11361140
int p_wmnu_save = p_wmnu;
11371141
p_wmnu = 0;
11381142
// remove match
1139-
nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
1143+
nextwild(&s->xpc, WILD_PREV, 0 | (options & ~kOptWimFlagNoselect), s->firstc != '@');
11401144
p_wmnu = p_wmnu_save;
11411145
}
11421146

@@ -1146,7 +1150,8 @@ static int command_line_wildchar_complete(CommandLineState *s)
11461150

11471151
if (wim_flags[s->wim_index] & kOptWimFlagLongest) {
11481152
nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@');
1149-
} else if (wim_flags[s->wim_index] & kOptWimFlagFull) {
1153+
} else if ((wim_flags[s->wim_index] & kOptWimFlagFull)
1154+
&& !(wim_flags[s->wim_index] & kOptWimFlagNoselect)) {
11501155
nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@');
11511156
}
11521157
} else {
@@ -2875,6 +2880,8 @@ int check_opt_wim(void)
28752880
new_wim_flags[idx] |= kOptWimFlagList;
28762881
} else if (i == 8 && strncmp(p, "lastused", 8) == 0) {
28772882
new_wim_flags[idx] |= kOptWimFlagLastused;
2883+
} else if (i == 8 && strncmp(p, "noselect", 8) == 0) {
2884+
new_wim_flags[idx] |= kOptWimFlagNoselect;
28782885
} else {
28792886
return FAIL;
28802887
}

src/nvim/options.lua

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10020,7 +10020,7 @@ local options = {
1002010020
cb = 'did_set_wildmode',
1002110021
defaults = 'full',
1002210022
-- Keep this in sync with check_opt_wim().
10023-
values = { 'full', 'longest', 'list', 'lastused' },
10023+
values = { 'full', 'longest', 'list', 'lastused', 'noselect' },
1002410024
flags = true,
1002510025
deny_duplicates = false,
1002610026
desc = [=[
@@ -10042,7 +10042,10 @@ local options = {
1004210042
"lastused" When completing buffer names and more than one buffer
1004310043
matches, sort buffers by time last used (other than
1004410044
the current buffer).
10045-
When there is only a single match, it is fully completed in all cases.
10045+
"noselect" Do not pre-select first menu item and start 'wildmenu'
10046+
if it is enabled.
10047+
When there is only a single match, it is fully completed in all cases
10048+
except when "noselect" is present.
1004610049
1004710050
Examples of useful colon-separated values:
1004810051
"longest:full" Like "longest", but also start 'wildmenu' if it is
@@ -10065,7 +10068,11 @@ local options = {
1006510068
set wildmode=list,full
1006610069
< List all matches without completing, then each full match >vim
1006710070
set wildmode=longest,list
10068-
< Complete longest common string, then list alternatives.
10071+
< Complete longest common string, then list alternatives >vim
10072+
set wildmode=noselect:full
10073+
< Display 'wildmenu' without completing, then each full match >vim
10074+
set wildmode=noselect:lastused,full
10075+
< Same as above, but sort buffers by time last used.
1006910076
More info here: |cmdline-completion|.
1007010077
]=],
1007110078
full_name = 'wildmode',

test/old/testdir/gen_opt_test.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ let test_values = {
352352
\ 'bs'],
353353
\ ['xxx']],
354354
\ 'wildmode': [['', 'full', 'longest', 'list', 'lastused', 'list:full',
355+
\ 'noselect', 'noselect,full', 'noselect:lastused,full',
355356
\ 'full,longest', 'full,full,full,full'],
356357
\ ['xxx', 'a4', 'full,full,full,full,full']],
357358
\ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']],

test/old/testdir/test_cmdline.vim

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,22 +2168,58 @@ func Wildmode_tests()
21682168
call assert_equal('AAA AAAA AAAAA', g:Sline)
21692169
call assert_equal('"b A', @:)
21702170

2171+
" When 'wildmenu' is not set, 'noselect' completes first item
2172+
set wildmode=noselect
2173+
call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
2174+
call assert_equal('"MyCmd oneA', @:)
2175+
2176+
" When 'noselect' is present, do not complete first <tab>.
2177+
set wildmenu
2178+
set wildmode=noselect
2179+
call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
2180+
call assert_equal('"MyCmd o', @:)
2181+
call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt')
2182+
call assert_equal('"MyCmd o', @:)
2183+
call feedkeys(":MyCmd o\t\t\<C-Y>\<C-B>\"\<CR>", 'xt')
2184+
call assert_equal('"MyCmd o', @:)
2185+
2186+
" When 'full' is present, complete after first <tab>.
2187+
set wildmode=noselect,full
2188+
call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
2189+
call assert_equal('"MyCmd o', @:)
2190+
call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt')
2191+
call assert_equal('"MyCmd oneA', @:)
2192+
call feedkeys(":MyCmd o\t\t\t\<C-B>\"\<CR>", 'xt')
2193+
call assert_equal('"MyCmd oneB', @:)
2194+
call feedkeys(":MyCmd o\t\t\t\<C-Y>\<C-B>\"\<CR>", 'xt')
2195+
call assert_equal('"MyCmd oneB', @:)
2196+
2197+
" 'noselect' has no effect when 'longest' is present.
2198+
set wildmode=noselect:longest
2199+
call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
2200+
call assert_equal('"MyCmd one', @:)
2201+
2202+
" Complete 'noselect' value in 'wildmode' option
2203+
set wildmode&
2204+
call feedkeys(":set wildmode=n\t\<C-B>\"\<CR>", 'xt')
2205+
call assert_equal('"set wildmode=noselect', @:)
2206+
call feedkeys(":set wildmode=\t\t\t\t\t\<C-B>\"\<CR>", 'xt')
2207+
call assert_equal('"set wildmode=noselect', @:)
2208+
21712209
" when using longest completion match, matches shorter than the argument
21722210
" should be ignored (happens with :help)
21732211
set wildmode=longest,full
2174-
set wildmenu
21752212
call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt')
21762213
call assert_equal('"help a', @:)
21772214
" non existing file
21782215
call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt')
21792216
call assert_equal('"e a1b2y3z4', @:)
2180-
set wildmenu&
21812217

21822218
" Test for longest file name completion with 'fileignorecase'
21832219
" On MS-Windows, file names are case insensitive.
21842220
if has('unix')
2185-
call writefile([], 'XTESTfoo')
2186-
call writefile([], 'Xtestbar')
2221+
call writefile([], 'XTESTfoo', 'D')
2222+
call writefile([], 'Xtestbar', 'D')
21872223
set nofileignorecase
21882224
call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt')
21892225
call assert_equal('"e XTESTfoo', @:)
@@ -2195,10 +2231,23 @@ func Wildmode_tests()
21952231
call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt')
21962232
call assert_equal('"e Xtest', @:)
21972233
set fileignorecase&
2198-
call delete('XTESTfoo')
2199-
call delete('Xtestbar')
22002234
endif
22012235

2236+
" If 'noselect' is present, single item menu should not insert item
2237+
func! T(a, c, p)
2238+
return "oneA"
2239+
endfunc
2240+
command! -nargs=1 -complete=custom,T MyCmd
2241+
set wildmode=noselect,full
2242+
call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
2243+
call assert_equal('"MyCmd o', @:)
2244+
call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt')
2245+
call assert_equal('"MyCmd oneA', @:)
2246+
" 'nowildmenu' should make 'noselect' ineffective
2247+
set nowildmenu
2248+
call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
2249+
call assert_equal('"MyCmd oneA', @:)
2250+
22022251
%argdelete
22032252
delcommand MyCmd
22042253
delfunc T

0 commit comments

Comments
 (0)