0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

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 <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Girish Palya
2025-03-02 22:55:57 +01:00
committed by Christian Brabandt
parent a250738303
commit 2bacc3e5fb
10 changed files with 86 additions and 12 deletions

View File

@@ -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 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 "lastused" When completing buffer names and more than one buffer
matches, sort buffers by time last used (other than matches, sort buffers by time last used (other than
the current buffer). 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: Examples of useful colon-separated values:
"longest:full" Like "longest", but also start 'wildmenu' if it is "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 :set wildmode=list,full
< List all matches without completing, then each full match > < List all matches without completing, then each full match >
:set wildmode=longest,list :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|. More info here: |cmdline-completion|.
*'wildoptions'* *'wop'* *'wildoptions'* *'wop'*

View File

@@ -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 VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41605,6 +41605,8 @@ Completion: ~
"preinsert" - highlight to be inserted values "preinsert" - highlight to be inserted values
- handle multi-line completion as expected - handle multi-line completion as expected
- improved commandline completion for the |:hi| command - improved commandline completion for the |:hi| command
- New option value for 'wildoptions':
"noselect" - do not auto select an entry in the wildmenu
Options: ~ Options: ~
- the default for 'commentstring' contains whitespace padding to have - the default for 'commentstring' contains whitespace padding to have

View File

@@ -286,6 +286,9 @@ nextwild(
{ {
int use_options = options | int use_options = options |
WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT;
if (use_options & WILD_KEEP_SOLE_ITEM)
use_options &= ~WILD_KEEP_SOLE_ITEM;
if (escape) if (escape)
use_options |= WILD_ESCAPE; use_options |= WILD_ESCAPE;
@@ -340,7 +343,7 @@ nextwild(
if (xp->xp_numfiles <= 0 && p2 == NULL) if (xp->xp_numfiles <= 0 && p2 == NULL)
beep_flush(); beep_flush();
else if (xp->xp_numfiles == 1) else if (xp->xp_numfiles == 1 && !(options & WILD_KEEP_SOLE_ITEM))
// free expanded pattern // free expanded pattern
(void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE);

View File

@@ -913,6 +913,8 @@ cmdline_wildchar_complete(
if (wim_flags[wim_index] & WIM_BUFLASTUSED) if (wim_flags[wim_index] & WIM_BUFLASTUSED)
options |= WILD_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 (xp->xp_numfiles > 0) // typed p_wc at least twice
{ {
// if 'wildmode' contains "list" may still need to list // 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 // when more than one match, and 'wildmode' first contains
// "list", or no change and 'wildmode' contains "longest,list", // "list", or no change and 'wildmode' contains "longest,list",
// list all matches // 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 // a "longest" that didn't do anything is skipped (but not
// "list:longest") // "list:longest")
if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j)
wim_index = 1; wim_index = 1;
if ((wim_flags[wim_index] & WIM_LIST) 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)) if (!(wim_flags[0] & WIM_LONGEST))
{ {
@@ -974,7 +977,7 @@ cmdline_wildchar_complete(
p_wmnu = 0; p_wmnu = 0;
// remove match // remove match
nextwild(xp, WILD_PREV, 0, escape); nextwild(xp, WILD_PREV, 0 | (options & ~WIM_NOSELECT), escape);
p_wmnu = p_wmnu_save; p_wmnu = p_wmnu_save;
} }
(void)showmatches(xp, p_wmnu (void)showmatches(xp, p_wmnu
@@ -983,7 +986,8 @@ cmdline_wildchar_complete(
*did_wild_list = TRUE; *did_wild_list = TRUE;
if (wim_flags[wim_index] & WIM_LONGEST) if (wim_flags[wim_index] & WIM_LONGEST)
nextwild(xp, WILD_LONGEST, options, escape); 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); nextwild(xp, WILD_NEXT, options, escape);
} }
else else
@@ -2716,6 +2720,8 @@ check_opt_wim(void)
new_wim_flags[idx] |= WIM_LIST; new_wim_flags[idx] |= WIM_LIST;
else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) else if (i == 8 && STRNCMP(p, "lastused", 8) == 0)
new_wim_flags[idx] |= WIM_BUFLASTUSED; new_wim_flags[idx] |= WIM_BUFLASTUSED;
else if (i == 8 && STRNCMP(p, "noselect", 8) == 0)
new_wim_flags[idx] |= WIM_NOSELECT;
else else
return FAIL; return FAIL;
p += i; p += i;

View File

@@ -369,6 +369,7 @@ typedef enum {
#define WIM_LONGEST 0x02 #define WIM_LONGEST 0x02
#define WIM_LIST 0x04 #define WIM_LIST 0x04
#define WIM_BUFLASTUSED 0x08 #define WIM_BUFLASTUSED 0x08
#define WIM_NOSELECT 0x10
// flags for the 'wildoptions' option // flags for the 'wildoptions' option
// each defined char should be unique over all values. // each defined char should be unique over all values.

View File

@@ -94,7 +94,7 @@ static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm"
#endif #endif
static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL}; static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL};
// Note: Keep this in sync with check_opt_wim() // 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}; static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", NULL};
#ifdef FEAT_WAK #ifdef FEAT_WAK
static char *(p_wak_values[]) = {"yes", "menu", "no", NULL}; static char *(p_wak_values[]) = {"yes", "menu", "no", NULL};

View File

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

View File

@@ -2170,16 +2170,52 @@ func Wildmode_tests()
call assert_equal('AAA AAAA AAAAA', g:Sline) call assert_equal('AAA AAAA AAAAA', g:Sline)
call assert_equal('"b A', @:) call assert_equal('"b A', @:)
" When 'wildmenu' is not set, 'noselect' completes first item
set wildmode=noselect
call feedkeys(":MyCmd o\t\<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\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
call feedkeys(":MyCmd o\t\t\<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\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneA', @:)
call feedkeys(":MyCmd o\t\t\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneB', @:)
call feedkeys(":MyCmd o\t\t\t\<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\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd one', @:)
" Complete 'noselect' value in 'wildmode' option
set wildmode&
call feedkeys(":set wildmode=n\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"set wildmode=noselect', @:)
call feedkeys(":set wildmode=\t\t\t\t\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"set wildmode=noselect', @:)
" when using longest completion match, matches shorter than the argument " when using longest completion match, matches shorter than the argument
" should be ignored (happens with :help) " should be ignored (happens with :help)
set wildmode=longest,full set wildmode=longest,full
set wildmenu
call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt') call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"help a', @:) call assert_equal('"help a', @:)
" non existing file " non existing file
call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt') call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"e a1b2y3z4', @:) call assert_equal('"e a1b2y3z4', @:)
set wildmenu&
" Test for longest file name completion with 'fileignorecase' " Test for longest file name completion with 'fileignorecase'
" On MS-Windows, file names are case insensitive. " On MS-Windows, file names are case insensitive.
@@ -2199,6 +2235,21 @@ func Wildmode_tests()
set fileignorecase& set fileignorecase&
endif 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\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd o', @:)
call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneA', @:)
" 'nowildmenu' should make 'noselect' ineffective
set nowildmenu
call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"MyCmd oneA', @:)
%argdelete %argdelete
delcommand MyCmd delcommand MyCmd
delfunc T delfunc T

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1166,
/**/ /**/
1165, 1165,
/**/ /**/

View File

@@ -881,6 +881,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
#define WILD_NOERROR 0x800 // sets EW_NOERROR #define WILD_NOERROR 0x800 // sets EW_NOERROR
#define WILD_BUFLASTUSED 0x1000 #define WILD_BUFLASTUSED 0x1000
#define BUF_DIFF_FILTER 0x2000 #define BUF_DIFF_FILTER 0x2000
#define WILD_KEEP_SOLE_ITEM 0x4000
// Flags for expand_wildcards() // Flags for expand_wildcards()
#define EW_DIR 0x01 // include directory names #define EW_DIR 0x01 // include directory names