]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.2025: no cmdline completion for ++opt args v9.0.2025
authorYee Cheng Chin <ychin.git@gmail.com>
Sat, 14 Oct 2023 09:46:51 +0000 (11:46 +0200)
committerChristian Brabandt <cb@256bit.org>
Sat, 14 Oct 2023 09:46:51 +0000 (11:46 +0200)
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>
12 files changed:
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

index 6d58d63873bf588a66ba97777f2264db831d7835..c5d0096ddb74c445f3d4683b52d1dc75b8ba6d40 100644 (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:
 
 - 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
index 20f3069ce21477326000b2af929737c26e0b52f9..d27e039443505f05a87c6a7157ea42c34e0a6408 100644 (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
  * 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.
index d4b972a2ef44ad647bff6a715d85f7ac699b8c6c..f0c7aad7df5bae4b2c0ba19e338b655604ef9e2a 100644 (file)
@@ -5407,6 +5407,25 @@ get_bad_opt(char_u *p, exarg_T *eap)
     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.
  * 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)
 {
index 202e93e57db0e1990270da5e1a1270b59ea22ddc..8458f2a4a4cbd20a299e22ced06af6bf8dae716e 100644 (file)
@@ -2104,6 +2104,19 @@ expand_set_fileformat(optexpand_T *args, int *numMatches, char_u ***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.
  */
index 3a1dc5fe4df9511bee9f7ead9e418923c19b5695..3fd20b27a71b0073cc8998576fb75b99deac8081 100644 (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);
 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);
index 88034fcec525d18c5e9235754fe0c8dba87c4ece..22601ba996b8f3b60a3732389029423f645e8341 100644 (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_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 : */
index f7ba72b7616863e833a653f7323fe5adac9bf748..dfa59cced12c14eadb6a511fc450fc43d969ddb7 100644 (file)
@@ -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);
index 3f461f8514073997ba935833cdc60c305c3aa53b..f7f3b2ec56a4f746b781f8d59f5295b6fd0c2af6 100644 (file)
@@ -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;
index 71566657142303614e1931df43c90104c4723d62..f79d102e8cba97a26f3dd3ecff91b1fff61ddf9b 100644 (file)
@@ -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
index e79fa72bbad3b108290c35c76ef28ba1bb239688..ddfeba28ff878bae3c34a6ed9c972174ff2aeda6 100644 (file)
@@ -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'
index 09a6b69eb94e2a7e3005b2b177362614c549c8aa..73a573df58680fe1112839c4d83e7dad2d3d5543 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2025,
 /**/
     2024,
 /**/
index 2bafda4249f3eb3d83215bd403594f10146936c0..2b6c787b99aaba3a78692ca78b54fc591d7825e7 100644 (file)
--- 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