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

0407d621bb

Co-authored-by: Ruslan Russkikh <dvrussk@yandex.ru>
This commit is contained in:
zeertzjq 2024-10-09 08:14:18 +08:00 committed by GitHub
parent e98b1b0235
commit f449a38f6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 161 additions and 23 deletions

View File

@ -3224,13 +3224,24 @@ getcharstr([{expr}]) *getcharstr()*
Return: ~ Return: ~
(`string`) (`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()* getcmdcompltype() *getcmdcompltype()*
Return the type of the current command-line completion. Return the type of the current command-line completion.
Only works when the command line is being edited, thus Only works when the command line is being edited, thus
requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
See |:command-completion| for the return string. See |:command-completion| for the return string.
Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|,
|getcmdprompt()| and |setcmdline()|. |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|.
Returns an empty string when completion is not defined. Returns an empty string when completion is not defined.
Return: ~ Return: ~

View File

@ -1413,6 +1413,8 @@ completion can be enabled:
-complete=runtime file and directory names in |'runtimepath'| -complete=runtime file and directory names in |'runtimepath'|
-complete=scriptnames sourced script names -complete=scriptnames sourced script names
-complete=shellcmd Shell command -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=sign |:sign| suboptions
-complete=syntax syntax file names |'syntax'| -complete=syntax syntax file names |'syntax'|
-complete=syntime |:syntime| suboptions -complete=syntime |:syntime| suboptions

View File

@ -906,6 +906,8 @@ Buffers, windows and the argument list:
swapname() get the swap file path of a buffer swapname() get the swap file path of a buffer
Command line: *command-line-functions* Command line: *command-line-functions*
getcmdcomplpat() get completion pattern of the current command
line
getcmdcompltype() get the type of the current command line getcmdcompltype() get the type of the current command line
completion completion
getcmdline() get the current command line input getcmdline() get the current command line input

View File

@ -2879,12 +2879,22 @@ function vim.fn.getcharsearch() end
--- @return string --- @return string
function vim.fn.getcharstr(expr) end 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. --- Return the type of the current command-line completion.
--- Only works when the command line is being edited, thus --- Only works when the command line is being edited, thus
--- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. --- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
--- See |:command-completion| for the return string. --- See |:command-completion| for the return string.
--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, --- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|,
--- |getcmdprompt()| and |setcmdline()|. --- |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|.
--- Returns an empty string when completion is not defined. --- Returns an empty string when completion is not defined.
--- ---
--- @return string --- @return string

View File

@ -381,7 +381,7 @@ endif
syn case ignore 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] 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='' " 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 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 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='' " GEN_SYN_VIM: vimUserCmdAttrAddr, START_STR='syn keyword vimUserCmdAttrAddr contained', END_STR=''

View File

@ -119,6 +119,7 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp)
&& xp->xp_context != EXPAND_PACKADD && xp->xp_context != EXPAND_PACKADD
&& xp->xp_context != EXPAND_RUNTIME && xp->xp_context != EXPAND_RUNTIME
&& xp->xp_context != EXPAND_SHELLCMD && xp->xp_context != EXPAND_SHELLCMD
&& xp->xp_context != EXPAND_SHELLCMDLINE
&& xp->xp_context != EXPAND_TAGS && xp->xp_context != EXPAND_TAGS
&& xp->xp_context != EXPAND_TAGS_LISTFILES && xp->xp_context != EXPAND_TAGS_LISTFILES
&& xp->xp_context != EXPAND_USER_LIST && 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; xp->xp_context = EXPAND_FILES;
// For a shell command more chars need to be escaped. // 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 #ifndef BACKSLASH_IN_FILENAME
xp->xp_shell = true; xp->xp_shell = true;
#endif #endif

View File

@ -106,6 +106,7 @@ enum {
EXPAND_ARGOPT, EXPAND_ARGOPT,
EXPAND_KEYMAP, EXPAND_KEYMAP,
EXPAND_DIRS_IN_CDPATH, EXPAND_DIRS_IN_CDPATH,
EXPAND_SHELLCMDLINE,
EXPAND_CHECKHEALTH, EXPAND_CHECKHEALTH,
EXPAND_LUA, EXPAND_LUA,
}; };

View File

@ -3611,6 +3611,20 @@ M.funcs = {
returns = 'string', returns = 'string',
signature = 'getcharstr([{expr}])', 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 = { getcmdcompltype = {
desc = [=[ desc = [=[
Return the type of the current command-line completion. 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_=|. requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
See |:command-completion| for the return string. See |:command-completion| for the return string.
Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|,
|getcmdprompt()| and |setcmdline()|. |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|.
Returns an empty string when completion is not defined. Returns an empty string when completion is not defined.
]=], ]=],
name = 'getcmdcompltype', name = 'getcmdcompltype',

View File

@ -4086,14 +4086,44 @@ static char *get_cmdline_str(void)
return xstrnsave(p->cmdbuff, (size_t)p->cmdlen); 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. /// Get the current command-line completion type.
static char *get_cmdline_completion(void) static char *get_cmdline_completion(void)
{ {
if (cmdline_star > 0) { if (cmdline_star > 0) {
return NULL; return NULL;
} }
CmdlineInfo *p = get_ccline_ptr();
CmdlineInfo *p = get_ccline_ptr();
if (p == NULL || p->xpc == NULL) { if (p == NULL || p->xpc == NULL) {
return NULL; return NULL;
} }
@ -4123,6 +4153,13 @@ static char *get_cmdline_completion(void)
return xstrdup(cmd_compl); 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 /// "getcmdcompltype()" function
void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{ {

View File

@ -91,6 +91,7 @@ static const char *command_complete[] = {
[EXPAND_PACKADD] = "packadd", [EXPAND_PACKADD] = "packadd",
[EXPAND_RUNTIME] = "runtime", [EXPAND_RUNTIME] = "runtime",
[EXPAND_SHELLCMD] = "shellcmd", [EXPAND_SHELLCMD] = "shellcmd",
[EXPAND_SHELLCMDLINE] = "shellcmdline",
[EXPAND_SIGN] = "sign", [EXPAND_SIGN] = "sign",
[EXPAND_TAGS] = "tag", [EXPAND_TAGS] = "tag",
[EXPAND_TAGS_LISTFILES] = "tag_listfiles", [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) { if (argt & EX_XFILE) {
// EX_XFILE: file names are handled above. // EX_XFILE: file names are handled before this call.
xp->xp_context = context;
return NULL; return NULL;
} }
@ -675,7 +675,8 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt,
*complp = i; *complp = i;
if (i == EXPAND_BUFFERS) { if (i == EXPAND_BUFFERS) {
*argt |= EX_BUFNAME; *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; *argt |= EX_XFILE;
} }
break; break;

View File

@ -1012,8 +1012,7 @@ func Test_cmdline_complete_user_names()
call feedkeys(':e ~' . first_letter . "\<c-a>\<c-B>\"\<cr>", 'tx') call feedkeys(':e ~' . first_letter . "\<c-a>\<c-B>\"\<cr>", 'tx')
call assert_match('^"e \~.*\<' . whoami . '\>', @:) call assert_match('^"e \~.*\<' . whoami . '\>', @:)
endif endif
endif elseif has('win32')
if has('win32')
" Just in case: check that the system has an Administrator account. " Just in case: check that the system has an Administrator account.
let names = system('net user') let names = system('net user')
if names =~ 'Administrator' if names =~ 'Administrator'
@ -1022,14 +1021,25 @@ func Test_cmdline_complete_user_names()
call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx') call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx')
call assert_match('^"e \~.*Administrator', @:) call assert_match('^"e \~.*Administrator', @:)
endif endif
else
throw 'Skipped: does not work on this platform'
endif endif
endfunc 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() func Test_cmdline_complete_bang()
if executable('whoami') CheckExecutable whoami
call feedkeys(":!whoam\<C-A>\<C-B>\"\<CR>", 'tx') call feedkeys(":!whoam\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_match('^".*\<whoami\>', @:) call assert_match('^".*\<whoami\>', @:)
endif
endfunc endfunc
func Test_cmdline_complete_languages() func Test_cmdline_complete_languages()
@ -3800,6 +3810,52 @@ func Test_cmdline_complete_substitute_short()
endfor endfor
endfunc 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 " Test for :! shell command argument completion
func Test_cmdline_complete_bang_cmd_argument() func Test_cmdline_complete_bang_cmd_argument()
set wildoptions=fuzzy set wildoptions=fuzzy
@ -3811,30 +3867,32 @@ func Test_cmdline_complete_bang_cmd_argument()
endfunc endfunc
func Call_cmd_funcs() func Call_cmd_funcs()
return [getcmdpos(), getcmdscreenpos(), getcmdcompltype()] return [getcmdpos(), getcmdscreenpos(), getcmdcompltype(), getcmdcomplpat()]
endfunc endfunc
func Test_screenpos_and_completion() func Test_screenpos_and_completion()
call assert_equal(0, getcmdpos()) call assert_equal(0, getcmdpos())
call assert_equal(0, getcmdscreenpos()) call assert_equal(0, getcmdscreenpos())
call assert_equal('', getcmdcompltype()) call assert_equal('', getcmdcompltype())
call assert_equal('', getcmdcomplpat())
cnoremap <expr> <F2> string(Call_cmd_funcs()) cnoremap <expr> <F2> string(Call_cmd_funcs())
call feedkeys(":let a\<F2>\<C-B>\"\<CR>", "xt") 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 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 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 = [] let g:results = []
cnoremap <F2> <Cmd>let g:results += [[getcmdline()] + Call_cmd_funcs()]<CR> 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 feedkeys(":sign un\<Tab>\<F2>\<Tab>\<F2>\<Tab>\<F2>\<C-C>", "xt")
call assert_equal([ call assert_equal([
\ ['sign undefine', 14, 15, 'sign'], \ ['sign undefine', 14, 15, 'sign', 'undefine'],
\ ['sign unplace', 13, 14, 'sign'], \ ['sign unplace', 13, 14, 'sign', 'unplace'],
\ ['sign un', 8, 9, 'sign']], g:results) \ ['sign un', 8, 9, 'sign', 'un']], g:results)
unlet g:results unlet g:results
cunmap <F2> cunmap <F2>