patch 9.1.1166: command-line auto-completion hard with wildmenu Commit: https://github.com/vim/vim/commit/2bacc3e5fb3569e0fd98e129cb1e422ca18b80a6 Author: Girish Palya <giris...@gmail.com> Date: Sun Mar 2 22:55:57 2025 +0100
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: #16551 closes: #16759 Signed-off-by: Girish Palya <giris...@gmail.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ccf26ceb5..5ed06ba02 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.1. Last change: 2025 Mar 01 +*options.txt* For Vim version 9.1. Last change: 2025 Mar 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -9566,7 +9566,10 @@ A jump table for the options with a short description can be found at |Q_op|. "lastused" When completing buffer names and more than one buffer matches, sort buffers by time last used (other than the current buffer). - When there is only a single match, it is fully completed in all cases. + "noselect" Do not pre-select first menu item and start 'wildmenu' + if it is enabled. + When there is only a single match, it is fully completed in all cases + except when "noselect" is present. Examples of useful colon-separated values: "longest:full" Like "longest", but also start 'wildmenu' if it is @@ -9589,7 +9592,11 @@ A jump table for the options with a short description can be found at |Q_op|. :set wildmode=list,full < List all matches without completing, then each full match > :set wildmode=longest,list -< Complete longest common string, then list alternatives. +< Complete longest common string, then list alternatives > + :set wildmode=noselect:full +< Display 'wildmenu' without completing, then each full match > + :set wildmode=noselect:lastused,full +< Same as above, but sort buffers by time last used. More info here: |cmdline-completion|. *'wildoptions'* *'wop'* diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 99a4002c1..bfd44574d 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.1. Last change: 2025 Feb 23 +*version9.txt* For Vim version 9.1. Last change: 2025 Mar 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -41605,6 +41605,8 @@ Completion: ~ "preinsert" - highlight to be inserted values - handle multi-line completion as expected - improved commandline completion for the |:hi| command +- New option value for 'wildoptions': + "noselect" - do not auto select an entry in the wildmenu Options: ~ - the default for 'commentstring' contains whitespace padding to have diff --git a/src/cmdexpand.c b/src/cmdexpand.c index 9f24429f4..597f78da2 100644 --- a/src/cmdexpand.c +++ b/src/cmdexpand.c @@ -286,6 +286,9 @@ nextwild( { int use_options = options | WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; + if (use_options & WILD_KEEP_SOLE_ITEM) + use_options &= ~WILD_KEEP_SOLE_ITEM; + if (escape) use_options |= WILD_ESCAPE; @@ -340,7 +343,7 @@ nextwild( if (xp->xp_numfiles <= 0 && p2 == NULL) beep_flush(); - else if (xp->xp_numfiles == 1) + else if (xp->xp_numfiles == 1 && !(options & WILD_KEEP_SOLE_ITEM)) // free expanded pattern (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); diff --git a/src/ex_getln.c b/src/ex_getln.c index 93d612a70..ff1b3eb7b 100644 --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -913,6 +913,8 @@ cmdline_wildchar_complete( if (wim_flags[wim_index] & WIM_BUFLASTUSED) options |= WILD_BUFLASTUSED; + if (wim_flags[0] & WIM_NOSELECT) + options |= WILD_KEEP_SOLE_ITEM; if (xp->xp_numfiles > 0) // typed p_wc at least twice { // if 'wildmode' contains "list" may still need to list @@ -958,14 +960,15 @@ cmdline_wildchar_complete( // when more than one match, and 'wildmode' first contains // "list", or no change and 'wildmode' contains "longest,list", // list all matches - if (res == OK && xp->xp_numfiles > 1) + if (res == OK + && xp->xp_numfiles > ((wim_flags[wim_index] & WIM_NOSELECT) ? 0 : 1)) { // a "longest" that didn't do anything is skipped (but not // "list:longest") if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) wim_index = 1; if ((wim_flags[wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[wim_index] & WIM_FULL) != 0)) + || (p_wmnu && (wim_flags[wim_index] & (WIM_FULL | WIM_NOSELECT)))) { if (!(wim_flags[0] & WIM_LONGEST)) { @@ -974,7 +977,7 @@ cmdline_wildchar_complete( p_wmnu = 0; // remove match - nextwild(xp, WILD_PREV, 0, escape); + nextwild(xp, WILD_PREV, 0 | (options & ~WIM_NOSELECT), escape); p_wmnu = p_wmnu_save; } (void)showmatches(xp, p_wmnu @@ -983,7 +986,8 @@ cmdline_wildchar_complete( *did_wild_list = TRUE; if (wim_flags[wim_index] & WIM_LONGEST) nextwild(xp, WILD_LONGEST, options, escape); - else if (wim_flags[wim_index] & WIM_FULL) + else if ((wim_flags[wim_index] & WIM_FULL) + && !(wim_flags[wim_index] & WIM_NOSELECT)) nextwild(xp, WILD_NEXT, options, escape); } else @@ -2716,6 +2720,8 @@ check_opt_wim(void) new_wim_flags[idx] |= WIM_LIST; else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) new_wim_flags[idx] |= WIM_BUFLASTUSED; + else if (i == 8 && STRNCMP(p, "noselect", 8) == 0) + new_wim_flags[idx] |= WIM_NOSELECT; else return FAIL; p += i; diff --git a/src/option.h b/src/option.h index 70206f3f1..182ab2678 100644 --- a/src/option.h +++ b/src/option.h @@ -369,6 +369,7 @@ typedef enum { #define WIM_LONGEST 0x02 #define WIM_LIST 0x04 #define WIM_BUFLASTUSED 0x08 +#define WIM_NOSELECT 0x10 // 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 f4daabac5..e2970b6e2 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -94,7 +94,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", NULL}; +static char *(p_wim_values[]) = {"full", "longest", "list", "lastused", "noselect", NULL}; static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", NULL}; #ifdef FEAT_WAK static char *(p_wak_values[]) = {"yes", "menu", "no", NULL}; diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim index 1918bdac2..950653245 100644 --- a/src/testdir/gen_opt_test.vim +++ b/src/testdir/gen_opt_test.vim @@ -321,6 +321,7 @@ let test_values = { \ 'bs'], \ ['xxx']], \ 'wildmode': [['', 'full', 'longest', 'list', 'lastused', 'list:full', + \ 'noselect', 'noselect,full', 'noselect:lastused,full', \ 'full,longest', 'full,full,full,full'], \ ['xxx', 'a4', 'full,full,full,full,full']], \ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']], diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim index be4ae4e18..2e9b32bc4 100644 --- a/src/testdir/test_cmdline.vim +++ b/src/testdir/test_cmdline.vim @@ -2170,16 +2170,52 @@ func Wildmode_tests() call assert_equal('AAA AAAA AAAAA', g:Sline) call assert_equal('"b A', @:) + " When 'wildmenu' is not set, 'noselect' completes first item + set wildmode=noselect + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + + " When 'noselect' is present, do not complete first <tab>. + set wildmenu + set wildmode=noselect + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o \<C-Y>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + + " When 'full' is present, complete after first <tab>. + set wildmode=noselect,full + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneB', @:) + call feedkeys(":MyCmd o \<C-Y>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneB', @:) + + " 'noselect' has no effect when 'longest' is present. + set wildmode=noselect:longest + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd one', @:) + + " Complete 'noselect' value in 'wildmode' option + set wildmode& + call feedkeys(":set wildmode=n \<C-B>\"\<CR>", 'xt') + call assert_equal('"set wildmode=noselect', @:) + call feedkeys(":set wildmode= \<C-B>\"\<CR>", 'xt') + call assert_equal('"set wildmode=noselect', @:) + " when using longest completion match, matches shorter than the argument " should be ignored (happens with :help) set wildmode=longest,full - set wildmenu call feedkeys(":help a* \<C-B>\"\<CR>", 'xt') call assert_equal('"help a', @:) " non existing file call feedkeys(":e a1b2y3z4 \<C-B>\"\<CR>", 'xt') call assert_equal('"e a1b2y3z4', @:) - set wildmenu& " Test for longest file name completion with 'fileignorecase' " On MS-Windows, file names are case insensitive. @@ -2199,6 +2235,21 @@ func Wildmode_tests() set fileignorecase& endif + " If 'noselect' is present, single item menu should not insert item + func! T(a, c, p) + return "oneA" + endfunc + command! -nargs=1 -complete=custom,T MyCmd + set wildmode=noselect,full + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + " 'nowildmenu' should make 'noselect' ineffective + set nowildmenu + call feedkeys(":MyCmd o \<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + %argdelete delcommand MyCmd delfunc T diff --git a/src/version.c b/src/version.c index 48d11c4b3..34459ef01 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1166, /**/ 1165, /**/ diff --git a/src/vim.h b/src/vim.h index da2835c1c..212b7e774 100644 --- a/src/vim.h +++ b/src/vim.h @@ -881,6 +881,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring); #define WILD_NOERROR 0x800 // sets EW_NOERROR #define WILD_BUFLASTUSED 0x1000 #define BUF_DIFF_FILTER 0x2000 +#define WILD_KEEP_SOLE_ITEM 0x4000 // Flags for expand_wildcards() #define EW_DIR 0x01 // include directory names -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1toraS-0052Ne-Ly%40256bit.org.