diff options
Diffstat (limited to 'src/nvim/option.c')
-rw-r--r-- | src/nvim/option.c | 386 |
1 files changed, 294 insertions, 92 deletions
diff --git a/src/nvim/option.c b/src/nvim/option.c index eef5e66aeb..c0353e52be 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -40,6 +40,7 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/cmdexpand_defs.h" #include "nvim/cursor_shape.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" @@ -136,12 +137,12 @@ static char *p_vsts_nopaste; #define OPTION_COUNT ARRAY_SIZE(options) +/// :set boolean option prefix typedef enum { - OP_NONE = 0, - OP_ADDING, ///< "opt+=arg" - OP_PREPENDING, ///< "opt^=arg" - OP_REMOVING, ///< "opt-=arg" -} set_op_T; + PREFIX_NO = 0, ///< "no" prefix + PREFIX_NONE, ///< no prefix + PREFIX_INV, ///< "inv" prefix +} set_prefix_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.c.generated.h" @@ -557,7 +558,7 @@ static char *find_dup_item(char *origval, const char *newval, uint32_t flags) /// Set the Vi-default value of a number option. /// Used for 'lines' and 'columns'. -void set_number_default(char *name, long val) +void set_number_default(char *name, OptInt val) { int opt_idx = findoption(name); if (opt_idx >= 0) { @@ -752,8 +753,8 @@ void ex_set(exarg_T *eap) (void)do_set(eap->arg, flags); } -static void do_set_bool(int opt_idx, int opt_flags, int prefix, int nextchar, const void *varp, - const char **errmsg) +static void do_set_bool(int opt_idx, int opt_flags, set_prefix_T prefix, int nextchar, + const void *varp, const char **errmsg) { varnumber_T value; @@ -772,10 +773,12 @@ static void do_set_bool(int opt_idx, int opt_flags, int prefix, int nextchar, co value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); } } else { - if (prefix == 2) { - value = *(int *)varp ^ 1; // ":set invopt": invert + // ":set invopt": invert + // ":set opt" or ":set noopt": set or reset + if (prefix == PREFIX_INV) { + value = *(int *)varp ^ 1; } else { - value = prefix; // ":set opt" or ":set noopt": set or reset + value = prefix == PREFIX_NO ? 0 : 1; } } @@ -797,7 +800,7 @@ static void do_set_num(int opt_idx, int opt_flags, char **argp, int nextchar, co // other error arg++; if (nextchar == '&') { - value = (long)(intptr_t)options[opt_idx].def_val; + value = (varnumber_T)options[opt_idx].def_val; } else if (nextchar == '<') { if ((OptInt *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) { // for 'undolevels' NO_LOCAL_UNDOLEVEL means using the global value @@ -843,7 +846,7 @@ static void do_set_num(int opt_idx, int opt_flags, char **argp, int nextchar, co if (op == OP_REMOVING) { value = *(OptInt *)varp - value; } - *errmsg = set_num_option(opt_idx, (void *)varp, (long)value, + *errmsg = set_num_option(opt_idx, (void *)varp, value, errbuf, errbuflen, opt_flags); } @@ -888,7 +891,7 @@ static char *stropt_copy_value(char *origval, char **argp, set_op_T op, // For MS-Windows backslashes before normal file name characters // are not removed, and keep backslash at start, for "\\machine\path", // but do remove it for "\\\\machine\\path". - // The reverse is found in ExpandOldSetting(). + // The reverse is found in escape_option_str_cmdline(). while (*arg != NUL && !ascii_iswhite(*arg)) { if (*arg == '\\' && arg[1] != NUL #ifdef BACKSLASH_IN_FILENAME @@ -1168,7 +1171,7 @@ static void do_set_option_string(int opt_idx, int opt_flags, char **argp, int ne // be triggered that can cause havoc. *errmsg = did_set_string_option(curbuf, curwin, opt_idx, (char **)varp, oldval, errbuf, errbuflen, - opt_flags, value_checked); + opt_flags, op, value_checked); secure = secure_saved; @@ -1204,17 +1207,17 @@ static set_op_T get_op(const char *arg) return op; } -static int get_option_prefix(char **argp) +static set_prefix_T get_option_prefix(char **argp) { if (strncmp(*argp, "no", 2) == 0) { *argp += 2; - return 0; + return PREFIX_NO; } else if (strncmp(*argp, "inv", 3) == 0) { *argp += 3; - return 2; + return PREFIX_INV; } - return 1; + return PREFIX_NONE; } /// @param[in] arg Pointer to start option name @@ -1273,11 +1276,11 @@ static int parse_option_name(char *arg, int *keyp, int *lenp, int *opt_idxp) return OK; } -static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t flags, int prefix, - const char **errmsg) +static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t flags, + set_prefix_T prefix, const char **errmsg) { // Only bools can have a prefix of 'inv' or 'no' - if (!(flags & P_BOOL) && prefix != 1) { + if (!(flags & P_BOOL) && prefix != PREFIX_NONE) { *errmsg = e_invarg; return FAIL; } @@ -1325,8 +1328,8 @@ static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t fla return OK; } -static void do_set_option_value(int opt_idx, int opt_flags, char **argp, int prefix, int nextchar, - set_op_T op, uint32_t flags, void *varp, char *errbuf, +static void do_set_option_value(int opt_idx, int opt_flags, char **argp, set_prefix_T prefix, + int nextchar, set_op_T op, uint32_t flags, void *varp, char *errbuf, size_t errbuflen, const char **errmsg) { bool value_checked = false; @@ -1355,7 +1358,7 @@ static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errb size_t errbuflen, const char **errmsg) { // 1: nothing, 0: "no", 2: "inv" in front of name - int prefix = get_option_prefix(argp); + set_prefix_T prefix = get_option_prefix(argp); char *arg = *argp; @@ -1434,7 +1437,7 @@ static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errb // '=' character per "set" command line. grrr. (jw) // if (nextchar == '?' - || (prefix == 1 + || (prefix == PREFIX_NONE && vim_strchr("=:&<", nextchar) == NULL && !(flags & P_BOOL))) { // print value @@ -1976,8 +1979,8 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) } /// Apply the OptionSet autocommand. -static void apply_optionset_autocmd(int opt_idx, long opt_flags, OptInt oldval, OptInt oldval_g, - long newval, const char *errmsg) +static void apply_optionset_autocmd(int opt_idx, int opt_flags, OptInt oldval, OptInt oldval_g, + OptInt newval, const char *errmsg) { // Don't do this while starting up, failure or recursively. if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { @@ -1988,7 +1991,7 @@ static void apply_optionset_autocmd(int opt_idx, long opt_flags, OptInt oldval, vim_snprintf(buf_old, sizeof(buf_old), "%" PRId64, oldval); vim_snprintf(buf_old_global, sizeof(buf_old_global), "%" PRId64, oldval_g); - vim_snprintf(buf_new, sizeof(buf_new), "%ld", newval); + vim_snprintf(buf_new, sizeof(buf_new), "%" PRId64, newval); vim_snprintf(buf_type, sizeof(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global"); set_vim_var_string(VV_OPTION_NEW, buf_new, -1); @@ -2899,9 +2902,9 @@ static const char *set_bool_option(const int opt_idx, char *const varp, const in options[opt_idx].flags |= P_WAS_SET; apply_optionset_autocmd(opt_idx, opt_flags, - (long)(old_value ? true : false), - (long)(old_global_value ? true : false), - (long)(value ? true : false), NULL); + (old_value ? true : false), + (old_global_value ? true : false), + (value ? true : false), NULL); if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2921,8 +2924,8 @@ static const char *set_bool_option(const int opt_idx, char *const varp, const in } /// Check the bounds of numeric options. -static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, long old_Rows, - char *errbuf, size_t errbuflen, const char *errmsg) +static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, int old_Rows, char *errbuf, + size_t errbuflen, const char *errmsg) { // Check the (new) bounds for Rows and Columns here. if (p_lines < min_rows() && full_screen) { @@ -2997,9 +3000,9 @@ static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, long ol } /// Options that need some validation. -static const char *validate_num_option(const OptInt *pp, long *valuep) +static const char *validate_num_option(const OptInt *pp, OptInt *valuep) { - long value = *valuep; + OptInt value = *valuep; // Many number options assume their value is in the signed int range. if (value < INT_MIN || value > INT_MAX) { @@ -3157,12 +3160,12 @@ static const char *validate_num_option(const OptInt *pp, long *valuep) /// @param[in] opt_flags OPT_LOCAL, OPT_GLOBAL or OPT_MODELINE. /// /// @return NULL on success, error message on error. -static const char *set_num_option(int opt_idx, void *varp, long value, char *errbuf, +static const char *set_num_option(int opt_idx, void *varp, OptInt value, char *errbuf, size_t errbuflen, int opt_flags) { OptInt old_value = *(OptInt *)varp; OptInt old_global_value = 0; // only used when setting a local and global option - long old_Rows = Rows; // remember old Rows + int old_Rows = Rows; // remember old Rows OptInt *pp = (OptInt *)varp; // Disallow changing some options from secure mode. @@ -3184,7 +3187,7 @@ static const char *set_num_option(int opt_idx, void *varp, long value, char *err return errmsg; } - *pp = (OptInt)value; + *pp = value; // Remember where the option was set. set_option_sctx_idx(opt_idx, opt_flags, current_sctx); @@ -3195,7 +3198,7 @@ static const char *set_num_option(int opt_idx, void *varp, long value, char *err .os_varp = varp, .os_flags = opt_flags, .os_oldval.number = old_value, - .os_newval.number = (OptInt)value, + .os_newval.number = value, .os_errbuf = NULL, .os_errbuflen = 0, .os_buf = curbuf, @@ -3215,7 +3218,7 @@ static const char *set_num_option(int opt_idx, void *varp, long value, char *err options[opt_idx].flags |= P_WAS_SET; apply_optionset_autocmd(opt_idx, opt_flags, old_value, old_global_value, - value, errmsg); + (int)value, errmsg); if (errmsg == NULL && options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -3786,7 +3789,7 @@ static const char *set_option(int opt_idx, void *varp, OptVal *v, int opt_flags, if (v->type == kOptValTypeBoolean) { errmsg = set_bool_option(opt_idx, varp, (int)v->data.boolean, opt_flags); } else if (v->type == kOptValTypeNumber) { - errmsg = set_num_option(opt_idx, varp, (long)v->data.number, errbuf, errbuflen, opt_flags); + errmsg = set_num_option(opt_idx, varp, v->data.number, errbuf, errbuflen, opt_flags); } else if (v->type == kOptValTypeString) { errmsg = set_string_option(opt_idx, varp, v->data.string.data, opt_flags, &value_checked, errbuf, errbuflen); @@ -5305,8 +5308,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 +5410,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 +5427,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 +5487,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 +5498,45 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) } } - // For an option that is a list of file names, find the start of the - // last file name. - for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) { - // count number of backslashes before ' ' or ',' - if (*p == ' ' || *p == ',') { - char *s = p; - while (s > xp->xp_pattern && *(s - 1) == '\\') { - s--; - } - if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3)) - || (*p == ',' && (flags & P_COMMA) && ((p - s) & 1) == 0)) { - xp->xp_pattern = p + 1; - break; + // For an option that is a list of file names, or comma/colon-separated + // values, split it by the delimiter and find the start of the current + // pattern, while accounting for backslash-escaped space/commas/colons. + // Triple-backslashed escaped file names (e.g. 'path') can also be + // delimited by space. + if ((flags & P_EXPAND) || (flags & P_COMMA) || (flags & P_COLON)) { + for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) { + // count number of backslashes before ' ' or ',' + if (*p == ' ' || *p == ',' || (*p == ':' && (flags & P_COLON))) { + char *s = p; + while (s > xp->xp_pattern && *(s - 1) == '\\') { + s--; + } + if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3)) + || (*p == ',' && (flags & P_COMMA) && ((p - s) % 1) == 0) + || (*p == ':' && (flags & P_COLON))) { + xp->xp_pattern = p + 1; + break; + } } } + } - // for 'spellsuggest' start at "file:" - if (options[opt_idx].var == &p_sps - && strncmp(p, "file:", 5) == 0) { - xp->xp_pattern = p + 5; - break; + // An option that is a list of single-character flags should always start + // at the end as we don't complete words. + if (flags & P_FLAGLIST) { + xp->xp_pattern = arg + strlen(arg); + } + + // Some options can either be using file/dir expansions, or custom value + // expansion depending on what the user typed. Unfortunately we have to + // manually handle it here to make sure we have the correct xp_context set. + // for 'spellsuggest' start at "file:" + if (options[opt_idx].var == &p_sps) { + if (strncmp(xp->xp_pattern, "file:", 5) == 0) { + xp->xp_pattern += 5; + return; + } else if (options[expand_option_idx].opt_expand_cb != NULL) { + xp->xp_context = EXPAND_STRING_SETTING; } } } @@ -5609,7 +5661,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 +5707,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 +5973,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; |