diff options
author | zeertzjq <zeertzjq@outlook.com> | 2023-10-15 17:52:08 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-15 17:52:08 +0800 |
commit | 75b488d3ef2e9dafba43c72487839aa78950d453 (patch) | |
tree | c538db61c1e2e17112f09e9432bdcb0a035dd9f6 | |
parent | d974a3dcbb3757ebeb78fa64054c795ab7acdf1a (diff) | |
download | rneovim-75b488d3ef2e9dafba43c72487839aa78950d453.tar.gz rneovim-75b488d3ef2e9dafba43c72487839aa78950d453.tar.bz2 rneovim-75b488d3ef2e9dafba43c72487839aa78950d453.zip |
vim-patch:9.0.2025: no cmdline completion for ++opt args (#25657)
Problem: no cmdline completion for ++opt args
Solution: Add cmdline completion for :e ++opt=arg and :terminal
[++options]
closes: vim/vim#13319
https://github.com/vim/vim/commit/989426be6e9ae23d2413943890206cbe15d9df38
Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
-rw-r--r-- | runtime/doc/cmdline.txt | 1 | ||||
-rw-r--r-- | src/nvim/cmdexpand.c | 41 | ||||
-rw-r--r-- | src/nvim/cmdexpand_defs.h | 4 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 82 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 11 | ||||
-rw-r--r-- | test/old/testdir/test_cmdline.vim | 45 |
6 files changed, 176 insertions, 8 deletions
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 1a5ad9a382..291286d8f7 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -364,6 +364,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/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index aef471a17b..bf0aa54bdd 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -1567,6 +1567,20 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use } } +/// Set the completion context for the "++opt=arg" argument. Always returns NULL. +static const char *set_context_in_argopt(expand_T *xp, const char *arg) +{ + char *p = vim_strchr(arg, '='); + if (p == NULL) { + xp->xp_pattern = (char *)arg; + } else { + xp->xp_pattern = p + 1; + } + + xp->xp_context = EXPAND_ARGOPT; + return NULL; +} + /// Set the completion context for the :filter command. Returns a pointer to the /// next command after the :filter command. static const char *set_context_in_filter_cmd(expand_T *xp, const char *arg) @@ -2238,13 +2252,23 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) const char *arg = skipwhite(p); - // Skip over ++argopt argument - if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) { - p = arg; - while (*p && !ascii_isspace(*p)) { - MB_PTR_ADV(p); + // Does command allow "++argopt" argument? + if (ea.argt & EX_ARGOPT) { + while (*arg != NUL && strncmp(arg, "++", 2) == 0) { + p = arg + 2; + while (*p && !ascii_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); + } + } + + arg = skipwhite(p); } - arg = skipwhite(p); } if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { @@ -2786,6 +2810,8 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM ret = ExpandSettingSubtract(xp, ®match, numMatches, matches); } else if (xp->xp_context == EXPAND_MAPPINGS) { ret = ExpandMappings(pat, ®match, numMatches, matches); + } else if (xp->xp_context == EXPAND_ARGOPT) { + ret = expand_argopt(pat, xp, ®match, matches, numMatches); } else if (xp->xp_context == EXPAND_USER_DEFINED) { ret = ExpandUserDefined(pat, xp, ®match, matches, numMatches); } else { @@ -2883,7 +2909,8 @@ void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, ch && 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; // <SNR> functions should be sorted to the end. const bool funcsort = xp->xp_context == EXPAND_EXPRESSION diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h index 7c422aca18..0e830a016f 100644 --- a/src/nvim/cmdexpand_defs.h +++ b/src/nvim/cmdexpand_defs.h @@ -17,7 +17,8 @@ enum { EXPAND_BUF_LEN = 256, }; /// used for completion on the command line typedef struct expand { - char *xp_pattern; ///< start of item to expand + char *xp_pattern; ///< start of item to expand, guaranteed + ///< to be part of xp_line int xp_context; ///< type of expansion size_t xp_pattern_len; ///< bytes in xp_pattern before cursor xp_prefix_T xp_prefix; @@ -104,6 +105,7 @@ enum { EXPAND_RUNTIME, EXPAND_STRING_SETTING, EXPAND_SETTING_SUBTRACT, + EXPAND_ARGOPT, EXPAND_CHECKHEALTH, EXPAND_LUA, }; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 279d14f001..664b06108a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4087,6 +4087,22 @@ int get_bad_opt(const char *p, exarg_T *eap) return OK; } +/// Function given to ExpandGeneric() to obtain the list of bad= names. +static char *get_bad_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + // Note: Keep this in sync with get_bad_opt(). + static char *(p_bad_values[]) = { + "?", + "keep", + "drop", + }; + + if (idx < (int)ARRAY_SIZE(p_bad_values)) { + return p_bad_values[idx]; + } + return NULL; +} + /// Get "++opt=arg" argument. /// /// @return FAIL or OK. @@ -4096,6 +4112,8 @@ static int getargopt(exarg_T *eap) int *pp = NULL; int bad_char_idx; + // 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) { if (*arg == 'n') { @@ -4174,6 +4192,70 @@ static int getargopt(exarg_T *eap) return OK; } +/// Function given to ExpandGeneric() to obtain the list of ++opt names. +static char *get_argopt_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + // Note: Keep this in sync with getargopt(). + static char *(p_opt_values[]) = { + "fileformat=", + "encoding=", + "binary", + "nobinary", + "bad=", + "edit", + "p", + }; + + if (idx < (int)ARRAY_SIZE(p_opt_values)) { + return p_opt_values[idx]; + } + return NULL; +} + +/// Command-line expansion for ++opt=name. +int expand_argopt(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches) +{ + if (xp->xp_pattern > xp->xp_line && *(xp->xp_pattern - 1) == '=') { + CompleteListItemGetter cb = NULL; + + char *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) { + ExpandGeneric(pat, xp, rmp, matches, numMatches, cb, false); + return OK; + } + 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 = xmalloc(sizeof(char *)); + *numMatches = 1; + (*matches)[0] = xstrdup("fileformat="); + return OK; + } + + ExpandGeneric(pat, xp, rmp, matches, numMatches, get_argopt_name, false); + return OK; +} + /// Handle the argument for a tabpage related ex command. /// When an error is encountered then eap->errmsg is set. /// diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 6f41bba99b..88e7ebd991 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -1490,6 +1490,17 @@ int expand_set_fileformat(optexpand_T *args, int *numMatches, char ***matches) matches); } +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// fileformat options. +char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= (int)ARRAY_SIZE(p_ff_values)) { + return NULL; + } + + return p_ff_values[idx]; +} + /// The 'fileformats' option is changed. const char *did_set_fileformats(optset_T *args) { diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 90748a6076..00c894058e 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -1039,6 +1039,51 @@ 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' +endfunc + " Test for various command-line completion func Test_cmdline_complete_various() " completion for a command starting with a comment |