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

patch 9.0.2025: no cmdline completion for ++opt args

Problem:  no cmdline completion for ++opt args
Solution: Add cmdline completion for :e ++opt=arg and :terminal
          [++options]

closes: #13319

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
This commit is contained in:
Yee Cheng Chin
2023-10-14 11:46:51 +02:00
committed by Christian Brabandt
parent bd734c3bea
commit 989426be6e
12 changed files with 335 additions and 8 deletions

View File

@@ -387,6 +387,7 @@ When editing the command-line, a few commands can be used to complete the
word before the cursor. This is available for: word before the cursor. This is available for:
- Command names: At the start of the command-line. - Command names: At the start of the command-line.
- |++opt| values.
- Tags: Only after the ":tag" command. - Tags: Only after the ":tag" command.
- File names: Only after a command that accepts a file name or a setting for - File names: Only after a command that accepts a file name or a setting for
an option that can be set to a file name. This is called file name an option that can be set to a file name. This is called file name

View File

@@ -1768,6 +1768,45 @@ set_context_for_wildcard_arg(
} }
} }
/*
* Set the completion context for the "++opt=arg" argument. Always returns
* NULL.
*/
static char_u *
set_context_in_argopt(expand_T *xp, char_u *arg)
{
char_u *p;
p = vim_strchr(arg, '=');
if (p == NULL)
xp->xp_pattern = arg;
else
xp->xp_pattern = p + 1;
xp->xp_context = EXPAND_ARGOPT;
return NULL;
}
#ifdef FEAT_TERMINAL
/*
* Set the completion context for :terminal's [options]. Always returns NULL.
*/
static char_u *
set_context_in_terminalopt(expand_T *xp, char_u *arg)
{
char_u *p;
p = vim_strchr(arg, '=');
if (p == NULL)
xp->xp_pattern = arg;
else
xp->xp_pattern = p + 1;
xp->xp_context = EXPAND_TERMINALOPT;
return NULL;
}
#endif
/* /*
* Set the completion context for the :filter command. Returns a pointer to the * Set the completion context for the :filter command. Returns a pointer to the
* next command after the :filter command. * next command after the :filter command.
@@ -2491,13 +2530,28 @@ set_one_cmd_context(
arg = skipwhite(p); arg = skipwhite(p);
// Skip over ++argopt argument // Does command allow "++argopt" argument?
if ((ea.argt & EX_ARGOPT) && *arg != NUL && STRNCMP(arg, "++", 2) == 0) if ((ea.argt & EX_ARGOPT) || ea.cmdidx == CMD_terminal)
{ {
p = arg; while (*arg != NUL && STRNCMP(arg, "++", 2) == 0)
while (*p && !vim_isspace(*p)) {
MB_PTR_ADV(p); p = arg + 2;
arg = skipwhite(p); while (*p && !vim_isspace(*p))
MB_PTR_ADV(p);
// Still touching the command after "++"?
if (*p == NUL)
{
if (ea.argt & EX_ARGOPT)
return set_context_in_argopt(xp, arg + 2);
#ifdef FEAT_TERMINAL
if (ea.cmdidx == CMD_terminal)
return set_context_in_terminalopt(xp, arg + 2);
#endif
}
arg = skipwhite(p);
}
} }
if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update)
@@ -3120,6 +3174,12 @@ ExpandFromContext(
ret = ExpandSettingSubtract(xp, &regmatch, numMatches, matches); ret = ExpandSettingSubtract(xp, &regmatch, numMatches, matches);
else if (xp->xp_context == EXPAND_MAPPINGS) else if (xp->xp_context == EXPAND_MAPPINGS)
ret = ExpandMappings(pat, &regmatch, numMatches, matches); ret = ExpandMappings(pat, &regmatch, numMatches, matches);
else if (xp->xp_context == EXPAND_ARGOPT)
ret = expand_argopt(pat, xp, &regmatch, matches, numMatches);
#if defined(FEAT_TERMINAL)
else if (xp->xp_context == EXPAND_TERMINALOPT)
ret = expand_terminal_opt(pat, xp, &regmatch, matches, numMatches);
#endif
#if defined(FEAT_EVAL) #if defined(FEAT_EVAL)
else if (xp->xp_context == EXPAND_USER_DEFINED) else if (xp->xp_context == EXPAND_USER_DEFINED)
ret = ExpandUserDefined(pat, xp, &regmatch, matches, numMatches); ret = ExpandUserDefined(pat, xp, &regmatch, matches, numMatches);
@@ -3253,7 +3313,9 @@ ExpandGeneric(
if (!fuzzy && xp->xp_context != EXPAND_MENUNAMES if (!fuzzy && xp->xp_context != EXPAND_MENUNAMES
&& xp->xp_context != EXPAND_STRING_SETTING && xp->xp_context != EXPAND_STRING_SETTING
&& xp->xp_context != EXPAND_MENUS && xp->xp_context != EXPAND_MENUS
&& xp->xp_context != EXPAND_SCRIPTNAMES) && xp->xp_context != EXPAND_SCRIPTNAMES
&& xp->xp_context != EXPAND_ARGOPT
&& xp->xp_context != EXPAND_TERMINALOPT)
sort_matches = TRUE; sort_matches = TRUE;
// <SNR> functions should be sorted to the end. // <SNR> functions should be sorted to the end.

View File

@@ -5407,6 +5407,25 @@ get_bad_opt(char_u *p, exarg_T *eap)
return OK; return OK;
} }
/*
* Function given to ExpandGeneric() to obtain the list of bad= names.
*/
static char_u *
get_bad_name(expand_T *xp UNUSED, int idx)
{
// Note: Keep this in sync with getargopt.
static char *(p_bad_values[]) =
{
"?",
"keep",
"drop",
};
if (idx < (int)ARRAY_LENGTH(p_bad_values))
return (char_u*)p_bad_values[idx];
return NULL;
}
/* /*
* Get "++opt=arg" argument. * Get "++opt=arg" argument.
* Return FAIL or OK. * Return FAIL or OK.
@@ -5419,6 +5438,8 @@ getargopt(exarg_T *eap)
int bad_char_idx; int bad_char_idx;
char_u *p; char_u *p;
// Note: Keep this in sync with get_argopt_name.
// ":edit ++[no]bin[ary] file" // ":edit ++[no]bin[ary] file"
if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0)
{ {
@@ -5499,6 +5520,96 @@ getargopt(exarg_T *eap)
return OK; return OK;
} }
/*
* Function given to ExpandGeneric() to obtain the list of ++opt names.
*/
static char_u *
get_argopt_name(expand_T *xp UNUSED, int idx)
{
// Note: Keep this in sync with getargopt.
static char *(p_opt_values[]) =
{
"fileformat=",
"encoding=",
"binary",
"nobinary",
"bad=",
"edit",
};
if (idx < (int)ARRAY_LENGTH(p_opt_values))
return (char_u*)p_opt_values[idx];
return NULL;
}
/*
* Command-line expansion for ++opt=name.
*/
int
expand_argopt(
char_u *pat,
expand_T *xp,
regmatch_T *rmp,
char_u ***matches,
int *numMatches)
{
if (xp->xp_pattern > xp->xp_line && *(xp->xp_pattern-1) == '=')
{
char_u *(*cb)(expand_T *, int) = NULL;
char_u *name_end = xp->xp_pattern - 1;
if (name_end - xp->xp_line >= 2
&& STRNCMP(name_end - 2, "ff", 2) == 0)
cb = get_fileformat_name;
else if (name_end - xp->xp_line >= 10
&& STRNCMP(name_end - 10, "fileformat", 10) == 0)
cb = get_fileformat_name;
else if (name_end - xp->xp_line >= 3
&& STRNCMP(name_end - 3, "enc", 3) == 0)
cb = get_encoding_name;
else if (name_end - xp->xp_line >= 8
&& STRNCMP(name_end - 8, "encoding", 8) == 0)
cb = get_encoding_name;
else if (name_end - xp->xp_line >= 3
&& STRNCMP(name_end - 3, "bad", 3) == 0)
cb = get_bad_name;
if (cb != NULL)
{
return ExpandGeneric(
pat,
xp,
rmp,
matches,
numMatches,
cb,
FALSE);
}
return FAIL;
}
// Special handling of "ff" which acts as a short form of
// "fileformat", as "ff" is not a substring of it.
if (STRCMP(xp->xp_pattern, "ff") == 0)
{
*matches = ALLOC_MULT(char_u *, 1);
if (*matches == NULL)
return FAIL;
*numMatches = 1;
(*matches)[0] = vim_strsave((char_u*)"fileformat=");
return OK;
}
return ExpandGeneric(
pat,
xp,
rmp,
matches,
numMatches,
get_argopt_name,
FALSE);
}
static void static void
ex_autocmd(exarg_T *eap) ex_autocmd(exarg_T *eap)
{ {

View File

@@ -2104,6 +2104,19 @@ expand_set_fileformat(optexpand_T *args, int *numMatches, char_u ***matches)
matches); matches);
} }
/*
* Function given to ExpandGeneric() to obtain the possible arguments of the
* fileformat options.
*/
char_u *
get_fileformat_name(expand_T *xp UNUSED, int idx)
{
if (idx >= (int)ARRAY_LENGTH(p_ff_values))
return NULL;
return (char_u*)p_ff_values[idx];
}
/* /*
* The 'fileformats' option is changed. * The 'fileformats' option is changed.
*/ */

View File

@@ -30,6 +30,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp);
void separate_nextcmd(exarg_T *eap, int keep_backslash); void separate_nextcmd(exarg_T *eap, int keep_backslash);
char_u *skip_cmd_arg(char_u *p, int rembs); char_u *skip_cmd_arg(char_u *p, int rembs);
int get_bad_opt(char_u *p, exarg_T *eap); int get_bad_opt(char_u *p, exarg_T *eap);
int expand_argopt(char_u *pat, expand_T *xp, regmatch_T *rmp, char_u ***matches, int *numMatches);
int ends_excmd(int c); int ends_excmd(int c);
int ends_excmd2(char_u *cmd_start, char_u *cmd); int ends_excmd2(char_u *cmd_start, char_u *cmd);
char_u *find_nextcmd(char_u *p); char_u *find_nextcmd(char_u *p);

View File

@@ -189,6 +189,7 @@ int expand_set_wildoptions(optexpand_T *args, int *numMatches, char_u ***matches
int expand_set_winaltkeys(optexpand_T *args, int *numMatches, char_u ***matches); int expand_set_winaltkeys(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_wincolor(optexpand_T *args, int *numMatches, char_u ***matches); int expand_set_wincolor(optexpand_T *args, int *numMatches, char_u ***matches);
int check_ff_value(char_u *p); int check_ff_value(char_u *p);
char_u *get_fileformat_name(expand_T *xp, int idx);
void save_clear_shm_value(void); void save_clear_shm_value(void);
void restore_shm_value(void); void restore_shm_value(void);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@@ -2,6 +2,7 @@
void init_job_options(jobopt_T *opt); void init_job_options(jobopt_T *opt);
buf_T *term_start(typval_T *argvar, char **argv, jobopt_T *opt, int flags); buf_T *term_start(typval_T *argvar, char **argv, jobopt_T *opt, int flags);
void ex_terminal(exarg_T *eap); void ex_terminal(exarg_T *eap);
int expand_terminal_opt(char_u *pat, expand_T *xp, regmatch_T *rmp, char_u ***matches, int *numMatches);
int term_write_session(FILE *fd, win_T *wp, hashtab_T *terminal_bufs); int term_write_session(FILE *fd, win_T *wp, hashtab_T *terminal_bufs);
int term_should_restore(buf_T *buf); int term_should_restore(buf_T *buf);
void free_terminal(buf_T *buf); void free_terminal(buf_T *buf);

View File

@@ -603,7 +603,8 @@ typedef enum {
*/ */
typedef struct expand typedef struct expand
{ {
char_u *xp_pattern; // start of item to expand char_u *xp_pattern; // start of item to expand, guaranteed
// to be part of xp_line
int xp_context; // type of expansion int xp_context; // type of expansion
int xp_pattern_len; // bytes in xp_pattern before cursor int xp_pattern_len; // bytes in xp_pattern before cursor
xp_prefix_T xp_prefix; xp_prefix_T xp_prefix;

View File

@@ -818,6 +818,8 @@ ex_terminal(exarg_T *eap)
ep = NULL; ep = NULL;
} }
// Note: Keep this in sync with get_terminalopt_name.
# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \ # define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
&& STRNICMP(cmd, name, sizeof(name) - 1) == 0) && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
if (OPTARG_HAS("close")) if (OPTARG_HAS("close"))
@@ -969,6 +971,96 @@ theend:
vim_free(opt.jo_eof_chars); vim_free(opt.jo_eof_chars);
} }
static char_u *
get_terminalopt_name(expand_T *xp UNUSED, int idx)
{
// Note: Keep this in sync with ex_terminal.
static char *(p_termopt_values[]) =
{
"close",
"noclose",
"open",
"curwin",
"hidden",
"norestore",
"shell",
"kill=",
"rows=",
"cols=",
"eof=",
"type=",
"api=",
};
if (idx < (int)ARRAY_LENGTH(p_termopt_values))
return (char_u*)p_termopt_values[idx];
return NULL;
}
static char_u *
get_termkill_name(expand_T *xp UNUSED, int idx)
{
// These are platform-specific values used for job_stop(). They are defined
// in each platform's mch_signal_job(). Just use a unified auto-complete
// list for simplicity.
static char *(p_termkill_values[]) =
{
"term",
"hup",
"quit",
"int",
"kill",
"winch",
};
if (idx < (int)ARRAY_LENGTH(p_termkill_values))
return (char_u*)p_termkill_values[idx];
return NULL;
}
/*
* Command-line expansion for :terminal [options]
*/
int
expand_terminal_opt(
char_u *pat,
expand_T *xp,
regmatch_T *rmp,
char_u ***matches,
int *numMatches)
{
if (xp->xp_pattern > xp->xp_line && *(xp->xp_pattern-1) == '=')
{
char_u *(*cb)(expand_T *, int) = NULL;
char_u *name_end = xp->xp_pattern - 1;
if (name_end - xp->xp_line >= 4
&& STRNCMP(name_end - 4, "kill", 4) == 0)
cb = get_termkill_name;
if (cb != NULL)
{
return ExpandGeneric(
pat,
xp,
rmp,
matches,
numMatches,
cb,
FALSE);
}
return FAIL;
}
return ExpandGeneric(
pat,
xp,
rmp,
matches,
numMatches,
get_terminalopt_name,
FALSE);
}
#if defined(FEAT_SESSION) || defined(PROTO) #if defined(FEAT_SESSION) || defined(PROTO)
/* /*
* Write a :terminal command to the session file to restore the terminal in * Write a :terminal command to the session file to restore the terminal in

View File

@@ -1083,6 +1083,46 @@ func Test_cmdline_complete_expression()
unlet g:SomeVar unlet g:SomeVar
endfunc endfunc
func Test_cmdline_complete_argopt()
" completion for ++opt=arg for file commands
call assert_equal('fileformat=', getcompletion('edit ++', 'cmdline')[0])
call assert_equal('encoding=', getcompletion('read ++e', 'cmdline')[0])
call assert_equal('edit', getcompletion('read ++bin ++edi', 'cmdline')[0])
call assert_equal(['fileformat='], getcompletion('edit ++ff', 'cmdline'))
call assert_equal('dos', getcompletion('write ++ff=d', 'cmdline')[0])
call assert_equal('mac', getcompletion('args ++fileformat=m', 'cmdline')[0])
call assert_equal('utf-8', getcompletion('split ++enc=ut*-8', 'cmdline')[0])
call assert_equal('latin1', getcompletion('tabedit ++encoding=lati', 'cmdline')[0])
call assert_equal('keep', getcompletion('edit ++bad=k', 'cmdline')[0])
call assert_equal([], getcompletion('edit ++bogus=', 'cmdline'))
" completion should skip the ++opt and continue
call writefile([], 'Xaaaaa.txt', 'D')
call feedkeys(":split ++enc=latin1 Xaaa\<C-A>\<C-B>\"\<CR>", 'xt')
call assert_equal('"split ++enc=latin1 Xaaaaa.txt', @:)
if has('terminal')
" completion for terminal's [options]
call assert_equal('close', getcompletion('terminal ++cl*e', 'cmdline')[0])
call assert_equal('hidden', getcompletion('terminal ++open ++hidd', 'cmdline')[0])
call assert_equal('term', getcompletion('terminal ++kill=ter', 'cmdline')[0])
call assert_equal([], getcompletion('terminal ++bogus=', 'cmdline'))
" :terminal completion should skip the ++opt when considering what is the
" first option, which is a list of shell commands, unlike second option
" onwards.
let first_param = getcompletion('terminal ', 'cmdline')
let second_param = getcompletion('terminal foo ', 'cmdline')
let skipped_opt_param = getcompletion('terminal ++close ', 'cmdline')
call assert_equal(first_param, skipped_opt_param)
call assert_notequal(first_param, second_param)
endif
endfunc
" Unique function name for completion below " Unique function name for completion below
func s:WeirdFunc() func s:WeirdFunc()
echo 'weird' echo 'weird'

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 */
/**/
2025,
/**/ /**/
2024, 2024,
/**/ /**/

View File

@@ -824,6 +824,8 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
#define EXPAND_RUNTIME 53 #define EXPAND_RUNTIME 53
#define EXPAND_STRING_SETTING 54 #define EXPAND_STRING_SETTING 54
#define EXPAND_SETTING_SUBTRACT 55 #define EXPAND_SETTING_SUBTRACT 55
#define EXPAND_ARGOPT 56
#define EXPAND_TERMINALOPT 57
// Values for exmode_active (0 is no exmode) // Values for exmode_active (0 is no exmode)
#define EXMODE_NORMAL 1 #define EXMODE_NORMAL 1