summaryrefslogtreecommitdiffstats
path: root/src/option.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/option.c')
-rw-r--r--src/option.c375
1 files changed, 319 insertions, 56 deletions
diff --git a/src/option.c b/src/option.c
index 9f20bb244b..5140c39260 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1632,7 +1632,7 @@ stropt_copy_value(
// For MS-DOS and WIN32 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 && !VIM_ISWHITE(*arg))
{
int i;
@@ -1837,7 +1837,7 @@ stropt_get_newval(
&(options[opt_idx]), OPT_GLOBAL));
else
{
- ++arg; // jump to after the '=' or ':'
+ ++arg; // joption_value2stringump to after the '=' or ':'
// Set 'keywordprg' to ":help" if an empty
// value was passed to :set by the user.
@@ -7232,8 +7232,10 @@ set_imsearch_global(void)
}
static int expand_option_idx = -1;
+static int expand_option_start_col = 0;
static char_u expand_option_name[5] = {'t', '_', NUL, NUL, NUL};
static int expand_option_flags = 0;
+static int expand_option_append = FALSE;
void
set_context_in_set_cmd(
@@ -7348,8 +7350,14 @@ set_context_in_set_cmd(
}
}
// handle "-=" and "+="
+ expand_option_append = FALSE;
+ int 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 = '=';
}
@@ -7359,22 +7367,20 @@ set_context_in_set_cmd(
xp->xp_context = EXPAND_UNSUCCESSFUL;
return;
}
- if (xp->xp_context != EXPAND_BOOL_SETTINGS && 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.
#ifdef FEAT_SYN_HL
if (options[opt_idx].var == (char_u *)&p_syn)
{
@@ -7382,7 +7388,38 @@ set_context_in_set_cmd(
return;
}
#endif
+ if (options[opt_idx].var == (char_u *)&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;
@@ -7403,10 +7440,6 @@ set_context_in_set_cmd(
else
xp->xp_backslash = XP_BS_ONE;
}
- else if (p == (char_u *)&p_ft)
- {
- xp->xp_context = EXPAND_FILETYPE;
- }
else
{
xp->xp_context = EXPAND_FILES;
@@ -7418,34 +7451,55 @@ set_context_in_set_cmd(
}
}
- // 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)
+ // 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))
{
- // count number of backslashes before ' ' or ','
- if (*p == ' ' || *p == ',')
+ for (p = arg + STRLEN(arg) - 1; p >= xp->xp_pattern; --p)
{
- 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))
+ // count number of backslashes before ' ' or ',' or ':'
+ if (*p == ' ' || *p == ',' ||
+ (*p == ':' && (flags & P_COLON)))
{
- xp->xp_pattern = p + 1;
- break;
+ 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;
+ }
}
}
+ }
+
+ // 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.
#ifdef FEAT_SPELL
- // for 'spellsuggest' start at "file:"
- if (options[opt_idx].var == (char_u *)&p_sps
- && STRNCMP(p, "file:", 5) == 0)
+ if (options[opt_idx].var == (char_u *)&p_sps)
+ {
+ if (STRNCMP(xp->xp_pattern, "file:", 5) == 0)
{
- xp->xp_pattern = p + 5;
- break;
+ xp->xp_pattern += 5;
+ return;
+ }
+ else if (options[expand_option_idx].opt_expand_cb != NULL)
+ {
+ xp->xp_context = EXPAND_STRING_SETTING;
}
-#endif
}
+#endif
}
/*
@@ -7464,7 +7518,7 @@ set_context_in_set_cmd(
* 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 int
+ int
match_str(
char_u *str,
regmatch_T *regmatch,
@@ -7711,6 +7765,37 @@ ExpandSettings(
return OK;
}
+// 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_u*
+escape_option_str_cmdline(char_u *var)
+{
+ char_u *buf;
+
+ // A backslash is required before some characters. This is the reverse of
+ // what happens in do_set().
+ buf = vim_strsave_escaped(var, escape_chars);
+ if (buf == NULL)
+ return NULL;
+
+#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(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_u ***matches)
{
@@ -7718,7 +7803,7 @@ ExpandOldSetting(int *numMatches, char_u ***matches)
char_u *buf;
*numMatches = 0;
- *matches = ALLOC_ONE(char_u *);
+ *matches = ALLOC_MULT(char_u *, 1);
if (*matches == NULL)
return FAIL;
@@ -7739,34 +7824,211 @@ ExpandOldSetting(int *numMatches, char_u ***matches)
else if (var == NULL)
var = (char_u *)"";
- // A backslash is required before some characters. This is the reverse of
- // what happens in do_set().
- buf = vim_strsave_escaped(var, escape_chars);
-
+ buf = escape_option_str_cmdline(var);
if (buf == NULL)
{
VIM_CLEAR(*matches);
return FAIL;
}
-#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(var[2])
- && (var[2] != '\\' || (var == buf && var[4] != '\\')))
- STRMOVE(var, var + 1);
-#endif
-
- *matches[0] = buf;
+ (*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_u ***matches)
+{
+ char_u *var = NULL; // init for GCC
+ char_u *buf;
+
+ 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;
+ args.oe_varp = get_varp_scope(&options[expand_option_idx], expand_option_flags);
+ args.oe_append = expand_option_append;
+ args.oe_regmatch = regmatch;
+ args.oe_xp = xp;
+ args.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);
+ var = NameBuff;
+ buf = escape_option_str_cmdline(var);
+ if (buf == NULL)
+ return FAIL;
+
+ args.oe_opt_value = buf;
+
+ int num_ret = options[expand_option_idx].opt_expand_cb(&args, numMatches, matches);
+
+ vim_free(buf);
+ return num_ret;
+}
+
+/*
+ * Expansion handler for :set-=
+ */
+ int
+ExpandSettingSubtract(
+ expand_T *xp,
+ regmatch_T *regmatch,
+ int *numMatches,
+ char_u ***matches)
+{
+ if (expand_option_idx < 0)
+ // term option
+ return ExpandOldSetting(numMatches, matches);
+
+ char_u *option_val = *(char_u**)get_option_varp_scope(
+ expand_option_idx, expand_option_flags);
+
+ long_u 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.
+ garray_T ga;
+
+ char_u *item;
+ char_u *option_copy;
+ char_u *next_val;
+ char_u *comma;
+
+ if (*option_val == NUL)
+ return FAIL;
+
+ // Make a copy as we need to inject null characters destructively.
+ option_copy = vim_strsave(option_val);
+ if (option_copy == NULL)
+ return FAIL;
+ next_val = option_copy;
+
+ ga_init2(&ga, sizeof(char_u *), 10);
+
+ do
+ {
+ item = next_val;
+ 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_u *buf = escape_option_str_cmdline(item);
+ if (buf == NULL)
+ {
+ vim_free(option_copy);
+ ga_clear_strings(&ga);
+ return FAIL;
+ }
+ if (ga_add_string(&ga, buf) != OK)
+ {
+ vim_free(buf);
+ break;
+ }
+ } while (next_val != NULL);
+
+ vim_free(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;
+ }
+
+ int num_flags = STRLEN(option_val);
+ if (num_flags == 0)
+ return FAIL;
+
+ *matches = ALLOC_MULT(char_u *, num_flags + 1);
+ if (*matches == NULL)
+ return FAIL;
+
+ int count = 0;
+ char_u *p;
+
+ p = vim_strsave(option_val);
+ if (p == NULL)
+ {
+ VIM_CLEAR(*matches);
+ return FAIL;
+ }
+ (*matches)[count++] = p;
+
+ if (num_flags > 1)
+ {
+ // If more than one flags, split the flags up and expose each
+ // character as individual choice.
+ for (char_u *flag = option_val; *flag != NUL; flag++)
+ {
+ char_u *p = vim_strnsave(flag, 1);
+ if (p == NULL)
+ break;
+ (*matches)[count++] = p;
+ }
+ }
+
+ *numMatches = count;
+ return OK;
+ }
+
+ return ExpandOldSetting(numMatches, matches);
+}
+
+/*
* Get the value for the numeric or string option *opp in a nice format into
* NameBuff[]. Must not be called with a hidden option!
*/
@@ -8097,6 +8359,7 @@ fill_culopt_flags(char_u *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;