changeset 33555:46d449fd4fe4 v9.0.2025

patch 9.0.2025: no cmdline completion for ++opt args Commit: https://github.com/vim/vim/commit/989426be6e9ae23d2413943890206cbe15d9df38 Author: Yee Cheng Chin <ychin.git@gmail.com> Date: Sat Oct 14 11:46:51 2023 +0200 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>
author Christian Brabandt <cb@256bit.org>
date Sun, 15 Oct 2023 09:11:10 +0200
parents 8498422688a9
children e2f6cf52e496
files runtime/doc/cmdline.txt src/cmdexpand.c src/ex_docmd.c src/optionstr.c src/proto/ex_docmd.pro src/proto/optionstr.pro src/proto/terminal.pro src/structs.h src/terminal.c src/testdir/test_cmdline.vim src/version.c src/vim.h
diffstat 12 files changed, 335 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -387,6 +387,7 @@ When editing the command-line, a few com
 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
--- 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.
--- 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)
 {
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -2105,6 +2105,19 @@ expand_set_fileformat(optexpand_T *args,
 }
 
 /*
+ * 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 *
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -30,6 +30,7 @@ int expand_filename(exarg_T *eap, char_u
 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);
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -189,6 +189,7 @@ int expand_set_wildoptions(optexpand_T *
 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 : */
--- 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);
--- 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;
--- 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
--- 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'
--- 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,
--- a/src/vim.h
+++ b/src/vim.h
@@ -824,6 +824,8 @@ extern int (*dyn_libintl_wputenv)(const 
 #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