aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/cmdline.txt20
-rw-r--r--runtime/doc/options.txt9
-rw-r--r--src/nvim/autocmd.c7
-rw-r--r--src/nvim/cmdexpand.c21
-rw-r--r--src/nvim/cmdexpand.h3
-rw-r--r--src/nvim/diff.c2
-rw-r--r--src/nvim/ex_getln.c1
-rw-r--r--src/nvim/generators/gen_options.lua5
-rw-r--r--src/nvim/indent.c1
-rw-r--r--src/nvim/mbyte.c11
-rw-r--r--src/nvim/option.c307
-rw-r--r--src/nvim/option.h9
-rw-r--r--src/nvim/option_defs.h37
-rw-r--r--src/nvim/option_vars.h12
-rw-r--r--src/nvim/options.lua66
-rw-r--r--src/nvim/optionstr.c650
-rw-r--r--src/nvim/optionstr.h1
-rw-r--r--src/nvim/spellsuggest.c1
-rw-r--r--src/nvim/vim.h2
-rw-r--r--test/old/testdir/test_options.vim296
20 files changed, 1358 insertions, 103 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/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/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..3969162b6e 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
@@ -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, &regmatch, pat, numMatches, matches, fuzzy);
+ } else if (xp->xp_context == EXPAND_STRING_SETTING) {
+ ret = ExpandStringSetting(xp, &regmatch, numMatches, matches);
+ } else if (xp->xp_context == EXPAND_SETTING_SUBTRACT) {
+ ret = ExpandSettingSubtract(xp, &regmatch, numMatches, matches);
} else if (xp->xp_context == EXPAND_MAPPINGS) {
ret = ExpandMappings(pat, &regmatch, numMatches, matches);
} else if (xp->xp_context == EXPAND_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/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..89e797d580 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -888,7 +888,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
@@ -5305,8 +5305,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 +5407,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 +5424,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 +5484,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 +5495,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;
}
}
}
@@ -5503,9 +5552,9 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
///
/// If "test_only" is false and "fuzzy" is true and if "str" fuzzy matches
/// "fuzzystr", then stores the match details in fuzmatch[idx] and returns true.
-static bool match_str(char *const str, regmatch_T *const regmatch, char **const matches,
- const int idx, const bool test_only, const bool fuzzy,
- const char *const fuzzystr, fuzmatch_str_T *const fuzmatch)
+bool match_str(char *const str, regmatch_T *const regmatch, char **const matches, const int idx,
+ const bool test_only, const bool fuzzy, const char *const fuzzystr,
+ fuzmatch_str_T *const fuzmatch)
{
if (!fuzzy) {
if (vim_regexec(regmatch, str, (colnr_T)0)) {
@@ -5609,7 +5658,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 +5704,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 +5970,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..7820cbaf4a 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
@@ -80,4 +81,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..13caba221f 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,7 @@
#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
#define HIGHLIGHT_INIT \
"8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \
@@ -183,7 +183,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 +757,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..fd7e1586ac 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' },
@@ -3045,6 +3074,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 +3152,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 +3249,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' },
@@ -4451,6 +4483,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 +4528,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 +4813,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 +4968,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 +5051,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 +5414,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 +5506,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'),
@@ -5645,6 +5684,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 +6347,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 +6675,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 +6729,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 +6750,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 +6802,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 +7337,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 +7414,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 +7577,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 +7876,7 @@ return {
security reasons.
]=],
expand = true,
+ expand_cb = 'expand_set_spellsuggest',
full_name = 'spellsuggest',
list = 'onecomma',
scope = { 'global' },
@@ -7852,7 +7901,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 +7941,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 +8376,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 +8631,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'),
@@ -9275,6 +9327,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 +9384,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 +9449,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 +9633,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 +9665,7 @@ return {
d #define
f function
]=],
+ expand_cb = 'expand_set_wildoptions',
full_name = 'wildoptions',
list = 'onecomma',
scope = { 'global' },
@@ -9637,6 +9694,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'),
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 750941da07..f820a9fc49 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 };
@@ -659,6 +675,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.
+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.
+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.
+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 +794,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 +827,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 +849,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 +892,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 +918,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 +942,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 +958,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 +987,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 +1074,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 +1100,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 +1199,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 +1221,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 +1241,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 +1260,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 +1273,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 +1292,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 +1325,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 +1361,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 +1427,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 +1441,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 +1479,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 +1518,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)
{
@@ -1229,12 +1596,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 +1628,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)
{
@@ -1321,6 +1711,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 +1783,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 +1803,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 +1857,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)
@@ -1518,6 +1950,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 +1994,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 +2016,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 +2034,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 +2073,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 +2148,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 +2173,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 +2201,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 +2255,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 +2273,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 +2354,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,6 +2396,15 @@ 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)
{
@@ -1988,12 +2543,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 +2576,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,6 +2609,15 @@ 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)
{
@@ -2536,6 +3134,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 +3190,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/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
index c56efe8786..d36e913a30 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,13 +364,17 @@ 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", @:)
+ 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", @:)
" Expand value for '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=
" Expand values for 'filetype'
@@ -386,6 +390,284 @@ 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(getcompletion('set listchars+=', 'cmdline')[0], 'eol')
+ call assert_equal(getcompletion('setl listchars+=', 'cmdline')[0], 'eol')
+ call assert_equal(getcompletion('set fillchars+=', 'cmdline')[0], 'stl')
+ call assert_equal(getcompletion('setl fillchars+=', 'cmdline')[0], 'stl')
+
+ "
+ " 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 +1715,7 @@ func Test_opt_cdhome()
set cdhome&
endfunc
-func Test_set_completion_2()
+func Test_set_completion_fuzzy()
CheckOption termguicolors
" Test default option completion