diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
| commit | 931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch) | |
| tree | d8c1843a95da5ea0bb4acc09f7e37843d9995c86 /src/nvim/option.c | |
| parent | 142d9041391780ac15b89886a54015fdc5c73995 (diff) | |
| parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
| download | rneovim-userreg.tar.gz rneovim-userreg.tar.bz2 rneovim-userreg.zip | |
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'src/nvim/option.c')
| -rw-r--r-- | src/nvim/option.c | 5335 |
1 files changed, 3028 insertions, 2307 deletions
diff --git a/src/nvim/option.c b/src/nvim/option.c index 392960f9f3..69f02bf2f6 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // User-settable options. Checklist for adding a new option: // - Put it in options.lua // - For a global option: Add a variable for it in option_defs.h. @@ -13,15 +10,13 @@ // add some code to didset_window_options(). // - For a buffer option, add some code to buf_copy_options(). // - For a buffer string option, add code to check_buf_options(). -// - If it's a numeric option, add any necessary bounds checks to -// set_num_option(). +// - If it's a numeric option, add any necessary bounds checks to check_num_option_bounds(). // - If it's a list of flags, add some code in do_set(), search for WW_ALL. // - Add documentation! doc/options.txt, and any other related places. // - Add an entry in runtime/optwin.vim. #define IN_OPTION_C #include <assert.h> -#include <ctype.h> #include <inttypes.h> #include <limits.h> #include <stdbool.h> @@ -30,38 +25,43 @@ #include <string.h> #include "auto/config.h" +#include "klib/kvec.h" +#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" -#include "nvim/ascii.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #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" #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/eval/typval_defs.h" +#include "nvim/eval/vars.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" #include "nvim/fold.h" +#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" -#include "nvim/locale.h" #include "nvim/log.h" -#include "nvim/macros.h" +#include "nvim/lua/executor.h" +#include "nvim/macros_defs.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" @@ -74,47 +74,46 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/option_defs.h" +#include "nvim/option_vars.h" #include "nvim/optionstr.h" +#include "nvim/os/input.h" +#include "nvim/os/lang.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/popupmenu.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sign_defs.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" +#include "nvim/state_defs.h" #include "nvim/strings.h" #include "nvim/tag.h" #include "nvim/terminal.h" -#include "nvim/types.h" +#include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/undo.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" -#ifdef MSWIN -# include "nvim/os/pty_conpty_win.h" + +#ifdef BACKSLASH_IN_FILENAME +# include "nvim/arglist.h" #endif -#include "nvim/api/extmark.h" -#include "nvim/api/private/helpers.h" -#include "nvim/lua/executor.h" -#include "nvim/os/input.h" -#include "nvim/os/lang.h" -static char e_unknown_option[] +static const char e_unknown_option[] = N_("E518: Unknown option"); -static char e_not_allowed_in_modeline[] +static const char e_not_allowed_in_modeline[] = N_("E520: Not allowed in a modeline"); -static char e_not_allowed_in_modeline_when_modelineexpr_is_off[] +static const char e_not_allowed_in_modeline_when_modelineexpr_is_off[] = N_("E992: Not allowed in a modeline when 'modelineexpr' is off"); -static char e_key_code_not_set[] +static const char e_key_code_not_set[] = N_("E846: Key code not set"); -static char e_number_required_after_equal[] +static const char e_number_required_after_equal[] = N_("E521: Number required after ="); -static char e_preview_window_already_exists[] +static const char e_preview_window_already_exists[] = N_("E590: A preview window already exists"); static char *p_term = NULL; @@ -123,17 +122,30 @@ static char *p_ttytype = NULL; // Saved values for when 'bin' is set. static int p_et_nobin; static int p_ml_nobin; -static long p_tw_nobin; -static long p_wm_nobin; +static OptInt p_tw_nobin; +static OptInt p_wm_nobin; // Saved values for when 'paste' is set. static int p_ai_nopaste; static int p_et_nopaste; -static long p_sts_nopaste; -static long p_tw_nopaste; -static long p_wm_nopaste; +static OptInt p_sts_nopaste; +static OptInt p_tw_nopaste; +static OptInt p_wm_nopaste; static char *p_vsts_nopaste; +#define OPTION_COUNT ARRAY_SIZE(options) + +/// :set boolean option prefix +typedef enum { + 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" +#endif + // options[] is initialized here. // The order of the options MUST be alphabetic for ":set all" and findoption(). // All option names MUST start with a lowercase letter (for findoption()). @@ -145,141 +157,190 @@ static char *p_vsts_nopaste; # include "options.generated.h" #endif -#define OPTION_COUNT ARRAY_SIZE(options) - -typedef enum { - OP_NONE = 0, - OP_ADDING, ///< "opt+=arg" - OP_PREPENDING, ///< "opt^=arg" - OP_REMOVING, ///< "opt-=arg" -} set_op_T; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "option.c.generated.h" -#endif +static char *(p_bin_dep_opts[]) = { + "textwidth", "wrapmargin", "modeline", "expandtab", NULL +}; +static char *(p_paste_dep_opts[]) = { + "autoindent", "expandtab", "ruler", "showmatch", "smarttab", + "softtabstop", "textwidth", "wrapmargin", "revins", "varsofttabstop", NULL +}; void set_init_tablocal(void) { // susy baka: cmdheight calls itself OPT_GLOBAL but is really tablocal! int ch_idx = findoption("cmdheight"); - p_ch = (long)options[ch_idx].def_val; + p_ch = (OptInt)(intptr_t)options[ch_idx].def_val; } -/// Initialize the options, first part. -/// -/// Called only once from main(), just after creating the first buffer. -/// If "clean_arg" is true, Nvim was started with --clean. -/// -/// NOTE: ELOG() etc calls are not allowed here, as log location depends on -/// env var expansion which depends on expression evaluation and other -/// editor state initialized here. Do logging in set_init_2 or later. -void set_init_1(bool clean_arg) +/// Initialize the 'shell' option to a default value. +static void set_init_default_shell(void) { - langmap_init(); - // Find default value for 'shell' option. // Don't use it if it is empty. - { - const char *shell = os_getenv("SHELL"); - if (shell != NULL) { - if (vim_strchr(shell, ' ') != NULL) { - const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL - char *const cmd = xmalloc(len); - snprintf(cmd, len, "\"%s\"", shell); - set_string_default("sh", cmd, true); - } else { - set_string_default("sh", (char *)shell, false); - } + const char *shell = os_getenv("SHELL"); + if (shell != NULL) { + if (vim_strchr(shell, ' ') != NULL) { + const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL + char *const cmd = xmalloc(len); + snprintf(cmd, len, "\"%s\"", shell); + set_string_default("sh", cmd, true); + } else { + set_string_default("sh", (char *)shell, false); } } +} - // Set the default for 'backupskip' to include environment variables for - // temp files. - { +/// Set the default for 'backupskip' to include environment variables for +/// temp files. +static void set_init_default_backupskip(void) +{ #ifdef UNIX - static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" }; + static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" }; #else - static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" }; + static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" }; #endif - garray_T ga; - int opt_idx = findoption("backupskip"); + garray_T ga; + int opt_idx = findoption("backupskip"); - ga_init(&ga, 1, 100); - for (size_t n = 0; n < ARRAY_SIZE(names); n++) { - bool mustfree = true; - char *p; + ga_init(&ga, 1, 100); + for (size_t n = 0; n < ARRAY_SIZE(names); n++) { + bool mustfree = true; + char *p; #ifdef UNIX - if (*names[n] == NUL) { + if (*names[n] == NUL) { # ifdef __APPLE__ - p = "/private/tmp"; + p = "/private/tmp"; # else - p = "/tmp"; + p = "/tmp"; # endif - mustfree = false; - } else // NOLINT(readability/braces) + mustfree = false; + } else // NOLINT(readability/braces) #endif - { - p = vim_getenv(names[n]); - } - if (p != NULL && *p != NUL) { - // First time count the NUL, otherwise count the ','. - const size_t len = strlen(p) + 3; - char *item = xmalloc(len); - xstrlcpy(item, p, len); - add_pathsep(item); - xstrlcat(item, "*", len); - if (find_dup_item(ga.ga_data, item, options[opt_idx].flags) - == NULL) { - ga_grow(&ga, (int)len); - if (!GA_EMPTY(&ga)) { - STRCAT(ga.ga_data, ","); - } - STRCAT(ga.ga_data, p); - add_pathsep(ga.ga_data); - STRCAT(ga.ga_data, "*"); - ga.ga_len += (int)len; + { + p = vim_getenv(names[n]); + } + if (p != NULL && *p != NUL) { + // First time count the NUL, otherwise count the ','. + const size_t len = strlen(p) + 3; + char *item = xmalloc(len); + xstrlcpy(item, p, len); + add_pathsep(item); + xstrlcat(item, "*", len); + if (find_dup_item(ga.ga_data, item, options[opt_idx].flags) + == NULL) { + ga_grow(&ga, (int)len); + if (!GA_EMPTY(&ga)) { + STRCAT(ga.ga_data, ","); } - xfree(item); - } - if (mustfree) { - xfree(p); + STRCAT(ga.ga_data, p); + add_pathsep(ga.ga_data); + STRCAT(ga.ga_data, "*"); + ga.ga_len += (int)len; } + xfree(item); + } + if (mustfree) { + xfree(p); } - if (ga.ga_data != NULL) { - set_string_default("bsk", ga.ga_data, true); + } + if (ga.ga_data != NULL) { + set_string_default("bsk", ga.ga_data, true); + } +} + +/// Initialize the 'cdpath' option to a default value. +static void set_init_default_cdpath(void) +{ + char *cdpath = vim_getenv("CDPATH"); + if (cdpath == NULL) { + return; + } + + char *buf = xmalloc(2 * strlen(cdpath) + 2); + buf[0] = ','; // start with ",", current dir first + int j = 1; + for (int i = 0; cdpath[i] != NUL; i++) { + if (vim_ispathlistsep(cdpath[i])) { + buf[j++] = ','; + } else { + if (cdpath[i] == ' ' || cdpath[i] == ',') { + buf[j++] = '\\'; + } + buf[j++] = cdpath[i]; } } + buf[j] = NUL; + int opt_idx = findoption("cdpath"); + if (opt_idx >= 0) { + options[opt_idx].def_val = buf; + options[opt_idx].flags |= P_DEF_ALLOCED; + } else { + xfree(buf); // cannot happen + } + xfree(cdpath); +} - { - // Initialize the 'cdpath' option's default value. - char *cdpath = vim_getenv("CDPATH"); - if (cdpath != NULL) { - char *buf = xmalloc(2 * strlen(cdpath) + 2); - { - buf[0] = ','; // start with ",", current dir first - int j = 1; - for (int i = 0; cdpath[i] != NUL; i++) { - if (vim_ispathlistsep(cdpath[i])) { - buf[j++] = ','; - } else { - if (cdpath[i] == ' ' || cdpath[i] == ',') { - buf[j++] = '\\'; - } - buf[j++] = cdpath[i]; - } - } - buf[j] = NUL; - int opt_idx = findoption("cdpath"); - if (opt_idx >= 0) { - options[opt_idx].def_val = buf; - options[opt_idx].flags |= P_DEF_ALLOCED; - } else { - xfree(buf); // cannot happen - } +/// Expand environment variables and things like "~" for the defaults. +/// If option_expand() returns non-NULL the variable is expanded. This can +/// only happen for non-indirect options. +/// Also set the default to the expanded value, so ":set" does not list +/// them. +/// Don't set the P_ALLOCED flag, because we don't want to free the +/// default. +static void set_init_expand_env(void) +{ + for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { + vimoption_T *opt = &options[opt_idx]; + if (opt->flags & P_NO_DEF_EXP) { + continue; + } + char *p; + if ((opt->flags & P_GETTEXT) && opt->var != NULL) { + p = _(*(char **)opt->var); + } else { + p = option_expand(opt_idx, NULL); + } + if (p != NULL) { + p = xstrdup(p); + *(char **)opt->var = p; + if (opt->flags & P_DEF_ALLOCED) { + xfree(opt->def_val); } - xfree(cdpath); + opt->def_val = p; + opt->flags |= P_DEF_ALLOCED; } } +} + +/// Initialize the encoding used for "default" in 'fileencodings'. +static void set_init_fenc_default(void) +{ + // enc_locale() will try to find the encoding of the current locale. + // This will be used when "default" is used as encoding specifier + // in 'fileencodings'. + char *p = enc_locale(); + if (p == NULL) { + // Use utf-8 as "default" if locale encoding can't be detected. + p = xmemdupz(S_LEN("utf-8")); + } + fenc_default = p; +} + +/// Initialize the options, first part. +/// +/// Called only once from main(), just after creating the first buffer. +/// If "clean_arg" is true, Nvim was started with --clean. +/// +/// NOTE: ELOG() etc calls are not allowed here, as log location depends on +/// env var expansion which depends on expression evaluation and other +/// editor state initialized here. Do logging in set_init_2 or later. +void set_init_1(bool clean_arg) +{ + langmap_init(); + + set_init_default_shell(); + set_init_default_backupskip(); + set_init_default_cdpath(); char *backupdir = stdpaths_user_state_subpath("backup", 2, true); const size_t backupdir_len = strlen(backupdir); @@ -328,33 +389,7 @@ void set_init_1(bool clean_arg) init_spell_chartab(); // Expand environment variables and things like "~" for the defaults. - // If option_expand() returns non-NULL the variable is expanded. This can - // only happen for non-indirect options. - // Also set the default to the expanded value, so ":set" does not list - // them. - // Don't set the P_ALLOCED flag, because we don't want to free the - // default. - for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { - vimoption_T *opt = &options[opt_idx]; - if (opt->flags & P_NO_DEF_EXP) { - continue; - } - char *p; - if ((opt->flags & P_GETTEXT) && opt->var != NULL) { - p = _(*(char **)opt->var); - } else { - p = option_expand(opt_idx, NULL); - } - if (p != NULL) { - p = xstrdup(p); - *(char **)opt->var = p; - if (opt->flags & P_DEF_ALLOCED) { - xfree(opt->def_val); - } - opt->def_val = p; - opt->flags |= P_DEF_ALLOCED; - } - } + set_init_expand_env(); save_file_ff(curbuf); // Buffer is unchanged @@ -364,22 +399,13 @@ void set_init_1(bool clean_arg) // NOTE: mlterm's author is being asked to 'set' a variable // instead of an environment variable due to inheritance. if (os_env_exists("MLTERM")) { - set_option_value_give_err("tbidi", 1L, NULL, 0); + set_option_value_give_err("tbidi", BOOLEAN_OPTVAL(true), 0); } didset_options2(); lang_init(); - - // enc_locale() will try to find the encoding of the current locale. - // This will be used when 'default' is used as encoding specifier - // in 'fileencodings' - char *p = enc_locale(); - if (p == NULL) { - // use utf-8 as 'default' if locale encoding can't be detected. - p = xmemdupz(S_LEN("utf-8")); - } - fenc_default = p; + set_init_fenc_default(); #ifdef HAVE_WORKING_LIBINTL // GNU gettext 0.10.37 supports this feature: set the codeset used for @@ -401,7 +427,7 @@ static void set_option_default(const int opt_idx, int opt_flags) // pointer to variable for current option vimoption_T *opt = &options[opt_idx]; - char_u *varp = (char_u *)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); + void *varp = get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); uint32_t flags = opt->flags; if (varp != NULL) { // skip hidden option, nothing to do for it if (flags & P_STRING) { @@ -420,19 +446,18 @@ static void set_option_default(const int opt_idx, int opt_flags) if (opt->indir == PV_SCROLL) { win_comp_scroll(curwin); } else { - long def_val = (long)opt->def_val; - if ((long *)varp == &curwin->w_p_so - || (long *)varp == &curwin->w_p_siso) { + OptInt def_val = (OptInt)(intptr_t)opt->def_val; + if ((OptInt *)varp == &curwin->w_p_so + || (OptInt *)varp == &curwin->w_p_siso) { // 'scrolloff' and 'sidescrolloff' local values have a // different default value than the global default. - *(long *)varp = -1; + *(OptInt *)varp = -1; } else { - *(long *)varp = def_val; + *(OptInt *)varp = def_val; } // May also set global value for local option. if (both) { - *(long *)get_varp_scope(opt, OPT_GLOBAL) = - def_val; + *(OptInt *)get_varp_scope(opt, OPT_GLOBAL) = def_val; } } } else { // P_BOOL @@ -531,11 +556,11 @@ 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) { - options[opt_idx].def_val = (char *)(intptr_t)val; + options[opt_idx].def_val = (void *)(intptr_t)val; } } @@ -547,14 +572,14 @@ void free_all_options(void) if (options[i].indir == PV_NONE) { // global option: free value and default value. if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) { - free_string_option(*(char **)options[i].var); + optval_free(optval_from_varp(i, options[i].var)); } if (options[i].flags & P_DEF_ALLOCED) { - free_string_option(options[i].def_val); + optval_free(optval_from_varp(i, &options[i].def_val)); } - } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) { + } else if (options[i].var != VAR_WIN) { // buffer-local option: free global value - clear_string_option((char **)options[i].var); + optval_free(optval_from_varp(i, options[i].var)); } } free_operatorfunc_option(); @@ -594,16 +619,16 @@ void set_init_3(void) // set, but only if they have not been set before. int idx_srr = findoption("srr"); int do_srr = (idx_srr < 0) - ? false - : !(options[idx_srr].flags & P_WAS_SET); + ? false + : !(options[idx_srr].flags & P_WAS_SET); int idx_sp = findoption("sp"); int do_sp = (idx_sp < 0) - ? false - : !(options[idx_sp].flags & P_WAS_SET); + ? false + : !(options[idx_sp].flags & P_WAS_SET); size_t len = 0; char *p = (char *)invocation_path_tail(p_sh, &len); - p = xstrnsave(p, len); + p = xmemdupz(p, len); { // @@ -726,69 +751,187 @@ void ex_set(exarg_T *eap) (void)do_set(eap->arg, flags); } -/// Part of do_set() for string options. -/// @return FAIL on failure, do not process further options. -static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, set_op_T op_arg, - uint32_t flags, char *varp_arg, char *errbuf, size_t errbuflen, - int *value_checked, char **errmsg) +/// Get the default value for a string option. +static char *stropt_get_default_val(int opt_idx, uint64_t flags) +{ + char *newval = options[opt_idx].def_val; + // expand environment variables and ~ since the default value was + // already expanded, only required when an environment variable was set + // later + if (newval == NULL) { + newval = empty_string_option; + } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { + char *s = option_expand(opt_idx, newval); + if (s == NULL) { + s = newval; + } + newval = xstrdup(s); + } else { + newval = xstrdup(newval); + } + return newval; +} + +/// Copy the new string value into allocated memory for the option. +/// Can't use set_string_option_direct(), because we need to remove the +/// backslashes. +static char *stropt_copy_value(char *origval, char **argp, set_op_T op, + uint32_t flags FUNC_ATTR_UNUSED) { char *arg = *argp; - set_op_T op = op_arg; - char *varp = varp_arg; - char *save_arg = NULL; - char *s = NULL; - char_u *origval_l = NULL; - char_u *origval_g = NULL; - char whichwrap[80]; - // When using ":set opt=val" for a global option - // with a local value the local value will be - // reset, use the global value here. - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - && ((int)options[opt_idx].indir & PV_BOTH)) { - varp = (char *)options[opt_idx].var; + // get a bit too much + size_t newlen = strlen(arg) + 1; + if (op != OP_NONE) { + newlen += strlen(origval) + 1; + } + char *newval = xmalloc(newlen); + char *s = newval; + + // Copy the string, skip over escaped chars. + // 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 escape_option_str_cmdline(). + while (*arg != NUL && !ascii_iswhite(*arg)) { + if (*arg == '\\' && arg[1] != NUL +#ifdef BACKSLASH_IN_FILENAME + && !((flags & P_EXPAND) + && vim_isfilec((uint8_t)arg[1]) + && !ascii_iswhite(arg[1]) + && (arg[1] != '\\' + || (s == newval && arg[2] != '\\'))) +#endif + ) { + arg++; // remove backslash + } + int i = utfc_ptr2len(arg); + if (i > 1) { + // copy multibyte char + memmove(s, arg, (size_t)i); + arg += i; + s += i; + } else { + *s++ = *arg++; + } } + *s = NUL; - // The old value is kept until we are sure that the new value is valid. - char *oldval = *(char **)varp; + *argp = arg; + return newval; +} - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); - origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); +/// Expand environment variables and ~ in string option value 'newval'. +static char *stropt_expand_envvar(int opt_idx, char *origval, char *newval, set_op_T op) +{ + char *s = option_expand(opt_idx, newval); + if (s == NULL) { + return newval; + } - // A global-local string option might have an empty option as value to - // indicate that the global value should be used. - if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == (char_u *)empty_option) { - origval_l = origval_g; - } + xfree(newval); + uint32_t newlen = (unsigned)strlen(s) + 1; + if (op != OP_NONE) { + newlen += (unsigned)strlen(origval) + 1; } + newval = xmalloc(newlen); + STRCPY(newval, s); - char *origval; - // When setting the local value of a global option, the old value may be - // the global value. - if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { - origval = *(char **)get_varp(&options[opt_idx]); + return newval; +} + +/// Concatenate the original and new values of a string option, adding a "," if +/// needed. +static void stropt_concat_with_comma(char *origval, char *newval, set_op_T op, uint32_t flags) +{ + int len = 0; + int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL); + if (op == OP_ADDING) { + len = (int)strlen(origval); + // Strip a trailing comma, would get 2. + if (comma && len > 1 + && (flags & P_ONECOMMA) == P_ONECOMMA + && origval[len - 1] == ',' + && origval[len - 2] != '\\') { + len--; + } + memmove(newval + len + comma, newval, strlen(newval) + 1); + memmove(newval, origval, (size_t)len); } else { - origval = oldval; + len = (int)strlen(newval); + STRMOVE(newval + len + comma, origval); } + if (comma) { + newval[len] = ','; + } +} - char *newval; - if (nextchar == '&') { // set to default val - newval = options[opt_idx].def_val; - // expand environment variables and ~ since the default value was - // already expanded, only required when an environment variable was set - // later - if (newval == NULL) { - newval = empty_option; - } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { - s = option_expand(opt_idx, newval); - if (s == NULL) { - s = newval; +/// Remove a value from a string option. Copy string option value in "origval" +/// to "newval" and then remove the string "strval" of length "len". +static void stropt_remove_val(char *origval, char *newval, uint32_t flags, char *strval, int len) +{ + // Remove newval[] from origval[]. (Note: "len" has been set above + // and is used here). + STRCPY(newval, origval); + if (*strval) { + // may need to remove a comma + if (flags & P_COMMA) { + if (strval == origval) { + // include comma after string + if (strval[len] == ',') { + len++; + } + } else { + // include comma before string + strval--; + len++; + } + } + STRMOVE(newval + (strval - origval), strval + len); + } +} + +/// Remove flags that appear twice in the string option value 'newval'. +static void stropt_remove_dupflags(char *newval, uint32_t flags) +{ + char *s = newval; + // Remove flags that appear twice. + for (s = newval; *s;) { + // if options have P_FLAGLIST and P_ONECOMMA such as 'whichwrap' + if (flags & P_ONECOMMA) { + if (*s != ',' && *(s + 1) == ',' + && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) { + // Remove the duplicated value and the next comma. + STRMOVE(s, s + 2); + continue; } - newval = xstrdup(s); } else { - newval = xstrdup(newval); + if ((!(flags & P_COMMA) || *s != ',') + && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) { + STRMOVE(s, s + 1); + continue; + } } + s++; + } +} + +/// Get the string value specified for a ":set" command. The following set +/// options are supported: +/// set {opt}& +/// set {opt}< +/// set {opt}={val} +/// set {opt}:{val} +static char *stropt_get_newval(int nextchar, int opt_idx, char **argp, void *varp, char *origval, + set_op_T *op_arg, uint32_t flags) +{ + char *arg = *argp; + set_op_T op = *op_arg; + char *save_arg = NULL; + char *newval; + char *s = NULL; + if (nextchar == '&') { // set to default val + newval = stropt_get_default_val(opt_idx, flags); } else if (nextchar == '<') { // set to global val newval = xstrdup(*(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL)); } else { @@ -796,123 +939,18 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, // Set 'keywordprg' to ":help" if an empty // value was passed to :set by the user. - if (varp == (char *)&p_kp && (*arg == NUL || *arg == ' ')) { + if (varp == &p_kp && (*arg == NUL || *arg == ' ')) { save_arg = arg; arg = ":help"; - } else if (varp == (char *)&p_bs && ascii_isdigit(**(char_u **)varp)) { - // Convert 'backspace' number to string, for - // adding, prepending and removing string. - int i = getdigits_int((char **)varp, true, 0); - switch (i) { - case 0: - *(char **)varp = empty_option; - break; - case 1: - *(char_u **)varp = (char_u *)xstrdup("indent,eol"); - break; - case 2: - *(char_u **)varp = (char_u *)xstrdup("indent,eol,start"); - break; - case 3: - *(char_u **)varp = (char_u *)xstrdup("indent,eol,nostop"); - break; - } - xfree(oldval); - if (origval == oldval) { - origval = *(char **)varp; - } - if (origval_l == (char_u *)oldval) { - origval_l = *(char_u **)varp; - } - if (origval_g == (char_u *)oldval) { - origval_g = *(char_u **)varp; - } - oldval = *(char **)varp; - } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) { - // Convert 'whichwrap' number to string, for backwards compatibility - // with Vim 3.0. - *whichwrap = NUL; - int i = getdigits_int(&arg, true, 0); - if (i & 1) { - xstrlcat(whichwrap, "b,", sizeof(whichwrap)); - } - if (i & 2) { - xstrlcat(whichwrap, "s,", sizeof(whichwrap)); - } - if (i & 4) { - xstrlcat(whichwrap, "h,l,", sizeof(whichwrap)); - } - if (i & 8) { - xstrlcat(whichwrap, "<,>,", sizeof(whichwrap)); - } - if (i & 16) { - xstrlcat(whichwrap, "[,],", sizeof(whichwrap)); - } - if (*whichwrap != NUL) { // remove trailing , - whichwrap[strlen(whichwrap) - 1] = NUL; - } - save_arg = arg; - arg = whichwrap; - } else if (*arg == '>' && (varp == (char *)&p_dir || varp == (char *)&p_bdir)) { - // Remove '>' before 'dir' and 'bdir', for backwards compatibility with - // version 3.0 - arg++; } // Copy the new string into allocated memory. - // Can't use set_string_option_direct(), because we need to remove the - // backslashes. - - // get a bit too much - size_t newlen = strlen(arg) + 1; - if (op != OP_NONE) { - newlen += strlen(origval) + 1; - } - newval = xmalloc(newlen); - s = newval; - - // Copy the string, skip over escaped chars. - // 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(). - while (*arg != NUL && !ascii_iswhite(*arg)) { - if (*arg == '\\' && arg[1] != NUL -#ifdef BACKSLASH_IN_FILENAME - && !((flags & P_EXPAND) - && vim_isfilec((uint8_t)arg[1]) - && !ascii_iswhite(arg[1]) - && (arg[1] != '\\' - || (s == newval && arg[2] != '\\'))) -#endif - ) { - arg++; // remove backslash - } - int i = utfc_ptr2len(arg); - if (i > 1) { - // copy multibyte char - memmove(s, arg, (size_t)i); - arg += i; - s += i; - } else { - *s++ = *arg++; - } - } - *s = NUL; + newval = stropt_copy_value(origval, &arg, op, flags); // Expand environment variables and ~. // Don't do it when adding without inserting a comma. if (op == OP_NONE || (flags & P_COMMA)) { - s = option_expand(opt_idx, newval); - if (s != NULL) { - xfree(newval); - newlen = (unsigned)strlen(s) + 1; - if (op != OP_NONE) { - newlen += (unsigned)strlen(origval) + 1; - } - newval = xmalloc(newlen); - STRCPY(newval, s); - } + newval = stropt_expand_envvar(opt_idx, origval, newval, op); } // locate newval[] in origval[] when removing it @@ -936,130 +974,439 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, // concatenate the two strings; add a ',' if needed if (op == OP_ADDING || op == OP_PREPENDING) { - int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL); - if (op == OP_ADDING) { - len = (int)strlen(origval); - // Strip a trailing comma, would get 2. - if (comma && len > 1 - && (flags & P_ONECOMMA) == P_ONECOMMA - && origval[len - 1] == ',' - && origval[len - 2] != '\\') { - len--; - } - memmove(newval + len + comma, newval, strlen(newval) + 1); - memmove(newval, origval, (size_t)len); - } else { - len = (int)strlen(newval); - STRMOVE(newval + len + comma, origval); + stropt_concat_with_comma(origval, newval, op, flags); + } else if (op == OP_REMOVING) { + // Remove newval[] from origval[]. (Note: "len" has been set above + // and is used here). + stropt_remove_val(origval, newval, flags, s, len); + } + + if (flags & P_FLAGLIST) { + // Remove flags that appear twice. + stropt_remove_dupflags(newval, flags); + } + } + + if (save_arg != NULL) { + arg = save_arg; // arg was temporarily changed, restore it + } + *argp = arg; + *op_arg = op; + + return newval; +} + +static set_op_T get_op(const char *arg) +{ + set_op_T op = OP_NONE; + if (*arg != NUL && *(arg + 1) == '=') { + if (*arg == '+') { + op = OP_ADDING; // "+=" + } else if (*arg == '^') { + op = OP_PREPENDING; // "^=" + } else if (*arg == '-') { + op = OP_REMOVING; // "-=" + } + } + return op; +} + +static set_prefix_T get_option_prefix(char **argp) +{ + if (strncmp(*argp, "no", 2) == 0) { + *argp += 2; |