diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index e2cc1eef59..3a1581b2b4 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -451,6 +451,8 @@ When repeating 'wildchar' or CTRL-N you cycle through the matches, eventually ending up back to what was typed. If the first match is not what you wanted, you can use or CTRL-P to go straight back to what you typed. +See also |wildtrigger()|. + The 'wildmenu' option can be set to show the matches just above the command line. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index a73b02fefa..0f02d5b586 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -296,6 +296,7 @@ VIMSCRIPT • |cmdcomplete_info()| gets current cmdline completion info. • |getcompletiontype()| gets command-line completion type for any string. • |prompt_getinput()| gets current user-input in prompt-buffer. +• |wildtrigger()| triggers command-line expansion. ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 1c2edae3cf..9f4f8e7274 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -7212,7 +7212,7 @@ A jump table for the options with a short description can be found at |Q_op|. < 'wildchar' also enables completion in search pattern contexts such as |/|, |?|, |:s|, |:g|, |:v|, and |:vim|. To insert a literal instead of triggering completion, type or "\t". - See also |'wildoptions'|. + See also 'wildoptions' and |wildtrigger()|. *'wildcharm'* *'wcm'* 'wildcharm' 'wcm' number (default 0) diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 1924198662..0657b5fefe 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1036,6 +1036,7 @@ Mappings and Menus: *mapping-functions* mapset() restore a mapping menu_info() get information about a menu item wildmenumode() check if the wildmode is active + wildtrigger() start wildcard expansion Signs: *sign-functions* sign_define() define or update a sign diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index 9bae87e29b..34903d7f70 100644 --- a/runtime/doc/vimfn.txt +++ b/runtime/doc/vimfn.txt @@ -11878,6 +11878,33 @@ wildmenumode() *wildmenumode()* Return: ~ (`any`) +wildtrigger() *wildtrigger()* + Start wildcard expansion in the command-line, using the + behavior defined by the 'wildmode' and 'wildoptions' settings. + See |cmdline-completion|. + + This function also enables completion in search patterns such + as |/|, |?|, |:s|, |:g|, |:v| and |:vimgrep|. + + Unlike pressing 'wildchar' manually, this function does not + produce a beep when no matches are found and generally + operates more quietly. This makes it suitable for triggering + completion automatically, such as from an |:autocmd|. + *cmdline-autocompletion* + Example: To make the completion menu pop up automatically as + you type on the command line, use: >vim + autocmd CmdlineChanged [:/?] call wildtrigger() + set wildmode=noselect:lastused,full wildoptions=pum +< + To retain normal history navigation (up/down keys): >vim + cnoremap + cnoremap +< + Return value is always 0. + + Return: ~ + (`number`) + win_execute({id}, {command} [, {silent}]) *win_execute()* Like `execute()` but in the context of window {id}. The window will temporarily be made the current window, diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index c80201e6e3..354e223cc8 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -7865,7 +7865,7 @@ vim.go.ww = vim.go.whichwrap --- 'wildchar' also enables completion in search pattern contexts such as --- `/`, `?`, `:s`, `:g`, `:v`, and `:vim`. To insert a literal --- instead of triggering completion, type or "\t". ---- See also `'wildoptions'`. +--- See also 'wildoptions' and `wildtrigger()`. --- --- @type integer vim.o.wildchar = 9 diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 490a414728..a8d986c48c 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -10813,6 +10813,32 @@ function vim.fn.wait(timeout, condition, interval) end --- @return any function vim.fn.wildmenumode() end +--- Start wildcard expansion in the command-line, using the +--- behavior defined by the 'wildmode' and 'wildoptions' settings. +--- See |cmdline-completion|. +--- +--- This function also enables completion in search patterns such +--- as |/|, |?|, |:s|, |:g|, |:v| and |:vimgrep|. +--- +--- Unlike pressing 'wildchar' manually, this function does not +--- produce a beep when no matches are found and generally +--- operates more quietly. This makes it suitable for triggering +--- completion automatically, such as from an |:autocmd|. +--- *cmdline-autocompletion* +--- Example: To make the completion menu pop up automatically as +--- you type on the command line, use: >vim +--- autocmd CmdlineChanged [:/?] call wildtrigger() +--- set wildmode=noselect:lastused,full wildoptions=pum +--- < +--- To retain normal history navigation (up/down keys): >vim +--- cnoremap +--- cnoremap +--- < +--- Return value is always 0. +--- +--- @return number +function vim.fn.wildtrigger() end + --- Like `execute()` but in the context of window {id}. --- The window will temporarily be made the current window, --- without triggering autocommands or changing directory. When diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 1b6a066334..244554bdb7 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -99,7 +99,7 @@ syn match vimUserAutoEvent contained "\<\h\w*\>" skipwhite nextgroup=vimUserAuto " Highlight commonly used Groupnames {{{2 " GEN_SYN_VIM: vimGroup, START_STR='syn keyword vimGroup contained', END_STR='' -syn keyword vimGroup contained Added Boolean Changed Character Comment Conditional Constant Debug Define Delimiter Error Exception Float Function Identifier Ignore Include Keyword Label Macro Number Operator PreCondit PreProc Removed Repeat Special SpecialChar SpecialComment Statement StorageClass String Structure Tag Todo Type Typedef Underlined +syn keyword vimGroup contained Added Bold BoldItalic Boolean Changed Character Comment Conditional Constant Debug Define Delimiter Error Exception Float Function Identifier Ignore Include Italic Keyword Label Macro Number Operator PreCondit PreProc Removed Repeat Special SpecialChar SpecialComment Statement StorageClass String Structure Tag Todo Type Typedef Underlined " Default highlighting groups {{{2 " GEN_SYN_VIM: vimHLGroup, START_STR='syn keyword vimHLGroup contained', END_STR='' diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 1ece28c693..a0cef9d23d 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -254,6 +254,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) { CmdlineInfo *const ccline = get_cmdline_info(); char *p; + bool from_wildtrigger_func = options & WILD_FUNC_TRIGGER; if (xp->xp_numfiles == -1) { pre_incsearch_pos = xp->xp_pre_incsearch_pos; @@ -280,17 +281,24 @@ int nextwild(expand_T *xp, int type, int options, bool escape) return FAIL; } - // If cmd_silent is set then don't show the dots, because redrawcmd() below - // won't remove them. - if (!cmd_silent && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { - msg_puts("..."); // show that we are busy - ui_flush(); - } - int i = (int)(xp->xp_pattern - ccline->cmdbuff); assert(ccline->cmdpos >= i); xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; + // Skip showing matches if prefix is invalid during wildtrigger() + if (from_wildtrigger_func && xp->xp_context == EXPAND_COMMANDS + && xp->xp_pattern_len == 0) { + return FAIL; + } + + // If cmd_silent is set then don't show the dots, because redrawcmd() below + // won't remove them. + if (!cmd_silent && !from_wildtrigger_func + && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { + msg_puts("..."); // show that we are busy + ui_flush(); + } + if (type == WILD_NEXT || type == WILD_PREV || type == WILD_PAGEUP || type == WILD_PAGEDOWN || type == WILD_PUM_WANT) { diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h index 9f6825f4d6..69f17e51a5 100644 --- a/src/nvim/cmdexpand.h +++ b/src/nvim/cmdexpand.h @@ -42,6 +42,7 @@ enum { BUF_DIFF_FILTER = 0x2000, WILD_KEEP_SOLE_ITEM = 0x4000, WILD_MAY_EXPAND_PATTERN = 0x8000, + WILD_FUNC_TRIGGER = 0x10000, ///< called from wildtrigger() }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 5b3606c416..bc878e0a8f 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -13069,6 +13069,36 @@ M.funcs = { params = {}, signature = 'wildmenumode()', }, + wildtrigger = { + desc = [==[ + Start wildcard expansion in the command-line, using the + behavior defined by the 'wildmode' and 'wildoptions' settings. + See |cmdline-completion|. + + This function also enables completion in search patterns such + as |/|, |?|, |:s|, |:g|, |:v| and |:vimgrep|. + + Unlike pressing 'wildchar' manually, this function does not + produce a beep when no matches are found and generally + operates more quietly. This makes it suitable for triggering + completion automatically, such as from an |:autocmd|. + *cmdline-autocompletion* + Example: To make the completion menu pop up automatically as + you type on the command line, use: >vim + autocmd CmdlineChanged [:/?] call wildtrigger() + set wildmode=noselect:lastused,full wildoptions=pum + < + To retain normal history navigation (up/down keys): >vim + cnoremap + cnoremap + < + Return value is always 0. + ]==], + name = 'wildtrigger', + params = {}, + returns = 'number', + signature = 'wildtrigger()', + }, win_execute = { args = { 2, 3 }, base = 2, diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index f74d71eb1d..d36d1b1818 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1147,8 +1147,11 @@ static int command_line_wildchar_complete(CommandLineState *s) res = OK; // don't insert 'wildchar' now } } else { // typed p_wc first time - if (s->c == p_wc || s->c == p_wcm) { + if (s->c == p_wc || s->c == p_wcm || s->c == K_WILD || s->c == Ctrl_Z) { options |= WILD_MAY_EXPAND_PATTERN; + if (s->c == K_WILD) { + options |= WILD_FUNC_TRIGGER; + } s->xpc.xp_pre_incsearch_pos = s->is_state.search_start; } s->wim_index = 0; @@ -1442,11 +1445,22 @@ static int command_line_execute(VimState *state, int key) } } - // Completion for 'wildchar' or 'wildcharm' key. - if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm || s->c == Ctrl_Z) { - if (command_line_wildchar_complete(s) == CMDLINE_CHANGED) { + // Completion for 'wildchar', 'wildcharm', and wildtrigger() + if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm || s->c == K_WILD + || s->c == Ctrl_Z) { + if (s->c == K_WILD) { + emsg_silent++; // Silence the bell + } + int res = command_line_wildchar_complete(s); + if (s->c == K_WILD) { + emsg_silent--; + } + if (res == CMDLINE_CHANGED) { return command_line_changed(s); } + if (s->c == K_WILD) { + return command_line_not_changed(s); + } } s->gotesc = false; @@ -4907,3 +4921,27 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const need_wait_return = false; msg_didout = false; } + +/// "wildtrigger()" function +void f_wildtrigger(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (!(State & MODE_CMDLINE) || char_avail() + || (wild_menu_showing != 0 && wild_menu_showing != WM_LIST) + || cmdline_pum_active()) { + return; + } + + int cmd_type = get_cmdline_type(); + + if (cmd_type == ':' || cmd_type == '/' || cmd_type == '?') { + // Add K_WILD as a single special key + uint8_t key_string[4]; + key_string[0] = K_SPECIAL; + key_string[1] = KS_EXTRA; + key_string[2] = KE_WILD; + key_string[3] = NUL; + + // Insert it into the typeahead buffer + ins_typebuf((char *)key_string, REMAP_NONE, 0, true, false); + } +} diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h index e24e30e7d0..66b43909d8 100644 --- a/src/nvim/keycodes.h +++ b/src/nvim/keycodes.h @@ -215,10 +215,15 @@ enum key_extra { // KE_FOCUSGAINED = 98, // focus gained // KE_FOCUSLOST = 99, // focus lost KE_MOUSEMOVE = 100, // mouse moved with no button down - // KE_CANCEL = 101, // return from vgetc() + // KE_MOUSEMOVE_XY = 101, + // KE_CANCEL = 102, // return from vgetc() KE_EVENT = 102, // event KE_LUA = 103, // Lua special key KE_COMMAND = 104, // special key + // KE_S_BS = 105, + // KE_SID = 106, + // KE_ESC = 107, + KE_WILD = 108, // triggers wildmode completion }; // the three byte codes are replaced with the following int when using vgetc() @@ -451,6 +456,8 @@ enum key_extra { #define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) #define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA) +#define K_WILD TERMCAP2KEY(KS_EXTRA, KE_WILD) + // Bits for modifier mask // 0x01 cannot be used, because the modifier must be 0x02 or higher #define MOD_MASK_SHIFT 0x02 diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 843bbcd7cc..ac01b75ffb 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -10173,7 +10173,7 @@ local options = { < 'wildchar' also enables completion in search pattern contexts such as |/|, |?|, |:s|, |:g|, |:v|, and |:vim|. To insert a literal instead of triggering completion, type or "\t". - See also |'wildoptions'|. + See also 'wildoptions' and |wildtrigger()|. ]=], full_name = 'wildchar', scope = { 'global' }, diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index a49603311e..8b63184abd 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -4361,42 +4361,64 @@ func Test_cmdcomplete_info() autocmd CmdlineLeavePre * call expand('test_cmdline.*') autocmd CmdlineLeavePre * let g:cmdcomplete_info = string(cmdcomplete_info()) augroup END - new - call assert_equal({}, cmdcomplete_info()) - call feedkeys(":h echom\", "tx") " No expansion - call assert_equal('{}', g:cmdcomplete_info) - call feedkeys(":h echoms\\", "tx") - call assert_equal('{''cmdline_orig'': '''', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) - call feedkeys(":h echom\\", "tx") - call assert_equal( - \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', - \ g:cmdcomplete_info) - call feedkeys(":h echom\\\", "tx") - call assert_equal( - \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 1}', - \ g:cmdcomplete_info) - call feedkeys(":h echom\\\\", "tx") - call assert_equal( - \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': -1}', - \ g:cmdcomplete_info) - set wildoptions=pum - call feedkeys(":h echoms\\", "tx") - call assert_equal('{''cmdline_orig'': '''', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) - call feedkeys(":h echom\\", "tx") - call assert_equal( - \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', - \ g:cmdcomplete_info) - call feedkeys(":h echom\\\", "tx") - call assert_equal( - \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': 1}', - \ g:cmdcomplete_info) - call feedkeys(":h echom\\\\", "tx") - call assert_equal( - \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': -1}', - \ g:cmdcomplete_info) - bw! - set wildoptions& + " Disable char_avail so that wildtrigger() does not bail out + call Ntest_override("char_avail", 1) + + cnoremap =wildtrigger()[-1] + + call assert_equal({}, cmdcomplete_info()) + + for trig in ["\", "\"] + new + call assert_equal({}, cmdcomplete_info()) + call feedkeys(":h echom\", "tx") " No expansion + call assert_equal('{}', g:cmdcomplete_info) + call feedkeys($":h echoms{trig}\", "tx") + call assert_equal('{''cmdline_orig'': '''', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) + call feedkeys($":h echom{trig}\", "tx") + call assert_equal( + \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', + \ g:cmdcomplete_info) + call feedkeys($":h echom{trig}\\", "tx") + call assert_equal( + \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 1}', + \ g:cmdcomplete_info) + call feedkeys($":h echom{trig}\\\", "tx") + call assert_equal( + \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': -1}', + \ g:cmdcomplete_info) + + set wildoptions=pum + call feedkeys($":h echoms{trig}\", "tx") + call assert_equal('{''cmdline_orig'': '''', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) + call feedkeys($":h echom{trig}\", "tx") + call assert_equal( + \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', + \ g:cmdcomplete_info) + call feedkeys($":h echom{trig}\\", "tx") + call assert_equal( + \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': 1}', + \ g:cmdcomplete_info) + call feedkeys($":h echom{trig}\\\", "tx") + call assert_equal( + \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': -1}', + \ g:cmdcomplete_info) + bw! + " set wildoptions& + set wildoptions= " Accommodate Nvim default + endfor + + " wildtrigger() should not show matches when prefix is invalid + for pat in ["", " ", "22"] + call feedkeys($":{pat}\\", "tx") " No expansion + call assert_equal('{}', g:cmdcomplete_info) + endfor + + augroup test_CmdlineLeavePre | autocmd! | augroup END + call Ntest_override("char_avail", 0) + unlet g:cmdcomplete_info + cunmap endfunc " Test wildcharm completion for '/' and '?' search @@ -4414,6 +4436,7 @@ func Test_search_complete() new cnoremap GetComplInfo() + cnoremap =wildtrigger()[-1] " Pressing inserts tab character set wildchar=0 @@ -4424,7 +4447,7 @@ func Test_search_complete() call setline(1, ['the', 'these', 'thethe', 'thethere', 'foobar']) - for trig in ["\", "\"] + for trig in ["\", "\", "\"] " Test menu first item and order call feedkeys($"gg2j/t{trig}\", 'tx') call assert_equal(['the', 'thethere', 'there', 'these', 'thethe'], g:compl_info.matches) @@ -4637,10 +4660,11 @@ func Test_range_complete() endfunc new cnoremap GetComplInfo() + cnoremap =wildtrigger()[-1] call setline(1, ['ab', 'ba', 'ca', 'af']) - for trig in ["\", "\"] + for trig in ["\", "\", "\"] call feedkeys($":%s/a{trig}\", 'xt') call assert_equal(['ab', 'a', 'af'], g:compl_info.matches) " call feedkeys($":vim9cmd :%s/a{trig}\", 'xt') @@ -4727,25 +4751,35 @@ func Test_cmdline_changed() autocmd CmdlineChanged * if getcmdline() =~ g:cmdprefix | let g:cmdchg_count += 1 | endif augroup END + " Disable char_avail so that wildtrigger() does not bail out + call Ntest_override("char_avail", 1) + new + cnoremap =wildtrigger()[-1] set wildmenu set wildmode=full let g:cmdprefix = 'echomsg' - let g:cmdchg_count = 0 - call feedkeys(":echomsg\", "tx") - call assert_equal(1, g:cmdchg_count) " once only for 'g', not again for + for trig in ["\", "\"] + let g:cmdchg_count = 0 + call feedkeys($":echomsg{trig}", "tx") + call assert_equal(1, g:cmdchg_count) " once only for 'g', not again for + endfor - let g:cmdchg_count = 0 let g:cmdprefix = 'echo' - call feedkeys(":ech\", "tx") - call assert_equal(1, g:cmdchg_count) " (once for 'h' and) once for 'o' + for trig in ["\", "\"] + let g:cmdchg_count = 0 + call feedkeys($":ech{trig}", "tx") + call assert_equal(1, g:cmdchg_count) " (once for 'h' and) once for 'o' + endfor set wildmode=noselect,full - let g:cmdchg_count = 0 let g:cmdprefix = 'ech' - call feedkeys(":ech\", "tx") - call assert_equal(1, g:cmdchg_count) " once for 'h', not again for + for trig in ["\", "\"] + let g:cmdchg_count = 0 + call feedkeys($":ech{trig}", "tx") + call assert_equal(1, g:cmdchg_count) " once for 'h', not again for + endfor command! -nargs=+ -complete=custom,TestComplete Test echo @@ -4754,10 +4788,12 @@ func Test_cmdline_changed() endfunc set wildoptions=fuzzy wildmode=full - let g:cmdchg_count = 0 let g:cmdprefix = 'Test \(AbC\|abc\)' - call feedkeys(":Test abc\", "tx") - call assert_equal(2, g:cmdchg_count) " once for 'c', again for 'AbC' + for trig in ["\", "\"] + let g:cmdchg_count = 0 + call feedkeys($":Test abc{trig}", "tx") + call assert_equal(2, g:cmdchg_count) " once for 'c', again for 'AbC' + endfor bw! set wildmode& wildmenu& wildoptions& @@ -4766,6 +4802,7 @@ func Test_cmdline_changed() unlet g:cmdprefix delfunc TestComplete delcommand Test + call Ntest_override("char_avail", 0) endfunc " vim: shiftwidth=2 sts=2 expandtab