summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYee Cheng Chin <ychin.git@gmail.com>2023-10-14 11:46:51 +0200
committerChristian Brabandt <cb@256bit.org>2023-10-14 11:46:51 +0200
commit989426be6e9ae23d2413943890206cbe15d9df38 (patch)
tree3931aedc70ed18a7e3ea0f735359995fb26d1ef5
parentbd734c3bead9e167eb6875f62cc06fab2379c422 (diff)
patch 9.0.2025: no cmdline completion for ++opt argsv9.0.2025
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>
-rw-r--r--runtime/doc/cmdline.txt1
-rw-r--r--src/cmdexpand.c76
-rw-r--r--src/ex_docmd.c111
-rw-r--r--src/optionstr.c13
-rw-r--r--src/proto/ex_docmd.pro1
-rw-r--r--src/proto/optionstr.pro1
-rw-r--r--src/proto/terminal.pro1
-rw-r--r--src/structs.h3
-rw-r--r--src/terminal.c92
-rw-r--r--src/testdir/test_cmdline.vim40
-rw-r--r--src/version.c2
-rw-r--r--src/vim.h2
12 files changed, 335 insertions, 8 deletions
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index 6d58d63873..c5d0096ddb 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -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:
- Command names: At the start of the command-line.
+- |++opt| values.
- Tags: Only after the ":tag" command.
- 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
diff --git a/src/cmdexpand.c b/src/cmdexpand.c
index 20f3069ce2..d27e039443 100644
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -1769,6 +1769,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
* next command after the :filter command.
*/
@@ -2491,13 +2530,28 @@ set_one_cmd_context(
arg = skipwhite(p);
- // Skip over ++argopt argument
- if ((ea.argt & EX_ARGOPT) && *arg != NUL && STRNCMP(arg, "++", 2) == 0)
+ // Does command allow "++argopt" argument?
+ if ((ea.argt & EX_ARGOPT) || ea.cmdidx == CMD_terminal)
{
- p = arg;
- while (*p && !vim_isspace(*p))
- MB_PTR_ADV(p);
- arg = skipwhite(p);
+ while (*arg != NUL && STRNCMP(arg, "++", 2) == 0)
+ {
+ p = arg + 2;
+ 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)
@@ -3120,6 +3174,12 @@ ExpandFromContext(
ret = ExpandSettingSubtract(xp, &regmatch, numMatches, matches);
else if (xp->xp_context == EXPAND_MAPPINGS)
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)
else if (xp->xp_context == EXPAND_USER_DEFINED)
ret = ExpandUserDefined(pat, xp, &regmatch, matches, numMatches);
@@ -3253,7 +3313,9 @@ ExpandGeneric(
if (!fuzzy && xp->xp_context != EXPAND_MENUNAMES
&& xp->xp_context != EXPAND_STRING_SETTING
&& 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;
// <SNR> functions should be sorted to the end.
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index d4b972a2ef..f0c7aad7df 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -5408,6 +5408,25 @@ get_bad_opt(char_u *p, exarg_T *eap)
}
/*
+ * 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.
* Return FAIL or OK.
*/
@@ -5419,6 +5438,8 @@ getargopt(exarg_T *eap)
int bad_char_idx;
char_u *p;
+ // Note: Keep this in sync with get_argopt_name.
+
// ":edit ++[no]bin[ary] file"
if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0)
{
@@ -5499,6 +5520,96 @@ getargopt(exarg_T *eap)
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
ex_autocmd(exarg_T *eap)
{
diff --git a/src/optionstr.c b/src/optionstr.c
index 202e93e57d..8458f2a4a4 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -2105,6 +2105,19 @@ expand_set_fileformat(optexpand_T *args, int *numMatches, char_u ***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.
*/
char *
diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro
index 3a1dc5fe4d..3fd20b27a7 100644
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -30,6 +30,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp);
void separate_nextcmd(exarg_T *eap, int keep_backslash);
char_u *skip_cmd_arg(char_u *p, int rembs);
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_excmd2(char_u *cmd_start, char_u *cmd);
char_u *find_nextcmd(char_u *p);
diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro
index 88034fcec5..22601ba996 100644
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -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_wincolor(optexpand_T *args, int *numMatches, char_u ***matches);
int check_ff_value(char_u *p);
+char_u *get_fileformat_name(expand_T *xp, int idx);
void save_clear_shm_value(void);
void restore_shm_value(void);
/* vim: set ft=c : */
diff --git a/src/proto/terminal.pro b/src/proto/terminal.pro
index f7ba72b761..dfa59cced1 100644
--- a/src/proto/terminal.pro
+++ b/src/proto/terminal.pro
@@ -2,6 +2,7 @@
void init_job_options(jobopt_T *opt);
buf_T *term_start(typval_T *argvar, char **argv, jobopt_T *opt, int flags);
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_should_restore(buf_T *buf);
void free_terminal(buf_T *buf);
diff --git a/src/structs.h b/src/structs.h
index 3f461f8514..f7f3b2ec56 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -603,7 +603,8 @@ typedef enum {
*/
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_pattern_len; // bytes in xp_pattern before cursor
xp_prefix_T xp_prefix;
diff --git a/src/terminal.c b/src/terminal.c
index 7156665714..f79d102e8c 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -818,6 +818,8 @@ ex_terminal(exarg_T *eap)
ep = NULL;
}
+ // Note: Keep this in sync with get_terminalopt_name.
+
# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
&& STRNICMP(cmd, name, sizeof(name) - 1) == 0)
if (OPTARG_HAS("close"))
@@ -969,6 +971,96 @@ theend:
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)
/*
* Write a :terminal command to the session file to restore the terminal in
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index e79fa72bba..ddfeba28ff 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -1083,6 +1083,46 @@ func Test_cmdline_complete_expression()
unlet g:SomeVar
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
func s:WeirdFunc()
echo 'weird'
diff --git a/src/version.c b/src/version.c
index 09a6b69eb9..73a573df58 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2025,
+/**/
2024,
/**/
2023,
diff --git a/src/vim.h b/src/vim.h
index 2bafda4249..2b6c787b99 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -824,6 +824,8 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
#define EXPAND_RUNTIME 53
#define EXPAND_STRING_SETTING 54
#define EXPAND_SETTING_SUBTRACT 55
+#define EXPAND_ARGOPT 56
+#define EXPAND_TERMINALOPT 57
// Values for exmode_active (0 is no exmode)
#define EXMODE_NORMAL 1