diff options
-rw-r--r-- | runtime/doc/cmdline.txt | 20 | ||||
-rw-r--r-- | runtime/doc/news.txt | 2 | ||||
-rw-r--r-- | runtime/doc/options.txt | 9 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 7 | ||||
-rw-r--r-- | src/nvim/autocmd.c | 7 | ||||
-rw-r--r-- | src/nvim/cmdexpand.c | 23 | ||||
-rw-r--r-- | src/nvim/cmdexpand.h | 3 | ||||
-rw-r--r-- | src/nvim/diff.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 1 | ||||
-rw-r--r-- | src/nvim/generators/gen_options.lua | 5 | ||||
-rw-r--r-- | src/nvim/highlight_group.c | 4 | ||||
-rw-r--r-- | src/nvim/indent.c | 1 | ||||
-rw-r--r-- | src/nvim/mbyte.c | 11 | ||||
-rw-r--r-- | src/nvim/option.c | 310 | ||||
-rw-r--r-- | src/nvim/option.h | 9 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 46 | ||||
-rw-r--r-- | src/nvim/option_vars.h | 15 | ||||
-rw-r--r-- | src/nvim/options.lua | 75 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 826 | ||||
-rw-r--r-- | src/nvim/optionstr.h | 1 | ||||
-rw-r--r-- | src/nvim/spellsuggest.c | 1 | ||||
-rw-r--r-- | src/nvim/vim.h | 2 | ||||
-rw-r--r-- | test/functional/editor/completion_spec.lua | 9 | ||||
-rw-r--r-- | test/old/testdir/test_history.vim | 5 | ||||
-rw-r--r-- | test/old/testdir/test_options.vim | 312 |
25 files changed, 1519 insertions, 187 deletions
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 5ee1a2af13..c586b30863 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -493,16 +493,26 @@ example, to match only files that end in ".c": > :e *.c$ This will not match a file ending in ".cpp". Without the "$" it does match. -The old value of an option can be obtained by hitting 'wildchar' just after -the '='. For example, typing 'wildchar' after ":set dir=" will insert the -current value of 'dir'. This overrules file name completion for the options -that take a file name. - If you would like using <S-Tab> for CTRL-P in an xterm, put this command in your .cshrc: > xmodmap -e "keysym Tab = Tab Find" And this in your vimrc: > :cmap <Esc>[1~ <C-P> +< *complete-set-option* +When setting an option using |:set=|, the old value of an option can be +obtained by hitting 'wildchar' just after the '='. For example, typing +'wildchar' after ":set dir=" will insert the current value of 'dir'. This +overrules file name completion for the options that take a file name. + +When using |:set=|, |:set+=|, or |:set^=|, string options that have +pre-defined names or syntax (e.g. 'diffopt', 'listchars') or are a list of +single-character flags (e.g. 'shortmess') will also present a list of possible +values for completion when using 'wildchar'. + +When using |:set-=|, comma-separated options like 'diffopt' or 'backupdir' +will show each item separately. Flag list options like 'shortmess' will show +both the entire old value and the individual flags. Otherwise completion will +just fill in with the entire old value. ============================================================================== 3. Ex command-lines *cmdline-lines* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b632cf0932..f82cb7b7e0 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -105,6 +105,8 @@ The following new APIs and features were added. • |nvim_set_keymap()| and |nvim_del_keymap()| now support abbreviations. +• Better cmdline completion for string option value. |complete-set-option| + • Builtin TUI can now recognize "super" (|<D-|) and "meta" (|<T-|) modifiers in a terminal emulator that supports |tui-csiu|. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 84cff775f6..35fb08a9a4 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -52,14 +52,16 @@ achieve special effects. These options come in three forms: 'lines' Warning: This may have a lot of side effects. - *:set-args* *E487* *E521* + *:set-args* *:set=* *E487* *E521* :se[t] {option}={value} or :se[t] {option}:{value} Set string or number option to {value}. For numeric options the value can be given in decimal, hex (preceded with 0x) or octal (preceded with '0'). The old value can be inserted by typing 'wildchar' (by - default this is a <Tab>). See |cmdline-completion|. + default this is a <Tab>). Many string options with + fixed syntax also support completing known values. + See |cmdline-completion| and |complete-set-option|. White space between {option} and '=' is allowed and will be ignored. White space between '=' and {value} is not allowed. @@ -93,6 +95,9 @@ achieve special effects. These options come in three forms: When the option is a list of flags, {value} must be exactly as they appear in the option. Remove flags one by one to avoid problems. + The individual values from a comma separated list or + list of flags can be inserted by typing 'wildchar'. + See |complete-set-option|. Also see |:set-args| above. The {option} arguments to ":set" may be repeated. For example: > diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index e636746616..3bf844a139 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -287,13 +287,12 @@ Options: 'diffopt' "linematch" feature 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is prompted whether to trust the file. - 'fillchars' flags: "msgsep", "horiz", "horizup", - "horizdown", "vertleft", "vertright", "verthoriz" + 'fillchars' flags: "msgsep", "horiz", "horizup", "horizdown", + "vertleft", "vertright", "verthoriz" 'foldcolumn' supports up to 9 dynamic/fixed columns 'guicursor' works in the terminal (TUI) 'inccommand' shows interactive results for |:substitute|-like commands and |:command-preview| commands - 'jumpoptions' "stack" behavior 'jumpoptions' "view" tries to restore the |mark-view| when moving through the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. 'laststatus' global statusline support @@ -304,6 +303,7 @@ Options: 'signcolumn' supports up to 9 dynamic/fixed columns 'statuscolumn' full control of columns using 'statusline' format 'tabline' %@Func@foo%X can call any function on mouse-click + 'termpastefilter' 'ttimeout', 'ttimeoutlen' behavior was simplified 'winblend' pseudo-transparency in floating windows |api-floatwin| 'winhighlight' window-local highlights @@ -382,6 +382,7 @@ Upstreamed features *nvim-upstreamed* These Nvim features were later integrated into Vim. - 'fillchars' flags: "eob" +- 'jumpoptions' "stack" behavior - 'wildoptions' flags: "pum" enables popupmenu for wildmode completion - |<Cmd>| - |WinClosed| diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 657760914f..a40f7d8c26 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2213,6 +2213,13 @@ char *expand_get_event_name(expand_T *xp, int idx) return event_names[idx - next_augroup_id].name; } +/// Function given to ExpandGeneric() to obtain the list of event names. Don't +/// include groups. +char *get_event_name_no_group(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + return event_names[idx].name; +} + /// Check whether given autocommand is supported /// /// @param[in] event Event to check. diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index d733ffe6ab..893deadd13 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -69,9 +69,6 @@ #include "nvim/vim.h" #include "nvim/window.h" -/// Type used by ExpandGeneric() -typedef char *(*CompleteListItemGetter)(expand_T *, int); - /// Type used by call_user_expand_func typedef void *(*user_expand_func_T)(const char *, int, typval_T *); @@ -107,6 +104,8 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) && xp->xp_context != EXPAND_HELP && xp->xp_context != EXPAND_LUA && xp->xp_context != EXPAND_OLD_SETTING + && xp->xp_context != EXPAND_STRING_SETTING + && xp->xp_context != EXPAND_SETTING_SUBTRACT && xp->xp_context != EXPAND_OWNSYNTAX && xp->xp_context != EXPAND_PACKADD && xp->xp_context != EXPAND_RUNTIME @@ -2599,7 +2598,7 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches { EXPAND_MENUNAMES, get_menu_names, false, true }, { EXPAND_SYNTAX, get_syntax_name, true, true }, { EXPAND_SYNTIME, get_syntime_arg, true, true }, - { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, false }, + { EXPAND_HIGHLIGHT, get_highlight_name, true, false }, { EXPAND_EVENTS, expand_get_event_name, true, false }, { EXPAND_AUGROUP, expand_get_augroup_name, true, false }, { EXPAND_SIGN, get_sign_name, true, true }, @@ -2694,8 +2693,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM return OK; } if (xp->xp_context == EXPAND_OLD_SETTING) { - ExpandOldSetting(numMatches, matches); - return OK; + return ExpandOldSetting(numMatches, matches); } if (xp->xp_context == EXPAND_BUFFERS) { return ExpandBufnames(pat, numMatches, matches, options); @@ -2765,6 +2763,10 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM if (xp->xp_context == EXPAND_SETTINGS || xp->xp_context == EXPAND_BOOL_SETTINGS) { ret = ExpandSettings(xp, ®match, pat, numMatches, matches, fuzzy); + } else if (xp->xp_context == EXPAND_STRING_SETTING) { + ret = ExpandStringSetting(xp, ®match, numMatches, matches); + } else if (xp->xp_context == EXPAND_SETTING_SUBTRACT) { + 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_USER_DEFINED) { @@ -2788,9 +2790,8 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM /// program. Matching strings are copied into an array, which is returned. /// /// @param func returns a string from the list -static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, - char ***matches, int *numMatches, CompleteListItemGetter func, - int escaped) +void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, char ***matches, + int *numMatches, CompleteListItemGetter func, bool escaped) { const bool fuzzy = cmdline_fuzzy_complete(pat); *matches = NULL; @@ -2863,6 +2864,7 @@ static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regma // in the specified order. const bool sort_matches = !fuzzy && xp->xp_context != EXPAND_MENUNAMES + && xp->xp_context != EXPAND_STRING_SETTING && xp->xp_context != EXPAND_MENUS && xp->xp_context != EXPAND_SCRIPTNAMES; @@ -3221,8 +3223,7 @@ void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dir char **p; int num_p = 0; - (void)ExpandFromContext(&xpc, buf, &p, &num_p, - WILD_SILENT | expand_options); + (void)ExpandFromContext(&xpc, buf, &p, &num_p, WILD_SILENT | expand_options); if (num_p > 0) { ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h index 32c23c5d66..f37c693a22 100644 --- a/src/nvim/cmdexpand.h +++ b/src/nvim/cmdexpand.h @@ -41,6 +41,9 @@ enum { BUF_DIFF_FILTER = 0x2000, }; +/// Type used by ExpandGeneric() +typedef char *(*CompleteListItemGetter)(expand_T *, int); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "cmdexpand.h.generated.h" #endif diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 3ab1da76f4..cb76cf17fc 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2467,6 +2467,7 @@ int diffopt_changed(void) char *p = p_dip; while (*p != NUL) { + // Note: Keep this in sync with p_dip_values if (strncmp(p, "filler", 6) == 0) { p += 6; diff_flags_new |= DIFF_FILLER; @@ -2513,6 +2514,7 @@ int diffopt_changed(void) p += 8; diff_flags_new |= DIFF_INTERNAL; } else if (strncmp(p, "algorithm:", 10) == 0) { + // Note: Keep this in sync with p_dip_algorithm_values. p += 10; if (strncmp(p, "myers", 5) == 0) { p += 5; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 6fa607d569..2d1633f312 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2766,6 +2766,7 @@ int check_opt_wim(void) } for (char *p = p_wim; *p; p++) { + // Note: Keep this in sync with p_wim_values. for (i = 0; ASCII_ISALPHA(p[i]); i++) {} if (p[i] != NUL && p[i] != ',' && p[i] != ':') { return FAIL; diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 0932a1357f..05def71caa 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -35,6 +35,8 @@ local redraw_flags={ local list_flags={ comma='P_COMMA', onecomma='P_ONECOMMA', + commacolon='P_COMMA|P_COLON', + onecommacolon='P_ONECOMMA|P_COLON', flags='P_FLAGLIST', flagscomma='P_COMMA|P_FLAGLIST', } @@ -166,6 +168,9 @@ local function dump_option(i, o) if o.cb then w(' .opt_did_set_cb=' .. o.cb) end + if o.expand_cb then + w(' .opt_expand_cb=' .. o.expand_cb) + end if o.enable_if then w('#else') w(' .var=NULL') diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 080dc79e0e..d2cdcb2516 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -2283,10 +2283,10 @@ static void highlight_list_two(int cnt, int attr) } /// Function given to ExpandGeneric() to obtain the list of group names. -const char *get_highlight_name(expand_T *const xp, int idx) +char *get_highlight_name(expand_T *const xp, int idx) FUNC_ATTR_WARN_UNUSED_RESULT { - return get_highlight_name_ext(xp, idx, true); + return (char *)get_highlight_name_ext(xp, idx, true); } /// Obtain a highlight group name. diff --git a/src/nvim/indent.c b/src/nvim/indent.c index b7bc23cda5..55235e454c 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -760,6 +760,7 @@ bool briopt_check(win_T *wp) char *p = wp->w_p_briopt; while (*p != NUL) { + // Note: Keep this in sync with p_briopt_values if (strncmp(p, "shift:", 6) == 0 && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { p += 6; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 7b7c822b3b..124855fd08 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -2830,3 +2830,14 @@ void f_charclass(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } rettv->vval.v_number = mb_get_class(argvars[0].vval.v_string); } + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// encoding options. +char *get_encoding_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= (int)ARRAY_SIZE(enc_canon_table)) { + return NULL; + } + + return (char *)enc_canon_table[idx].name; +} diff --git a/src/nvim/option.c b/src/nvim/option.c index eef5e66aeb..f771760822 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -136,13 +136,6 @@ static char *p_vsts_nopaste; #define OPTION_COUNT ARRAY_SIZE(options) -typedef enum { - OP_NONE = 0, - OP_ADDING, ///< "opt+=arg" - OP_PREPENDING, ///< "opt^=arg" - OP_REMOVING, ///< "opt-=arg" -} set_op_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.c.generated.h" #endif @@ -888,7 +881,7 @@ static char *stropt_copy_value(char *origval, char **argp, set_op_T op, // For MS-Windows backslashes before normal file name characters // are not removed, and keep backslash at start, for "\\machine\path", // but do remove it for "\\\\machine\\path". - // The reverse is found in ExpandOldSetting(). + // The reverse is found in escape_option_str_cmdline(). while (*arg != NUL && !ascii_iswhite(*arg)) { if (*arg == '\\' && arg[1] != NUL #ifdef BACKSLASH_IN_FILENAME @@ -1168,7 +1161,7 @@ static void do_set_option_string(int opt_idx, int opt_flags, char **argp, int ne // be triggered that can cause havoc. *errmsg = did_set_string_option(curbuf, curwin, opt_idx, (char **)varp, oldval, errbuf, errbuflen, - opt_flags, value_checked); + opt_flags, op, value_checked); secure = secure_saved; @@ -5305,8 +5298,10 @@ void set_imsearch_global(buf_T *buf) } static int expand_option_idx = -1; +static int expand_option_start_col = 0; static char expand_option_name[5] = { 't', '_', NUL, NUL, NUL }; static int expand_option_flags = 0; +static bool expand_option_append = false; /// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) @@ -5405,7 +5400,15 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) } } // handle "-=" and "+=" + expand_option_append = false; + bool expand_option_subtract = false; if ((nextchar == '-' || nextchar == '+' || nextchar == '^') && p[1] == '=') { + if (nextchar == '-') { + expand_option_subtract = true; + } + if (nextchar == '+' || nextchar == '^') { + expand_option_append = true; + } p++; nextchar = '='; } @@ -5414,28 +5417,51 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) xp->xp_context = EXPAND_UNSUCCESSFUL; return; } - if (p[1] == NUL) { - xp->xp_context = EXPAND_OLD_SETTING; - if (is_term_option) { - expand_option_idx = -1; - } else { - expand_option_idx = opt_idx; - } - xp->xp_pattern = p + 1; - return; - } - xp->xp_context = EXPAND_NOTHING; - if (is_term_option || (flags & P_NUM)) { - return; + + // Below are for handling expanding a specific option's value after the '=' or ':' + + if (is_term_option) { + expand_option_idx = -1; + } else { + expand_option_idx = opt_idx; } xp->xp_pattern = p + 1; + expand_option_start_col = (int)(p + 1 - xp->xp_line); + // Certain options currently have special case handling to reuse the + // expansion logic with other commands. if (options[opt_idx].var == &p_syn) { xp->xp_context = EXPAND_OWNSYNTAX; return; } + if (options[opt_idx].var == &p_ft) { + xp->xp_context = EXPAND_FILETYPE; + return; + } + // Now pick. If the option has a custom expander, use that. Otherwise, just + // fill with the existing option value. + if (expand_option_subtract) { + xp->xp_context = EXPAND_SETTING_SUBTRACT; + return; + } else if (expand_option_idx >= 0 + && options[expand_option_idx].opt_expand_cb != NULL) { + xp->xp_context = EXPAND_STRING_SETTING; + } else if (*xp->xp_pattern == NUL) { + xp->xp_context = EXPAND_OLD_SETTING; + return; + } else { + xp->xp_context = EXPAND_NOTHING; + } + + if (is_term_option || (flags & P_NUM)) { + return; + } + + // Only string options below + + // Options that have P_EXPAND are considered to all use file/dir expansion. if (flags & P_EXPAND) { p = options[opt_idx].var; if (p == (char *)&p_bdir @@ -5451,8 +5477,6 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) } else { xp->xp_backslash = XP_BS_ONE; } - } else if (p == (char *)&p_ft) { - xp->xp_context = EXPAND_FILETYPE; } else { xp->xp_context = EXPAND_FILES; // for 'tags' need three backslashes for a space @@ -5464,27 +5488,45 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) } } - // For an option that is a list of file names, find the start of the - // last file name. - for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) { - // count number of backslashes before ' ' or ',' - if (*p == ' ' || *p == ',') { - char *s = p; - while (s > xp->xp_pattern && *(s - 1) == '\\') { - s--; - } - if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3)) - || (*p == ',' && (flags & P_COMMA) && ((p - s) & 1) == 0)) { - xp->xp_pattern = p + 1; - break; + // For an option that is a list of file names, or comma/colon-separated + // values, split it by the delimiter and find the start of the current + // pattern, while accounting for backslash-escaped space/commas/colons. + // Triple-backslashed escaped file names (e.g. 'path') can also be + // delimited by space. + if ((flags & P_EXPAND) || (flags & P_COMMA) || (flags & P_COLON)) { + for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) { + // count number of backslashes before ' ' or ',' + if (*p == ' ' || *p == ',' || (*p == ':' && (flags & P_COLON))) { + char *s = p; + while (s > xp->xp_pattern && *(s - 1) == '\\') { + s--; + } + if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3)) + || (*p == ',' && (flags & P_COMMA) && ((p - s) % 1) == 0) + || (*p == ':' && (flags & P_COLON))) { + xp->xp_pattern = p + 1; + break; + } } } + } - // for 'spellsuggest' start at "file:" - if (options[opt_idx].var == &p_sps - && strncmp(p, "file:", 5) == 0) { - xp->xp_pattern = p + 5; - break; + // An option that is a list of single-character flags should always start + // at the end as we don't complete words. + if (flags & P_FLAGLIST) { + xp->xp_pattern = arg + strlen(arg); + } + + // Some options can either be using file/dir expansions, or custom value + // expansion depending on what the user typed. Unfortunately we have to + // manually handle it here to make sure we have the correct xp_context set. + // for 'spellsuggest' start at "file:" + if (options[opt_idx].var == &p_sps) { + if (strncmp(xp->xp_pattern, "file:", 5) == 0) { + xp->xp_pattern += 5; + return; + } else if (options[expand_option_idx].opt_expand_cb != NULL) { + xp->xp_context = EXPAND_STRING_SETTING; } } } @@ -5609,7 +5651,33 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM return OK; } -void ExpandOldSetting(int *numMatches, char ***matches) +/// Escape an option value that can be used on the command-line with :set. +/// Caller needs to free the returned string, unless NULL is returned. +static char *escape_option_str_cmdline(char *var) +{ + // A backslash is required before some characters. This is the reverse of + // what happens in do_set(). + char *buf = vim_strsave_escaped(var, escape_chars); + +#ifdef BACKSLASH_IN_FILENAME + // For MS-Windows et al. we don't double backslashes at the start and + // before a file name character. + // The reverse is found at stropt_copy_value(). + for (var = buf; *var != NUL; MB_PTR_ADV(var)) { + if (var[0] == '\\' && var[1] == '\\' + && expand_option_idx >= 0 + && (options[expand_option_idx].flags & P_EXPAND) + && vim_isfilec((uint8_t)var[2]) + && (var[2] != '\\' || (var == buf && var[4] != '\\'))) { + STRMOVE(var, var + 1); + } + } +#endif + return buf; +} + +/// Expansion handler for :set= when we just want to fill in with the existing value. +int ExpandOldSetting(int *numMatches, char ***matches) { char *var = NULL; @@ -5629,26 +5697,149 @@ void ExpandOldSetting(int *numMatches, char ***matches) var = ""; } - // A backslash is required before some characters. This is the reverse of - // what happens in do_set(). - char *buf = vim_strsave_escaped(var, escape_chars); + char *buf = escape_option_str_cmdline(var); -#ifdef BACKSLASH_IN_FILENAME - // For MS-Windows et al. we don't double backslashes at the start and - // before a file name character. - for (var = buf; *var != NUL; MB_PTR_ADV(var)) { - if (var[0] == '\\' && var[1] == '\\' - && expand_option_idx >= 0 - && (options[expand_option_idx].flags & P_EXPAND) - && vim_isfilec((uint8_t)var[2]) - && (var[2] != '\\' || (var == buf && var[4] != '\\'))) { - STRMOVE(var, var + 1); + (*matches)[0] = buf; + *numMatches = 1; + return OK; +} + +/// Expansion handler for :set=/:set+= when the option has a custom expansion handler. +int ExpandStringSetting(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches) +{ + if (expand_option_idx < 0 + || options[expand_option_idx].opt_expand_cb == NULL) { + // Not supposed to reach this. This function is only for options with + // custom expansion callbacks. + return FAIL; + } + + optexpand_T args = { + .oe_varp = get_varp_scope(&options[expand_option_idx], expand_option_flags), + .oe_append = expand_option_append, + .oe_regmatch = regmatch, + .oe_xp = xp, + .oe_set_arg = xp->xp_line + expand_option_start_col, + }; + args.oe_include_orig_val = !expand_option_append && (*args.oe_set_arg == NUL); + + // Retrieve the existing value, but escape it as a reverse of setting it. + // We technically only need to do this when oe_append or + // oe_include_orig_val is true. + option_value2string(&options[expand_option_idx], expand_option_flags); + char *var = NameBuff; + char *buf = escape_option_str_cmdline(var); + args.oe_opt_value = buf; + + int num_ret = options[expand_option_idx].opt_expand_cb(&args, numMatches, matches); + + xfree(buf); + return num_ret; +} + +/// Expansion handler for :set-= +int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches) +{ + if (expand_option_idx < 0) { + // term option + return ExpandOldSetting(numMatches, matches); + } + + char *option_val = *(char **)get_option_varp_scope_from(expand_option_idx, + expand_option_flags, + curbuf, curwin); + + uint32_t option_flags = options[expand_option_idx].flags; + + if (option_flags & P_NUM) { + return ExpandOldSetting(numMatches, matches); + } else if (option_flags & P_COMMA) { + // Split the option by comma, then present each option to the user if + // it matches the pattern. + // This condition needs to go first, because 'whichwrap' has both + // P_COMMA and P_FLAGLIST. + + if (*option_val == NUL) { + return FAIL; } + + // Make a copy as we need to inject null characters destructively. + char *option_copy = xstrdup(option_val); + char *next_val = option_copy; + + garray_T ga; + ga_init(&ga, sizeof(char *), 10); + + do { + char *item = next_val; + char *comma = vim_strchr(next_val, ','); + while (comma != NULL && comma != next_val && *(comma - 1) == '\\') { + // "\," is interpreted as a literal comma rather than option + // separator when reading options in copy_option_part(). Skip + // it. + comma = vim_strchr(comma + 1, ','); + } + if (comma != NULL) { + *comma = NUL; // null-terminate this value, required by later functions + next_val = comma + 1; + } else { + next_val = NULL; + } + + if (*item == NUL) { + // empty value, don't add to list + continue; + } + + if (!vim_regexec(regmatch, item, (colnr_T)0)) { + continue; + } + + char *buf = escape_option_str_cmdline(item); + GA_APPEND(char *, &ga, buf); + } while (next_val != NULL); + + xfree(option_copy); + + *matches = ga.ga_data; + *numMatches = ga.ga_len; + return OK; + } else if (option_flags & P_FLAGLIST) { + // Only present the flags that are set on the option as the other flags + // are not meaningful to do set-= on. + + if (*xp->xp_pattern != NUL) { + // Don't suggest anything if cmdline is non-empty. Vim's set-= + // behavior requires consecutive strings and it's usually + // unintuitive to users if ther try to subtract multiple flags at + // once. + return FAIL; + } + + size_t num_flags = strlen(option_val); + if (num_flags == 0) { + return FAIL; + } + + *matches = xmalloc(sizeof(char *) * (num_flags + 1)); + + int count = 0; + + (*matches)[count++] = xstrdup(option_val); + + if (num_flags > 1) { + // If more than one flags, split the flags up and expose each + // character as individual choice. + for (char *flag = option_val; *flag != NUL; flag++) { + (*matches)[count++] = xstrnsave(flag, 1); + } + } + + *numMatches = count; + return OK; } -#endif - *matches[0] = buf; - *numMatches = 1; + return ExpandOldSetting(numMatches, matches); } /// Get the value for the numeric or string option///opp in a nice format into @@ -5772,6 +5963,7 @@ int fill_culopt_flags(char *val, win_T *wp) p = val; } while (*p != NUL) { + // Note: Keep this in sync with p_culopt_values. if (strncmp(p, "line", 4) == 0) { p += 4; culopt_flags_new |= CULOPT_LINE; diff --git a/src/nvim/option.h b/src/nvim/option.h index 9e3bf25bc3..8417d152e7 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -7,6 +7,7 @@ #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/option_defs.h" +#include "nvim/search.h" /// The options that are local to a window or buffer have "indir" set to one of /// these values. Special values: @@ -45,7 +46,15 @@ typedef struct vimoption { ///< local option: indirect option index ///< callback function to invoke after an option is modified to validate and ///< apply the new value. + + /// callback function to invoke after an option is modified to validate and + /// apply the new value. opt_did_set_cb_T opt_did_set_cb; + + /// callback function to invoke when expanding possible values on the + /// cmdline. Only useful for string options. + opt_expand_cb_T opt_expand_cb; + void *def_val; ///< default values for variable (neovim!!) LastSet last_set; ///< script in which the option was last set } vimoption_T; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index f078f6073c..dd637aacf5 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -3,6 +3,7 @@ #include "nvim/api/private/defs.h" #include "nvim/eval/typval_defs.h" +#include "nvim/regexp_defs.h" #include "nvim/types.h" /// Option value type @@ -25,6 +26,14 @@ typedef struct { } data; } OptVal; +/// :set operator types +typedef enum { + OP_NONE = 0, + OP_ADDING, ///< "opt+=arg" + OP_PREPENDING, ///< "opt^=arg" + OP_REMOVING, ///< "opt-=arg" +} set_op_T; + /// Argument for the callback function (opt_did_set_cb_T) invoked after an /// option value is modified. typedef struct { @@ -33,6 +42,7 @@ typedef struct { void *os_varp; int os_idx; int os_flags; + set_op_T os_op; /// old value of the option (can be a string, number or a boolean) union { @@ -80,4 +90,40 @@ typedef struct { /// Otherwise returns an error message. typedef const char *(*opt_did_set_cb_T)(optset_T *args); +/// Argument for the callback function (opt_expand_cb_T) invoked after a string +/// option value is expanded for cmdline completion. +typedef struct { + /// Pointer to the option variable. It's always a string. + char *oe_varp; + /// The original option value, escaped. + char *oe_opt_value; + + /// true if using set+= instead of set= + bool oe_append; + /// true if we would like to add the original option value as the first choice. + bool oe_include_orig_val; + + /// Regex from the cmdline, for matching potential options against. + regmatch_T *oe_regmatch; + /// The expansion context. + expand_T *oe_xp; + + /// The full argument passed to :set. For example, if the user inputs + /// ":set dip=icase,algorithm:my<Tab>", oe_xp->xp_pattern will only have + /// "my", but oe_set_arg will contain the whole "icase,algorithm:my". + char *oe_set_arg; +} optexpand_T; + +/// Type for the callback function that is invoked when expanding possible +/// string option values during cmdline completion. +/// +/// Strings in returned matches will be managed and freed by caller. +/// +/// Returns OK if the expansion succeeded (numMatches and matches have to be +/// set). Otherwise returns FAIL. +/// +/// Note: If returned FAIL or *numMatches is 0, *matches will NOT be freed by +/// caller. +typedef int (*opt_expand_cb_T)(optexpand_T *args, int *numMatches, char ***matches); + #endif // NVIM_OPTION_DEFS_H diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index d8bbce21b3..86e7f4cee8 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -16,6 +16,7 @@ ///< the same. #define P_EXPAND 0x10U ///< environment expansion. NOTE: P_EXPAND can ///< never be used for local or hidden options +#define P_NO_DEF_EXP 0x20U ///< do not expand default value #define P_NODEFAULT 0x40U ///< don't set to default value #define P_DEF_ALLOCED 0x80U ///< default value is in allocated memory, must ///< use free() when assigning new value @@ -51,8 +52,10 @@ #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_MLE 0x20000000U ///< under control of 'modelineexpr' #define P_FUNC 0x40000000U ///< accept a function reference or a lambda - -#define P_NO_DEF_EXP 0x80000000U ///< Do not expand default value. +#define P_COLON 0x80000000U ///< values use colons to create sublists +// Warning: Currently we have used all 32 bits for option flags, and adding more +// flags will overflow it. Adding another flag will need to change how +// it's stored first. #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ @@ -183,7 +186,7 @@ #define CPO_VI "aAbBcCdDeEfFiIJKlLmMnoOpPqrRsStuvWxXyZ$!%+>;_" // characters for p_ww option: -#define WW_ALL "bshl<>[],~" +#define WW_ALL "bshl<>[]~" // characters for p_mouse option: #define MOUSE_NORMAL 'n' // use mouse in Normal mode @@ -757,9 +760,9 @@ extern char *p_vfile; ///< 'verbosefile' EXTERN int p_warn; ///< 'warn' EXTERN char *p_wop; ///< 'wildoptions' EXTERN unsigned wop_flags; -#define WOP_TAGFILE 0x01 -#define WOP_PUM 0x02 -#define WOP_FUZZY 0x04 +#define WOP_FUZZY 0x01 +#define WOP_TAGFILE 0x02 +#define WOP_PUM 0x04 EXTERN OptInt p_window; ///< 'window' EXTERN char *p_wak; ///< 'winaltkeys' EXTERN char *p_wig; ///< 'wildignore' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index cd1d760836..bae9b56ef4 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -6,7 +6,7 @@ --- @field varname? string --- @field pv_name? string --- @field type 'bool'|'number'|'string' ---- @field list? 'comma'|'onecomma'|'flags'|'flagscomma' +--- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma' --- @field scope vim.option_scope[] --- @field deny_duplicates? boolean --- @field enable_if? string|false @@ -25,6 +25,7 @@ --- @field alloced? true --- @field redraw? vim.option_redraw[] --- @field cb? string +--- @field expand_cb? string --- @field tags? string[] --- @class vim.option_defaults @@ -192,6 +193,7 @@ return { set to one of CJK locales. See Unicode Standard Annex #11 (https://www.unicode.org/reports/tr11). ]=], + expand_cb = 'expand_set_ambiwidth', full_name = 'ambiwidth', redraw = { 'all_windows', 'ui_option' }, scope = { 'global' }, @@ -331,6 +333,7 @@ return { option, you must load syntax.vim again to see the result. This can be done with ":syntax on". ]=], + expand_cb = 'expand_set_background', full_name = 'background', scope = { 'global' }, short_desc = N_('"dark" or "light", used for highlight colors'), @@ -357,6 +360,7 @@ return { When the value is empty, Vi compatible backspacing is used, none of the ways mentioned for the items above are possible. ]=], + expand_cb = 'expand_set_backspace', full_name = 'backspace', list = 'onecomma', scope = { 'global' }, @@ -453,6 +457,7 @@ return { the system may refuse to do this. In that case the "auto" value will again not rename the file. ]=], + expand_cb = 'expand_set_backupcopy', full_name = 'backupcopy', list = 'onecomma', scope = { 'global', 'buffer' }, @@ -621,6 +626,7 @@ return { indicate that an error occurred. It can be silenced by adding the "error" keyword. ]=], + expand_cb = 'expand_set_belloff', full_name = 'belloff', list = 'comma', scope = { 'global' }, @@ -763,6 +769,7 @@ return { added for the 'showbreak' setting. (default: off) ]=], + expand_cb = 'expand_set_breakindentopt', full_name = 'breakindentopt', list = 'onecomma', redraw = { 'current_buffer' }, @@ -816,6 +823,7 @@ return { This option is used together with 'buftype' and 'swapfile' to specify special kinds of buffers. See |special-buffers|. ]=], + expand_cb = 'expand_set_bufhidden', full_name = 'bufhidden', noglob = true, scope = { 'buffer' }, @@ -893,6 +901,7 @@ return { without saving. For writing there must be matching |BufWriteCmd|, |FileWriteCmd| or |FileAppendCmd| autocommands. ]=], + expand_cb = 'expand_set_buftype', full_name = 'buftype', noglob = true, scope = { 'buffer' }, @@ -917,6 +926,7 @@ return { case mapping, the current locale is not effective. This probably only matters for Turkish. ]=], + expand_cb = 'expand_set_casemap', full_name = 'casemap', list = 'onecomma', scope = { 'global' }, @@ -1183,6 +1193,7 @@ return { "*". See |clipboard|. ]=], deny_duplicates = true, + expand_cb = 'expand_set_clipboard', full_name = 'clipboard', list = 'onecomma', scope = { 'global' }, @@ -1369,6 +1380,7 @@ return { based expansion (e.g., dictionary |i_CTRL-X_CTRL-K|, included patterns |i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]| and normal expansions). ]=], + expand_cb = 'expand_set_complete', full_name = 'complete', list = 'onecomma', scope = { 'buffer' }, @@ -1399,7 +1411,9 @@ return { Keep in mind that the cursor position is not always where it's displayed. E.g., when moving vertically it may change column. ]=], + expand_cb = 'expand_set_concealcursor', full_name = 'concealcursor', + list = 'flags', redraw = { 'current_window' }, scope = { 'window' }, short_desc = N_('whether concealable text is hidden in cursor line'), @@ -1492,6 +1506,7 @@ return { select one from the menu. Only works in combination with "menu" or "menuone". ]=], + expand_cb = 'expand_set_completeopt', full_name = 'completeopt', list = 'onecomma', scope = { 'global' }, @@ -1517,6 +1532,7 @@ return { command line completion the global value is used. ]=], enable_if = 'BACKSLASH_IN_FILENAME', + expand_cb = 'expand_set_completeslash', full_name = 'completeslash', scope = { 'buffer' }, type = 'string', @@ -1797,6 +1813,7 @@ return { _ When using |cw| on a word, do not include the whitespace following the word in the motion. ]=], + expand_cb = 'expand_set_cpoptions', full_name = 'cpoptions', list = 'flags', redraw = { 'all_windows' }, @@ -1878,6 +1895,7 @@ return { "line" and "screenline" cannot be used together. ]=], + expand_cb = 'expand_set_cursorlineopt', full_name = 'cursorlineopt', list = 'onecomma', redraw = { 'current_window_only' }, @@ -1900,6 +1918,7 @@ return { "msg" and "throw" are useful for debugging 'foldexpr', 'formatexpr' or 'indentexpr'. ]=], + expand_cb = 'expand_set_debug', full_name = 'debug', scope = { 'global' }, short_desc = N_('to "msg" to see all error messages'), @@ -2140,8 +2159,9 @@ return { :set diffopt-=internal " do NOT use the internal diff parser < ]=], + expand_cb = 'expand_set_diffopt', full_name = 'diffopt', - list = 'onecomma', + list = 'onecommacolon', redraw = { 'current_window' }, scope = { 'global' }, short_desc = N_('options for using diff mode'), @@ -2242,6 +2262,7 @@ return { The "@" character can be changed by setting the "lastline" item in 'fillchars'. The character is highlighted with |hl-NonText|. ]=], + expand_cb = 'expand_set_display', full_name = 'display', list = 'onecomma', redraw = { 'all_windows' }, @@ -2260,6 +2281,7 @@ return { hor horizontally, height of windows is not affected both width and height of windows is affected ]=], + expand_cb = 'expand_set_eadirection', full_name = 'eadirection', scope = { 'global' }, short_desc = N_("in which direction 'equalalways' works"), @@ -2470,6 +2492,7 @@ return { :set ei=WinEnter,WinLeave < ]=], + expand_cb = 'expand_set_eventignore', full_name = 'eventignore', list = 'onecomma', scope = { 'global' }, @@ -2558,6 +2581,7 @@ return { This option cannot be changed when 'modifiable' is off. ]=], + expand_cb = 'expand_set_encoding', full_name = 'fileencoding', no_mkrc = true, redraw = { 'statuslines', 'current_buffer' }, @@ -2619,6 +2643,7 @@ return { Setting this option does not have an effect until the next time a file is read. ]=], + expand_cb = 'expand_set_encoding', full_name = 'fileencodings', list = 'onecomma', scope = { 'global' }, @@ -2651,6 +2676,7 @@ return { option is set, because the file would be different when written. This option cannot be changed when 'modifiable' is off. ]=], + expand_cb = 'expand_set_fileformat', full_name = 'fileformat', no_mkrc = true, redraw = { 'curswant', 'statuslines' }, @@ -2714,6 +2740,7 @@ return { used. Also see |file-formats|. ]=], + expand_cb = 'expand_set_fileformat', full_name = 'fileformats', list = 'onecomma', scope = { 'global' }, @@ -2846,6 +2873,7 @@ return { eob EndOfBuffer |hl-EndOfBuffer| lastline NonText |hl-NonText| ]=], + expand_cb = 'expand_set_chars_option', full_name = 'fillchars', list = 'onecomma', redraw = { 'current_window' }, @@ -2884,6 +2912,7 @@ return { its level is higher than 'foldlevel'. Useful if you want folds to automatically close when moving out of them. ]=], + expand_cb = 'expand_set_foldclose', full_name = 'foldclose', list = 'onecomma', redraw = { 'current_window' }, @@ -2906,6 +2935,7 @@ return { "[1-9]": to display a fixed number of columns See |folding|. ]=], + expand_cb = 'expand_set_foldcolumn', full_name = 'foldcolumn', redraw = { 'current_window' }, scope = { 'window' }, @@ -3045,6 +3075,7 @@ return { |fold-syntax| syntax Syntax highlighting items specify folds. |fold-diff| diff Fold text that is not changed. ]=], + expand_cb = 'expand_set_foldmethod', full_name = 'foldmethod', redraw = { 'current_window' }, scope = { 'window' }, @@ -3122,6 +3153,7 @@ return { To close folds you can re-apply 'foldlevel' with the |zx| command or set the 'foldclose' option to "all". ]=], + expand_cb = 'expand_set_foldopen', full_name = 'foldopen', list = 'onecomma', redraw = { 'curswant' }, @@ -3218,6 +3250,7 @@ return { To avoid problems with flags that are added in the future, use the "+=" and "-=" feature of ":set" |add-option-flags|. ]=], + expand_cb = 'expand_set_formatoptions', full_name = 'formatoptions', list = 'flags', scope = { 'buffer' }, @@ -4050,6 +4083,7 @@ return { 'redrawtime') then 'inccommand' is automatically disabled until |Command-line-mode| is done. ]=], + expand_cb = 'expand_set_inccommand', full_name = 'inccommand', scope = { 'global' }, short_desc = N_('Live preview of substitution'), @@ -4451,6 +4485,7 @@ return { |alternate-file| or using |mark-motions| try to restore the |mark-view| in which the action occurred. ]=], + expand_cb = 'expand_set_jumpoptions', full_name = 'jumpoptions', list = 'onecomma', scope = { 'global' }, @@ -4495,6 +4530,7 @@ return { Special keys in this context are the cursor keys, <End>, <Home>, <PageUp> and <PageDown>. ]=], + expand_cb = 'expand_set_keymodel', full_name = 'keymodel', list = 'onecomma', scope = { 'global' }, @@ -4779,6 +4815,7 @@ return { Note that when using 'indentexpr' the `=` operator indents all the lines, otherwise the first line is not indented (Vi-compatible). ]=], + expand_cb = 'expand_set_lispoptions', full_name = 'lispoptions', list = 'onecomma', pv_name = 'p_lop', @@ -4933,6 +4970,7 @@ return { "precedes". |hl-Whitespace| for "nbsp", "space", "tab", "multispace", "lead" and "trail". ]=], + expand_cb = 'expand_set_chars_option', full_name = 'listchars', list = 'onecomma', redraw = { 'current_window' }, @@ -5015,6 +5053,7 @@ return { :set makeencoding=char " system locale is used < ]=], + expand_cb = 'expand_set_encoding', full_name = 'makeencoding', scope = { 'global', 'buffer' }, short_desc = N_('Converts the output of external commands'), @@ -5377,6 +5416,7 @@ return { 'mousehide' hide mouse pointer while typing text 'selectmode' whether to start Select mode or Visual mode ]=], + expand_cb = 'expand_set_mouse', full_name = 'mouse', list = 'flags', scope = { 'global' }, @@ -5468,6 +5508,7 @@ return { "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click) "g<RightMouse>" is "<C-RightMouse> ("CTRL-T") ]=], + expand_cb = 'expand_set_mousemodel', full_name = 'mousemodel', scope = { 'global' }, short_desc = N_('changes meaning of mouse buttons'), @@ -5513,6 +5554,7 @@ return { < Will make Nvim scroll 5 lines at a time when scrolling vertically, and scroll 2 columns at a time when scrolling horizontally. ]=], + expand_cb = 'expand_set_mousescroll', full_name = 'mousescroll', list = 'comma', scope = { 'global' }, @@ -5645,6 +5687,7 @@ return { considered decimal. This also happens for numbers that are not recognized as octal or hex. ]=], + expand_cb = 'expand_set_nrformats', full_name = 'nrformats', list = 'onecomma', scope = { 'buffer' }, @@ -6307,6 +6350,7 @@ return { This is useful for languages such as Hebrew, Arabic and Farsi. The 'rightleft' option must be set for 'rightleftcmd' to take effect. ]=], + expand_cb = 'expand_set_rightleftcmd', full_name = 'rightleftcmd', redraw = { 'current_window' }, scope = { 'window' }, @@ -6634,6 +6678,7 @@ return { When 'diff' mode is active there always is vertical scroll binding, even when "ver" isn't there. ]=], + expand_cb = 'expand_set_scrollopt', full_name = 'scrollopt', list = 'onecomma', scope = { 'global' }, @@ -6687,6 +6732,7 @@ return { backwards, you cannot include the last character of a line, when starting in Normal mode and 'virtualedit' empty. ]=], + expand_cb = 'expand_set_selection', full_name = 'selection', scope = { 'global' }, short_desc = N_('what type of selection to use'), @@ -6707,6 +6753,7 @@ return { cmd when using "v", "V" or CTRL-V See |Select-mode|. ]=], + expand_cb = 'expand_set_selectmode', full_name = 'selectmode', list = 'onecomma', scope = { 'global' }, @@ -6758,6 +6805,7 @@ return { If you leave out "options" many things won't work well after restoring the session. ]=], + expand_cb = 'expand_set_sessionoptions', full_name = 'sessionoptions', list = 'onecomma', scope = { 'global' }, @@ -7292,6 +7340,7 @@ return { shm=a Abbreviation, but no loss of information. shm=at Abbreviation, and truncate message when necessary. ]=], + expand_cb = 'expand_set_shortmess', full_name = 'shortmess', list = 'flags', scope = { 'global' }, @@ -7368,6 +7417,7 @@ return { place the text. Without a custom 'statusline' or 'tabline' it will be displayed in a convenient location. ]=], + expand_cb = 'expand_set_showcmdloc', full_name = 'showcmdloc', scope = { 'global' }, short_desc = N_('change location of partial command'), @@ -7530,6 +7580,7 @@ return { This is done in order for the signcolumn appearance not appear weird during line deletion. ]=], + expand_cb = 'expand_set_signcolumn', full_name = 'signcolumn', redraw = { 'current_window' }, scope = { 'window' }, @@ -7828,6 +7879,7 @@ return { security reasons. ]=], expand = true, + expand_cb = 'expand_set_spellsuggest', full_name = 'spellsuggest', list = 'onecomma', scope = { 'global' }, @@ -7852,7 +7904,7 @@ return { designated regions of the buffer are spellchecked in this case. ]=], - expand = true, + expand_cb = 'expand_set_spelloptions', full_name = 'spelloptions', list = 'onecomma', redraw = { 'current_buffer' }, @@ -7892,6 +7944,7 @@ return { with the previous cursor position. For "screen", the text cannot always be kept on the same screen line when 'wrap' is enabled. ]=], + expand_cb = 'expand_set_splitkeep', full_name = 'splitkeep', scope = { 'global' }, short_desc = N_('determines scroll behavior for split windows'), @@ -8326,6 +8379,7 @@ return { uselast If included, jump to the previously used window when jumping to errors with |quickfix| commands. ]=], + expand_cb = 'expand_set_switchbuf', full_name = 'switchbuf', list = 'onecomma', scope = { 'global' }, @@ -8580,6 +8634,7 @@ return { match Match case smart Ignore case unless an upper case letter is used ]=], + expand_cb = 'expand_set_tagcase', full_name = 'tagcase', scope = { 'global', 'buffer' }, short_desc = N_('how to handle case when searching in tags files'), @@ -8728,6 +8783,7 @@ return { C1 Control characters 0x80...0x9F ]=], + expand_cb = 'expand_set_termpastefilter', full_name = 'termpastefilter', list = 'onecomma', scope = { 'global' }, @@ -9275,6 +9331,7 @@ return { slash |deprecated| Always enabled. Uses "/" in filenames. unix |deprecated| Always enabled. Uses "\n" line endings. ]=], + expand_cb = 'expand_set_sessionoptions', full_name = 'viewoptions', list = 'onecomma', scope = { 'global' }, @@ -9331,6 +9388,7 @@ return { not get a warning for it. When combined with other words, "none" is ignored. ]=], + expand_cb = 'expand_set_virtualedit', full_name = 'virtualedit', list = 'onecomma', redraw = { 'curswant' }, @@ -9395,6 +9453,7 @@ return { line (not an empty line) then it will not move to the next line. This makes "dl", "cl", "yl" etc. work normally. ]=], + expand_cb = 'expand_set_whichwrap', full_name = 'whichwrap', list = 'flagscomma', scope = { 'global' }, @@ -9578,8 +9637,9 @@ return { < Complete longest common string, then list alternatives. More info here: |cmdline-completion|. ]=], + expand_cb = 'expand_set_wildmode', full_name = 'wildmode', - list = 'onecomma', + list = 'onecommacolon', scope = { 'global' }, short_desc = N_("mode for 'wildchar' command-line expansion"), type = 'string', @@ -9609,6 +9669,7 @@ return { d #define f function ]=], + expand_cb = 'expand_set_wildoptions', full_name = 'wildoptions', list = 'onecomma', scope = { 'global' }, @@ -9637,6 +9698,7 @@ return { key is never used for the menu. This option is not used for <F10>; on Win32. ]=], + expand_cb = 'expand_set_winaltkeys', full_name = 'winaltkeys', scope = { 'global' }, short_desc = N_('when the windows system handles ALT keys'), @@ -9691,7 +9753,7 @@ return { { abbreviation = 'winhl', alloced = true, - cb = 'did_set_winhl', + cb = 'did_set_winhighlight', defaults = { if_true = '' }, deny_duplicates = true, desc = [=[ @@ -9713,8 +9775,9 @@ return { set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC < ]=], + expand_cb = 'expand_set_winhighlight', full_name = 'winhighlight', - list = 'onecomma', + list = 'onecommacolon', redraw = { 'current_window' }, scope = { 'window' }, short_desc = N_('Setup window-local highlights'), diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 750941da07..c8a589f96a 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -11,6 +11,7 @@ #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/diff.h" @@ -41,6 +42,7 @@ #include "nvim/optionstr.h" #include "nvim/os/os.h" #include "nvim/pos.h" +#include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/spell.h" #include "nvim/spellfile.h" @@ -72,12 +74,25 @@ static char *(p_bkc_values[]) = { "yes", "auto", "no", "breaksymlink", "breakhar static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", "copy", "ctrlg", "error", "esc", "ex", "hangul", "lang", "mess", "showmatch", "operator", "register", "shell", "spell", "wildmode", NULL }; +// Note: Keep this in sync with briopt_check() +static char *(p_briopt_values[]) = { "shift:", "min:", "sbr", "list:", "column:", NULL }; +// Note: Keep this in sync with diffopt_changed() +static char *(p_dip_values[]) = { "filler", "context:", "iblank", "icase", + "iwhite", "iwhiteall", "iwhiteeol", "horizontal", "vertical", + "closeoff", "hiddenoff", "foldcolumn:", "followwrap", "internal", + "indent-heuristic", "linematch:", "algorithm:", NULL }; +static char *(p_dip_algorithm_values[]) = { "myers", "minimal", "patience", "histogram", NULL }; static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", NULL }; static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL }; +static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; static char *(p_cmp_values[]) = { "internal", "keepascii", NULL }; +// Note: Keep this in sync with fill_culopt_flags() +static char *(p_culopt_values[]) = { "line", "screenline", "number", "both", NULL }; static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", NULL }; static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", "quickfix", "search", "tag", "insert", "undo", "jump", NULL }; +// Note: Keep this in sync with spell_check_sps() +static char *(p_sps_values[]) = { "best", "fast", "double", "expr:", "file:", "timeout:", NULL }; /// Also used for 'viewoptions'! Keep in sync with SSOP_ flags. static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "localoptions", "options", "help", "blank", "globals", "slash", "unix", "sesdir", @@ -89,7 +104,9 @@ static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vspli static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL }; static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL }; static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; -static char *(p_wop_values[]) = { "tagfile", "pum", "fuzzy", NULL }; +// Note: Keep this in sync with check_opt_wim() +static char *(p_wim_values[]) = { "full", "longest", "list", "lastused", NULL }; +static char *(p_wop_values[]) = { "fuzzy", "tagfile", "pum", NULL }; static char *(p_wak_values[]) = { "yes", "menu", "no", NULL }; static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL }; static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL }; @@ -118,7 +135,6 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; -static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL }; static char *(p_icm_values[]) = { "nosplit", "split", NULL }; static char *(p_jop_values[]) = { "stack", "view", NULL }; @@ -487,8 +503,9 @@ const char *set_string_option(const int opt_idx, void *varp_arg, const char *val secure = 1; } - const char *const errmsg = did_set_string_option(curbuf, curwin, opt_idx, varp, oldval, errbuf, - errbuflen, opt_flags, value_checked); + const char *const errmsg = did_set_string_option(curbuf, curwin, opt_idx, varp, oldval, + errbuf, errbuflen, + opt_flags, OP_NONE, value_checked); secure = secure_saved; @@ -659,6 +676,116 @@ static const char *did_set_option_listflag(char *val, char *flags, char *errbuf, return NULL; } +/// Expand an option that accepts a list of string values. +static int expand_set_opt_string(optexpand_T *args, char **values, size_t numValues, + int *numMatches, char ***matches) +{ + regmatch_T *regmatch = args->oe_regmatch; + bool include_orig_val = args->oe_include_orig_val; + char *option_val = args->oe_opt_value; + + // Assume numValues is small since they are fixed enums, so just allocate + // upfront instead of needing two passes to calculate output size. + *matches = xmalloc(sizeof(char *) * (numValues + 1)); + + int count = 0; + + if (include_orig_val && *option_val != NUL) { + (*matches)[count++] = xstrdup(option_val); + } + + for (char **val = values; *val != NULL; val++) { + if (include_orig_val && *option_val != NUL) { + if (strcmp(*val, option_val) == 0) { + continue; + } + } + if (vim_regexec(regmatch, *val, (colnr_T)0)) { + (*matches)[count++] = xstrdup(*val); + } + } + if (count == 0) { + XFREE_CLEAR(*matches); + return FAIL; + } + *numMatches = count; + return OK; +} + +static char *set_opt_callback_orig_option = NULL; +static char *((*set_opt_callback_func)(expand_T *, int)); + +/// Callback used by expand_set_opt_generic to also include the original value. +static char *expand_set_opt_callback(expand_T *xp, int idx) +{ + if (idx == 0) { + if (set_opt_callback_orig_option != NULL) { + return set_opt_callback_orig_option; + } else { + return ""; // empty strings are ignored + } + } + return set_opt_callback_func(xp, idx - 1); +} + +/// Expand an option with a callback that iterates through a list of possible names. +static int expand_set_opt_generic(optexpand_T *args, CompleteListItemGetter func, int *numMatches, + char ***matches) +{ + set_opt_callback_orig_option = args->oe_include_orig_val ? args->oe_opt_value : NULL; + set_opt_callback_func = func; + + // not using fuzzy as currently EXPAND_STRING_SETTING doesn't use it + ExpandGeneric("", args->oe_xp, args->oe_regmatch, matches, numMatches, + expand_set_opt_callback, false); + + set_opt_callback_orig_option = NULL; + set_opt_callback_func = NULL; + return OK; +} + +/// Expand an option which is a list of flags. +static int expand_set_opt_listflag(optexpand_T *args, char *flags, int *numMatches, char ***matches) +{ + char *option_val = args->oe_opt_value; + char *cmdline_val = args->oe_set_arg; + bool append = args->oe_append; + bool include_orig_val = args->oe_include_orig_val && (*option_val != NUL); + + size_t num_flags = strlen(flags); + + // Assume we only have small number of flags, so just allocate max size. + *matches = xmalloc(sizeof(char *) * (num_flags + 1)); + + int count = 0; + + if (include_orig_val) { + (*matches)[count++] = xstrdup(option_val); + } + + for (char *flag = flags; *flag != NUL; flag++) { + if (append && vim_strchr(option_val, *flag) != NULL) { + continue; + } + + if (vim_strchr(cmdline_val, *flag) == NULL) { + if (include_orig_val && option_val[1] == NUL && *flag == option_val[0]) { + // This value is already used as the first choice as it's the + // existing flag. Just skip it to avoid duplicate. + continue; + } + (*matches)[count++] = xstrnsave(flag, 1); + } + } + + if (count == 0) { + XFREE_CLEAR(*matches); + return FAIL; + } + *numMatches = count; + return OK; +} + /// The 'ambiwidth' option is changed. const char *did_set_ambiwidth(optset_T *args FUNC_ATTR_UNUSED) { @@ -668,6 +795,15 @@ const char *did_set_ambiwidth(optset_T *args FUNC_ATTR_UNUSED) return check_chars_options(); } +int expand_set_ambiwidth(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_ambw_values, + ARRAY_SIZE(p_ambw_values) - 1, + numMatches, + matches); +} + /// The 'background' option is changed. const char *did_set_background(optset_T *args FUNC_ATTR_UNUSED) { @@ -692,6 +828,15 @@ const char *did_set_background(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_background(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_bg_values, + ARRAY_SIZE(p_bg_values) - 1, + numMatches, + matches); +} + /// The 'backspace' option is changed. const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED) { @@ -705,6 +850,15 @@ const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_bs_values, + ARRAY_SIZE(p_bs_values) - 1, + numMatches, + matches); +} + /// The 'backupcopy' option is changed. const char *did_set_backupcopy(optset_T *args) { @@ -739,6 +893,15 @@ const char *did_set_backupcopy(optset_T *args) return NULL; } +int expand_set_backupcopy(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_bkc_values, + ARRAY_SIZE(p_bkc_values) - 1, + numMatches, + matches); +} + /// The 'backupext' or the 'patchmode' option is changed. const char *did_set_backupext_or_patchmode(optset_T *args FUNC_ATTR_UNUSED) { @@ -756,6 +919,15 @@ const char *did_set_belloff(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true); } +int expand_set_belloff(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_bo_values, + ARRAY_SIZE(p_bo_values) - 1, + numMatches, + matches); +} + /// The 'breakindentopt' option is changed. const char *did_set_breakindentopt(optset_T *args) { @@ -771,6 +943,15 @@ const char *did_set_breakindentopt(optset_T *args) return NULL; } +int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_briopt_values, + ARRAY_SIZE(p_briopt_values) - 1, + numMatches, + matches); +} + /// The 'bufhidden' option is changed. const char *did_set_bufhidden(optset_T *args) { @@ -778,6 +959,15 @@ const char *did_set_bufhidden(optset_T *args) return did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false); } +int expand_set_bufhidden(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_bufhidden_values, + ARRAY_SIZE(p_bufhidden_values) - 1, + numMatches, + matches); +} + /// The 'buftype' option is changed. const char *did_set_buftype(optset_T *args) { @@ -798,12 +988,30 @@ const char *did_set_buftype(optset_T *args) return NULL; } +int expand_set_buftype(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_buftype_values, + ARRAY_SIZE(p_buftype_values) - 1, + numMatches, + matches); +} + /// The 'casemap' option is changed. const char *did_set_casemap(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true); } +int expand_set_casemap(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_cmp_values, + ARRAY_SIZE(p_cmp_values) - 1, + numMatches, + matches); +} + /// The global 'listchars' or 'fillchars' option is changed. static const char *did_set_global_listfillchars(win_T *win, char *val, bool opt_lcs, int opt_flags) { @@ -867,6 +1075,17 @@ const char *did_set_chars_option(optset_T *args) return errmsg; } +/// Expand 'fillchars' or 'listchars' option value. +int expand_set_chars_option(optexpand_T *args, int *numMatches, char ***matches) +{ + char **varp = (char **)args->oe_varp; + bool is_lcs = (varp == &p_lcs || varp == &curwin->w_p_lcs); + return expand_set_opt_generic(args, + is_lcs ? get_listchars_name : get_fillchars_name, + numMatches, + matches); +} + /// The 'cinoptions' option is changed. const char *did_set_cinoptions(optset_T *args FUNC_ATTR_UNUSED) { @@ -882,6 +1101,15 @@ const char *did_set_clipboard(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true); } +int expand_set_clipboard(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_cb_values, + ARRAY_SIZE(p_cb_values) - 1, + numMatches, + matches); +} + /// The 'colorcolumn' option is changed. const char *did_set_colorcolumn(optset_T *args) { @@ -972,6 +1200,18 @@ const char *did_set_complete(optset_T *args) return NULL; } +int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches) +{ + static char *(p_cpt_values[]) = { + ".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U", NULL + }; + return expand_set_opt_string(args, + p_cpt_values, + ARRAY_SIZE(p_cpt_values) - 1, + numMatches, + matches); +} + /// The 'completeopt' option is changed. const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) { @@ -982,6 +1222,15 @@ const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_cot_values, + ARRAY_SIZE(p_cot_values) - 1, + numMatches, + matches); +} + #ifdef BACKSLASH_IN_FILENAME /// The 'completeslash' option is changed. const char *did_set_completeslash(optset_T *args) @@ -993,6 +1242,15 @@ const char *did_set_completeslash(optset_T *args) } return NULL; } + +int expand_set_completeslash(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_csl_values, + ARRAY_SIZE(p_csl_values) - 1, + numMatches, + matches); +} #endif /// The 'concealcursor' option is changed. @@ -1003,6 +1261,11 @@ const char *did_set_concealcursor(optset_T *args) return did_set_option_listflag(*varp, COCU_ALL, args->os_errbuf, args->os_errbuflen); } +int expand_set_concealcursor(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_listflag(args, COCU_ALL, numMatches, matches); +} + /// The 'cpoptions' option is changed. const char *did_set_cpoptions(optset_T *args) { @@ -1011,12 +1274,18 @@ const char *did_set_cpoptions(optset_T *args) return did_set_option_listflag(*varp, CPO_VI, args->os_errbuf, args->os_errbuflen); } +int expand_set_cpoptions(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_listflag(args, CPO_VI, numMatches, matches); +} + /// The 'cursorlineopt' option is changed. const char *did_set_cursorlineopt(optset_T *args) { win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; + // This could be changed to use opt_strings_flags() instead. if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) { return e_invarg; } @@ -1024,12 +1293,30 @@ const char *did_set_cursorlineopt(optset_T *args) return NULL; } +int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_culopt_values, + ARRAY_SIZE(p_culopt_values) - 1, + numMatches, + matches); +} + /// The 'debug' option is changed. const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_strings(p_debug, p_debug_values, false); } +int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_debug_values, + ARRAY_SIZE(p_debug_values) - 1, + numMatches, + matches); +} + /// The 'diffopt' option is changed. const char *did_set_diffopt(optset_T *args FUNC_ATTR_UNUSED) { @@ -1039,6 +1326,31 @@ const char *did_set_diffopt(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) +{ + expand_T *xp = args->oe_xp; + + if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern - 1) == ':') { + // Within "algorithm:", we have a subgroup of possible options. + const size_t algo_len = strlen("algorithm:"); + if (xp->xp_pattern - args->oe_set_arg >= (int)algo_len + && strncmp(xp->xp_pattern - algo_len, "algorithm:", algo_len) == 0) { + return expand_set_opt_string(args, + p_dip_algorithm_values, + ARRAY_SIZE(p_dip_algorithm_values) - 1, + numMatches, + matches); + } + return FAIL; + } + + return expand_set_opt_string(args, + p_dip_values, + ARRAY_SIZE(p_dip_values) - 1, + numMatches, + matches); +} + /// The 'display' option is changed. const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED) { @@ -1050,12 +1362,30 @@ const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_display(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_dy_values, + ARRAY_SIZE(p_dy_values) - 1, + numMatches, + matches); +} + /// The 'eadirection' option is changed. const char *did_set_eadirection(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_strings(p_ead, p_ead_values, false); } +int expand_set_eadirection(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_ead_values, + ARRAY_SIZE(p_ead_values) - 1, + numMatches, + matches); +} + /// One of the 'encoding', 'fileencoding' or 'makeencoding' /// options is changed. const char *did_set_encoding(optset_T *args) @@ -1098,6 +1428,11 @@ const char *did_set_encoding(optset_T *args) return NULL; } +int expand_set_encoding(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_generic(args, get_encoding_name, numMatches, matches); +} + /// The 'eventignore' option is changed. const char *did_set_eventignore(optset_T *args FUNC_ATTR_UNUSED) { @@ -1107,6 +1442,21 @@ const char *did_set_eventignore(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +static char *get_eventignore_name(expand_T *xp, int idx) +{ + // 'eventignore' allows special keyword "all" in addition to + // all event names. + if (idx == 0) { + return "all"; + } + return get_event_name_no_group(xp, idx - 1); +} + +int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_generic(args, get_eventignore_name, numMatches, matches); +} + /// The 'fileformat' option is changed. const char *did_set_fileformat(optset_T *args) { @@ -1130,6 +1480,15 @@ const char *did_set_fileformat(optset_T *args) return NULL; } +int expand_set_fileformat(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_ff_values, + ARRAY_SIZE(p_ff_values) - 1, + numMatches, + matches); +} + /// The 'fileformats' option is changed. const char *did_set_fileformats(optset_T *args) { @@ -1160,6 +1519,15 @@ const char *did_set_foldclose(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_strings(p_fcl, p_fcl_values, true); } +int expand_set_foldclose(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_fcl_values, + ARRAY_SIZE(p_fcl_values) - 1, + numMatches, + matches); +} + /// The 'foldcolumn' option is changed. const char *did_set_foldcolumn(optset_T *args) { @@ -1170,6 +1538,15 @@ const char *did_set_foldcolumn(optset_T *args) return NULL; } +int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_fdc_values, + ARRAY_SIZE(p_fdc_values) - 1, + numMatches, + matches); +} + /// The 'foldexpr' option is changed. const char *did_set_foldexpr(optset_T *args) { @@ -1229,12 +1606,30 @@ const char *did_set_foldmethod(optset_T *args) return NULL; } +int expand_set_foldmethod(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_fdm_values, + ARRAY_SIZE(p_fdm_values) - 1, + numMatches, + matches); +} + /// The 'foldopen' option is changed. const char *did_set_foldopen(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true); } +int expand_set_foldopen(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_fdo_values, + ARRAY_SIZE(p_fdo_values) - 1, + numMatches, + matches); +} + /// The 'formatoptions' option is changed. const char *did_set_formatoptions(optset_T *args) { @@ -1243,6 +1638,11 @@ const char *did_set_formatoptions(optset_T *args) return did_set_option_listflag(*varp, FO_ALL, args->os_errbuf, args->os_errbuflen); } +int expand_set_formatoptions(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_listflag(args, FO_ALL, numMatches, matches); +} + /// The 'guicursor' option is changed. const char *did_set_guicursor(optset_T *args FUNC_ATTR_UNUSED) { @@ -1300,6 +1700,15 @@ const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_strings(p_icm, p_icm_values, false); } +int expand_set_inccommand(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_icm_values, + ARRAY_SIZE(p_icm_values) - 1, + numMatches, + matches); +} + /// The 'isident' or the 'iskeyword' or the 'isprint' or the 'isfname' option is /// changed. const char *did_set_isopt(optset_T *args) @@ -1321,6 +1730,15 @@ const char *did_set_jumpoptions(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true); } +int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_jop_values, + ARRAY_SIZE(p_jop_values) - 1, + numMatches, + matches); +} + /// The 'keymap' option has changed. const char *did_set_keymap(optset_T *args) { @@ -1384,6 +1802,15 @@ const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_keymodel(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_km_values, + ARRAY_SIZE(p_km_values) - 1, + numMatches, + matches); +} + /// The 'lispoptions' option is changed. const char *did_set_lispoptions(optset_T *args) { @@ -1395,6 +1822,16 @@ const char *did_set_lispoptions(optset_T *args) return NULL; } +int expand_set_lispoptions(optexpand_T *args, int *numMatches, char ***matches) +{ + static char *(p_lop_values[]) = { "expr:0", "expr:1", NULL }; + return expand_set_opt_string(args, + p_lop_values, + ARRAY_SIZE(p_lop_values) - 1, + numMatches, + matches); +} + /// The 'matchpairs' option is changed. const char *did_set_matchpairs(optset_T *args) { @@ -1439,12 +1876,26 @@ const char *did_set_mouse(optset_T *args) return did_set_option_listflag(*varp, MOUSE_ALL, args->os_errbuf, args->os_errbuflen); } +int expand_set_mouse(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_listflag(args, MOUSE_ALL, numMatches, matches); +} + /// The 'mousemodel' option is changed. const char *did_set_mousemodel(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_strings(p_mousem, p_mousem_values, false); } +int expand_set_mousemodel(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_mousem_values, + ARRAY_SIZE(p_mousem_values) - 1, + numMatches, + matches); +} + /// Handle setting 'mousescroll'. /// @return error message, NULL if it's OK. const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED) @@ -1510,6 +1961,16 @@ const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_mousescroll(optexpand_T *args, int *numMatches, char ***matches) +{ + static char *(p_mousescroll_values[]) = { "hor:", "ver:", NULL }; + return expand_set_opt_string(args, + p_mousescroll_values, + ARRAY_SIZE(p_mousescroll_values) - 1, + numMatches, + matches); +} + /// The 'nrformats' option is changed. const char *did_set_nrformats(optset_T *args) { @@ -1518,6 +1979,15 @@ const char *did_set_nrformats(optset_T *args) return did_set_opt_strings(*varp, p_nf_values, true); } +int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_nf_values, + ARRAY_SIZE(p_nf_values) - 1, + numMatches, + matches); +} + /// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext', /// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'. const char *did_set_optexpr(optset_T *args) @@ -1553,6 +2023,16 @@ const char *did_set_rightleftcmd(optset_T *args) return NULL; } +int expand_set_rightleftcmd(optexpand_T *args, int *numMatches, char ***matches) +{ + static char *(p_rlc_values[]) = { "search", NULL }; + return expand_set_opt_string(args, + p_rlc_values, + ARRAY_SIZE(p_rlc_values) - 1, + numMatches, + matches); +} + /// The 'rulerformat' option is changed. const char *did_set_rulerformat(optset_T *args) { @@ -1565,6 +2045,15 @@ const char *did_set_scrollopt(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_strings(p_sbo, p_scbopt_values, true); } +int expand_set_scrollopt(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_scbopt_values, + ARRAY_SIZE(p_scbopt_values) - 1, + numMatches, + matches); +} + /// The 'selection' option is changed. const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED) { @@ -1574,12 +2063,30 @@ const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_selection(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_sel_values, + ARRAY_SIZE(p_sel_values) - 1, + numMatches, + matches); +} + /// The 'selectmode' option is changed. const char *did_set_selectmode(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_strings(p_slm, p_slm_values, true); } +int expand_set_selectmode(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_slm_values, + ARRAY_SIZE(p_slm_values) - 1, + numMatches, + matches); +} + /// The 'sessionoptions' option is changed. const char *did_set_sessionoptions(optset_T *args) { @@ -1595,6 +2102,15 @@ const char *did_set_sessionoptions(optset_T *args) return NULL; } +int expand_set_sessionoptions(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_ssop_values, + ARRAY_SIZE(p_ssop_values) - 1, + numMatches, + matches); +} + static const char *did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, char *errbuf, size_t errbuflen) { @@ -1661,6 +2177,11 @@ const char *did_set_shortmess(optset_T *args) return did_set_option_listflag(*varp, SHM_ALL, args->os_errbuf, args->os_errbuflen); } +int expand_set_shortmess(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_listflag(args, SHM_ALL, numMatches, matches); +} + /// The 'showbreak' option is changed. const char *did_set_showbreak(optset_T *args) { @@ -1681,6 +2202,15 @@ const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_strings(p_sloc, p_sloc_values, true); } +int expand_set_showcmdloc(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_sloc_values, + ARRAY_SIZE(p_sloc_values) - 1, + numMatches, + matches); +} + /// The 'signcolumn' option is changed. const char *did_set_signcolumn(optset_T *args) { @@ -1700,6 +2230,15 @@ const char *did_set_signcolumn(optset_T *args) return NULL; } +int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_scl_values, + ARRAY_SIZE(p_scl_values) - 1, + numMatches, + matches); +} + /// The 'spellcapcheck' option is changed. const char *did_set_spellcapcheck(optset_T *args) { @@ -1745,6 +2284,15 @@ const char *did_set_spelloptions(optset_T *args) return NULL; } +int expand_set_spelloptions(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_spo_values, + ARRAY_SIZE(p_spo_values) - 1, + numMatches, + matches); +} + /// The 'spellsuggest' option is changed. const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED) { @@ -1754,12 +2302,30 @@ const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_spellsuggest(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_sps_values, + ARRAY_SIZE(p_sps_values) - 1, + numMatches, + matches); +} + /// The 'splitkeep' option is changed. const char *did_set_splitkeep(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_strings(p_spk, p_spk_values, false); } +int expand_set_splitkeep(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_spk_values, + ARRAY_SIZE(p_spk_values) - 1, + numMatches, + matches); +} + /// The 'statuscolumn' option is changed. const char *did_set_statuscolumn(optset_T *args) { @@ -1817,6 +2383,15 @@ const char *did_set_switchbuf(optset_T *args FUNC_ATTR_UNUSED) return did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true); } +int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_swb_values, + ARRAY_SIZE(p_swb_values) - 1, + numMatches, + matches); +} + /// The 'tabline' option is changed. const char *did_set_tabline(optset_T *args) { @@ -1850,12 +2425,30 @@ const char *did_set_tagcase(optset_T *args) return NULL; } +int expand_set_tagcase(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_tc_values, + ARRAY_SIZE(p_tc_values) - 1, + numMatches, + matches); +} + /// The 'termpastefilter' option is changed. const char *did_set_termpastefilter(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true); } +int expand_set_termpastefilter(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_tpf_values, + ARRAY_SIZE(p_tpf_values) - 1, + numMatches, + matches); +} + /// The 'titlestring' or the 'iconstring' option is changed. static const char *did_set_titleiconstring(optset_T *args, int flagval) { @@ -1988,12 +2581,28 @@ const char *did_set_virtualedit(optset_T *args) return NULL; } +int expand_set_virtualedit(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_ve_values, + ARRAY_SIZE(p_ve_values) - 1, + numMatches, + matches); +} + /// The 'whichwrap' option is changed. const char *did_set_whichwrap(optset_T *args) { char **varp = (char **)args->os_varp; - return did_set_option_listflag(*varp, WW_ALL, args->os_errbuf, args->os_errbuflen); + // Add ',' to the list flags because 'whichwrap' is a flag + // list that is comma-separated. + return did_set_option_listflag(*varp, WW_ALL ",", args->os_errbuf, args->os_errbuflen); +} + +int expand_set_whichwrap(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_listflag(args, WW_ALL, numMatches, matches); } /// The 'wildmode' option is changed. @@ -2005,12 +2614,30 @@ const char *did_set_wildmode(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_wildmode(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_wim_values, + ARRAY_SIZE(p_wim_values) - 1, + numMatches, + matches); +} + /// The 'wildoptions' option is changed. const char *did_set_wildoptions(optset_T *args FUNC_ATTR_UNUSED) { return did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true); } +int expand_set_wildoptions(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_wop_values, + ARRAY_SIZE(p_wop_values) - 1, + numMatches, + matches); +} + /// The 'winaltkeys' option is changed. const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED) { @@ -2020,13 +2647,23 @@ const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +int expand_set_winaltkeys(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_wak_values, + ARRAY_SIZE(p_wak_values) - 1, + numMatches, + matches); +} + /// The 'winbar' option is changed. const char *did_set_winbar(optset_T *args) { return did_set_statustabline_rulerformat(args, false, false); } -const char *did_set_winhl(optset_T *args) +/// The 'winhighlight' option is changed. +const char *did_set_winhighlight(optset_T *args) { win_T *win = (win_T *)args->os_win; if (!parse_winhl_opt(win)) { @@ -2035,6 +2672,11 @@ const char *did_set_winhl(optset_T *args) return NULL; } +int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_generic(args, get_highlight_name, numMatches, matches); +} + // When 'syntax' is set, load the syntax of that name static void do_syntax_autocmd(buf_T *buf, bool value_changed) { @@ -2084,11 +2726,12 @@ static void do_spelllang_source(win_T *win) /// @param errbuf buffer for errors, or NULL /// @param errbuflen length of errors buffer /// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL +/// @param op OP_ADDING/OP_PREPENDING/OP_REMOVING /// @param value_checked value was checked to be safe, no need to set P_INSECURE /// /// @return NULL for success, or an untranslated error message for an error const char *did_set_string_option(buf_T *buf, win_T *win, int opt_idx, char **varp, char *oldval, - char *errbuf, size_t errbuflen, int opt_flags, + char *errbuf, size_t errbuflen, int opt_flags, set_op_T op, bool *value_checked) { const char *errmsg = NULL; @@ -2102,6 +2745,7 @@ const char *did_set_string_option(buf_T *buf, win_T *win, int opt_idx, char **va .os_varp = varp, .os_idx = opt_idx, .os_flags = opt_flags, + .os_op = op, .os_oldval.string = oldval, .os_newval.string = *varp, .os_value_checked = false, @@ -2324,15 +2968,17 @@ static const struct chars_tab fcs_tab[] = { static lcs_chars_T lcs_chars; static const struct chars_tab lcs_tab[] = { - { &lcs_chars.eol, "eol", NUL, NUL }, - { &lcs_chars.ext, "extends", NUL, NUL }, - { &lcs_chars.nbsp, "nbsp", NUL, NUL }, - { &lcs_chars.prec, "precedes", NUL, NUL }, - { &lcs_chars.space, "space", NUL, NUL }, - { &lcs_chars.tab2, "tab", NUL, NUL }, - { &lcs_chars.lead, "lead", NUL, NUL }, - { &lcs_chars.trail, "trail", NUL, NUL }, - { &lcs_chars.conceal, "conceal", NUL, NUL }, + { &lcs_chars.eol, "eol", NUL, NUL }, + { &lcs_chars.ext, "extends", NUL, NUL }, + { &lcs_chars.nbsp, "nbsp", NUL, NUL }, + { &lcs_chars.prec, "precedes", NUL, NUL }, + { &lcs_chars.space, "space", NUL, NUL }, + { &lcs_chars.tab2, "tab", NUL, NUL }, + { &lcs_chars.lead, "lead", NUL, NUL }, + { &lcs_chars.trail, "trail", NUL, NUL }, + { &lcs_chars.conceal, "conceal", NUL, NUL }, + { NULL, "multispace", NUL, NUL }, + { NULL, "leadmultispace", NUL, NUL }, }; /// Handle setting 'listchars' or 'fillchars'. @@ -2403,54 +3049,13 @@ static const char *set_chars_option(win_T *wp, const char *value, const bool is_ int i; for (i = 0; i < entries; i++) { const size_t len = strlen(tab[i].name); - if (strncmp(p, tab[i].name, len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { - const char *s = p + len + 1; - int c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - int c2 = 0, c3 = 0; - if (tab[i].cp == &lcs_chars.tab2) { - if (*s == NUL) { - return e_invarg; - } - c2 = get_encoded_char_adv(&s); - if (c2 == 0 || char2cells(c2) > 1) { - return e_invarg; - } - if (!(*s == ',' || *s == NUL)) { - c3 = get_encoded_char_adv(&s); - if (c3 == 0 || char2cells(c3) > 1) { - return e_invarg; - } - } - } - - if (*s == ',' || *s == NUL) { - if (round > 0) { - if (tab[i].cp == &lcs_chars.tab2) { - lcs_chars.tab1 = c1; - lcs_chars.tab2 = c2; - lcs_chars.tab3 = c3; - } else if (tab[i].cp != NULL) { - *(tab[i].cp) = c1; - } - } - p = s; - break; - } + if (!(strncmp(p, tab[i].name, len) == 0 + && p[len] == ':' + && p[len + 1] != NUL)) { + continue; } - } - if (i == entries) { - const size_t len = strlen("multispace"); - const size_t len2 = strlen("leadmultispace"); - if (is_listchars - && strncmp(p, "multispace", len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { + if (is_listchars && strcmp(tab[i].name, "multispace") == 0) { const char *s = p + len + 1; if (round == 0) { // Get length of lcs-multispace string in the first round @@ -2478,11 +3083,11 @@ static const char *set_chars_option(win_T *wp, const char *value, const bool is_ } p = s; } - } else if (is_listchars - && strncmp(p, "leadmultispace", len2) == 0 - && p[len2] == ':' - && p[len2 + 1] != NUL) { - const char *s = p + len2 + 1; + break; + } + + if (is_listchars && strcmp(tab[i].name, "leadmultispace") == 0) { + const char *s = p + len + 1; if (round == 0) { // get length of lcs-leadmultispace string in first round last_lmultispace = p; @@ -2509,9 +3114,48 @@ static const char *set_chars_option(win_T *wp, const char *value, const bool is_ } p = s; } - } else { + break; + } + + const char *s = p + len + 1; + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } + int c2 = 0, c3 = 0; + if (tab[i].cp == &lcs_chars.tab2) { + if (*s == NUL) { + return e_invarg; + } + c2 = get_encoded_char_adv(&s); + if (c2 == 0 || char2cells(c2) > 1) { + return e_invarg; + } + if (!(*s == ',' || *s == NUL)) { + c3 = get_encoded_char_adv(&s); + if (c3 == 0 || char2cells(c3) > 1) { + return e_invarg; + } + } + } + + if (*s == ',' || *s == NUL) { + if (round > 0) { + if (tab[i].cp == &lcs_chars.tab2) { + lcs_chars.tab1 = c1; + lcs_chars.tab2 = c2; + lcs_chars.tab3 = c3; + } else if (tab[i].cp != NULL) { + *(tab[i].cp) = c1; + } + } + p = s; + break; + } + } + + if (i == entries) { + return e_invarg; } if (*p == ',') { @@ -2536,6 +3180,40 @@ static const char *set_chars_option(win_T *wp, const char *value, const bool is_ return NULL; // no error } +/// Handle the new value of 'fillchars'. +const char *set_fillchars_option(win_T *wp, char *val, bool apply) +{ + return set_chars_option(wp, val, false, apply); +} + +/// Handle the new value of 'listchars'. +const char *set_listchars_option(win_T *wp, char *val, bool apply) +{ + return set_chars_option(wp, val, true, apply); +} + +/// Function given to ExpandGeneric() to obtain possible arguments of the +/// 'fillchars' option. +char *get_fillchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= (int)ARRAY_SIZE(fcs_tab)) { + return NULL; + } + + return (char *)fcs_tab[idx].name; +} + +/// Function given to ExpandGeneric() to obtain possible arguments of the +/// 'listchars' option. +char *get_listchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= (int)ARRAY_SIZE(lcs_tab)) { + return NULL; + } + + return (char *)lcs_tab[idx].name; +} + /// Check all global and local values of 'listchars' and 'fillchars'. /// May set different defaults in case character widths change. /// @@ -2558,15 +3236,3 @@ const char *check_chars_options(void) } return NULL; } - -/// Handle the new value of 'fillchars'. -const char *set_fillchars_option(win_T *wp, char *val, bool apply) -{ - return set_chars_option(wp, val, false, apply); -} - -/// Handle the new value of 'listchars'. -const char *set_listchars_option(win_T *wp, char *val, bool apply) -{ - return set_chars_option(wp, val, true, apply); -} diff --git a/src/nvim/optionstr.h b/src/nvim/optionstr.h index 3520cc2061..0993b67c9a 100644 --- a/src/nvim/optionstr.h +++ b/src/nvim/optionstr.h @@ -2,6 +2,7 @@ #define NVIM_OPTIONSTR_H #include "nvim/buffer_defs.h" +#include "nvim/cmdexpand.h" #include "nvim/option_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 2ee93b4934..7b92e69821 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -403,6 +403,7 @@ int spell_check_sps(void) if (*s != NUL && !ascii_isdigit(*s)) { f = -1; } + // Note: Keep this in sync with p_sps_values. } else if (strcmp(buf, "best") == 0) { f = SPS_BEST; } else if (strcmp(buf, "fast") == 0) { diff --git a/src/nvim/vim.h b/src/nvim/vim.h index fc1f15b285..da88202525 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -164,6 +164,8 @@ enum { EXPAND_BREAKPOINT, EXPAND_SCRIPTNAMES, EXPAND_RUNTIME, + EXPAND_STRING_SETTING, + EXPAND_SETTING_SUBTRACT, EXPAND_CHECKHEALTH, EXPAND_LUA, }; diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 8c299636cc..ea3397d50d 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -941,6 +941,15 @@ describe('completion', function() end) end) + it('cmdline completion supports various string options', function() + eq('auto', funcs.getcompletion('set foldcolumn=', 'cmdline')[2]) + eq({'nosplit', 'split'}, funcs.getcompletion('set inccommand=', 'cmdline')) + eq({'ver:3,hor:6', 'hor:', 'ver:'}, funcs.getcompletion('set mousescroll=', 'cmdline')) + eq('BS', funcs.getcompletion('set termpastefilter=', 'cmdline')[2]) + eq('SpecialKey', funcs.getcompletion('set winhighlight=', 'cmdline')[1]) + eq('SpecialKey', funcs.getcompletion('set winhighlight=NonText:', 'cmdline')[1]) + end) + describe('from the commandline window', function() it('is cleared after CTRL-C', function () feed('q:') diff --git a/test/old/testdir/test_history.vim b/test/old/testdir/test_history.vim index f1c31dee04..bb6d671725 100644 --- a/test/old/testdir/test_history.vim +++ b/test/old/testdir/test_history.vim @@ -244,8 +244,13 @@ endfunc " Test for making sure the key value is not stored in history func Test_history_crypt_key() CheckFeature cryptv + call feedkeys(":set bs=2 key=abc ts=8\<CR>", 'xt') call assert_equal('set bs=2 key= ts=8', histget(':')) + + call assert_fails("call feedkeys(':set bs=2 key-=abc ts=8\<CR>', 'xt')") + call assert_equal('set bs=2 key-= ts=8', histget(':')) + set key& bs& ts& endfunc diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index c56efe8786..d524ea85a8 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -299,11 +299,11 @@ func Test_set_completion() call assert_equal('"set tabstop thesaurus thesaurusfunc', @:) " Expand current value - call feedkeys(":set fileencodings=\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"set fileencodings=ucs-bom,utf-8,default,latin1', @:) + call feedkeys(":set suffixes=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set suffixes=.bak,~,.o,.h,.info,.swp,.obj', @:) - call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) + call feedkeys(":set suffixes:\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set suffixes:.bak,~,.o,.h,.info,.swp,.obj', @:) " Expand key codes. " call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx') @@ -364,14 +364,20 @@ func Test_set_completion() call assert_equal("\"set invtabstop=", @:) " Expand options for 'spellsuggest' - call feedkeys(":set spellsuggest=best,file:xyz\<Tab>\<C-B>\"\<CR>", 'xt') - call assert_equal("\"set spellsuggest=best,file:xyz", @:) - - " Expand value for 'key' - " set key=abcd - " call feedkeys(":set key=\<Tab>\<C-B>\"\<CR>", 'xt') - " call assert_equal('"set key=*****', @:) - " set key= + call feedkeys(":set spellsuggest=file:test_options.v\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"set spellsuggest=file:test_options.vim", @:) + call feedkeys(":set spellsuggest=best,file:test_options.v\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"set spellsuggest=best,file:test_options.vim", @:) + + " Expanding value for 'key' is disallowed + if exists('+key') + set key=abcd + call feedkeys(":set key=\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set key=', @:) + call feedkeys(":set key-=\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set key-=', @:) + set key= + endif " Expand values for 'filetype' call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt') @@ -386,6 +392,286 @@ func Test_set_completion() call assert_equal('"set syntax=' .. getcompletion('a*', 'syntax')->join(), @:) endfunc +" Test handling of expanding individual string option values +func Test_set_completion_string_values() + " + " Test basic enum string options that have well-defined enum names + " + + " call assert_equal(getcompletion('set display=', 'cmdline'), ['lastline', 'truncate', 'uhex']) + call assert_equal(['lastline', 'truncate', 'uhex', 'msgsep'], getcompletion('set display=', 'cmdline')) + call assert_equal(getcompletion('set display=t', 'cmdline'), ['truncate']) + call assert_equal(getcompletion('set display=*ex*', 'cmdline'), ['uhex']) + + " Test that if a value is set, it will populate the results, but only if + " typed value is empty. + set display=uhex,lastline + " call assert_equal(getcompletion('set display=', 'cmdline'), ['uhex,lastline', 'lastline', 'truncate', 'uhex']) + call assert_equal(['uhex,lastline', 'lastline', 'truncate', 'uhex', 'msgsep'], getcompletion('set display=', 'cmdline')) + call assert_equal(getcompletion('set display=u', 'cmdline'), ['uhex']) + " If the set value is part of the enum list, it will show as the first + " result with no duplicate. + set display=uhex + " call assert_equal(getcompletion('set display=', 'cmdline'), ['uhex', 'lastline', 'truncate']) + call assert_equal(['uhex', 'lastline', 'truncate', 'msgsep'], getcompletion('set display=', 'cmdline')) + " If empty value, will just show the normal list without an empty item + set display= + " call assert_equal(getcompletion('set display=', 'cmdline'), ['lastline', 'truncate', 'uhex']) + call assert_equal(['lastline', 'truncate', 'uhex', 'msgsep'], getcompletion('set display=', 'cmdline')) + " Test escaping of the values + " call assert_equal(getcompletion('set fillchars=', 'cmdline')[0], 'vert:\|,fold:-,eob:~,lastline:@') + call assert_equal('vert:\|,foldsep:\|,fold:-', getcompletion('set fillchars=', 'cmdline')[0]) + + " Test comma-separated lists will expand after a comma. + call assert_equal(getcompletion('set display=truncate,*ex*', 'cmdline'), ['uhex']) + " Also test the positioning of the expansion is correct + call feedkeys(":set display=truncate,l\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set display=truncate,lastline', @:) + set display& + + " Test single-value options will not expand after a comma + call assert_equal(getcompletion('set ambw=single,', 'cmdline'), []) + + " Test the other simple options to make sure they have basic auto-complete, + " but don't exhaustively validate their results. + call assert_equal(getcompletion('set ambw=', 'cmdline')[0], 'single') + call assert_match('light\|dark', getcompletion('set bg=', 'cmdline')[1]) + call assert_equal(getcompletion('set backspace=', 'cmdline')[0], 'indent') + call assert_equal(getcompletion('set backupcopy=', 'cmdline')[1], 'yes') + call assert_equal(getcompletion('set belloff=', 'cmdline')[1], 'backspace') + call assert_equal(getcompletion('set briopt=', 'cmdline')[1], 'min:') + if exists('+browsedir') + call assert_equal(getcompletion('set browsedir=', 'cmdline')[1], 'current') + endif + call assert_equal(getcompletion('set bufhidden=', 'cmdline')[1], 'unload') + call assert_equal(getcompletion('set buftype=', 'cmdline')[1], 'nowrite') + call assert_equal(getcompletion('set casemap=', 'cmdline')[1], 'internal') + if exists('+clipboard') + " call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1]) + call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[0]) + endif + call assert_equal(getcompletion('set complete=', 'cmdline')[1], '.') + call assert_equal(getcompletion('set completeopt=', 'cmdline')[1], 'menu') + if exists('+completeslash') + call assert_equal(getcompletion('set completeslash=', 'cmdline')[1], 'backslash') + endif + if exists('+cryptmethod') + call assert_equal(getcompletion('set cryptmethod=', 'cmdline')[1], 'zip') + endif + if exists('+cursorlineopt') + call assert_equal(getcompletion('set cursorlineopt=', 'cmdline')[1], 'line') + endif + call assert_equal(getcompletion('set debug=', 'cmdline')[1], 'throw') + call assert_equal(getcompletion('set eadirection=', 'cmdline')[1], 'ver') + call assert_equal(getcompletion('set fileformat=', 'cmdline')[2], 'mac') + if exists('+foldclose') + call assert_equal(getcompletion('set foldclose=', 'cmdline')[0], 'all') + endif + if exists('+foldmethod') + call assert_equal(getcompletion('set foldmethod=', 'cmdline')[1], 'expr') + endif + if exists('+foldopen') + call assert_equal(getcompletion('set foldopen=', 'cmdline')[1], 'all') + endif + call assert_equal(getcompletion('set jumpoptions=', 'cmdline')[0], 'stack') + call assert_equal(getcompletion('set keymodel=', 'cmdline')[1], 'stopsel') + call assert_equal(getcompletion('set lispoptions=', 'cmdline')[1], 'expr:1') + call assert_match('popup', getcompletion('set mousemodel=', 'cmdline')[2]) + call assert_equal(getcompletion('set nrformats=', 'cmdline')[1], 'bin') + if exists('+rightleftcmd') + call assert_equal(getcompletion('set rightleftcmd=', 'cmdline')[0], 'search') + endif + call assert_equal(getcompletion('set scrollopt=', 'cmdline')[1], 'ver') + call assert_equal(getcompletion('set selection=', 'cmdline')[1], 'exclusive') + call assert_equal(getcompletion('set selectmode=', 'cmdline')[1], 'key') + if exists('+ssop') + call assert_equal(getcompletion('set ssop=', 'cmdline')[1], 'buffers') + endif + call assert_equal(getcompletion('set showcmdloc=', 'cmdline')[1], 'statusline') + if exists('+signcolumn') + call assert_equal(getcompletion('set signcolumn=', 'cmdline')[1], 'yes') + endif + if exists('+spelloptions') + call assert_equal(getcompletion('set spelloptions=', 'cmdline')[0], 'camel') + endif + if exists('+spellsuggest') + call assert_equal(getcompletion('set spellsuggest+=', 'cmdline')[0], 'best') + endif + call assert_equal(getcompletion('set splitkeep=', 'cmdline')[1], 'screen') + " call assert_equal(getcompletion('set swapsync=', 'cmdline')[1], 'sync') + call assert_equal(getcompletion('set switchbuf=', 'cmdline')[1], 'usetab') + call assert_equal(getcompletion('set tagcase=', 'cmdline')[1], 'ignore') + if exists('+termwintype') + call assert_equal(getcompletion('set termwintype=', 'cmdline')[1], 'conpty') + endif + if exists('+toolbar') + call assert_equal(getcompletion('set toolbar=', 'cmdline')[1], 'text') + endif + if exists('+tbis') + call assert_equal(getcompletion('set tbis=', 'cmdline')[2], 'medium') + endif + if exists('+ttymouse') + set ttymouse= + call assert_equal(getcompletion('set ttymouse=', 'cmdline')[1], 'xterm2') + set ttymouse& + endif + call assert_equal(getcompletion('set virtualedit=', 'cmdline')[1], 'insert') + call assert_equal(getcompletion('set wildmode=', 'cmdline')[1], 'longest') + call assert_equal(getcompletion('set wildmode=list,longest:', 'cmdline')[0], 'full') + call assert_equal(getcompletion('set wildoptions=', 'cmdline')[1], 'tagfile') + if exists('+winaltkeys') + call assert_equal(getcompletion('set winaltkeys=', 'cmdline')[1], 'yes') + endif + + " Other string options that queries the system rather than fixed enum names + call assert_equal(getcompletion('set eventignore=', 'cmdline')[0:1], ['all', 'BufAdd']) + call assert_equal(getcompletion('set fileencodings=', 'cmdline')[1], 'latin1') + " call assert_equal(getcompletion('set printoptions=', 'cmdline')[0], 'top') + " call assert_equal(getcompletion('set wincolor=', 'cmdline')[0], 'SpecialKey') + + call assert_equal('eol', getcompletion('set listchars+=', 'cmdline')[0]) + call assert_equal(['multispace', 'leadmultispace'], getcompletion('set listchars+=', 'cmdline')[-2:]) + call assert_equal('eol', getcompletion('setl listchars+=', 'cmdline')[0]) + call assert_equal(['multispace', 'leadmultispace'], getcompletion('setl listchars+=', 'cmdline')[-2:]) + call assert_equal('stl', getcompletion('set fillchars+=', 'cmdline')[0]) + call assert_equal('stl', getcompletion('setl fillchars+=', 'cmdline')[0]) + + " + " Unique string options below + " + + " keyprotocol: only auto-complete when after ':' with known protocol types + " call assert_equal(getcompletion('set keyprotocol=', 'cmdline'), [&keyprotocol]) + " call feedkeys(":set keyprotocol+=someterm:m\<Tab>\<C-B>\"\<CR>", 'xt') + " call assert_equal('"set keyprotocol+=someterm:mok2', @:) + " set keyprotocol& + + " previewpopup / completepopup + " call assert_equal(getcompletion('set previewpopup=', 'cmdline')[0], 'height:') + " call assert_equal(getcompletion('set previewpopup=highlight:End*Buffer', 'cmdline')[0], 'EndOfBuffer') + " call feedkeys(":set previewpopup+=border:\<Tab>\<C-B>\"\<CR>", 'xt') + " call assert_equal('"set previewpopup+=border:on', @:) + " call feedkeys(":set completepopup=height:10,align:\<Tab>\<C-B>\"\<CR>", 'xt') + " call assert_equal('"set completepopup=height:10,align:item', @:) + " call assert_equal(getcompletion('set completepopup=bogusname:', 'cmdline'), []) + " set previewpopup& completepopup& + + " diffopt: special handling of algorithm:<alg_list> + call assert_equal(getcompletion('set diffopt+=', 'cmdline')[0], 'filler') + call assert_equal(getcompletion('set diffopt+=iblank,foldcolumn:', 'cmdline'), []) + call assert_equal(getcompletion('set diffopt+=iblank,algorithm:pat*', 'cmdline')[0], 'patience') + + " highlight: special parsing, including auto-completing highlight groups + " after ':' + " call assert_equal(getcompletion('set hl=', 'cmdline')[0:1], [&hl, '8']) + " call assert_equal(getcompletion('set hl+=', 'cmdline')[0], '8') + " call assert_equal(getcompletion('set hl+=8', 'cmdline')[0:2], ['8:', '8b', '8i']) + " call assert_equal(getcompletion('set hl+=8b', 'cmdline')[0], '8bi') + " call assert_equal(getcompletion('set hl+=8:No*ext', 'cmdline')[0], 'NonText') + " If all the display modes are used up we should be suggesting nothing. Make + " a hl typed option with all the modes which will look like '8bi-nrsuc2d=t', + " and make sure nothing is suggested from that. + " let hl_display_modes = join( + " \ filter(map(getcompletion('set hl+=8', 'cmdline'), + " \ {idx, val -> val[1]}), + " \ {idx, val -> val != ':'}), + " \ '') + " call assert_equal(getcompletion('set hl+=8'..hl_display_modes, 'cmdline'), []) + + " + " Test flag lists + " + + " Test set=. Show the original value if nothing is typed after '='. + " Otherwise, the list should avoid showing what's already typed. + set mouse=v + call assert_equal(getcompletion('set mouse=', 'cmdline'), ['v','a','n','i','c','h','r']) + set mouse=nvi + call assert_equal(getcompletion('set mouse=', 'cmdline'), ['nvi','a','n','v','i','c','h','r']) + call assert_equal(getcompletion('set mouse=hn', 'cmdline'), ['a','v','i','c','r']) + + " Test set+=. Never show original value, and it also tries to avoid listing + " flags that's already in the option value. + call assert_equal(getcompletion('set mouse+=', 'cmdline'), ['a','c','h','r']) + call assert_equal(getcompletion('set mouse+=hn', 'cmdline'), ['a','c','r']) + call assert_equal(getcompletion('set mouse+=acrhn', 'cmdline'), []) + + " Test that the position of the expansion is correct (even if there are + " additional values after the current cursor) + call feedkeys(":set mouse=hn\<Left>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set mouse=han', @:) + set mouse& + + " Test that other flag list options have auto-complete, but don't + " exhaustively validate their results. + if exists('+concealcursor') + call assert_equal(getcompletion('set cocu=', 'cmdline')[0], 'n') + endif + call assert_equal(getcompletion('set cpo=', 'cmdline')[1], 'a') + call assert_equal(getcompletion('set fo=', 'cmdline')[1], 't') + if exists('+guioptions') + call assert_equal(getcompletion('set go=', 'cmdline')[1], '!') + endif + call assert_equal(getcompletion('set shortmess=', 'cmdline')[1], 'r') + call assert_equal(getcompletion('set whichwrap=', 'cmdline')[1], 'b') + + " + "Test set-= + " + + " Normal single-value option just shows the existing value + set ambiwidth=double + call assert_equal(getcompletion('set ambw-=', 'cmdline'), ['double']) + set ambiwidth& + + " Works on numbers and term options as well + call assert_equal(getcompletion('set laststatus-=', 'cmdline'), [string(&laststatus)]) + set t_Ce=testCe + " call assert_equal(getcompletion('set t_Ce-=', 'cmdline'), ['testCe']) + set t_Ce& + + " Comma-separated lists should present each option + set diffopt=context:123,,,,,iblank,iwhiteall + call assert_equal(getcompletion('set diffopt-=', 'cmdline'), ['context:123', 'iblank', 'iwhiteall']) + call assert_equal(getcompletion('set diffopt-=*n*', 'cmdline'), ['context:123', 'iblank']) + call assert_equal(getcompletion('set diffopt-=i', 'cmdline'), ['iblank', 'iwhiteall']) + " Don't present more than one option as it doesn't make sense in set-= + call assert_equal(getcompletion('set diffopt-=iblank,', 'cmdline'), []) + " Test empty option + set diffopt= + call assert_equal(getcompletion('set diffopt-=', 'cmdline'), []) + set diffopt& + + " Test escaping output + call assert_equal(getcompletion('set fillchars-=', 'cmdline')[0], 'vert:\|') + + " Test files with commas in name are being parsed and escaped properly + set path=has\\\ space,file\\,with\\,comma,normal_file + if exists('+completeslash') + call assert_equal(getcompletion('set path-=', 'cmdline'), ['has\\\ space', 'file\,with\,comma', 'normal_file']) + else + call assert_equal(getcompletion('set path-=', 'cmdline'), ['has\\\ space', 'file\\,with\\,comma', 'normal_file']) + endif + set path& + + " Flag list should present orig value, then individual flags + set mouse=v + call assert_equal(getcompletion('set mouse-=', 'cmdline'), ['v']) + set mouse=avn + call assert_equal(getcompletion('set mouse-=', 'cmdline'), ['avn','a','v','n']) + " Don't auto-complete when we have at least one flags already + call assert_equal(getcompletion('set mouse-=n', 'cmdline'), []) + " Test empty option + set mouse= + call assert_equal(getcompletion('set mouse-=', 'cmdline'), []) + set mouse& + + " 'whichwrap' is an odd case where it's both flag list and comma-separated + set ww=b,h + call assert_equal(getcompletion('set ww-=', 'cmdline'), ['b','h']) + set ww& +endfunc + func Test_set_option_errors() call assert_fails('set scroll=-1', 'E49:') call assert_fails('set backupcopy=', 'E474:') @@ -1433,7 +1719,7 @@ func Test_opt_cdhome() set cdhome& endfunc -func Test_set_completion_2() +func Test_set_completion_fuzzy() CheckOption termguicolors " Test default option completion |