diff options
Diffstat (limited to 'src/nvim/optionstr.c')
-rw-r--r-- | src/nvim/optionstr.c | 3090 |
1 files changed, 2024 insertions, 1066 deletions
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index ca50c3ab00..281ec86171 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -1,71 +1,65 @@ -// 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 - #include <assert.h> #include <stdbool.h> #include <stdint.h> #include <string.h> -#include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" +#include "nvim/cmdexpand_defs.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" -#include "nvim/eval.h" #include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/ex_getln.h" #include "nvim/fold.h" +#include "nvim/func_attr.h" #include "nvim/gettext.h" #include "nvim/globals.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/macros.h" -#include "nvim/mapping.h" +#include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/mouse.h" #include "nvim/move.h" -#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/os.h" -#include "nvim/pos.h" -#include "nvim/quickfix.h" -#include "nvim/runtime.h" -#include "nvim/screen.h" +#include "nvim/pos_defs.h" +#include "nvim/regexp.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" -#include "nvim/tag.h" -#include "nvim/ui.h" -#include "nvim/vim.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "optionstr.c.generated.h" #endif -static char e_unclosed_expression_sequence[] +static const char e_unclosed_expression_sequence[] = N_("E540: Unclosed expression sequence"); -static char e_unbalanced_groups[] - = N_("E542: unbalanced groups"); -static char e_backupext_and_patchmode_are_equal[] +static const char e_comma_required[] + = N_("E536: Comma required"); +static const char e_unbalanced_groups[] + = N_("E542: Unbalanced groups"); +static const char e_backupext_and_patchmode_are_equal[] = N_("E589: 'backupext' and 'patchmode' are equal"); -static char e_showbreak_contains_unprintable_or_wide_character[] +static const char e_showbreak_contains_unprintable_or_wide_character[] = N_("E595: 'showbreak' contains unprintable or wide character"); static char *(p_ambw_values[]) = { "single", "double", NULL }; @@ -74,12 +68,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", @@ -91,7 +98,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 }; @@ -120,20 +129,21 @@ 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 }; static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; -static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", NULL }; +static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line", + "flush", NULL }; static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL }; /// All possible flags for 'shm'. -static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, +/// the literal chars before 0 are removed flags. these are safely ignored +static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO, - SHM_SEARCHCOUNT, 0, }; + SHM_SEARCHCOUNT, 'n', 'f', 'x', 'i', 0, }; /// After setting various option values: recompute variables that depend on /// option values. @@ -146,61 +156,17 @@ void didset_string_options(void) (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); + (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); - (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); } -/// Trigger the OptionSet autocommand. -/// "opt_idx" is the index of the option being set. -/// "opt_flags" can be OPT_LOCAL etc. -/// "oldval" the old value -/// "oldval_l" the old local value (only non-NULL if global and local value are set) -/// "oldval_g" the old global value (only non-NULL if global and local value are set) -/// "newval" the new value -void trigger_optionset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, - char *oldval_g, char *newval) -{ - // Don't do this recursively. - if (oldval == NULL || newval == NULL - || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { - return; - } - - char buf_type[7]; - - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_OLD, oldval, -1); - set_vim_var_string(VV_OPTION_NEW, newval, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - if (opt_flags & OPT_LOCAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); - } - if (opt_flags & OPT_GLOBAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); - } - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - set_vim_var_string(VV_OPTION_COMMAND, "set", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); - } - if (opt_flags & OPT_MODELINE) { - set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); - } - apply_autocmds(EVENT_OPTIONSET, get_option(opt_idx)->fullname, NULL, false, NULL); - reset_v_option_vars(); -} - -static char *illegal_char(char *errbuf, size_t errbuflen, int c) +char *illegal_char(char *errbuf, size_t errbuflen, int c) { if (errbuf == NULL) { return ""; @@ -270,29 +236,28 @@ void check_buf_options(buf_T *buf) } /// Free the string allocated for an option. -/// Checks for the string being empty_option. This may happen if we're out of -/// memory, xstrdup() returned NULL, which was replaced by empty_option by -/// check_options(). +/// Checks for the string being empty_string_option. This may happen if we're out of memory, +/// xstrdup() returned NULL, which was replaced by empty_string_option by check_options(). /// Does NOT check for P_ALLOCED flag! void free_string_option(char *p) { - if (p != empty_option) { + if (p != empty_string_option) { xfree(p); } } void clear_string_option(char **pp) { - if (*pp != empty_option) { + if (*pp != empty_string_option) { xfree(*pp); } - *pp = empty_option; + *pp = empty_string_option; } void check_string_option(char **pp) { if (*pp == NULL) { - *pp = empty_option; + *pp = empty_string_option; } } @@ -324,12 +289,12 @@ static void set_string_option_global(vimoption_T *opt, char **varp) /// "set_sid" is SID_NONE don't set the scriptID. Otherwise set the scriptID to /// "set_sid". /// -/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL +/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL. +/// +/// TODO(famiu): Remove this and its win/buf variants. void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags, int set_sid) { - char *s; - char **varp; int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; int idx = opt_idx; @@ -348,11 +313,11 @@ void set_string_option_direct(const char *name, int opt_idx, const char *val, in return; } - assert((void *)opt->var != (void *)&p_shada); + assert(opt->var != &p_shada); - s = xstrdup(val); + char *s = xstrdup(val); { - varp = (char **)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); + char **varp = (char **)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); if ((opt_flags & OPT_FREE) && (opt->flags & P_ALLOCED)) { free_string_option(*varp); } @@ -369,7 +334,7 @@ void set_string_option_direct(const char *name, int opt_idx, const char *val, in // make the local value empty, so that the global value is used. if ((opt->indir & PV_BOTH) && both) { free_string_option(*varp); - *varp = empty_option; + *varp = empty_string_option; } if (set_sid != SID_NONE) { sctx_T script_ctx; @@ -402,69 +367,18 @@ void set_string_option_direct_in_win(win_T *wp, const char *name, int opt_idx, c unblock_autocmds(); } -/// Set a string option to a new value, handling the effects -/// -/// @param[in] opt_idx Option to set. -/// @param[in] value New value. -/// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or -/// #OPT_GLOBAL. -/// -/// @return NULL on success, an untranslated error message on error. -char *set_string_option(const int opt_idx, const char *const value, const int opt_flags) - FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT +/// Like set_string_option_direct(), but for a buffer-local option in "buf". +/// Blocks autocommands to avoid the old curwin becoming invalid. +void set_string_option_direct_in_buf(buf_T *buf, const char *name, int opt_idx, const char *val, + int opt_flags, int set_sid) { - vimoption_T *opt = get_option(opt_idx); - - if (opt->var == NULL) { // don't set hidden option - return NULL; - } - - char *const s = xstrdup(value); - char **const varp - = (char **)get_varp_scope(opt, ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - ? ((opt->indir & PV_BOTH) ? OPT_GLOBAL : OPT_LOCAL) - : opt_flags)); - char *const oldval = *varp; - char *oldval_l = NULL; - char *oldval_g = NULL; - - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - oldval_l = *(char **)get_varp_scope(opt, OPT_LOCAL); - oldval_g = *(char **)get_varp_scope(opt, OPT_GLOBAL); - } + buf_T *save_curbuf = curbuf; - *varp = s; - - char *const saved_oldval = xstrdup(oldval); - char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; - char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; - char *const saved_newval = xstrdup(s); - - int value_checked = false; - char *const errmsg = did_set_string_option(opt_idx, varp, oldval, - NULL, 0, - opt_flags, &value_checked); - if (errmsg == NULL) { - did_set_option(opt_idx, opt_flags, true, value_checked); - } - - // call autocommand after handling side effects - if (errmsg == NULL) { - if (!starting) { - trigger_optionset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, - saved_newval); - } - if (opt->flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(opt->fullname), - STRING_OBJ(cstr_as_string(saved_newval))); - } - } - xfree(saved_oldval); - xfree(saved_oldval_l); - xfree(saved_oldval_g); - xfree(saved_newval); - - return errmsg; + block_autocmds(); + curbuf = buf; + set_string_option_direct(name, opt_idx, val, opt_flags, set_sid); + curbuf = save_curbuf; + unblock_autocmds(); } /// Return true if "val" is a valid 'filetype' name. @@ -475,102 +389,58 @@ static bool valid_filetype(const char *val) return valid_name(val, ".-_"); } -/// Handle setting 'mousescroll'. -/// @return error message, NULL if it's OK. -static char *check_mousescroll(char *string) -{ - long vertical = -1; - long horizontal = -1; - - for (;;) { - char *end = vim_strchr(string, ','); - size_t length = end ? (size_t)(end - string) : strlen(string); - - // Both "ver:" and "hor:" are 4 bytes long. - // They should be followed by at least one digit. - if (length <= 4) { - return e_invarg; - } - - long *direction; - - if (memcmp(string, "ver:", 4) == 0) { - direction = &vertical; - } else if (memcmp(string, "hor:", 4) == 0) { - direction = &horizontal; - } else { - return e_invarg; - } - - // If the direction has already been set, this is a duplicate. - if (*direction != -1) { - return e_invarg; - } - - // Verify that only digits follow the colon. - for (size_t i = 4; i < length; i++) { - if (!ascii_isdigit(string[i])) { - return N_("E548: digit expected"); - } - } - - string += 4; - *direction = getdigits_int(&string, false, -1); - - // Num options are generally kept within the signed int range. - // We know this number won't be negative because we've already checked for - // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. - if (*direction == -1) { - return e_invarg; - } - - if (!end) { - break; - } - - string = end + 1; - } - - // If a direction wasn't set, fallback to the default value. - p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; - p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; - - return NULL; -} - -/// Handle setting 'signcolumn' for value 'val' +/// Handle setting 'signcolumn' for value 'val'. Store minimum and maximum width. /// /// @return OK when the value is valid, FAIL otherwise -static int check_signcolumn(char *val) +int check_signcolumn(win_T *wp) { + char *val = wp->w_p_scl; if (*val == NUL) { return FAIL; } - // check for basic match + if (check_opt_strings(val, p_scl_values, false) == OK) { + if (!strncmp(val, "no", 2)) { // no + wp->w_minscwidth = wp->w_maxscwidth = SCL_NO; + } else if (!strncmp(val, "nu", 2) && (wp->w_p_nu || wp->w_p_rnu)) { // number + wp->w_minscwidth = wp->w_maxscwidth = SCL_NUM; + } else if (!strncmp(val, "yes:", 4)) { // yes:<NUM> + wp->w_minscwidth = wp->w_maxscwidth = val[4] - '0'; + } else if (*val == 'y') { // yes + wp->w_minscwidth = wp->w_maxscwidth = 1; + } else if (!strncmp(val, "auto:", 5)) { // auto:<NUM> + wp->w_minscwidth = 0; + wp->w_maxscwidth = val[5] - '0'; + } else { // auto + wp->w_minscwidth = 0; + wp->w_maxscwidth = 1; + } return OK; } - // check for 'auto:<NUMBER>-<NUMBER>' - if (strlen(val) == 8 - && !strncmp(val, "auto:", 5) - && ascii_isdigit(val[5]) - && val[6] == '-' - && ascii_isdigit(val[7])) { - int min = val[5] - '0'; - int max = val[7] - '0'; - if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) { - return FAIL; - } - return OK; + if (strncmp(val, "auto:", 5) != 0 + || strlen(val) != 8 + || !ascii_isdigit(val[5]) + || val[6] != '-' + || !ascii_isdigit(val[7])) { + return FAIL; } - return FAIL; + // auto:<NUM>-<NUM> + int min = val[5] - '0'; + int max = val[7] - '0'; + if (min < 1 || max < 2 || min > 8 || min >= max) { + return FAIL; + } + + wp->w_minscwidth = min; + wp->w_maxscwidth = max; + return OK; } /// Check validity of options with the 'statusline' format. /// Return an untranslated error message or NULL. -char *check_stl_option(char *s) +const char *check_stl_option(char *s) { int groupdepth = 0; static char errbuf[80]; @@ -641,7 +511,7 @@ char *check_stl_option(char *s) /// Check for a "normal" directory or file name in some options. Disallow a /// path separator (slash and/or backslash), wildcards and characters that are /// often illegal in a file name. Be more permissive if "secure" is off. -static bool check_illegal_path_names(char *val, uint32_t flags) +bool check_illegal_path_names(char *val, uint32_t flags) { return (((flags & P_NFNAME) && strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) @@ -649,10 +519,226 @@ static bool check_illegal_path_names(char *val, uint32_t flags) && strpbrk(val, "*?[|;&<>\r\n") != NULL)); } -static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **errmsg) +/// An option that accepts a list of flags is changed. +/// e.g. 'viewoptions', 'switchbuf', 'casemap', etc. +static const char *did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list) +{ + if (opt_strings_flags(val, values, flagp, list) != OK) { + return e_invarg; + } + return NULL; +} + +/// An option that accepts a list of string values is changed. +/// e.g. 'nrformats', 'scrollopt', 'wildoptions', etc. +static const char *did_set_opt_strings(char *val, char **values, bool list) +{ + return did_set_opt_flags(val, values, NULL, list); +} + +/// An option which is a list of flags is set. Valid values are in "flags". +static const char *did_set_option_listflag(char *val, char *flags, char *errbuf, size_t errbuflen) +{ + for (char *s = val; *s; s++) { + if (vim_strchr(flags, (uint8_t)(*s)) == NULL) { + return illegal_char(errbuf, errbuflen, (uint8_t)(*s)); + } + } + + return NULL; +} + +/// Expand an option that accepts a list of string values. +static int expand_set_opt_string(optexpand_T *args, char **values, size_t numValues, + int *numMatches, char ***matches) +{ + regmatch_T *regmatch = args->oe_regmatch; + bool include_orig_val = args->oe_include_orig_val; + char *option_val = args->oe_opt_value; + + // Assume numValues is small since they are fixed enums, so just allocate + // upfront instead of needing two passes to calculate output size. + *matches = xmalloc(sizeof(char *) * (numValues + 1)); + + int count = 0; + + if (include_orig_val && *option_val != NUL) { + (*matches)[count++] = xstrdup(option_val); + } + + for (char **val = values; *val != NULL; val++) { + if (include_orig_val && *option_val != NUL) { + if (strcmp(*val, option_val) == 0) { + continue; + } + } + if (vim_regexec(regmatch, *val, 0)) { + (*matches)[count++] = xstrdup(*val); + } + } + if (count == 0) { + XFREE_CLEAR(*matches); + return FAIL; + } + *numMatches = count; + return OK; +} + +static char *set_opt_callback_orig_option = NULL; +static char *((*set_opt_callback_func)(expand_T *, int)); + +/// Callback used by expand_set_opt_generic to also include the original value. +static char *expand_set_opt_callback(expand_T *xp, int idx) +{ + if (idx == 0) { + if (set_opt_callback_orig_option != NULL) { + return set_opt_callback_orig_option; + } else { + return ""; // empty strings are ignored + } + } + return set_opt_callback_func(xp, idx - 1); +} + +/// Expand an option with a callback that iterates through a list of possible names. +static int expand_set_opt_generic(optexpand_T *args, CompleteListItemGetter func, int *numMatches, + char ***matches) +{ + set_opt_callback_orig_option = args->oe_include_orig_val ? args->oe_opt_value : NULL; + set_opt_callback_func = func; + + // not using fuzzy as currently EXPAND_STRING_SETTING doesn't use it + ExpandGeneric("", args->oe_xp, args->oe_regmatch, matches, numMatches, + expand_set_opt_callback, false); + + set_opt_callback_orig_option = NULL; + set_opt_callback_func = NULL; + return OK; +} + +/// Expand an option which is a list of flags. +static int expand_set_opt_listflag(optexpand_T *args, char *flags, int *numMatches, char ***matches) { + char *option_val = args->oe_opt_value; + char *cmdline_val = args->oe_set_arg; + bool append = args->oe_append; + bool include_orig_val = args->oe_include_orig_val && (*option_val != NUL); + + size_t num_flags = strlen(flags); + + // Assume we only have small number of flags, so just allocate max size. + *matches = xmalloc(sizeof(char *) * (num_flags + 1)); + + int count = 0; + + if (include_orig_val) { + (*matches)[count++] = xstrdup(option_val); + } + + for (char *flag = flags; *flag != NUL; flag++) { + if (append && vim_strchr(option_val, *flag) != NULL) { + continue; + } + + if (vim_strchr(cmdline_val, *flag) == NULL) { + if (include_orig_val && option_val[1] == NUL && *flag == option_val[0]) { + // This value is already used as the first choice as it's the + // existing flag. Just skip it to avoid duplicate. + continue; + } + (*matches)[count++] = xmemdupz(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) +{ + if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { + return e_invarg; + } + 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) +{ + if (check_opt_strings(p_bg, p_bg_values, false) != OK) { + return e_invarg; + } + + int dark = (*p_bg == 'd'); + + init_highlight(false, false); + + if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { + // The color scheme must have set 'background' back to another + // value, that's not what we want here. Disable the color + // scheme and set the colors again. + do_unlet(S_LEN("g:colors_name"), true); + free_string_option(p_bg); + p_bg = xstrdup((dark ? "dark" : "light")); + check_string_option(&p_bg); + init_highlight(false, false); + } + 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) +{ + if (ascii_isdigit(*p_bs)) { + if (*p_bs != '2') { + return e_invarg; + } + } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { + return e_invarg; + } + 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) +{ + buf_T *buf = (buf_T *)args->os_buf; + const char *oldval = args->os_oldval.string.data; + int opt_flags = args->os_flags; char *bkc = p_bkc; - unsigned int *flags = &bkc_flags; + unsigned *flags = &bkc_flags; if (opt_flags & OPT_LOCAL) { bkc = buf->b_p_bkc; @@ -664,7 +750,7 @@ static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **e *flags = 0; } else { if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { - *errmsg = e_invarg; + return e_invarg; } if (((*flags & BKC_AUTO) != 0) @@ -672,171 +758,526 @@ static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **e + ((*flags & BKC_NO) != 0) != 1) { // Must have exactly one of "auto", "yes" and "no". (void)opt_strings_flags(oldval, p_bkc_values, flags, true); - *errmsg = e_invarg; + return e_invarg; } } + + return NULL; } -static void did_set_backupext_or_patchmode(char **errmsg) +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) { if (strcmp(*p_bex == '.' ? p_bex + 1 : p_bex, *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { - *errmsg = e_backupext_and_patchmode_are_equal; + return e_backupext_and_patchmode_are_equal; } + + return NULL; } -static void did_set_breakindentopt(win_T *win, char **errmsg) +/// The 'belloff' option is changed. +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) +{ + win_T *win = (win_T *)args->os_win; if (briopt_check(win) == FAIL) { - *errmsg = e_invarg; + return e_invarg; } // list setting requires a redraw if (win == curwin && win->w_briopt_list) { redraw_all_later(UPD_NOT_VALID); } + + return NULL; } -static void did_set_isopt(buf_T *buf, bool *did_chartab, char **errmsg) +int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matches) { - // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] - // If the new option is invalid, use old value. - // 'lisp' option: refill g_chartab[] for '-' char - if (buf_init_chartab(buf, true) == FAIL) { - *did_chartab = true; // need to restore it below - *errmsg = e_invarg; // error in value + 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) +{ + buf_T *buf = (buf_T *)args->os_buf; + 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) +{ + buf_T *buf = (buf_T *)args->os_buf; + win_T *win = (win_T *)args->os_win; + // When 'buftype' is set, check for valid value. + if ((buf->terminal && buf->b_p_bt[0] != 't') + || (!buf->terminal && buf->b_p_bt[0] == 't') + || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) { + return e_invarg; } + if (win->w_status_height || global_stl_height()) { + win->w_redr_status = true; + redraw_later(win, UPD_VALID); + } + buf->b_help = (buf->b_p_bt[0] == 'h'); + redraw_titles(); + return NULL; } -static void did_set_helpfile(void) +int expand_set_buftype(optexpand_T *args, int *numMatches, char ***matches) { - // May compute new values for $VIM and $VIMRUNTIME - if (didset_vim) { - vim_unsetenv_ext("VIM"); + 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) +{ + const char *errmsg = NULL; + char **local_ptr = opt_lcs ? &win->w_p_lcs : &win->w_p_fcs; + + // only apply the global value to "win" when it does not have a + // local value + if (opt_lcs) { + errmsg = set_listchars_option(win, val, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); + } else { + errmsg = set_fillchars_option(win, val, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); } - if (didset_vimruntime) { - vim_unsetenv_ext("VIMRUNTIME"); + if (errmsg != NULL) { + return errmsg; + } + + // If the current window is set to use the global + // 'listchars'/'fillchars' value, clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(local_ptr); + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + // If the current window has a local value need to apply it + // again, it was changed when setting the global value. + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + if (opt_lcs) { + if (*wp->w_p_lcs == NUL) { + (void)set_listchars_option(wp, wp->w_p_lcs, true); + } + } else { + if (*wp->w_p_fcs == NUL) { + (void)set_fillchars_option(wp, wp->w_p_fcs, true); + } + } } + + redraw_all_later(UPD_NOT_VALID); + + return NULL; } -static void did_set_cursorlineopt(win_T *win, char **varp, char **errmsg) +/// The 'fillchars' option or the 'listchars' option is changed. +const char *did_set_chars_option(optset_T *args) { - if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) { - *errmsg = e_invarg; + win_T *win = (win_T *)args->os_win; + char **varp = (char **)args->os_varp; + const char *errmsg = NULL; + + if (varp == &p_lcs // global 'listchars' + || varp == &p_fcs) { // global 'fillchars' + errmsg = did_set_global_listfillchars(win, *varp, varp == &p_lcs, args->os_flags); + } else if (varp == &win->w_p_lcs) { // local 'listchars' + errmsg = set_listchars_option(win, *varp, true); + } else if (varp == &win->w_p_fcs) { // local 'fillchars' + errmsg = set_fillchars_option(win, *varp, true); } + + return errmsg; } -static void did_set_helplang(char **errmsg) +/// Expand 'fillchars' or 'listchars' option value. +int expand_set_chars_option(optexpand_T *args, int *numMatches, char ***matches) { - // Check for "", "ab", "ab,cd", etc. - for (char *s = p_hlg; *s != NUL; s += 3) { - if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { - *errmsg = e_invarg; - break; + 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) +{ + // TODO(vim): recognize errors + parse_cino(curbuf); + + return NULL; +} + +/// The 'clipboard' option is changed. +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) +{ + win_T *win = (win_T *)args->os_win; + return check_colorcolumn(win); +} + +/// The 'comments' option is changed. +const char *did_set_comments(optset_T *args) +{ + char **varp = (char **)args->os_varp; + char *errmsg = NULL; + for (char *s = *varp; *s;) { + while (*s && *s != ':') { + if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL + && !ascii_isdigit(*s) && *s != '-') { + errmsg = illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s)); + break; + } + s++; } - if (s[2] == NUL) { + if (*s++ == NUL) { + errmsg = N_("E524: Missing colon"); + } else if (*s == ',' || *s == NUL) { + errmsg = N_("E525: Zero length string"); + } + if (errmsg != NULL) { break; } + while (*s && *s != ',') { + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; + } + s = skip_to_option_part(s); } + return errmsg; } -static void did_set_highlight(char **varp, char **errmsg) +/// The 'commentstring' option is changed. +const char *did_set_commentstring(optset_T *args) { - if (strcmp(*varp, HIGHLIGHT_INIT) != 0) { - *errmsg = e_unsupportedoption; + char **varp = (char **)args->os_varp; + + if (**varp != NUL && strstr(*varp, "%s") == NULL) { + return N_("E537: 'commentstring' must be empty or contain %s"); } + return NULL; } -static void did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list, char **errmsg) +/// The 'complete' option is changed. +const char *did_set_complete(optset_T *args) { - if (opt_strings_flags(val, values, flagp, list) != OK) { - *errmsg = e_invarg; + char **varp = (char **)args->os_varp; + + // check if it is a valid value for 'complete' -- Acevedo + for (char *s = *varp; *s;) { + while (*s == ',' || *s == ' ') { + s++; + } + if (!*s) { + break; + } + if (vim_strchr(".wbuksid]tUf", (uint8_t)(*s)) == NULL) { + return illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s)); + } + if (*++s != NUL && *s != ',' && *s != ' ') { + if (s[-1] == 'k' || s[-1] == 's') { + // skip optional filename after 'k' and 's' + while (*s && *s != ',' && *s != ' ') { + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; + } + } else { + if (args->os_errbuf != NULL) { + vim_snprintf(args->os_errbuf, args->os_errbuflen, + _("E535: Illegal character after <%c>"), + *--s); + return args->os_errbuf; + } + return ""; + } + } } + return NULL; } -static void did_set_opt_strings(char *val, char **values, bool list, char **errmsg) +int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches) { - did_set_opt_flags(val, values, NULL, list, errmsg); + static char *(p_cpt_values[]) = { + ".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U", "f", NULL + }; + return expand_set_opt_string(args, + p_cpt_values, + ARRAY_SIZE(p_cpt_values) - 1, + numMatches, + matches); } -static void did_set_sessionoptions(char *oldval, char **errmsg) +/// The 'completeopt' option is changed. +const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) { - if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { - *errmsg = e_invarg; - } - if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { - // Don't allow both "sesdir" and "curdir". - (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); - *errmsg = e_invarg; + if (check_opt_strings(p_cot, p_cot_values, true) != OK) { + return e_invarg; } + completeopt_was_set(); + return NULL; } -static void did_set_ambiwidth(char **errmsg) +int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches) { - if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { - *errmsg = e_invarg; - } else { - *errmsg = check_chars_options(); - } + return expand_set_opt_string(args, + p_cot_values, + ARRAY_SIZE(p_cot_values) - 1, + numMatches, + matches); } -static void did_set_background(char **errmsg) +#ifdef BACKSLASH_IN_FILENAME +/// The 'completeslash' option is changed. +const char *did_set_completeslash(optset_T *args) { - if (check_opt_strings(p_bg, p_bg_values, false) != OK) { - *errmsg = e_invarg; - return; + buf_T *buf = (buf_T *)args->os_buf; + if (check_opt_strings(p_csl, p_csl_values, false) != OK + || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) { + return e_invarg; } + return NULL; +} - int dark = (*p_bg == 'd'); +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 - init_highlight(false, false); +/// The 'concealcursor' option is changed. +const char *did_set_concealcursor(optset_T *args) +{ + char **varp = (char **)args->os_varp; - if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { - // The color scheme must have set 'background' back to another - // value, that's not what we want here. Disable the color - // scheme and set the colors again. - do_unlet(S_LEN("g:colors_name"), true); - free_string_option(p_bg); - p_bg = xstrdup((dark ? "dark" : "light")); - check_string_option(&p_bg); - init_highlight(false, false); + 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) +{ + char **varp = (char **)args->os_varp; + + 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; } + + return NULL; } -static void did_set_wildmode(char **errmsg) +int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches) { - if (check_opt_wim() == FAIL) { - *errmsg = e_invarg; + 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) +{ + if (diffopt_changed() == FAIL) { + return e_invarg; } + return NULL; } -static void did_set_winaltkeys(char **errmsg) +int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) { - if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) { - *errmsg = e_invarg; + 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); } -static void did_set_eventignore(char **errmsg) +/// The 'display' option is changed. +const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED) { - if (check_ei() == FAIL) { - *errmsg = e_invarg; + if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { + return e_invarg; } + (void)init_chartab(); + msg_grid_validate(); + 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); } -// 'encoding', 'fileencoding' and 'makeencoding' -static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flags, char **errmsg) +/// 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) +{ + buf_T *buf = (buf_T *)args->os_buf; + char **varp = (char **)args->os_varp; + int opt_flags = args->os_flags; + // Get the global option to compare with, otherwise we would have to check + // two values for all local options. + char **gvarp = (char **)get_option_varp_scope_from(args->os_idx, OPT_GLOBAL, buf, NULL); + if (gvarp == &p_fenc) { if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) { - *errmsg = e_modifiable; - return; + return e_modifiable; } if (vim_strchr(*varp, ',') != NULL) { // No comma allowed in 'fileencoding'; catches confusing it // with 'fileencodings'. - *errmsg = e_invarg; - return; + return e_invarg; } // May show a "+" in the title now. @@ -852,19 +1293,347 @@ static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flag if (varp == &p_enc) { // only encoding=utf-8 allowed if (strcmp(p_enc, "utf-8") != 0) { - *errmsg = e_unsupportedoption; - return; + return e_unsupportedoption; } spell_reload(); } + 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) +{ + if (check_ei() == FAIL) { + return e_invarg; + } + 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); } -static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_checked, - char **errmsg) +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) +{ + buf_T *buf = (buf_T *)args->os_buf; + char **varp = (char **)args->os_varp; + const char *oldval = args->os_oldval.string.data; + int opt_flags = args->os_flags; + if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) { + return e_modifiable; + } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { + return e_invarg; + } + redraw_titles(); + // update flag in swap file + ml_setflags(buf); + // Redraw needed when switching to/from "mac": a CR in the text + // will be displayed differently. + if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') { + redraw_buf_later(buf, UPD_NOT_VALID); + } + 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); +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// fileformat options. +char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= (int)ARRAY_SIZE(p_ff_values)) { + return NULL; + } + + return p_ff_values[idx]; +} + +/// The 'fileformats' option is changed. +const char *did_set_fileformats(optset_T *args) +{ + return did_set_opt_strings(p_ffs, p_ff_values, true); +} + +/// The 'filetype' or the 'syntax' option is changed. +const char *did_set_filetype_or_syntax(optset_T *args) +{ + char **varp = (char **)args->os_varp; + if (!valid_filetype(*varp)) { - *errmsg = e_invarg; - return; + return e_invarg; + } + + args->os_value_changed = strcmp(args->os_oldval.string.data, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + args->os_value_checked = true; + + return NULL; +} + +/// The 'foldclose' option is changed. +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) +{ + char **varp = (char **)args->os_varp; + if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { + return e_invarg; + } + return NULL; +} + +int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_fdc_values, + ARRAY_SIZE(p_fdc_values) - 1, + numMatches, + matches); +} + +/// The 'foldexpr' option is changed. +const char *did_set_foldexpr(optset_T *args) +{ + win_T *win = (win_T *)args->os_win; + (void)did_set_optexpr(args); + if (foldmethodIsExpr(win)) { + foldUpdateAll(win); + } + return NULL; +} + +/// The 'foldignore' option is changed. +const char *did_set_foldignore(optset_T *args) +{ + win_T *win = (win_T *)args->os_win; + if (foldmethodIsIndent(win)) { + foldUpdateAll(win); + } + return NULL; +} + +/// The 'foldmarker' option is changed. +const char *did_set_foldmarker(optset_T *args) +{ + win_T *win = (win_T *)args->os_win; + char **varp = (char **)args->os_varp; + char *p = vim_strchr(*varp, ','); + + if (p == NULL) { + return e_comma_required; + } + + if (p == *varp || p[1] == NUL) { + return e_invarg; + } + + if (foldmethodIsMarker(win)) { + foldUpdateAll(win); + } + + return NULL; +} + +/// The 'foldmethod' option is changed. +const char *did_set_foldmethod(optset_T *args) +{ + win_T *win = (win_T *)args->os_win; + char **varp = (char **)args->os_varp; + if (check_opt_strings(*varp, p_fdm_values, false) != OK + || *win->w_p_fdm == NUL) { + return e_invarg; + } + foldUpdateAll(win); + if (foldmethodIsDiff(win)) { + newFoldLevel(); + } + 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) +{ + char **varp = (char **)args->os_varp; + + 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) +{ + return parse_shape_opt(SHAPE_CURSOR); +} + +/// The 'helpfile' option is changed. +const char *did_set_helpfile(optset_T *args FUNC_ATTR_UNUSED) +{ + // May compute new values for $VIM and $VIMRUNTIME + if (didset_vim) { + vim_unsetenv_ext("VIM"); + } + if (didset_vimruntime) { + vim_unsetenv_ext("VIMRUNTIME"); + } + return NULL; +} + +/// The 'helplang' option is changed. +const char *did_set_helplang(optset_T *args FUNC_ATTR_UNUSED) +{ + // Check for "", "ab", "ab,cd", etc. + for (char *s = p_hlg; *s != NUL; s += 3) { + if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { + return e_invarg; + } + if (s[2] == NUL) { + break; + } + } + return NULL; +} + +/// The 'highlight' option is changed. +const char *did_set_highlight(optset_T *args) +{ + char **varp = (char **)args->os_varp; + + if (strcmp(*varp, HIGHLIGHT_INIT) != 0) { + return e_unsupportedoption; + } + return NULL; +} + +/// The 'iconstring' option is changed. +const char *did_set_iconstring(optset_T *args) +{ + return did_set_titleiconstring(args, STL_IN_ICON); +} + +/// The 'inccommand' option is changed. +const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED) +{ + if (cmdpreview) { + return e_invarg; + } + return did_set_opt_strings(p_icm, p_icm_values, false); +} + +int expand_set_inccommand(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + p_icm_values, + ARRAY_SIZE(p_icm_values) - 1, + numMatches, + matches); +} + +/// The 'isident' or the 'iskeyword' or the 'isprint' or the 'isfname' option is +/// changed. +const char *did_set_isopt(optset_T *args) +{ + buf_T *buf = (buf_T *)args->os_buf; + // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] + // If the new option is invalid, use old value. + // 'lisp' option: refill g_chartab[] for '-' char + if (buf_init_chartab(buf, true) == FAIL) { + args->os_restore_chartab = true; // need to restore it below + return e_invarg; // error in value + } + return NULL; +} + +/// The 'jumpoptions' option is changed. +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) +{ + buf_T *buf = (buf_T *)args->os_buf; + char **varp = (char **)args->os_varp; + int opt_flags = args->os_flags; + + if (!valid_filetype(*varp)) { + return e_invarg; } int secure_save = secure; @@ -874,15 +1643,15 @@ static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_ch secure = 0; // load or unload key mapping tables - *errmsg = keymap_init(); + const char *errmsg = keymap_init(); secure = secure_save; // Since we check the value, there is no need to set P_INSECURE, // even when the value comes from a modeline. - *value_checked = true; + args->os_value_checked = true; - if (*errmsg == NULL) { + if (errmsg == NULL) { if (*buf->b_p_keymap != NUL) { // Installed a new keymap, switch on using it. buf->b_p_iminsert = B_IMODE_LMAP; @@ -904,29 +1673,56 @@ static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_ch } status_redraw_buf(buf); } + + return errmsg; } -static void did_set_fileformat(buf_T *buf, char **varp, const char *oldval, int opt_flags, - char **errmsg) +/// The 'keymodel' option is changed. +const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED) { - if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) { - *errmsg = e_modifiable; - } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { - *errmsg = e_invarg; - } else { - redraw_titles(); - // update flag in swap file - ml_setflags(buf); - // Redraw needed when switching to/from "mac": a CR in the text - // will be displayed differently. - if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') { - redraw_buf_later(buf, UPD_NOT_VALID); - } + if (check_opt_strings(p_km, p_km_values, true) != OK) { + return e_invarg; + } + km_stopsel = (vim_strchr(p_km, 'o') != NULL); + km_startsel = (vim_strchr(p_km, 'a') != NULL); + 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) +{ + char **varp = (char **)args->os_varp; + + if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) { + return e_invarg; } + return NULL; } -static void did_set_matchpairs(char **varp, char **errmsg) +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) +{ + char **varp = (char **)args->os_varp; + for (char *p = *varp; *p != NUL; p++) { int x2 = -1; int x3 = -1; @@ -940,97 +1736,276 @@ static void did_set_matchpairs(char **varp, char **errmsg) p += utfc_ptr2len(p); } if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { - *errmsg = e_invarg; - break; + return e_invarg; } if (*p == NUL) { break; } } + return NULL; } -static void did_set_comments(char **varp, char *errbuf, size_t errbuflen, char **errmsg) +/// The 'mkspellmem' option is changed. +const char *did_set_mkspellmem(optset_T *args FUNC_ATTR_UNUSED) { - for (char *s = *varp; *s;) { - while (*s && *s != ':') { - if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL - && !ascii_isdigit(*s) && *s != '-') { - *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s)); - break; - } - s++; + if (spell_check_msm() != OK) { + return e_invarg; + } + return NULL; +} + +/// The 'mouse' option is changed. +const char *did_set_mouse(optset_T *args) +{ + char **varp = (char **)args->os_varp; + + 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) +{ + OptInt vertical = -1; + OptInt horizontal = -1; + + char *string = p_mousescroll; + + while (true) { + char *end = vim_strchr(string, ','); + size_t length = end ? (size_t)(end - string) : strlen(string); + + // Both "ver:" and "hor:" are 4 bytes long. + // They should be followed by at least one digit. + if (length <= 4) { + return e_invarg; } - if (*s++ == NUL) { - *errmsg = N_("E524: Missing colon"); - } else if (*s == ',' || *s == NUL) { - *errmsg = N_("E525: Zero length string"); + + OptInt *direction; + + if (memcmp(string, "ver:", 4) == 0) { + direction = &vertical; + } else if (memcmp(string, "hor:", 4) == 0) { + direction = &horizontal; + } else { + return e_invarg; } - if (*errmsg != NULL) { - break; + + // If the direction has already been set, this is a duplicate. + if (*direction != -1) { + return e_invarg; } - while (*s && *s != ',') { - if (*s == '\\' && s[1] != NUL) { - s++; + + // Verify that only digits follow the colon. + for (size_t i = 4; i < length; i++) { + if (!ascii_isdigit(string[i])) { + return N_("E5080: Digit expected"); } - s++; } - s = skip_to_option_part(s); + + string += 4; + *direction = getdigits_int(&string, false, -1); + + // Num options are generally kept within the signed int range. + // We know this number won't be negative because we've already checked for + // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. + if (*direction == -1) { + return e_invarg; + } + + if (!end) { + break; + } + + string = end + 1; + } + + // If a direction wasn't set, fallback to the default value. + p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; + p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; + + return NULL; +} + +int expand_set_mousescroll(optexpand_T *args, int *numMatches, char ***matches) +{ + static char *(p_mousescroll_values[]) = { "hor:", "ver:", NULL }; + return expand_set_opt_string(args, + p_mousescroll_values, + ARRAY_SIZE(p_mousescroll_values) - 1, + numMatches, + matches); +} + +/// The 'nrformats' option is changed. +const char *did_set_nrformats(optset_T *args) +{ + char **varp = (char **)args->os_varp; + + 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) +{ + char **varp = (char **)args->os_varp; + + // If the option value starts with <SID> or s:, then replace that with + // the script identifier. + char *name = get_scriptlocal_funcname(*varp); + if (name != NULL) { + free_string_option(*varp); + *varp = name; } + return NULL; } -static void did_set_global_listfillchars(win_T *win, char **varp, int opt_flags, char **errmsg) +/// The 'redrawdebug' option is changed. +const char *did_set_redrawdebug(optset_T *args FUNC_ATTR_UNUSED) { - char **local_ptr = varp == &p_lcs ? &win->w_p_lcs : &win->w_p_fcs; - // only apply the global value to "win" when it does not have a local value - *errmsg = set_chars_option(win, varp, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); - if (*errmsg == NULL) { - // If the current window is set to use the global - // 'listchars'/'fillchars' value, clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(local_ptr); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If the current window has a local value need to apply it - // again, it was changed when setting the global value. - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - local_ptr = varp == &p_lcs ? &wp->w_p_lcs : &wp->w_p_fcs; - if (**local_ptr == NUL) { - (void)set_chars_option(wp, local_ptr, true); - } - } - redraw_all_later(UPD_NOT_VALID); + return did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true); +} + +/// The 'rightleftcmd' option is changed. +const char *did_set_rightleftcmd(optset_T *args) +{ + char **varp = (char **)args->os_varp; + + // Currently only "search" is a supported value. + if (**varp != NUL && strcmp(*varp, "search") != 0) { + return e_invarg; } + + return NULL; } -static void did_set_verbosefile(char **errmsg) +int expand_set_rightleftcmd(optexpand_T *args, int *numMatches, char ***matches) { - verbose_stop(); - if (*p_vfile != NUL && verbose_open() == FAIL) { - *errmsg = e_invarg; + 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) +{ + return did_set_statustabline_rulerformat(args, true, false); +} + +/// The 'scrollopt' option is changed. +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) +{ + if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) { + return e_invarg; + } + 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) +{ + if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { + return e_invarg; + } + if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { + // Don't allow both "sesdir" and "curdir". + const char *oldval = args->os_oldval.string.data; + (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); + return e_invarg; } + return NULL; } -static int shada_idx = -1; +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 void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, char *errbuf, - size_t errbuflen, char **errmsg) +const char *did_set_shada(optset_T *args) { - // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo - // option. - *opt_idx = (((*opt)->fullname[0] == 'v') - ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx) - : *opt_idx); - *opt = get_option(*opt_idx); - // Update free_oldval now that we have the opt_idx for 'shada', otherwise - // there would be a disconnect between the check for P_ALLOCED at the start - // of the function and the set of P_ALLOCED at the end of the function. - *free_oldval = ((*opt)->flags & P_ALLOCED); + char *errbuf = args->os_errbuf; + size_t errbuflen = args->os_errbuflen; + for (char *s = p_shada; *s;) { // Check it's a valid character if (vim_strchr("!\"%'/:<@cfhnrs", (uint8_t)(*s)) == NULL) { - *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s)); - break; + return illegal_char(errbuf, errbuflen, (uint8_t)(*s)); } if (*s == 'n') { // name is always last one break; @@ -1049,290 +2024,261 @@ static void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, ch vim_snprintf(errbuf, errbuflen, _("E526: Missing number after <%s>"), transchar_byte((uint8_t)(*(s - 1)))); - *errmsg = errbuf; + return errbuf; } else { - *errmsg = ""; + return ""; } - break; } } if (*s == ',') { s++; } else if (*s) { if (errbuf != NULL) { - *errmsg = N_("E527: Missing comma"); + return N_("E527: Missing comma"); } else { - *errmsg = ""; + return ""; } - break; } } - if (*p_shada && *errmsg == NULL && get_shada_parameter('\'') < 0) { - *errmsg = N_("E528: Must specify a ' value"); + if (*p_shada && get_shada_parameter('\'') < 0) { + return N_("E528: Must specify a ' value"); } + return NULL; +} + +/// The 'shortmess' option is changed. +const char *did_set_shortmess(optset_T *args) +{ + char **varp = (char **)args->os_varp; + + return did_set_option_listflag(*varp, SHM_ALL, args->os_errbuf, args->os_errbuflen); } -static void did_set_showbreak(char **varp, char **errmsg) +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) +{ + char **varp = (char **)args->os_varp; + for (char *s = *varp; *s;) { if (ptr2cells(s) != 1) { - *errmsg = e_showbreak_contains_unprintable_or_wide_character; + return e_showbreak_contains_unprintable_or_wide_character; } MB_PTR_ADV(s); } + return NULL; } -static void did_set_titleiconstring(char **varp) +/// The 'showcmdloc' option is changed. +const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED) { - // 'titlestring' and 'iconstring' - int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; + return did_set_opt_strings(p_sloc, p_sloc_values, true); +} - // NULL => statusline syntax - if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) { - stl_syntax |= flagval; - } else { - stl_syntax &= ~flagval; - } - did_set_title(); +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); } -static void did_set_selection(char **errmsg) +/// The 'signcolumn' option is changed. +const char *did_set_signcolumn(optset_T *args) { - if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) { - *errmsg = e_invarg; + win_T *win = (win_T *)args->os_win; + const char *oldval = args->os_oldval.string.data; + if (check_signcolumn(win) != OK) { + return e_invarg; } + // When changing the 'signcolumn' to or from 'number', recompute the + // width of the number column if 'number' or 'relativenumber' is set. + if ((*oldval == 'n' && *(oldval + 1) == 'u') || win->w_minscwidth == SCL_NUM) { + win->w_nrwidth_line_count = 0; + } + return NULL; } -static void did_set_keymodel(char **errmsg) +int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches) { - if (check_opt_strings(p_km, p_km_values, true) != OK) { - *errmsg = e_invarg; - return; - } - km_stopsel = (vim_strchr(p_km, 'o') != NULL); - km_startsel = (vim_strchr(p_km, 'a') != NULL); + return expand_set_opt_string(args, + p_scl_values, + ARRAY_SIZE(p_scl_values) - 1, + numMatches, + matches); } -static void did_set_display(char **errmsg) +/// The 'spellcapcheck' option is changed. +const char *did_set_spellcapcheck(optset_T *args) { - if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { - *errmsg = e_invarg; - return; - } - (void)init_chartab(); - msg_grid_validate(); + win_T *win = (win_T *)args->os_win; + // When 'spellcapcheck' is set compile the regexp program. + return compile_cap_prog(win->w_s); } -static void did_set_spellfile(char **varp, char **errmsg) +/// The 'spellfile' option is changed. +const char *did_set_spellfile(optset_T *args) { + char **varp = (char **)args->os_varp; + // When there is a window for this buffer in which 'spell' // is set load the wordlists. - if ((!valid_spellfile(*varp))) { - *errmsg = e_invarg; - } else { - *errmsg = did_set_spell_option(true); + return e_invarg; } + return did_set_spell_option(true); } -static void did_set_spell(char **varp, char **errmsg) +/// The 'spelllang' option is changed. +const char *did_set_spelllang(optset_T *args) { + char **varp = (char **)args->os_varp; + // When there is a window for this buffer in which 'spell' // is set load the wordlists. if (!valid_spelllang(*varp)) { - *errmsg = e_invarg; - } else { - *errmsg = did_set_spell_option(false); + return e_invarg; } + return did_set_spell_option(false); } -static void did_set_spellcapcheck(win_T *win, char **errmsg) -{ - // When 'spellcapcheck' is set compile the regexp program. - *errmsg = compile_cap_prog(win->w_s); -} - -static void did_set_spelloptions(win_T *win, char **errmsg) +/// The 'spelloptions' option is changed. +const char *did_set_spelloptions(optset_T *args) { + win_T *win = (win_T *)args->os_win; if (opt_strings_flags(win->w_s->b_p_spo, p_spo_values, &(win->w_s->b_p_spo_flags), true) != OK) { - *errmsg = e_invarg; + return e_invarg; } + return NULL; } -static void did_set_spellsuggest(char **errmsg) +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) { if (spell_check_sps() != OK) { - *errmsg = e_invarg; + return e_invarg; } + return NULL; } -static void did_set_mkspellmem(char **errmsg) +int expand_set_spellsuggest(optexpand_T *args, int *numMatches, char ***matches) { - if (spell_check_msm() != OK) { - *errmsg = e_invarg; - } + return expand_set_opt_string(args, + p_sps_values, + ARRAY_SIZE(p_sps_values) - 1, + numMatches, + matches); } -static void did_set_buftype(buf_T *buf, win_T *win, char **errmsg) +/// The 'splitkeep' option is changed. +const char *did_set_splitkeep(optset_T *args FUNC_ATTR_UNUSED) { - // When 'buftype' is set, check for valid value. - if ((buf->terminal && buf->b_p_bt[0] != 't') - || (!buf->terminal && buf->b_p_bt[0] == 't') - || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) { - *errmsg = e_invarg; - } else { - if (win->w_status_height || global_stl_height()) { - win->w_redr_status = true; - redraw_later(win, UPD_VALID); - } - buf->b_help = (buf->b_p_bt[0] == 'h'); - redraw_titles(); - } + 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); } -// 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' -static void did_set_statusline(win_T *win, char **varp, char **gvarp, char **errmsg) +/// The 'statuscolumn' option is changed. +const char *did_set_statuscolumn(optset_T *args) { - if (varp == &p_ruf) { // reset ru_wid first + return did_set_statustabline_rulerformat(args, false, true); +} + +/// The 'statusline' option is changed. +const char *did_set_statusline(optset_T *args) +{ + return did_set_statustabline_rulerformat(args, false, false); +} + +/// The 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' option is changed. +/// +/// @param rulerformat true if the 'rulerformat' option is changed +/// @param statuscolumn true if the 'statuscolumn' option is changed +static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerformat, + bool statuscolumn) +{ + win_T *win = (win_T *)args->os_win; + char **varp = (char **)args->os_varp; + if (rulerformat) { // reset ru_wid first ru_wid = 0; - } else if (varp == &win->w_p_stc) { + } else if (statuscolumn) { + // reset 'statuscolumn' width win->w_nrwidth_line_count = 0; } + const char *errmsg = NULL; char *s = *varp; - if (varp == &p_ruf && *s == '%') { + if (rulerformat && *s == '%') { // set ru_wid if 'ruf' starts with "%99(" if (*++s == '-') { // ignore a '-' s++; } int wid = getdigits_int(&s, true, 0); - if (wid && *s == '(' && (*errmsg = check_stl_option(p_ruf)) == NULL) { + if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) { ru_wid = wid; } else { - *errmsg = check_stl_option(p_ruf); + errmsg = check_stl_option(p_ruf); } - } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { + } else if (rulerformat || s[0] != '%' || s[1] != '!') { // check 'statusline', 'winbar', 'tabline' or 'statuscolumn' // only if it doesn't start with "%!" - *errmsg = check_stl_option(s); + errmsg = check_stl_option(s); } - if (varp == &p_ruf && *errmsg == NULL) { + if (rulerformat && errmsg == NULL) { comp_col(); } - // add / remove window bars for 'winbar' - if (gvarp == &p_wbr) { - set_winbar(true); - } -} - -static void did_set_complete(char **varp, char *errbuf, size_t errbuflen, char **errmsg) -{ - // check if it is a valid value for 'complete' -- Acevedo - for (char *s = *varp; *s;) { - while (*s == ',' || *s == ' ') { - s++; - } - if (!*s) { - break; - } - if (vim_strchr(".wbuksid]tU", (uint8_t)(*s)) == NULL) { - *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s)); - break; - } - if (*++s != NUL && *s != ',' && *s != ' ') { - if (s[-1] == 'k' || s[-1] == 's') { - // skip optional filename after 'k' and 's' - while (*s && *s != ',' && *s != ' ') { - if (*s == '\\' && s[1] != NUL) { - s++; - } - s++; - } - } else { - if (errbuf != NULL) { - vim_snprintf(errbuf, errbuflen, - _("E535: Illegal character after <%c>"), - *--s); - *errmsg = errbuf; - } else { - *errmsg = ""; - } - break; - } - } - } -} - -static void did_set_completeopt(char **errmsg) -{ - if (check_opt_strings(p_cot, p_cot_values, true) != OK) { - *errmsg = e_invarg; - } else { - completeopt_was_set(); - } -} - -#ifdef BACKSLASH_IN_FILENAME -static void did_set_completeslash(buf_T *buf, char **errmsg) -{ - if (check_opt_strings(p_csl, p_csl_values, false) != OK - || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) { - *errmsg = e_invarg; - } + return errmsg; } -#endif -static void did_set_signcolumn(win_T *win, char **varp, const char *oldval, char **errmsg) +/// The 'switchbuf' option is changed. +const char *did_set_switchbuf(optset_T *args FUNC_ATTR_UNUSED) { - if (check_signcolumn(*varp) != OK) { - *errmsg = e_invarg; - } - // When changing the 'signcolumn' to or from 'number', recompute the - // width of the number column if 'number' or 'relativenumber' is set. - if (((*oldval == 'n' && *(oldval + 1) == 'u') - || (*win->w_p_scl == 'n' && *(win->w_p_scl + 1) == 'u')) - && (win->w_p_nu || win->w_p_rnu)) { - win->w_nrwidth_line_count = 0; - } + return did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true); } -static void did_set_foldcolumn(char **varp, char **errmsg) +int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches) { - if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { - *errmsg = e_invarg; - } + return expand_set_opt_string(args, + p_swb_values, + ARRAY_SIZE(p_swb_values) - 1, + numMatches, + matches); } -static void did_set_pastetoggle(void) +/// The 'tabline' option is changed. +const char *did_set_tabline(optset_T *args) { - // 'pastetoggle': translate key codes like in a mapping - if (*p_pt) { - char *p = NULL; - (void)replace_termcodes(p_pt, - strlen(p_pt), - &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, - CPO_TO_CPO_FLAGS); - if (p != NULL) { - free_string_option(p_pt); - p_pt = p; - } - } + return did_set_statustabline_rulerformat(args, false, false); } -static void did_set_backspace(char **errmsg) +/// The 'tagcase' option is changed. +const char *did_set_tagcase(optset_T *args) { - if (ascii_isdigit(*p_bs)) { - if (*p_bs > '3' || p_bs[1] != NUL) { - *errmsg = e_invarg; - } - } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { - *errmsg = e_invarg; - } -} + buf_T *buf = (buf_T *)args->os_buf; + int opt_flags = args->os_flags; -static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg) -{ - unsigned int *flags; + unsigned *flags; char *p; if (opt_flags & OPT_LOCAL) { @@ -1348,116 +2294,66 @@ static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg) *flags = 0; } else if (*p == NUL || opt_strings_flags(p, p_tc_values, flags, false) != OK) { - *errmsg = e_invarg; - } -} - -static void did_set_diffopt(char **errmsg) -{ - if (diffopt_changed() == FAIL) { - *errmsg = e_invarg; - } -} - -static void did_set_foldmethod(win_T *win, char **varp, char **errmsg) -{ - if (check_opt_strings(*varp, p_fdm_values, false) != OK - || *win->w_p_fdm == NUL) { - *errmsg = e_invarg; - } else { - foldUpdateAll(win); - if (foldmethodIsDiff(win)) { - newFoldLevel(); - } + return e_invarg; } + return NULL; } -static void did_set_foldmarker(win_T *win, char **varp, char **errmsg) +int expand_set_tagcase(optexpand_T *args, int *numMatches, char ***matches) { - char *p = vim_strchr(*varp, ','); - if (p == NULL) { - *errmsg = N_("E536: comma required"); - } else if (p == *varp || p[1] == NUL) { - *errmsg = e_invarg; - } else if (foldmethodIsMarker(win)) { - foldUpdateAll(win); - } + return expand_set_opt_string(args, + p_tc_values, + ARRAY_SIZE(p_tc_values) - 1, + numMatches, + matches); } -static void did_set_commentstring(char **varp, char **errmsg) +/// The 'termpastefilter' option is changed. +const char *did_set_termpastefilter(optset_T *args FUNC_ATTR_UNUSED) { - if (**varp != NUL && strstr(*varp, "%s") == NULL) { - *errmsg = N_("E537: 'commentstring' must be empty or contain %s"); - } + return did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true); } -static void did_set_foldignore(win_T *win) +int expand_set_termpastefilter(optexpand_T *args, int *numMatches, char ***matches) { - if (foldmethodIsIndent(win)) { - foldUpdateAll(win); - } + return expand_set_opt_string(args, + p_tpf_values, + ARRAY_SIZE(p_tpf_values) - 1, + numMatches, + matches); } -static void did_set_virtualedit(win_T *win, int opt_flags, char *oldval, char **errmsg) +/// The 'titlestring' or the 'iconstring' option is changed. +static const char *did_set_titleiconstring(optset_T *args, int flagval) { - char *ve = p_ve; - unsigned int *flags = &ve_flags; + char **varp = (char **)args->os_varp; - if (opt_flags & OPT_LOCAL) { - ve = win->w_p_ve; - flags = &win->w_ve_flags; - } - - if ((opt_flags & OPT_LOCAL) && *ve == NUL) { - // make the local value empty: use the global value - *flags = 0; + // NULL => statusline syntax + if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) { + stl_syntax |= flagval; } else { - if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { - *errmsg = e_invarg; - } else if (strcmp(ve, oldval) != 0) { - // Recompute cursor position in case the new 've' setting - // changes something. - validate_virtcol_win(win); - // XXX: this only works when win == curwin - coladvance(win->w_virtcol); - } + stl_syntax &= ~flagval; } -} + did_set_title(); -static void did_set_lispoptions(char **varp, char **errmsg) -{ - if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) { - *errmsg = e_invarg; - } + return NULL; } -static void did_set_filetype_or_syntax(char **varp, char *oldval, int *value_checked, - bool *value_changed, char **errmsg) +/// The 'titlestring' option is changed. +const char *did_set_titlestring(optset_T *args) { - if (!valid_filetype(*varp)) { - *errmsg = e_invarg; - return; - } - - *value_changed = strcmp(oldval, *varp) != 0; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; + return did_set_titleiconstring(args, STL_IN_TITLE); } -static void did_set_winhl(win_T *win, char **errmsg) +/// The 'varsofttabstop' option is changed. +const char *did_set_varsofttabstop(optset_T *args) { - if (!parse_winhl_opt(win)) { - *errmsg = e_invarg; - } -} + buf_T *buf = (buf_T *)args->os_buf; + char **varp = (char **)args->os_varp; -static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg) -{ if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { XFREE_CLEAR(buf->b_p_vsts_array); - return; + return NULL; } for (char *cp = *varp; *cp; cp++) { @@ -1467,23 +2363,28 @@ static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg) if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { continue; } - *errmsg = e_invarg; - return; + return e_invarg; } - long *oldarray = buf->b_p_vsts_array; + colnr_T *oldarray = buf->b_p_vsts_array; if (tabstop_set(*varp, &(buf->b_p_vsts_array))) { xfree(oldarray); } else { - *errmsg = e_invarg; + return e_invarg; } + return NULL; } -static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errmsg) +/// The 'varstabstop' option is changed. +const char *did_set_vartabstop(optset_T *args) { + buf_T *buf = (buf_T *)args->os_buf; + win_T *win = (win_T *)args->os_win; + char **varp = (char **)args->os_varp; + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { XFREE_CLEAR(buf->b_p_vts_array); - return; + return NULL; } for (char *cp = *varp; *cp; cp++) { @@ -1493,436 +2394,161 @@ static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errms if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { continue; } - *errmsg = e_invarg; - return; + return e_invarg; } - long *oldarray = buf->b_p_vts_array; + colnr_T *oldarray = buf->b_p_vts_array; if (tabstop_set(*varp, &(buf->b_p_vts_array))) { xfree(oldarray); if (foldmethodIsIndent(win)) { foldUpdateAll(win); } } else { - *errmsg = e_invarg; + return e_invarg; } + return NULL; } -static void did_set_optexpr(char **varp) +/// The 'verbosefile' option is changed. +const char *did_set_verbosefile(optset_T *args) { - char *name = get_scriptlocal_funcname(*varp); - if (name != NULL) { - free_string_option(*varp); - *varp = name; + verbose_stop(); + if (*p_vfile != NUL && verbose_open() == FAIL) { + return (char *)e_invarg; } + return NULL; } -// handle option that is a list of flags. -static void did_set_option_listflag(char **varp, char *flags, char *errbuf, size_t errbuflen, - char **errmsg) +/// The 'viewoptions' option is changed. +const char *did_set_viewoptions(optset_T *args FUNC_ATTR_UNUSED) { - for (char *s = *varp; *s; s++) { - if (vim_strchr(flags, (uint8_t)(*s)) == NULL) { - *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s)); - break; - } - } + return did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true); } -// When 'syntax' is set, load the syntax of that name -static void do_syntax_autocmd(buf_T *buf, bool value_changed) +/// The 'virtualedit' option is changed. +const char *did_set_virtualedit(optset_T *args) { - static int syn_recursive = 0; + win_T *win = (win_T *)args->os_win; - syn_recursive++; - // Only pass true for "force" when the value changed or not used - // recursively, to avoid endless recurrence. - apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, - value_changed || syn_recursive == 1, buf); - buf->b_flags |= BF_SYN_SET; - syn_recursive--; -} - -static void do_filetype_autocmd(buf_T *buf, char **varp, int opt_flags, bool value_changed) -{ - // 'filetype' is set, trigger the FileType autocommand - // Skip this when called from a modeline and the filetype was - // already set to this value. - if (!(opt_flags & OPT_MODELINE) || value_changed) { - static int ft_recursive = 0; - int secure_save = secure; + char *ve = p_ve; + unsigned *flags = &ve_flags; - // Reset the secure flag, since the value of 'filetype' has - // been checked to be safe. - secure = 0; + if (args->os_flags & OPT_LOCAL) { + ve = win->w_p_ve; + flags = &win->w_ve_flags; + } - ft_recursive++; - did_filetype = true; - // Only pass true for "force" when the value changed or not - // used recursively, to avoid endless recurrence. - apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, - value_changed || ft_recursive == 1, buf); - ft_recursive--; - // Just in case the old "buf" is now invalid - if (varp != &(buf->b_p_ft)) { - varp = NULL; + if ((args->os_flags & OPT_LOCAL) && *ve == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { + return e_invarg; + } else if (strcmp(ve, args->os_oldval.string.data) != 0) { + // Recompute cursor position in case the new 've' setting + // changes something. + validate_virtcol_win(win); + // XXX: this only works when win == curwin + coladvance(win->w_virtcol); } - secure = secure_save; } + return NULL; } -static void do_spelllang_source(win_T *win) +int expand_set_virtualedit(optexpand_T *args, int *numMatches, char ***matches) { - char fname[200]; - char *q = win->w_s->b_p_spl; + return expand_set_opt_string(args, + p_ve_values, + ARRAY_SIZE(p_ve_values) - 1, + numMatches, + matches); +} - // Skip the first name if it is "cjk". - if (strncmp(q, "cjk,", 4) == 0) { - q += 4; - } +/// The 'whichwrap' option is changed. +const char *did_set_whichwrap(optset_T *args) +{ + char **varp = (char **)args->os_varp; - // Source the spell/LANG.vim in 'runtimepath'. - // They could set 'spellcapcheck' depending on the language. - // Use the first name in 'spelllang' up to '_region' or - // '.encoding'. - char *p; - for (p = q; *p != NUL; p++) { - if (!ASCII_ISALNUM(*p) && *p != '-') { - break; - } - } - if (p > q) { - vim_snprintf(fname, sizeof(fname), "spell/%.*s.vim", (int)(p - q), q); - source_runtime(fname, DIP_ALL); - } + // 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); } -/// Handle string options that need some action to perform when changed. -/// The new value must be allocated. -/// -/// @param opt_idx index in options[] table -/// @param varp pointer to the option variable -/// @param oldval previous value of the option -/// @param errbuf buffer for errors, or NULL -/// @param errbuflen length of errors buffer -/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL -/// @param value_checked value was checked to be safe, no need to set P_INSECURE -/// -/// @return NULL for success, or an untranslated error message for an error -static char *did_set_string_option_for(buf_T *buf, win_T *win, int opt_idx, char **varp, - char *oldval, char *errbuf, size_t errbuflen, int opt_flags, - int *value_checked) +int expand_set_whichwrap(optexpand_T *args, int *numMatches, char ***matches) { - char *errmsg = NULL; - bool did_chartab = false; - vimoption_T *opt = get_option(opt_idx); - bool free_oldval = (opt->flags & P_ALLOCED); - bool value_changed = false; + return expand_set_opt_listflag(args, WW_ALL, numMatches, matches); +} - // Get the global option to compare with, otherwise we would have to check - // two values for all local options. - char **gvarp = (char **)get_varp_scope(opt, OPT_GLOBAL); - - // Disallow changing some options from secure mode - if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) { - errmsg = e_secure; - // Check for a "normal" directory or file name in some options. - } else if (check_illegal_path_names(*varp, opt->flags)) { - errmsg = e_invarg; - } else if (gvarp == &p_bkc) { // 'backupcopy' - did_set_backupcopy(buf, oldval, opt_flags, &errmsg); - } else if (varp == &p_bex // 'backupext' - || varp == &p_pm) { // 'patchmode' - did_set_backupext_or_patchmode(&errmsg); - } else if (varp == &win->w_p_briopt) { // 'breakindentopt' - did_set_breakindentopt(win, &errmsg); - } else if (varp == &p_isi // 'isident' - || varp == &buf->b_p_isk // 'iskeyword' - || varp == &p_isp // 'isprint' - || varp == &p_isf) { // 'isfname' - did_set_isopt(buf, &did_chartab, &errmsg); - } else if (varp == &p_hf) { // 'helpfile' - did_set_helpfile(); - } else if (varp == &p_rtp // 'runtimepath' - || varp == &p_pp) { // 'packpath' - runtime_search_path_invalidate(); - } else if (gvarp == &win->w_allbuf_opt.wo_culopt) { // 'cursorlineopt' - did_set_cursorlineopt(win, varp, &errmsg); - } else if (varp == &win->w_p_cc) { // 'colorcolumn' - errmsg = check_colorcolumn(win); - } else if (varp == &p_hlg) { // 'helplang' - did_set_helplang(&errmsg); - } else if (varp == &p_hl) { // 'highlight' - did_set_highlight(varp, &errmsg); - } else if (varp == &p_jop) { // 'jumpoptions' - did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true, &errmsg); - } else if (gvarp == &p_nf) { // 'nrformats' - did_set_opt_strings(*varp, p_nf_values, true, &errmsg); - } else if (varp == &p_ssop) { // 'sessionoptions' - did_set_sessionoptions(oldval, &errmsg); - } else if (varp == &p_vop) { // 'viewoptions' - did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true, &errmsg); - } else if (varp == &p_rdb) { // 'redrawdebug' - did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true, &errmsg); - } else if (varp == &p_sbo) { // 'scrollopt' - did_set_opt_strings(p_sbo, p_scbopt_values, true, &errmsg); - } else if (varp == &p_ambw // 'ambiwidth' - || (int *)varp == &p_emoji) { // 'emoji' - did_set_ambiwidth(&errmsg); - } else if (varp == &p_bg) { // 'background' - did_set_background(&errmsg); - } else if (varp == &p_wim) { // 'wildmode' - did_set_wildmode(&errmsg); - } else if (varp == &p_wop) { // 'wildoptions' - did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true, &errmsg); - } else if (varp == &p_wak) { // 'winaltkeys' - did_set_winaltkeys(&errmsg); - } else if (varp == &p_ei) { // 'eventignore' - did_set_eventignore(&errmsg); - } else if (varp == &p_enc // 'encoding' - || gvarp == &p_fenc // 'fileencoding' - || gvarp == &p_menc) { // 'makeencoding' - did_set_encoding(buf, varp, gvarp, opt_flags, &errmsg); - } else if (varp == &buf->b_p_keymap) { // 'keymap' - did_set_keymap(buf, varp, opt_flags, value_checked, &errmsg); - } else if (gvarp == &p_ff) { // 'fileformat' - did_set_fileformat(buf, varp, oldval, opt_flags, &errmsg); - } else if (varp == &p_ffs) { // 'fileformats' - did_set_opt_strings(p_ffs, p_ff_values, true, &errmsg); - } else if (gvarp == &p_mps) { // 'matchpairs' - did_set_matchpairs(varp, &errmsg); - } else if (gvarp == &p_com) { // 'comments' - did_set_comments(varp, errbuf, errbuflen, &errmsg); - } else if (varp == &p_lcs // global 'listchars' - || varp == &p_fcs) { // global 'fillchars' - did_set_global_listfillchars(win, varp, opt_flags, &errmsg); - } else if (varp == &win->w_p_lcs) { // local 'listchars' - errmsg = set_chars_option(win, varp, true); - } else if (varp == &win->w_p_fcs) { // local 'fillchars' - errmsg = set_chars_option(win, varp, true); - } else if (varp == &p_cedit) { // 'cedit' - errmsg = check_cedit(); - } else if (varp == &p_vfile) { // 'verbosefile' - did_set_verbosefile(&errmsg); - } else if (varp == &p_shada) { // 'shada' - did_set_shada(&opt, &opt_idx, &free_oldval, errbuf, errbuflen, &errmsg); - } else if (gvarp == &p_sbr) { // 'showbreak' - did_set_showbreak(varp, &errmsg); - } else if (varp == &p_guicursor) { // 'guicursor' - errmsg = parse_shape_opt(SHAPE_CURSOR); - } else if (varp == &p_langmap) { // 'langmap' - langmap_set(); - } else if (varp == &p_breakat) { // 'breakat' - fill_breakat_flags(); - } else if (varp == &p_titlestring // 'titlestring' - || varp == &p_iconstring) { // 'iconstring' - did_set_titleiconstring(varp); - } else if (varp == &p_sel) { // 'selection' - did_set_selection(&errmsg); - } else if (varp == &p_slm) { // 'selectmode' - did_set_opt_strings(p_slm, p_slm_values, true, &errmsg); - } else if (varp == &p_km) { // 'keymodel' - did_set_keymodel(&errmsg); - } else if (varp == &p_mousem) { // 'mousemodel' - did_set_opt_strings(p_mousem, p_mousem_values, false, &errmsg); - } else if (varp == &p_mousescroll) { // 'mousescroll' - errmsg = check_mousescroll(p_mousescroll); - } else if (varp == &p_swb) { // 'switchbuf' - did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true, &errmsg); - } else if (varp == &p_spk) { // 'splitkeep' - did_set_opt_strings(p_spk, p_spk_values, false, &errmsg); - } else if (varp == &p_debug) { // 'debug' - did_set_opt_strings(p_debug, p_debug_values, true, &errmsg); - } else if (varp == &p_dy) { // 'display' - did_set_display(&errmsg); - } else if (varp == &p_ead) { // 'eadirection' - did_set_opt_strings(p_ead, p_ead_values, false, &errmsg); - } else if (varp == &p_cb) { // 'clipboard' - did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true, &errmsg); - } else if (varp == &win->w_s->b_p_spf) { // 'spellfile' - did_set_spellfile(varp, &errmsg); - } else if (varp == &win->w_s->b_p_spl) { // 'spell' - did_set_spell(varp, &errmsg); - } else if (varp == &win->w_s->b_p_spc) { // 'spellcapcheck' - did_set_spellcapcheck(win, &errmsg); - } else if (varp == &win->w_s->b_p_spo) { // 'spelloptions' - did_set_spelloptions(win, &errmsg); - } else if (varp == &p_sps) { // 'spellsuggest' - did_set_spellsuggest(&errmsg); - } else if (varp == &p_msm) { // 'mkspellmem' - did_set_mkspellmem(&errmsg); - } else if (gvarp == &p_bh) { // 'bufhidden' - did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false, &errmsg); - } else if (gvarp == &p_bt) { // 'buftype' - did_set_buftype(buf, win, &errmsg); - } else if (gvarp == &p_stl // 'statusline' - || gvarp == &p_wbr // 'winbar' - || varp == &p_tal // 'tabline' - || varp == &p_ruf // 'rulerformat' - || varp == &win->w_p_stc) { // 'statuscolumn' - did_set_statusline(win, varp, gvarp, &errmsg); - } else if (gvarp == &p_cpt) { // 'complete' - did_set_complete(varp, errbuf, errbuflen, &errmsg); - } else if (varp == &p_cot) { // 'completeopt' - did_set_completeopt(&errmsg); -#ifdef BACKSLASH_IN_FILENAME - } else if (gvarp == &p_csl) { // 'completeslash' - did_set_completeslash(buf, &errmsg); -#endif - } else if (varp == &win->w_p_scl) { // 'signcolumn' - did_set_signcolumn(win, varp, oldval, &errmsg); - } else if (varp == &p_sloc) { // 'showcmdloc' - did_set_opt_strings(*varp, p_sloc_values, false, &errmsg); - } else if (gvarp == &win->w_allbuf_opt.wo_fdc) { // 'foldcolumn' - did_set_foldcolumn(varp, &errmsg); - } else if (varp == &p_pt) { // 'pastetoggle' - did_set_pastetoggle(); - } else if (varp == &p_bs) { // 'backspace' - did_set_backspace(&errmsg); - } else if (varp == &p_bo) { - did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true, &errmsg); - } else if (gvarp == &p_tc) { // 'tagcase' - did_set_tagcase(buf, opt_flags, &errmsg); - } else if (varp == &p_cmp) { // 'casemap' - did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true, &errmsg); - } else if (varp == &p_dip) { // 'diffopt' - did_set_diffopt(&errmsg); - } else if (gvarp == &win->w_allbuf_opt.wo_fdm) { // 'foldmethod' - did_set_foldmethod(win, varp, &errmsg); - } else if (gvarp == &win->w_allbuf_opt.wo_fmr) { // 'foldmarker' - did_set_foldmarker(win, varp, &errmsg); - } else if (gvarp == &p_cms) { // 'commentstring' - did_set_commentstring(varp, &errmsg); - } else if (varp == &p_fdo) { // 'foldopen' - did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true, &errmsg); - } else if (varp == &p_fcl) { // 'foldclose' - did_set_opt_strings(*varp, p_fcl_values, true, &errmsg); - } else if (gvarp == &win->w_allbuf_opt.wo_fdi) { // 'foldignore' - did_set_foldignore(win); - } else if (gvarp == &p_ve) { // 'virtualedit' - did_set_virtualedit(win, opt_flags, oldval, &errmsg); - } else if (gvarp == &p_cino) { // 'cinoptions' - // TODO(vim): recognize errors - parse_cino(buf); - } else if (gvarp == &p_lop) { // 'lispoptions' - did_set_lispoptions(varp, &errmsg); - } else if (varp == &p_icm) { // 'inccommand' - did_set_opt_strings(*varp, p_icm_values, false, &errmsg); - } else if (gvarp == &p_ft // 'filetype' - || gvarp == &p_syn) { // 'syntax' - did_set_filetype_or_syntax(varp, oldval, value_checked, &value_changed, &errmsg); - } else if (varp == &win->w_p_winhl) { // 'winhighlight' - did_set_winhl(win, &errmsg); - } else if (varp == &p_tpf) { - did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true, &errmsg); - } else if (varp == &buf->b_p_vsts) { // 'varsofttabstop' - did_set_varsoftabstop(buf, varp, &errmsg); - } else if (varp == &buf->b_p_vts) { // 'vartabstop' - did_set_vartabstop(buf, win, varp, &errmsg); - } else if (varp == &p_dex // 'diffexpr' - || gvarp == &win->w_allbuf_opt.wo_fde // 'foldexpr' - || gvarp == &win->w_allbuf_opt.wo_fdt // 'foldtext' - || gvarp == &p_fex // 'formatexpr' - || gvarp == &p_inex // 'includeexpr' - || gvarp == &p_inde // 'indentexpr' - || varp == &p_pex // 'patchexpr' - || varp == &p_ccv) { // 'charconvert' - did_set_optexpr(varp); - if (varp == &win->w_p_fde && foldmethodIsExpr(win)) { - foldUpdateAll(win); - } - } else if (gvarp == &p_cfu) { // 'completefunc' - set_completefunc_option(&errmsg); - } else if (gvarp == &p_ofu) { // 'omnifunc' - set_omnifunc_option(buf, &errmsg); - } else if (gvarp == &p_tsrfu) { // 'thesaurusfunc' - set_thesaurusfunc_option(&errmsg); - } else if (varp == &p_opfunc) { // 'operatorfunc' - set_operatorfunc_option(&errmsg); - } else if (varp == &p_qftf) { // 'quickfixtextfunc' - qf_process_qftf_option(&errmsg); - } else if (gvarp == &p_tfu) { // 'tagfunc' - set_tagfunc_option(&errmsg); - } else if (varp == &p_ww) { // 'whichwrap' - did_set_option_listflag(varp, WW_ALL, errbuf, errbuflen, &errmsg); - } else if (varp == &p_shm) { // 'shortmess' - did_set_option_listflag(varp, SHM_ALL, errbuf, errbuflen, &errmsg); - } else if (varp == &p_cpo) { // 'cpoptions' - did_set_option_listflag(varp, CPO_VI, errbuf, errbuflen, &errmsg); - } else if (varp == &buf->b_p_fo) { // 'formatoptions' - did_set_option_listflag(varp, FO_ALL, errbuf, errbuflen, &errmsg); - } else if (varp == &win->w_p_cocu) { // 'concealcursor' - did_set_option_listflag(varp, COCU_ALL, errbuf, errbuflen, &errmsg); - } else if (varp == &p_mouse) { // 'mouse' - did_set_option_listflag(varp, MOUSE_ALL, errbuf, errbuflen, &errmsg); - } else if (gvarp == &p_flp) { // 'formatlistpat' - if (win->w_briopt_list) { - // Changing Formatlistpattern when briopt includes the list setting: - // redraw - redraw_all_later(UPD_NOT_VALID); - } - } - - // If an error is detected, restore the previous value. - if (errmsg != NULL) { - free_string_option(*varp); - *varp = oldval; - // When resetting some values, need to act on it. - if (did_chartab) { - (void)buf_init_chartab(buf, true); - } - } else { - // Remember where the option was set. - set_option_sctx_idx(opt_idx, opt_flags, current_sctx); - // Free string options that are in allocated memory. - // Use "free_oldval", because recursiveness may change the flags under - // our fingers (esp. init_highlight()). - if (free_oldval) { - free_string_option(oldval); - } - opt->flags |= P_ALLOCED; +/// The 'wildmode' option is changed. +const char *did_set_wildmode(optset_T *args FUNC_ATTR_UNUSED) +{ + if (check_opt_wim() == FAIL) { + return e_invarg; + } + return NULL; +} - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - && (opt->indir & PV_BOTH)) { - // global option with local value set to use global value; free - // the local value and make it empty - char *p = get_varp_scope(opt, OPT_LOCAL); - free_string_option(*(char **)p); - *(char **)p = empty_option; - } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { - // May set global value for local option. - set_string_option_global(opt, varp); - } +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); +} - // Trigger the autocommand only after setting the flags. - if (varp == &buf->b_p_syn) { - do_syntax_autocmd(buf, value_changed); - } else if (varp == &buf->b_p_ft) { - do_filetype_autocmd(buf, varp, opt_flags, value_changed); - } else if (varp == &win->w_s->b_p_spl) { - do_spelllang_source(win); - } - } +/// 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); +} - if (varp == &p_mouse) { - setmouse(); // in case 'mouse' changed - } +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); +} - if (win->w_curswant != MAXCOL - && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { - win->w_set_curswant = true; +/// The 'winaltkeys' option is changed. +const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED) +{ + if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) { + return e_invarg; } + return NULL; +} - check_redraw_for(buf, win, opt->flags); +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); +} - return errmsg; +/// The 'winbar' option is changed. +const char *did_set_winbar(optset_T *args) +{ + return did_set_statustabline_rulerformat(args, false, false); +} + +/// The 'winhighlight' option is changed. +const char *did_set_winhighlight(optset_T *args) +{ + win_T *win = (win_T *)args->os_win; + if (!parse_winhl_opt(win)) { + return e_invarg; + } + return NULL; } -char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf, size_t errbuflen, - int opt_flags, int *value_checked) +int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) { - return did_set_string_option_for(curbuf, curwin, opt_idx, varp, oldval, errbuf, errbuflen, - opt_flags, value_checked); + return expand_set_opt_generic(args, get_highlight_name, numMatches, matches); } /// Check an option that can be a range of string values. @@ -1943,12 +2569,12 @@ static int check_opt_strings(char *val, char **values, int list) /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool list) +static int opt_strings_flags(const char *val, char **values, unsigned *flagp, bool list) { - unsigned int new_flags = 0; + unsigned new_flags = 0; while (*val) { - for (unsigned int i = 0;; i++) { + for (unsigned i = 0;; i++) { if (values[i] == NULL) { // val not found in values[] return FAIL; } @@ -1957,7 +2583,7 @@ static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool lis if (strncmp(values[i], val, len) == 0 && ((list && val[len] == ',') || val[len] == NUL)) { val += len + (val[len] == ','); - assert(i < sizeof(1U) * 8); + assert(i < sizeof(new_flags) * 8); new_flags |= (1U << i); break; // check next item in val list } @@ -1975,3 +2601,335 @@ int check_ff_value(char *p) { return check_opt_strings(p, p_ff_values, false); } + +static const char e_conflicts_with_value_of_listchars[] + = N_("E834: Conflicts with value of 'listchars'"); +static const char e_conflicts_with_value_of_fillchars[] + = N_("E835: Conflicts with value of 'fillchars'"); + +/// Calls mb_cptr2char_adv(p) and returns the character. +/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used. +/// Returns 0 for invalid hex or invalid UTF-8 byte. +static int get_encoded_char_adv(const char **p) +{ + const char *s = *p; + + if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { + int64_t num = 0; + for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { + *p += 2; + int n = hexhex2nr(*p); + if (n < 0) { + return 0; + } + num = num * 256 + n; + } + *p += 2; + return (int)num; + } + + // TODO(bfredl): use schar_T representation and utfc_ptr2len + int clen = utf_ptr2len(s); + int c = mb_cptr2char_adv(p); + if (clen == 1 && c > 127) { // Invalid UTF-8 byte + return 0; + } + return c; +} + +struct chars_tab { + int *cp; ///< char value + const char *name; ///< char id + int def; ///< default value + int fallback; ///< default value when "def" isn't single-width +}; + +static fcs_chars_T fcs_chars; +static const struct chars_tab fcs_tab[] = { + { &fcs_chars.stl, "stl", ' ', NUL }, + { &fcs_chars.stlnc, "stlnc", ' ', NUL }, + { &fcs_chars.wbr, "wbr", ' ', NUL }, + { &fcs_chars.horiz, "horiz", 0x2500, '-' }, // ─ + { &fcs_chars.horizup, "horizup", 0x2534, '-' }, // ┴ + { &fcs_chars.horizdown, "horizdown", 0x252c, '-' }, // ┬ + { &fcs_chars.vert, "vert", 0x2502, '|' }, // │ + { &fcs_chars.vertleft, "vertleft", 0x2524, '|' }, // ┤ + { &fcs_chars.vertright, "vertright", 0x251c, '|' }, // ├ + { &fcs_chars.verthoriz, "verthoriz", 0x253c, '+' }, // ┼ + { &fcs_chars.fold, "fold", 0x00b7, '-' }, // · + { &fcs_chars.foldopen, "foldopen", '-', NUL }, + { &fcs_chars.foldclosed, "foldclose", '+', NUL }, + { &fcs_chars.foldsep, "foldsep", 0x2502, '|' }, // │ + { &fcs_chars.diff, "diff", '-', NUL }, + { &fcs_chars.msgsep, "msgsep", ' ', NUL }, + { &fcs_chars.eob, "eob", '~', NUL }, + { &fcs_chars.lastline, "lastline", '@', NUL }, +}; + +static lcs_chars_T lcs_chars; +static const struct chars_tab lcs_tab[] = { + { &lcs_chars.eol, "eol", NUL, NUL }, + { &lcs_chars.ext, "extends", NUL, NUL }, + { &lcs_chars.nbsp, "nbsp", NUL, NUL }, + { &lcs_chars.prec, "precedes", NUL, NUL }, + { &lcs_chars.space, "space", NUL, NUL }, + { &lcs_chars.tab2, "tab", NUL, NUL }, + { &lcs_chars.lead, "lead", NUL, NUL }, + { &lcs_chars.trail, "trail", NUL, NUL }, + { &lcs_chars.conceal, "conceal", NUL, NUL }, + { NULL, "multispace", NUL, NUL }, + { NULL, "leadmultispace", NUL, NUL }, +}; + +/// Handle setting 'listchars' or 'fillchars'. +/// Assume monocell characters +/// +/// @param value points to either the global or the window-local value. +/// @param is_listchars is true for "listchars" and false for "fillchars". +/// @param apply if false, do not store the flags, only check for errors. +/// @return error message, NULL if it's OK. +static const char *set_chars_option(win_T *wp, const char *value, const bool is_listchars, + const bool apply) +{ + const char *last_multispace = NULL; // Last occurrence of "multispace:" + const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" + int multispace_len = 0; // Length of lcs-multispace string + int lead_multispace_len = 0; // Length of lcs-leadmultispace string + + const struct chars_tab *tab; + int entries; + if (is_listchars) { + tab = lcs_tab; + entries = ARRAY_SIZE(lcs_tab); + if (wp->w_p_lcs[0] == NUL) { + value = p_lcs; // local value is empty, use the global value + } + } else { + tab = fcs_tab; + entries = ARRAY_SIZE(fcs_tab); + if (wp->w_p_fcs[0] == NUL) { + value = p_fcs; // local value is empty, use the global value + } + } + + // first round: check for valid value, second round: assign values + for (int round = 0; round <= (apply ? 1 : 0); round++) { + if (round > 0) { + // After checking that the value is valid: set defaults + for (int i = 0; i < entries; i++) { + if (tab[i].cp != NULL) { + // XXX: Characters taking 2 columns is forbidden (TUI limitation?). + // Set old defaults in this case. + *(tab[i].cp) = char2cells(tab[i].def) == 1 ? tab[i].def : tab[i].fallback; + } + } + + if (is_listchars) { + lcs_chars.tab1 = NUL; + lcs_chars.tab3 = NUL; + + if (multispace_len > 0) { + lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int)); + lcs_chars.multispace[multispace_len] = NUL; + } else { + lcs_chars.multispace = NULL; + } + + if (lead_multispace_len > 0) { + lcs_chars.leadmultispace = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int)); + lcs_chars.leadmultispace[lead_multispace_len] = NUL; + } else { + lcs_chars.leadmultispace = NULL; + } + } + } + + const char *p = value; + while (*p) { + int i; + for (i = 0; i < entries; i++) { + const size_t len = strlen(tab[i].name); + if (!(strncmp(p, tab[i].name, len) == 0 + && p[len] == ':' + && p[len + 1] != NUL)) { + continue; + } + + if (is_listchars && strcmp(tab[i].name, "multispace") == 0) { + const char *s = p + len + 1; + if (round == 0) { + // Get length of lcs-multispace string in the first round + last_multispace = p; + multispace_len = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + multispace_len++; + } + if (multispace_len == 0) { + // lcs-multispace cannot be an empty string + return e_invarg; + } + p = s; + } else { + int multispace_pos = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (p == last_multispace) { + lcs_chars.multispace[multispace_pos++] = c1; + } + } + p = s; + } + break; + } + + if (is_listchars && strcmp(tab[i].name, "leadmultispace") == 0) { + const char *s = p + len + 1; + if (round == 0) { + // get length of lcs-leadmultispace string in first round + last_lmultispace = p; + lead_multispace_len = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + lead_multispace_len++; + } + if (lead_multispace_len == 0) { + // lcs-leadmultispace cannot be an empty string + return e_invarg; + } + p = s; + } else { + int multispace_pos = 0; + while (*s != NUL && *s != ',') { + int c1 = get_encoded_char_adv(&s); + if (p == last_lmultispace) { + lcs_chars.leadmultispace[multispace_pos++] = c1; + } + } + p = s; + } + break; + } + + const char *s = p + len + 1; + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + int c2 = 0, c3 = 0; + if (tab[i].cp == &lcs_chars.tab2) { + if (*s == NUL) { + return e_invarg; + } + c2 = get_encoded_char_adv(&s); + if (c2 == 0 || char2cells(c2) > 1) { + return e_invarg; + } + if (!(*s == ',' || *s == NUL)) { + c3 = get_encoded_char_adv(&s); + if (c3 == 0 || char2cells(c3) > 1) { + return e_invarg; + } + } + } + + if (*s == ',' || *s == NUL) { + if (round > 0) { + if (tab[i].cp == &lcs_chars.tab2) { + lcs_chars.tab1 = c1; + lcs_chars.tab2 = c2; + lcs_chars.tab3 = c3; + } else if (tab[i].cp != NULL) { + *(tab[i].cp) = c1; + } + } + p = s; + break; + } + } + + if (i == entries) { + return e_invarg; + } + + if (*p == ',') { + p++; + } + } + } + + if (apply) { + if (is_listchars) { + xfree(wp->w_p_lcs_chars.multispace); + xfree(wp->w_p_lcs_chars.leadmultispace); + wp->w_p_lcs_chars = lcs_chars; + } else { + wp->w_p_fcs_chars = fcs_chars; + } + } + + 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. +/// +/// @return an untranslated error message if any of them is invalid, NULL otherwise. +const char *check_chars_options(void) +{ + if (set_listchars_option(curwin, p_lcs, false) != NULL) { + return e_conflicts_with_value_of_listchars; + } + if (set_fillchars_option(curwin, p_fcs, false) != NULL) { + return e_conflicts_with_value_of_fillchars; + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (set_listchars_option(wp, wp->w_p_lcs, true) != NULL) { + return e_conflicts_with_value_of_listchars; + } + if (set_fillchars_option(wp, wp->w_p_fcs, true) != NULL) { + return e_conflicts_with_value_of_fillchars; + } + } + return NULL; +} |