diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-10-09 08:14:18 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-09 08:14:18 +0800 |
commit | f449a38f6a47bee30f0d4e291d8234d1ac8288a7 (patch) | |
tree | e65db9b696571e64352b16f849b8cc7582183f8c | |
parent | e98b1b0235a5e817c00814549606631703ab2041 (diff) | |
download | rneovim-f449a38f6a47bee30f0d4e291d8234d1ac8288a7.tar.gz rneovim-f449a38f6a47bee30f0d4e291d8234d1ac8288a7.tar.bz2 rneovim-f449a38f6a47bee30f0d4e291d8234d1ac8288a7.zip |
vim-patch:9.1.0770: current command line completion is a bit limited (#30728)
Problem: current command completion is a bit limited
Solution: Add the shellcmdline completion type and getmdcomplpat()
function (Ruslan Russkikh).
closes: vim/vim#15823
https://github.com/vim/vim/commit/0407d621bbad020b840ffbbbd25ba023bbc05edd
Co-authored-by: Ruslan Russkikh <dvrussk@yandex.ru>
-rw-r--r-- | runtime/doc/builtin.txt | 13 | ||||
-rw-r--r-- | runtime/doc/map.txt | 2 | ||||
-rw-r--r-- | runtime/doc/usr_41.txt | 2 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 12 | ||||
-rw-r--r-- | runtime/syntax/vim.vim | 2 | ||||
-rw-r--r-- | src/nvim/cmdexpand.c | 4 | ||||
-rw-r--r-- | src/nvim/cmdexpand_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/eval.lua | 16 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 39 | ||||
-rw-r--r-- | src/nvim/usercmd.c | 7 | ||||
-rw-r--r-- | test/old/testdir/test_cmdline.vim | 86 |
11 files changed, 161 insertions, 23 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 4c726f86d2..617fd87553 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -3224,13 +3224,24 @@ getcharstr([{expr}]) *getcharstr()* Return: ~ (`string`) +getcmdcomplpat() *getcmdcomplpat()* + Return completion pattern of the current command-line. + Only works when the command line is being edited, thus + requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. + Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, + |getcmdprompt()|, |getcmdcompltype()| and |setcmdline()|. + Returns an empty string when completion is not defined. + + Return: ~ + (`string`) + getcmdcompltype() *getcmdcompltype()* Return the type of the current command-line completion. Only works when the command line is being edited, thus requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. See |:command-completion| for the return string. Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, - |getcmdprompt()| and |setcmdline()|. + |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|. Returns an empty string when completion is not defined. Return: ~ diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 4a4e34661f..11048aee30 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -1413,6 +1413,8 @@ completion can be enabled: -complete=runtime file and directory names in |'runtimepath'| -complete=scriptnames sourced script names -complete=shellcmd Shell command + -complete=shellcmdline First is a shell command and subsequent ones + are filenames. The same behavior as |:!cmd| -complete=sign |:sign| suboptions -complete=syntax syntax file names |'syntax'| -complete=syntime |:syntime| suboptions diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 8c7ed875cf..3202a70b76 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -906,6 +906,8 @@ Buffers, windows and the argument list: swapname() get the swap file path of a buffer Command line: *command-line-functions* + getcmdcomplpat() get completion pattern of the current command + line getcmdcompltype() get the type of the current command line completion getcmdline() get the current command line input diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 3f6deba092..1e1e87fca8 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -2879,12 +2879,22 @@ function vim.fn.getcharsearch() end --- @return string function vim.fn.getcharstr(expr) end +--- Return completion pattern of the current command-line. +--- Only works when the command line is being edited, thus +--- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. +--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, +--- |getcmdprompt()|, |getcmdcompltype()| and |setcmdline()|. +--- Returns an empty string when completion is not defined. +--- +--- @return string +function vim.fn.getcmdcomplpat() end + --- Return the type of the current command-line completion. --- Only works when the command line is being edited, thus --- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. --- See |:command-completion| for the return string. --- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, ---- |getcmdprompt()| and |setcmdline()|. +--- |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|. --- Returns an empty string when completion is not defined. --- --- @return string diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 9073c6e7bf..6e38076d35 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -381,7 +381,7 @@ endif syn case ignore syn keyword vimUserCmdAttrKey contained a[ddr] ban[g] bar bu[ffer] com[plete] cou[nt] k[eepscript] n[args] ra[nge] re[gister] " GEN_SYN_VIM: vimUserCmdAttrCmplt, START_STR='syn keyword vimUserCmdAttrCmplt contained', END_STR='' -syn keyword vimUserCmdAttrCmplt contained arglist augroup behave breakpoint buffer color command compiler cscope diff_buffer dir dir_in_path environment event expression file file_in_path filetype function help highlight history keymap locale mapclear mapping menu messages option packadd runtime scriptnames shellcmd sign syntax syntime tag tag_listfiles user var +syn keyword vimUserCmdAttrCmplt contained arglist augroup behave breakpoint buffer color command compiler cscope diff_buffer dir dir_in_path environment event expression file file_in_path filetype function help highlight history keymap locale mapclear mapping menu messages option packadd runtime scriptnames shellcmd shellcmdline sign syntax syntime tag tag_listfiles user var syn keyword vimUserCmdAttrCmplt contained custom customlist nextgroup=vimUserCmdAttrCmpltFunc,vimUserCmdError syn match vimUserCmdAttrCmpltFunc contained ",\%([sS]:\|<[sS][iI][dD]>\)\=\%(\h\w*\%([.#]\h\w*\)\+\|\h\w*\)"hs=s+1 nextgroup=vimUserCmdError " GEN_SYN_VIM: vimUserCmdAttrAddr, START_STR='syn keyword vimUserCmdAttrAddr contained', END_STR='' diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 402a891099..b37a1d690f 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -119,6 +119,7 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) && xp->xp_context != EXPAND_PACKADD && xp->xp_context != EXPAND_RUNTIME && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_SHELLCMDLINE && xp->xp_context != EXPAND_TAGS && xp->xp_context != EXPAND_TAGS_LISTFILES && xp->xp_context != EXPAND_USER_LIST @@ -1527,7 +1528,8 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use xp->xp_context = EXPAND_FILES; // For a shell command more chars need to be escaped. - if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal) { + if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal + || *complp == EXPAND_SHELLCMDLINE) { #ifndef BACKSLASH_IN_FILENAME xp->xp_shell = true; #endif diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h index 3369790151..86725eafd6 100644 --- a/src/nvim/cmdexpand_defs.h +++ b/src/nvim/cmdexpand_defs.h @@ -106,6 +106,7 @@ enum { EXPAND_ARGOPT, EXPAND_KEYMAP, EXPAND_DIRS_IN_CDPATH, + EXPAND_SHELLCMDLINE, EXPAND_CHECKHEALTH, EXPAND_LUA, }; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 50aaf9e03b..24f986ef4e 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -3611,6 +3611,20 @@ M.funcs = { returns = 'string', signature = 'getcharstr([{expr}])', }, + getcmdcomplpat = { + desc = [=[ + Return completion pattern of the current command-line. + Only works when the command line is being edited, thus + requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. + Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, + |getcmdprompt()|, |getcmdcompltype()| and |setcmdline()|. + Returns an empty string when completion is not defined. + ]=], + name = 'getcmdcomplpat', + params = {}, + returns = 'string', + signature = 'getcmdcomplpat()', + }, getcmdcompltype = { desc = [=[ Return the type of the current command-line completion. @@ -3618,7 +3632,7 @@ M.funcs = { requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. See |:command-completion| for the return string. Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, - |getcmdprompt()| and |setcmdline()|. + |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|. Returns an empty string when completion is not defined. ]=], name = 'getcmdcompltype', diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index ca01a158a3..b58a6b16f1 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4086,14 +4086,44 @@ static char *get_cmdline_str(void) return xstrnsave(p->cmdbuff, (size_t)p->cmdlen); } +/// Get the current command-line completion pattern. +static char *get_cmdline_completion_pattern(void) +{ + if (cmdline_star > 0) { + return NULL; + } + + CmdlineInfo *p = get_ccline_ptr(); + if (p == NULL || p->xpc == NULL) { + return NULL; + } + + int xp_context = p->xpc->xp_context; + if (xp_context == EXPAND_NOTHING) { + set_expand_context(p->xpc); + xp_context = p->xpc->xp_context; + p->xpc->xp_context = EXPAND_NOTHING; + } + if (xp_context == EXPAND_UNSUCCESSFUL) { + return NULL; + } + + char *compl_pat = p->xpc->xp_pattern; + if (compl_pat == NULL) { + return NULL; + } + + return xstrdup(compl_pat); +} + /// Get the current command-line completion type. static char *get_cmdline_completion(void) { if (cmdline_star > 0) { return NULL; } - CmdlineInfo *p = get_ccline_ptr(); + CmdlineInfo *p = get_ccline_ptr(); if (p == NULL || p->xpc == NULL) { return NULL; } @@ -4123,6 +4153,13 @@ static char *get_cmdline_completion(void) return xstrdup(cmd_compl); } +/// "getcmdcomplpat()" function +void f_getcmdcomplpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_cmdline_completion_pattern(); +} + /// "getcmdcompltype()" function void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index d32e0ee319..8404b2bc14 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -91,6 +91,7 @@ static const char *command_complete[] = { [EXPAND_PACKADD] = "packadd", [EXPAND_RUNTIME] = "runtime", [EXPAND_SHELLCMD] = "shellcmd", + [EXPAND_SHELLCMDLINE] = "shellcmdline", [EXPAND_SIGN] = "sign", [EXPAND_TAGS] = "tag", [EXPAND_TAGS_LISTFILES] = "tag_listfiles", @@ -285,8 +286,7 @@ const char *set_context_in_user_cmdarg(const char *cmd FUNC_ATTR_UNUSED, const c } if (argt & EX_XFILE) { - // EX_XFILE: file names are handled above. - xp->xp_context = context; + // EX_XFILE: file names are handled before this call. return NULL; } @@ -675,7 +675,8 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, *complp = i; if (i == EXPAND_BUFFERS) { *argt |= EX_BUFNAME; - } else if (i == EXPAND_DIRECTORIES || i == EXPAND_FILES) { + } else if (i == EXPAND_DIRECTORIES || i == EXPAND_FILES + || i == EXPAND_SHELLCMDLINE) { *argt |= EX_XFILE; } break; diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index b3179686b1..a5df637cc2 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -1012,8 +1012,7 @@ func Test_cmdline_complete_user_names() call feedkeys(':e ~' . first_letter . "\<c-a>\<c-B>\"\<cr>", 'tx') call assert_match('^"e \~.*\<' . whoami . '\>', @:) endif - endif - if has('win32') + elseif has('win32') " Just in case: check that the system has an Administrator account. let names = system('net user') if names =~ 'Administrator' @@ -1022,14 +1021,25 @@ func Test_cmdline_complete_user_names() call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx') call assert_match('^"e \~.*Administrator', @:) endif + else + throw 'Skipped: does not work on this platform' endif endfunc +func Test_cmdline_complete_shellcmdline() + CheckExecutable whoami + command -nargs=1 -complete=shellcmdline MyCmd + + call feedkeys(":MyCmd whoam\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^".*\<whoami\>', @:) + + delcommand MyCmd +endfunc + func Test_cmdline_complete_bang() - if executable('whoami') - call feedkeys(":!whoam\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^".*\<whoami\>', @:) - endif + CheckExecutable whoami + call feedkeys(":!whoam\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^".*\<whoami\>', @:) endfunc func Test_cmdline_complete_languages() @@ -3800,6 +3810,52 @@ func Test_cmdline_complete_substitute_short() endfor endfunc +" Test for shellcmdline command argument completion +func Test_cmdline_complete_shellcmdline_argument() + command -nargs=+ -complete=shellcmdline MyCmd + + set wildoptions=fuzzy + + call feedkeys(":MyCmd vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim test_cmdline.vim', @:) + + call feedkeys(":MyCmd vim nonexistentfile\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim nonexistentfile', @:) + + let compl1 = getcompletion('', 'file')[0] + let compl2 = getcompletion('', 'file')[1] + call feedkeys(":MyCmd vim \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim ' .. compl1, @:) + + call feedkeys(":MyCmd vim \<Tab> \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl1, @:) + + let compl = getcompletion('', 'file')[1] + call feedkeys(":MyCmd vim \<Tab> \<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl2, @:) + + set wildoptions& + call feedkeys(":MyCmd vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim test_cmdline.vim', @:) + + call feedkeys(":MyCmd vim nonexistentfile\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim nonexistentfile', @:) + + let compl1 = getcompletion('', 'file')[0] + let compl2 = getcompletion('', 'file')[1] + call feedkeys(":MyCmd vim \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim ' .. compl1, @:) + + call feedkeys(":MyCmd vim \<Tab> \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl1, @:) + + let compl = getcompletion('', 'file')[1] + call feedkeys(":MyCmd vim \<Tab> \<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl2, @:) + + delcommand MyCmd +endfunc + " Test for :! shell command argument completion func Test_cmdline_complete_bang_cmd_argument() set wildoptions=fuzzy @@ -3811,30 +3867,32 @@ func Test_cmdline_complete_bang_cmd_argument() endfunc func Call_cmd_funcs() - return [getcmdpos(), getcmdscreenpos(), getcmdcompltype()] + return [getcmdpos(), getcmdscreenpos(), getcmdcompltype(), getcmdcomplpat()] endfunc func Test_screenpos_and_completion() call assert_equal(0, getcmdpos()) call assert_equal(0, getcmdscreenpos()) call assert_equal('', getcmdcompltype()) + call assert_equal('', getcmdcomplpat()) cnoremap <expr> <F2> string(Call_cmd_funcs()) call feedkeys(":let a\<F2>\<C-B>\"\<CR>", "xt") - call assert_equal("\"let a[6, 7, 'var']", @:) + call assert_equal("\"let a[6, 7, 'var', 'a']", @:) call feedkeys(":quit \<F2>\<C-B>\"\<CR>", "xt") - call assert_equal("\"quit [6, 7, '']", @:) + call assert_equal("\"quit [6, 7, '', '']", @:) call feedkeys(":nosuchcommand \<F2>\<C-B>\"\<CR>", "xt") - call assert_equal("\"nosuchcommand [15, 16, '']", @:) + call assert_equal("\"nosuchcommand [15, 16, '', '']", @:) - " Check that getcmdcompltype() doesn't interfere with cmdline completion. + " Check that getcmdcompltype() and getcmdcomplpat() don't interfere with + " cmdline completion. let g:results = [] cnoremap <F2> <Cmd>let g:results += [[getcmdline()] + Call_cmd_funcs()]<CR> call feedkeys(":sign un\<Tab>\<F2>\<Tab>\<F2>\<Tab>\<F2>\<C-C>", "xt") call assert_equal([ - \ ['sign undefine', 14, 15, 'sign'], - \ ['sign unplace', 13, 14, 'sign'], - \ ['sign un', 8, 9, 'sign']], g:results) + \ ['sign undefine', 14, 15, 'sign', 'undefine'], + \ ['sign unplace', 13, 14, 'sign', 'unplace'], + \ ['sign un', 8, 9, 'sign', 'un']], g:results) unlet g:results cunmap <F2> |