diff options
author | bfredl <bjorn.linse@gmail.com> | 2023-10-16 20:41:35 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-16 20:41:35 +0200 |
commit | a63c67005b9ea17214d86391e2fd649658c1bdec (patch) | |
tree | bd732635435cb15d0d7e0bbe4eb49ceae73c0999 | |
parent | b80a8e2c16b6d6eb16ac84232c27eb7cfa4a434a (diff) | |
parent | 3642f2fb44b6a3681e6a637671690258aa83cc62 (diff) | |
download | rneovim-a63c67005b9ea17214d86391e2fd649658c1bdec.tar.gz rneovim-a63c67005b9ea17214d86391e2fd649658c1bdec.tar.bz2 rneovim-a63c67005b9ea17214d86391e2fd649658c1bdec.zip |
Merge pull request #25394 from famiu/refactor/options/set_option
refactor(options)!: unify interfaces for setting options
-rw-r--r-- | runtime/doc/news.txt | 4 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 3 | ||||
-rw-r--r-- | src/nvim/api/options.c | 41 | ||||
-rw-r--r-- | src/nvim/arglist.c | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 17 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 12 | ||||
-rw-r--r-- | src/nvim/eval/vars.c | 45 | ||||
-rw-r--r-- | src/nvim/eval/vars.h | 1 | ||||
-rw-r--r-- | src/nvim/ex_eval.c | 2 | ||||
-rw-r--r-- | src/nvim/globals.h | 9 | ||||
-rw-r--r-- | src/nvim/normal.c | 4 | ||||
-rw-r--r-- | src/nvim/ops.c | 4 | ||||
-rw-r--r-- | src/nvim/option.c | 1008 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 35 | ||||
-rw-r--r-- | src/nvim/options.lua | 1 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 80 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 8 | ||||
-rw-r--r-- | src/nvim/syntax.c | 12 | ||||
-rw-r--r-- | src/nvim/types.h | 2 | ||||
-rw-r--r-- | test/functional/legacy/autocmd_option_spec.lua | 97 |
20 files changed, 665 insertions, 722 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 3dae3c71b1..d55f43ce89 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -71,6 +71,10 @@ The following changes may require adaptations in user config or plugins. defined by LSP, and hence previously parsed snippets might now be considered invalid input. +• |OptionSet| autocommand args |v:option_new|, |v:option_old|, + |v:option_oldlocal|, |v:option_oldglobal| now have the type of the option + instead of always being strings. + ============================================================================== NEW FEATURES *news-features* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index d000d7c35c..7d0047bb1d 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -379,6 +379,9 @@ UI/Display: Variables: |v:progpath| is always absolute ("full") |v:windowid| is always available (for use by external UIs) + |OptionSet| autocommand args |v:option_new|, |v:option_old|, + |v:option_oldlocal|, |v:option_oldglobal| have the type of the option + instead of always being strings. Vimscript: |:redir| nested in |execute()| works. diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 867d1d5e5c..498638d606 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -133,47 +133,6 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err) return ftbuf; } -/// Consume an OptVal and convert it to an API Object. -static Object optval_as_object(OptVal o) -{ - switch (o.type) { - case kOptValTypeNil: - return NIL; - case kOptValTypeBoolean: - switch (o.data.boolean) { - case kFalse: - case kTrue: - return BOOLEAN_OBJ(o.data.boolean); - case kNone: - return NIL; - } - UNREACHABLE; - case kOptValTypeNumber: - return INTEGER_OBJ(o.data.number); - case kOptValTypeString: - return STRING_OBJ(o.data.string); - } - UNREACHABLE; -} - -/// Consume an API Object and convert it to an OptVal. -static OptVal object_as_optval(Object o, bool *error) -{ - switch (o.type) { - case kObjectTypeNil: - return NIL_OPTVAL; - case kObjectTypeBoolean: - return BOOLEAN_OPTVAL(o.data.boolean); - case kObjectTypeInteger: - return NUMBER_OPTVAL((OptInt)o.data.integer); - case kObjectTypeString: - return STRING_OPTVAL(o.data.string); - default: - *error = true; - return NIL_OPTVAL; - } -} - /// Gets the value of an option. The behavior of this function matches that of /// |:set|: the local value of an option is returned if it exists; otherwise, /// the global value is returned. Local values always correspond to the current diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index fb8849541d..bcf343a5b4 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -140,7 +140,7 @@ void alist_expand(int *fnum_list, int fnum_len) // Don't use 'suffixes' here. This should work like the shell did the // expansion. Also, the vimrc file isn't read yet, thus the user // can't set the options. - p_su = empty_option; + p_su = empty_string_option; for (int i = 0; i < GARGCOUNT; i++) { old_arg_files[i] = xstrdup(GARGLIST[i].ae_fname); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b5b40061b5..20d76334f6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2237,7 +2237,7 @@ int pattern_match(const char *pat, const char *text, bool ic) // avoid 'l' flag in 'cpoptions' char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = ic; @@ -7226,6 +7226,17 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val) tv_dict_set_keys_readonly(val); } +/// Set v:variable to tv. +/// +/// @param[in] idx Index of variable to set. +/// @param[in,out] val Value to set to. Reference count will be incremented. +/// Also keys of the dictionary will be made read-only. +void set_vim_var_tv(const VimVarIndex idx, typval_T *const tv) +{ + tv_clear(&vimvars[idx].vv_di.di_tv); + vimvars[idx].vv_di.di_tv = *tv; +} + /// Set the v:argv list. void set_argv_var(char **argv, int argc) { @@ -8634,7 +8645,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char // Make 'cpoptions' empty, so that the 'l' flag doesn't work here char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; ga_init(&ga, 1, 200); @@ -8699,7 +8710,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char char *ret = xstrdup(ga.ga_data == NULL ? str : ga.ga_data); ga_clear(&ga); - if (p_cpo == empty_option) { + if (p_cpo == empty_string_option) { p_cpo = save_cpo; } else { // Darn, evaluating {sub} expression or {expr} changed the value. diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0979087c79..8e5bf68be1 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -482,7 +482,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) int save_magic = p_magic; p_magic = true; char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; buf_T *buf = buflist_findnr(buflist_findpat(name, name + strlen(name), true, false, curtab_only)); @@ -1733,7 +1733,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char *p_csl_save = p_csl; // avoid using 'completeslash' here - p_csl = empty_option; + p_csl = empty_string_option; #endif rettv->v_type = VAR_STRING; @@ -4516,7 +4516,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; rettv->vval.v_number = -1; switch (type) { @@ -7108,7 +7108,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; // Set the time limit, if there is one. proftime_T tm = profile_setlimit(time_limit); @@ -7234,7 +7234,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir xfree(pat2); xfree(pat3); - if (p_cpo == empty_option) { + if (p_cpo == empty_string_option) { p_cpo = save_cpo; } else { // Darn, evaluating the {skip} expression changed the value. @@ -7966,7 +7966,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; const char *str = tv_get_string(&argvars[0]); const char *pat = NULL; diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index f9dcfb3d9d..ed400b2ee9 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -20,6 +20,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/eval/window.h" @@ -822,7 +823,7 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, if (curval.type == kOptValTypeNumber) { newval = NUMBER_OPTVAL(new_n); } else { - newval = BOOLEAN_OPTVAL(new_n == 0 ? kFalse : (new_n >= 1 ? kTrue : kNone)); + newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n)); } } else if (!hidden && is_string && curval.data.string.data != NULL && newval.data.string.data != NULL) { // string @@ -1875,8 +1876,7 @@ static OptVal tv_to_optval(typval_T *tv, const char *option, uint32_t flags, boo semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string); } } - value = (flags & P_NUM) ? NUMBER_OPTVAL((OptInt)n) - : BOOLEAN_OPTVAL(n == 0 ? kFalse : (n >= 1 ? kTrue : kNone)); + value = (flags & P_NUM) ? NUMBER_OPTVAL((OptInt)n) : BOOLEAN_OPTVAL(TRISTATE_FROM_INT(n)); } else if ((flags & P_STRING) || is_tty_option(option)) { // Avoid setting string option to a boolean or a special value. if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { @@ -1897,6 +1897,45 @@ static OptVal tv_to_optval(typval_T *tv, const char *option, uint32_t flags, boo return value; } +/// Convert an option value to typval. +/// +/// @param[in] value Option value to convert. +/// +/// @return OptVal converted to typval. +typval_T optval_as_tv(OptVal value) +{ + typval_T rettv = { .v_type = VAR_SPECIAL, .vval = { .v_special = kSpecialVarNull } }; + + switch (value.type) { + case kOptValTypeNil: + break; + case kOptValTypeBoolean: + switch (value.data.boolean) { + case kTrue: + rettv.v_type = VAR_BOOL; + rettv.vval.v_bool = kBoolVarTrue; + break; + case kFalse: + rettv.v_type = VAR_BOOL; + rettv.vval.v_bool = kBoolVarFalse; + break; + case kNone: + break; // return v:null for None boolean value + } + break; + case kOptValTypeNumber: + rettv.v_type = VAR_NUMBER; + rettv.vval.v_number = value.data.number; + break; + case kOptValTypeString: + rettv.v_type = VAR_STRING; + rettv.vval.v_string = value.data.string.data; + break; + } + + return rettv; +} + /// Set option "varname" to the value of "varp" for the current buffer/window. static void set_option_from_tv(const char *varname, typval_T *varp) { diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h index b87c9d62cb..12ff5b11cb 100644 --- a/src/nvim/eval/vars.h +++ b/src/nvim/eval/vars.h @@ -2,6 +2,7 @@ #define NVIM_EVAL_VARS_H #include "nvim/ex_cmds_defs.h" +#include "nvim/option_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/vars.h.generated.h" diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index b49563f44d..c656f785c9 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -1342,7 +1342,7 @@ void ex_catch(exarg_T *eap) *end = NUL; } save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; // Disable error messages, it will make current exception // invalid emsg_off++; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index d462c83710..a719fedb22 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -789,10 +789,11 @@ EXTERN char *escape_chars INIT(= " \t\\\"|"); // need backslash in cmd line EXTERN bool keep_help_flag INIT(= false); // doing :ta from help file -// When a string option is NULL (which only happens in out-of-memory -// situations), it is set to empty_option, to avoid having to check for NULL -// everywhere. -EXTERN char *empty_option INIT(= ""); +// When a string option is NULL (which only happens in out-of-memory situations), it is set to +// empty_string_option, to avoid having to check for NULL everywhere. +// +// TODO(famiu): Remove this when refcounted strings are used for string options. +EXTERN char *empty_string_option INIT(= ""); EXTERN bool redir_off INIT(= false); // no redirection for a moment EXTERN FILE *redir_fd INIT(= NULL); // message redirection file diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f1bc46412f..1f2403450f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1878,8 +1878,8 @@ void clear_showcmd(void) char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. - p_sbr = empty_option; - curwin->w_p_sbr = empty_option; + p_sbr = empty_string_option; + curwin->w_p_sbr = empty_string_option; getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); p_sbr = saved_sbr; curwin->w_p_sbr = saved_w_sbr; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index cb8d1dabc0..260019da33 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5365,8 +5365,8 @@ void cursor_pos_info(dict_T *dict) char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. - p_sbr = empty_option; - curwin->w_p_sbr = empty_option; + p_sbr = empty_string_option; + curwin->w_p_sbr = empty_string_option; oparg.is_VIsual = true; oparg.motion_type = kMTBlockWise; oparg.op_type = OP_NOP; diff --git a/src/nvim/option.c b/src/nvim/option.c index 4b50f27ef2..0c8230d7c9 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -13,8 +13,7 @@ // add some code to didset_window_options(). // - For a buffer option, add some code to buf_copy_options(). // - For a buffer string option, add code to check_buf_options(). -// - If it's a numeric option, add any necessary bounds checks to -// set_num_option(). +// - If it's a numeric option, add any necessary bounds checks to check_num_option_bounds(). // - If it's a list of flags, add some code in do_set(), search for WW_ALL. // - Add documentation! doc/options.txt, and any other related places. // - Add an entry in runtime/optwin.vim. @@ -47,6 +46,7 @@ #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/vars.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" @@ -753,103 +753,6 @@ void ex_set(exarg_T *eap) (void)do_set(eap->arg, flags); } -static void do_set_bool(int opt_idx, int opt_flags, set_prefix_T prefix, int nextchar, - const void *varp, const char **errmsg) -{ - varnumber_T value; - - // ":set opt!": invert - // ":set opt&": reset to default value - // ":set opt<": reset to global value - if (nextchar == '!') { - value = *(int *)(varp) ^ 1; - } else if (nextchar == '&') { - value = (int)(intptr_t)options[opt_idx].def_val; - } else if (nextchar == '<') { - // For 'autoread' -1 means to use global value. - if ((int *)varp == &curbuf->b_p_ar && opt_flags == OPT_LOCAL) { - value = -1; - } else { - value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - } - } else { - // ":set invopt": invert - // ":set opt" or ":set noopt": set or reset - if (prefix == PREFIX_INV) { - value = *(int *)varp ^ 1; - } else { - value = prefix == PREFIX_NO ? 0 : 1; - } - } - - *errmsg = set_bool_option(opt_idx, (void *)varp, (int)value, opt_flags); -} - -static void do_set_num(int opt_idx, int opt_flags, char **argp, int nextchar, const set_op_T op, - const void *varp, char *errbuf, size_t errbuflen, const char **errmsg) -{ - varnumber_T value; - char *arg = *argp; - - // Different ways to set a number option: - // & set to default value - // < set to global value - // <xx> accept special key codes for 'wildchar' - // c accept any non-digit for 'wildchar' - // [-]0-9 set number - // other error - arg++; - if (nextchar == '&') { - value = (varnumber_T)options[opt_idx].def_val; - } else if (nextchar == '<') { - if ((OptInt *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) { - // for 'undolevels' NO_LOCAL_UNDOLEVEL means using the global value - value = NO_LOCAL_UNDOLEVEL; - } else if (opt_flags == OPT_LOCAL - && ((OptInt *)varp == &curwin->w_p_siso - || (OptInt *)varp == &curwin->w_p_so)) { - // for 'scrolloff'/'sidescrolloff' -1 means using the global value - value = -1; - } else { - value = *(OptInt *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - } - } else if (((OptInt *)varp == &p_wc - || (OptInt *)varp == &p_wcm) - && (*arg == '<' - || *arg == '^' - || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1])) - && !ascii_isdigit(*arg)))) { - value = string_to_key(arg); - if (value == 0 && (OptInt *)varp != &p_wcm) { - *errmsg = e_invarg; - return; - } - } else if (*arg == '-' || ascii_isdigit(*arg)) { - int i; - // Allow negative, octal and hex numbers. - vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true, NULL); - if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) { - *errmsg = e_number_required_after_equal; - return; - } - } else { - *errmsg = e_number_required_after_equal; - return; - } - - if (op == OP_ADDING) { - value = *(OptInt *)varp + value; - } - if (op == OP_PREPENDING) { - value = *(OptInt *)varp * value; - } - if (op == OP_REMOVING) { - value = *(OptInt *)varp - value; - } - *errmsg = set_num_option(opt_idx, (void *)varp, value, - errbuf, errbuflen, opt_flags); -} - /// Get the default value for a string option. static char *stropt_get_default_val(int opt_idx, uint64_t flags) { @@ -858,7 +761,7 @@ static char *stropt_get_default_val(int opt_idx, uint64_t flags) // already expanded, only required when an environment variable was set // later if (newval == NULL) { - newval = empty_option; + newval = empty_string_option; } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { char *s = option_expand(opt_idx, newval); if (s == NULL) { @@ -1095,103 +998,6 @@ static char *stropt_get_newval(int nextchar, int opt_idx, char **argp, void *var return newval; } -/// Part of do_set() for string options. -static void do_set_option_string(int opt_idx, int opt_flags, char **argp, int nextchar, - set_op_T op_arg, uint32_t flags, void *varp_arg, char *errbuf, - size_t errbuflen, bool *value_checked, const char **errmsg) -{ - vimoption_T *opt = get_option(opt_idx); - - set_op_T op = op_arg; - void *varp = varp_arg; - char *origval_l = NULL; - char *origval_g = NULL; - - // When using ":set opt=val" for a global option - // with a local value the local value will be - // reset, use the global value here. - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - && ((int)opt->indir & PV_BOTH)) { - varp = opt->var; - } - - // The old value is kept until we are sure that the new value is valid. - char *oldval = *(char **)varp; - - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - origval_l = *(char **)get_varp_scope(opt, OPT_LOCAL); - origval_g = *(char **)get_varp_scope(opt, OPT_GLOBAL); - - // A global-local string option might have an empty option as value to - // indicate that the global value should be used. - if (((int)opt->indir & PV_BOTH) && origval_l == empty_option) { - origval_l = origval_g; - } - } - - char *origval; - // When setting the local value of a global option, the old value may be - // the global value. - if (((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { - origval = *(char **)get_varp(opt); - } else { - origval = oldval; - } - - // Get the new value for the option - char *newval = stropt_get_newval(nextchar, opt_idx, argp, varp, origval, &op, flags); - - // Set the new value. - *(char **)(varp) = newval != NULL ? newval : empty_option; - - // origval may be freed by did_set_string_option(), make a copy. - char *const saved_origval = (origval != NULL) ? xstrdup(origval) : NULL; - char *const saved_origval_l = (origval_l != NULL) ? xstrdup(origval_l) : NULL; - char *const saved_origval_g = (origval_g != NULL) ? xstrdup(origval_g) : NULL; - - // newval (and varp) may become invalid if the buffer is closed by - // autocommands. - char *const saved_newval = (newval != NULL) ? xstrdup(newval) : NULL; - - uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); - const int secure_saved = secure; - - // When an option is set in the sandbox, from a modeline or in secure - // mode, then deal with side effects in secure mode. Also when the - // value was set with the P_INSECURE flag and is not completely - // replaced. - if ((opt_flags & OPT_MODELINE) - || sandbox != 0 - || (op != OP_NONE && (*p & P_INSECURE))) { - secure = 1; - } - - // Handle side effects, and set the global value for ":set" on local - // options. Note: when setting 'syntax' or 'filetype' autocommands may - // be triggered that can cause havoc. - *errmsg = did_set_string_option(curbuf, curwin, opt_idx, (char **)varp, oldval, - errbuf, errbuflen, - opt_flags, op, value_checked); - - secure = secure_saved; - - // call autocommand after handling side effects - if (*errmsg == NULL) { - if (!starting) { - trigger_optionset_string(opt_idx, opt_flags, saved_origval, saved_origval_l, - saved_origval_g, saved_newval); - } - if (options[opt_idx].flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(opt->fullname), - CSTR_AS_OBJ(saved_newval)); - } - } - xfree(saved_origval); - xfree(saved_origval_l); - xfree(saved_origval_g); - xfree(saved_newval); -} - static set_op_T get_op(const char *arg) { set_op_T op = OP_NONE; @@ -1328,34 +1134,145 @@ static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t fla return OK; } -static void do_set_option_value(int opt_idx, int opt_flags, char **argp, set_prefix_T prefix, +/// Get new option value from argp. Allocated OptVal must be freed by caller. +static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix, char **argp, int nextchar, set_op_T op, uint32_t flags, void *varp, char *errbuf, - size_t errbuflen, const char **errmsg) + const size_t errbuflen, const char **errmsg) + FUNC_ATTR_WARN_UNUSED_RESULT { - bool value_checked = false; - if (flags & P_BOOL) { // boolean - do_set_bool(opt_idx, opt_flags, prefix, nextchar, varp, errmsg); - } else if (flags & P_NUM) { // numeric - do_set_num(opt_idx, opt_flags, argp, nextchar, op, varp, errbuf, errbuflen, errmsg); - } else if (opt_idx >= 0) { // string. - do_set_option_string(opt_idx, opt_flags, argp, nextchar, op, flags, varp, errbuf, - errbuflen, &value_checked, errmsg); - } else { - // key code option(FIXME(tarruda): Show a warning or something - // similar) + assert(varp != NULL); + + char *arg = *argp; + OptVal oldval = optval_from_varp_flags(varp, flags); + OptVal newval = NIL_OPTVAL; + + switch (oldval.type) { + case kOptValTypeNil: + abort(); + case kOptValTypeBoolean: { + TriState newval_bool; + + // ":set opt!": invert + // ":set opt&": reset to default value + // ":set opt<": reset to global value + if (nextchar == '!') { + switch (oldval.data.boolean) { + case kNone: + newval_bool = kNone; + break; + case kTrue: + newval_bool = kFalse; + break; + case kFalse: + newval_bool = kTrue; + break; + } + } else if (nextchar == '&') { + newval_bool = TRISTATE_FROM_INT((int)(intptr_t)options[opt_idx].def_val); + } else if (nextchar == '<') { + // For 'autoread', kNone means to use global value. + if ((int *)varp == &curbuf->b_p_ar && opt_flags == OPT_LOCAL) { + newval_bool = kNone; + } else { + newval_bool = TRISTATE_FROM_INT(*(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL)); + } + } else { + // ":set invopt": invert + // ":set opt" or ":set noopt": set or reset + if (prefix == PREFIX_INV) { + newval_bool = *(int *)varp ^ 1; + } else { + newval_bool = prefix == PREFIX_NO ? 0 : 1; + } + } + + newval = BOOLEAN_OPTVAL(newval_bool); + break; } + case kOptValTypeNumber: { + OptInt oldval_num = oldval.data.number; + OptInt newval_num; + + // Different ways to set a number option: + // & set to default value + // < set to global value + // <xx> accept special key codes for 'wildchar' + // c accept any non-digit for 'wildchar' + // [-]0-9 set number + // other error + arg++; + if (nextchar == '&') { + newval_num = (OptInt)(intptr_t)options[opt_idx].def_val; + } else if (nextchar == '<') { + if ((OptInt *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) { + // for 'undolevels' NO_LOCAL_UNDOLEVEL means using the global newval_num + newval_num = NO_LOCAL_UNDOLEVEL; + } else if (opt_flags == OPT_LOCAL + && ((OptInt *)varp == &curwin->w_p_siso || (OptInt *)varp == &curwin->w_p_so)) { + // for 'scrolloff'/'sidescrolloff' -1 means using the global newval_num + newval_num = -1; + } else { + newval_num = *(OptInt *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + } + } else if (((OptInt *)varp == &p_wc || (OptInt *)varp == &p_wcm) + && (*arg == '<' || *arg == '^' + || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1])) + && !ascii_isdigit(*arg)))) { + newval_num = string_to_key(arg); + if (newval_num == 0 && (OptInt *)varp != &p_wcm) { + *errmsg = e_invarg; + return newval; + } + } else if (*arg == '-' || ascii_isdigit(*arg)) { + int i; + // Allow negative, octal and hex numbers. + vim_str2nr(arg, NULL, &i, STR2NR_ALL, &newval_num, NULL, 0, true, NULL); + if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) { + *errmsg = e_number_required_after_equal; + return newval; + } + } else { + *errmsg = e_number_required_after_equal; + return newval; + } - if (*errmsg != NULL) { - return; + if (op == OP_ADDING) { + newval_num = oldval_num + newval_num; + } + if (op == OP_PREPENDING) { + newval_num = oldval_num * newval_num; + } + if (op == OP_REMOVING) { + newval_num = oldval_num - newval_num; + } + + newval = NUMBER_OPTVAL(newval_num); + break; } + case kOptValTypeString: { + char *oldval_str; + vimoption_T *opt = get_option(opt_idx); - if (opt_idx >= 0) { - did_set_option(opt_idx, opt_flags, op == OP_NONE, value_checked); + // When setting the local value of a global option, the old value may be + // the global value. + if (((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { + oldval_str = *(char **)get_varp(opt); + } else { + oldval_str = *(char **)varp; + } + + // Get the new value for the option + char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op, flags); + newval = CSTR_AS_OPTVAL(newval_str); + break; } + } + + return newval; } -static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errbuf, - size_t errbuflen, const char **errmsg) +static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char *errbuf, + size_t errbuflen, const char **errmsg) { // 1: nothing, 0: "no", 2: "inv" in front of name set_prefix_T prefix = get_option_prefix(argp); @@ -1486,8 +1403,21 @@ static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errb } } - do_set_option_value(opt_idx, opt_flags, argp, prefix, nextchar, op, flags, varp, - errbuf, errbuflen, errmsg); + // Don't try to change hidden option. + if (varp == NULL) { + return; + } + + OptVal newval = get_option_newval(opt_idx, opt_flags, prefix, argp, nextchar, op, flags, varp, + errbuf, errbuflen, errmsg); + + if (newval.type == kOptValTypeNil || *errmsg != NULL) { + return; + } + + *errmsg = set_option(opt_idx, varp, newval, opt_flags, op == OP_NONE, errbuf, errbuflen); + // `set_option` copies the new option value, so it needs to be freed here. + optval_free(newval); } /// Parse 'arg' for option settings. @@ -1536,7 +1466,7 @@ int do_set(char *arg, int opt_flags) const char *errmsg = NULL; char errbuf[80]; - do_set_option(opt_flags, &arg, &did_show, errbuf, sizeof(errbuf), &errmsg); + do_one_set_option(opt_flags, &arg, &did_show, errbuf, sizeof(errbuf), &errmsg); // Advance to next argument. // - skip until a blank found, taking care of backslashes @@ -1587,29 +1517,6 @@ int do_set(char *arg, int opt_flags) return OK; } -/// Call this when an option has been given a new value through a user command. -/// Sets the P_WAS_SET flag and takes care of the P_INSECURE flag. -/// -/// @param opt_flags possibly with OPT_MODELINE -/// @param new_value value was replaced completely -/// @param value_checked value was checked to be safe, no need to set P_INSECURE -void did_set_option(int opt_idx, int opt_flags, bool new_value, bool value_checked) -{ - options[opt_idx].flags |= P_WAS_SET; - - // When an option is set in the sandbox, from a modeline or in secure mode - // set the P_INSECURE flag. Otherwise, if a new value is stored reset the - // flag. - uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); - if (!value_checked && (secure - || sandbox != 0 - || (opt_flags & OPT_MODELINE))) { - *p = *p | P_INSECURE; - } else if (new_value) { - *p = *p & ~P_INSECURE; - } -} - /// Convert a key name or string into a key value. /// Used for 'wildchar' and 'cedit' options. int string_to_key(char *arg) @@ -1828,7 +1735,7 @@ int was_set_insecurely(win_T *const wp, char *opt, int opt_flags) /// "opt_idx". For some local options a local flags field is used. /// NOTE: Caller must make sure that "wp" is set to the window from which /// the option is used. -static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) +uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) { if (opt_flags & OPT_LOCAL) { assert(wp != NULL); @@ -1979,62 +1886,62 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) } /// Apply the OptionSet autocommand. -static void apply_optionset_autocmd(int opt_idx, int opt_flags, OptInt oldval, OptInt oldval_g, - OptInt newval, const char *errmsg) +static void apply_optionset_autocmd(int opt_idx, int opt_flags, OptVal oldval, OptVal oldval_g, + OptVal oldval_l, OptVal newval, const char *errmsg) { // Don't do this while starting up, failure or recursively. if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { return; } - char buf_old[12], buf_old_global[12], buf_new[12], buf_type[12]; + char buf_type[7]; + typval_T oldval_tv = optval_as_tv(oldval); + typval_T oldval_g_tv = optval_as_tv(oldval_g); + typval_T oldval_l_tv = optval_as_tv(oldval_l); + typval_T newval_tv = optval_as_tv(newval); - vim_snprintf(buf_old, sizeof(buf_old), "%" PRId64, oldval); - vim_snprintf(buf_old_global, sizeof(buf_old_global), "%" PRId64, oldval_g); - vim_snprintf(buf_new, sizeof(buf_new), "%" PRId64, newval); - vim_snprintf(buf_type, sizeof(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_NEW, buf_new, -1); - set_vim_var_string(VV_OPTION_OLD, buf_old, -1); + vim_snprintf(buf_type, sizeof(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global"); + set_vim_var_tv(VV_OPTION_NEW, &newval_tv); + set_vim_var_tv(VV_OPTION_OLD, &oldval_tv); 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, buf_old, -1); + set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv); } if (opt_flags & OPT_GLOBAL) { set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); + set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_tv); } if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { set_vim_var_string(VV_OPTION_COMMAND, "set", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); + set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_l_tv); + set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_g_tv); } if (opt_flags & OPT_MODELINE) { set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv); } apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } /// Ensure that options set to p_force_on cannot be disabled. -static const char *did_set_force_on(bool *doskip) +static const char *did_set_force_on(optset_T *args FUNC_ATTR_UNUSED) { if (p_force_on == false) { p_force_on = true; - *doskip = true; + args->os_doskip = true; return e_unsupportedoption; } return NULL; } /// Ensure that options set to p_force_off cannot be enabled. -static const char *did_set_force_off(bool *doskip) +static const char *did_set_force_off(optset_T *args FUNC_ATTR_UNUSED) { if (p_force_off == true) { p_force_off = false; - *doskip = true; + args->os_doskip = true; return e_unsupportedoption; } return NULL; @@ -2387,7 +2294,7 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED) if (buf->b_p_vsts_nopaste) { xfree(buf->b_p_vsts_nopaste); } - buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option + buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_string_option ? xstrdup(buf->b_p_vsts) : NULL; } @@ -2406,7 +2313,7 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED) if (p_vsts_nopaste) { xfree(p_vsts_nopaste); } - p_vsts_nopaste = p_vsts && p_vsts != empty_option ? xstrdup(p_vsts) : NULL; + p_vsts_nopaste = p_vsts && p_vsts != empty_string_option ? xstrdup(p_vsts) : NULL; } // Always set the option values, also when 'paste' is set when it is @@ -2421,7 +2328,7 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED) if (buf->b_p_vsts) { free_string_option(buf->b_p_vsts); } - buf->b_p_vsts = empty_option; + buf->b_p_vsts = empty_string_option; XFREE_CLEAR(buf->b_p_vsts_array); } @@ -2442,7 +2349,7 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED) if (p_vsts) { free_string_option(p_vsts); } - p_vsts = empty_option; + p_vsts = empty_string_option; } else if (old_p_paste) { // Paste switched from on to off: Restore saved values. @@ -2456,9 +2363,9 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED) if (buf->b_p_vsts) { free_string_option(buf->b_p_vsts); } - buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_option; + buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_string_option; xfree(buf->b_p_vsts_array); - if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { + if (buf->b_p_vsts && buf->b_p_vsts != empty_string_option) { (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); } else { buf->b_p_vsts_array = NULL; @@ -2482,7 +2389,7 @@ static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED) if (p_vsts) { free_string_option(p_vsts); } - p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_option; + p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_string_option; } old_p_paste = p_paste; @@ -2833,100 +2740,11 @@ static const char *did_set_wrap(optset_T *args) } return NULL; } - -/// Set the value of a boolean option, taking care of side effects -/// -/// @param[in] opt_idx Option index in options[] table. -/// @param[out] varp Pointer to the option variable. -/// @param[in] value New value. -/// @param[in] opt_flags OPT_LOCAL and/or OPT_GLOBAL. -/// -/// @return NULL on success, error message on error. -static const char *set_bool_option(const int opt_idx, char *const varp, const int value, - const int opt_flags) -{ - int old_value = *(int *)varp; - int old_global_value = 0; - - // Disallow changing some options from secure mode - if ((secure || sandbox != 0) - && (options[opt_idx].flags & P_SECURE)) { - return e_secure; - } - - // Save the global value before changing anything. This is needed as for - // a global-only option setting the "local value" in fact sets the global - // value (since there is only one value). - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - old_global_value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - } - - *(int *)varp = value; // set the new value - // Remember where the option was set. - set_option_sctx_idx(opt_idx, opt_flags, current_sctx); - - // May set global value for local option. - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = value; - } - - // Handle side effects for changing a bool option. - const char *errmsg = NULL; - bool doskip = false; - if ((int *)varp == &p_force_on) { - errmsg = did_set_force_on(&doskip); - } else if ((int *)varp == &p_force_off) { - errmsg = did_set_force_off(&doskip); - } else if (options[opt_idx].opt_did_set_cb != NULL) { - optset_T args = { - .os_varp = varp, - .os_flags = opt_flags, - .os_oldval.boolean = old_value, - .os_newval.boolean = value, - .os_doskip = false, - .os_errbuf = NULL, - .os_errbuflen = 0, - .os_buf = curbuf, - .os_win = curwin - }; - - errmsg = options[opt_idx].opt_did_set_cb(&args); - doskip = args.os_doskip; - } - if (doskip) { - return errmsg; - } - - // after handling side effects, call autocommand - - options[opt_idx].flags |= P_WAS_SET; - - apply_optionset_autocmd(opt_idx, opt_flags, - (old_value ? true : false), - (old_global_value ? true : false), - (value ? true : false), NULL); - - if (options[opt_idx].flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(options[opt_idx].fullname), - BOOLEAN_OBJ(*varp)); - } - if ((int *)varp == &p_ru || (int *)varp == &p_sc) { - // in case 'ruler' or 'showcmd' changed - comp_col(); - } - if (curwin->w_curswant != MAXCOL - && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) { - curwin->w_set_curswant = true; - } - check_redraw(options[opt_idx].flags); - - return errmsg; -} - /// Check the bounds of numeric options. -static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, int old_Rows, char *errbuf, +static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, char *errbuf, size_t errbuflen, const char *errmsg) { + int old_Rows = Rows; // remember old Rows // Check the (new) bounds for Rows and Columns here. if (p_lines < min_rows() && full_screen) { if (errbuf != NULL) { @@ -3150,91 +2968,6 @@ static const char *validate_num_option(const OptInt *pp, OptInt *valuep) return NULL; } -/// Set the value of a number option, taking care of side effects -/// -/// @param[in] opt_idx Option index in options[] table. -/// @param[out] varp Pointer to the option variable. -/// @param[in] value New value. -/// @param errbuf Buffer for error messages. -/// @param[in] errbuflen Length of `errbuf`. -/// @param[in] opt_flags OPT_LOCAL, OPT_GLOBAL or OPT_MODELINE. -/// -/// @return NULL on success, error message on error. -static const char *set_num_option(int opt_idx, void *varp, OptInt value, char *errbuf, - size_t errbuflen, int opt_flags) -{ - OptInt old_value = *(OptInt *)varp; - OptInt old_global_value = 0; // only used when setting a local and global option - int old_Rows = Rows; // remember old Rows - OptInt *pp = (OptInt *)varp; - - // Disallow changing some options from secure mode. - if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { - return e_secure; - } - - // Save the global value before changing anything. This is needed as for - // a global-only option setting the "local value" in fact sets the global - // value (since there is only one value). - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - old_global_value = *(OptInt *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - } - - const char *errmsg = validate_num_option(pp, &value); - - // Don't change the value and return early if validation failed. - if (errmsg != NULL) { - return errmsg; - } - - *pp = value; - // Remember where the option was set. - set_option_sctx_idx(opt_idx, opt_flags, current_sctx); - - // Invoke the option specific callback function to validate and apply the - // new value. - if (options[opt_idx].opt_did_set_cb != NULL) { - optset_T args = { - .os_varp = varp, - .os_flags = opt_flags, - .os_oldval.number = old_value, - .os_newval.number = value, - .os_errbuf = NULL, - .os_errbuflen = 0, - .os_buf = curbuf, - .os_win = curwin - }; - errmsg = options[opt_idx].opt_did_set_cb(&args); - } - - // Check the bounds for numeric options here - errmsg = check_num_option_bounds(pp, old_value, old_Rows, errbuf, errbuflen, errmsg); - - // May set global value for local option. - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - *(OptInt *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; - } - - options[opt_idx].flags |= P_WAS_SET; - - apply_optionset_autocmd(opt_idx, opt_flags, old_value, old_global_value, - (int)value, errmsg); - - if (errmsg == NULL && options[opt_idx].flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(options[opt_idx].fullname), - INTEGER_OBJ(*pp)); - } - - comp_col(); // in case 'columns' or 'ls' changed - if (curwin->w_curswant != MAXCOL - && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) { - curwin->w_set_curswant = true; - } - check_redraw(options[opt_idx].flags); - - return errmsg; -} - /// Called after an option changed: check if something needs to be redrawn. void check_redraw_for(buf_T *buf, win_T *win, uint32_t flags) { @@ -3465,8 +3198,8 @@ OptVal optval_copy(OptVal o) UNREACHABLE; } -// Match type of OptVal with the type of the target option. Returns true if the types match and -// false otherwise. +/// Match type of OptVal with the type of the target option. Returns true if the types match and +/// false otherwise. static bool optval_match_type(OptVal o, int opt_idx) { assert(opt_idx >= 0); @@ -3485,7 +3218,60 @@ static bool optval_match_type(OptVal o, int opt_idx) UNREACHABLE; } -// Return C-string representation of OptVal. Caller must free the returned C-string. +/// Create OptVal from var pointer. +static OptVal optval_from_varp(OptValType type, void *varp) +{ + switch (type) { + case kOptValTypeNil: + return NIL_OPTVAL; + case kOptValTypeBoolean: + return BOOLEAN_OPTVAL(TRISTATE_FROM_INT(*(int *)varp)); + case kOptValTypeNumber: + return NUMBER_OPTVAL(*(OptInt *)varp); + case kOptValTypeString: + return STRING_OPTVAL(cstr_as_string(*(char **)varp)); + } + UNREACHABLE; +} + +/// Get option value from var pointer and option flags. +static OptVal optval_from_varp_flags(void *varp, const uint32_t flags) +{ + OptValType type = kOptValTypeNil; + + if (flags & P_BOOL) { + type = kOptValTypeBoolean; + } else if (flags & P_NUM) { + type = kOptValTypeNumber; + } else if (flags & P_STRING) { + type = kOptValTypeString; + } else { + abort(); + } + + return optval_from_varp(type, varp); +} + +/// Set option var pointer value from Optval. +static void set_option_varp(void *varp, OptVal value) +{ + switch (value.type) { + case kOptValTypeNil: + return; + case kOptValTypeBoolean: + *(int *)varp = value.data.boolean; + return; + case kOptValTypeNumber: + *(OptInt *)varp = value.data.number; + return; + case kOptValTypeString: + *(char **)varp = value.data.string.data; + return; + } + UNREACHABLE; +} + +/// Return C-string representation of OptVal. Caller must free the returned C-string. static char *optval_to_cstr(OptVal o) { switch (o.type) { @@ -3507,10 +3293,91 @@ static char *optval_to_cstr(OptVal o) UNREACHABLE; } -// Get an allocated string containing a list of valid types for an option. -// For options with a singular type, it returns the name of the type. For options with multiple -// possible types, it returns a slash separated list of types. For example, if an option can be a -// number, boolean or string, the function returns "Number/Boolean/String" +/// Convert an OptVal to an API Object. +Object optval_as_object(OptVal o) +{ + switch (o.type) { + case kOptValTypeNil: + return NIL; + case kOptValTypeBoolean: + switch (o.data.boolean) { + case kFalse: + case kTrue: + return BOOLEAN_OBJ(o.data.boolean); + case kNone: + return NIL; + } + UNREACHABLE; + case kOptValTypeNumber: + return INTEGER_OBJ(o.data.number); + case kOptValTypeString: + return STRING_OBJ(o.data.string); + } + UNREACHABLE; +} + +/// Convert an API Object to an OptVal. +OptVal object_as_optval(Object o, bool *error) +{ + switch (o.type) { + case kObjectTypeNil: + return NIL_OPTVAL; + case kObjectTypeBoolean: + return BOOLEAN_OPTVAL(o.data.boolean); + case kObjectTypeInteger: + return NUMBER_OPTVAL((OptInt)o.data.integer); + case kObjectTypeString: + return STRING_OPTVAL(o.data.string); + default: + *error = true; + return NIL_OPTVAL; + } + UNREACHABLE; +} + +/// Clear an option value. +/// +/// The exact semantics of this depend on the option. +static OptVal optval_clear(const char *name, uint32_t flags, void *varp, buf_T *buf, win_T *win) +{ + OptVal v = NIL_OPTVAL; + + // Change the type of the OptVal to the type used by the option so that it can be cleared. + // TODO(famiu): Clean up all of this after set_(num|bool|string)_option() is unified. + + if (flags & P_BOOL) { + v.type = kOptValTypeBoolean; + if ((int *)varp == &buf->b_p_ar) { + // TODO(lewis6991): replace this with a more general condition that + // indicates we are setting the local value of a global-local option + v.data.boolean = kNone; + } else { + v = get_option_value(name, NULL, OPT_GLOBAL, NULL); + } + } else if (flags & P_NUM) { + v.type = kOptValTypeNumber; + if ((OptInt *)varp == &curbuf->b_p_ul) { + // The one true special case + v.data.number = NO_LOCAL_UNDOLEVEL; + } else if ((OptInt *)varp == &win->w_p_so || (OptInt *)varp == &win->w_p_siso) { + // TODO(lewis6991): replace this with a more general condition that + // indicates we are setting the local value of a global-local option + v.data.number = -1; + } else { + v = get_option_value(name, NULL, OPT_GLOBAL, NULL); + } + } else if (flags & P_STRING) { + v.type = kOptValTypeString; + v.data.string.data = NULL; + } + + return v; +} + +/// Get an allocated string containing a list of valid types for an option. +/// For options with a singular type, it returns the name of the type. For options with multiple +/// possible types, it returns a slash separated list of types. For example, if an option can be a +/// number, boolean or string, the function returns "Number/Boolean/String" static char *option_get_valid_types(int opt_idx) { uint32_t flags = options[opt_idx].flags; @@ -3604,7 +3471,7 @@ OptVal get_option_value(const char *name, uint32_t *flagsp, int scope, bool *hid return BOOLEAN_OPTVAL(curbufIsChanged()); } else { int n = *(int *)varp; - return BOOLEAN_OPTVAL(n == 0 ? kFalse : (n >= 1 ? kTrue : kNone)); + return BOOLEAN_OPTVAL(TRISTATE_FROM_INT(n)); } } } @@ -3731,69 +3598,148 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return rv; } -// Return information for option at 'opt_idx' +/// Return information for option at 'opt_idx' vimoption_T *get_option(int opt_idx) { return &options[opt_idx]; } -/// Clear an option +/// Set the value of an option using an OptVal. /// -/// The exact semantics of this depend on the option. -static OptVal clear_optval(const char *name, uint32_t flags, void *varp, buf_T *buf, win_T *win) +/// @param opt_idx Option index. Must be >=0. +/// @param[in] varp Option variable pointer, cannot be NULL. +/// @param value New option value. +/// @param opt_flags Option flags. +/// @param new_value Whether value was replaced completely. +/// @param[out] errbuf Buffer for error message. +/// @param errbuflen Length of error buffer. +/// +/// @return Error message. NULL if there are no errors. +static const char *set_option(const int opt_idx, void *varp, OptVal value, int opt_flags, + const bool new_value, char *errbuf, size_t errbuflen) { - OptVal v = NIL_OPTVAL; + assert(opt_idx >= 0 && varp != NULL); - // Change the type of the OptVal to the type used by the option so that it can be cleared. - // TODO(famiu): Clean up all of this after set_(num|bool|string)_option() is unified. + const char *errmsg = NULL; + bool value_checked = false; + vimoption_T *opt = &options[opt_idx]; - if (flags & P_BOOL) { - v.type = kOptValTypeBoolean; - if ((int *)varp == &buf->b_p_ar) { - // TODO(lewis6991): replace this with a more general condition that - // indicates we are setting the local value of a global-local option - v.data.boolean = kNone; - } else { - v = get_option_value(name, NULL, OPT_GLOBAL, NULL); - } - } else if (flags & P_NUM) { - v.type = kOptValTypeNumber; - if ((OptInt *)varp == &curbuf->b_p_ul) { - // The one true special case - v.data.number = NO_LOCAL_UNDOLEVEL; - } else if ((OptInt *)varp == &win->w_p_so || (OptInt *)varp == &win->w_p_siso) { - // TODO(lewis6991): replace this with a more general condition that - // indicates we are setting the local value of a global-local option - v.data.number = -1; - } else { - v = get_option_value(name, NULL, OPT_GLOBAL, NULL); + // TODO(famiu): Unify set_string_option with set_option. + if (value.type == kOptValTypeString) { + errmsg = set_string_option(opt_idx, varp, value.data.string.data, opt_flags, true, + &value_checked, errbuf, errbuflen); + goto end; + } + + // Disallow changing some options from secure mode. + if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { + return e_secure; + } + + OptVal old_value = optval_from_varp(value.type, varp); + OptVal old_global_value = NIL_OPTVAL; + OptVal old_local_value = NIL_OPTVAL; + + // Save the local and global values before changing anything. This is needed as for a global-only + // option setting the "local value" in fact sets the global value (since there is only one value). + // + // TODO(famiu): This needs to be changed to use the current type of the old value instead of + // value.type, when multi-type options are added. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + old_local_value = + optval_from_varp(value.type, get_varp_scope(opt, OPT_LOCAL)); + old_global_value = + optval_from_varp(value.type, get_varp_scope(opt, OPT_GLOBAL)); + } + + if (value.type == kOptValTypeNumber) { + errmsg = validate_num_option((OptInt *)varp, &value.data.number); + + // Don't change the value and return early if validation failed. + if (errmsg != NULL) { + return errmsg; } - } else if (flags & P_STRING) { - v.type = kOptValTypeString; - v.data.string.data = NULL; } - return v; -} + // Set the new option value. + set_option_varp(varp, value); + // Remember where the option was set. + set_option_sctx_idx(opt_idx, opt_flags, current_sctx); + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_option_varp(get_varp_scope(opt, OPT_GLOBAL), value); + } -static const char *set_option(int opt_idx, void *varp, OptVal *v, int opt_flags, char *errbuf, - size_t errbuflen) -{ - const char *errmsg = NULL; + // Invoke the option specific callback function to validate and apply the new value. + bool doskip = false; + opt_did_set_cb_T did_set_cb; - bool value_checked = false; + if ((int *)varp == &p_force_on) { + did_set_cb = did_set_force_on; + } else if ((int *)varp == &p_force_off) { + did_set_cb = did_set_force_off; + } else { + did_set_cb = opt->opt_did_set_cb; + } + if (did_set_cb != NULL) { + // TODO(famiu): make os_oldval and os_newval use OptVal. + optset_T did_set_cb_args = { + .os_varp = varp, + .os_flags = opt_flags, + .os_oldval = old_value.data, + .os_newval = value.data, + .os_doskip = false, + .os_errbuf = NULL, + .os_errbuflen = 0, + .os_buf = curbuf, + .os_win = curwin + }; + + errmsg = did_set_cb(&did_set_cb_args); + doskip = did_set_cb_args.os_doskip; + } + if (doskip) { + return errmsg; + } + + // Check the bound for num options. + if (value.type == kOptValTypeNumber) { + errmsg + = check_num_option_bounds((OptInt *)varp, old_value.data.number, errbuf, errbuflen, errmsg); + } + + apply_optionset_autocmd(opt_idx, opt_flags, + old_value, + old_global_value, + old_local_value, + value, + errmsg); + + if (opt->flags & P_UI_OPTION) { + OptVal value_copy = optval_copy(optval_from_varp(value.type, varp)); + ui_call_option_set(cstr_as_string(opt->fullname), + optval_as_object(value_copy)); + } - if (v->type == kOptValTypeBoolean) { - errmsg = set_bool_option(opt_idx, varp, (int)v->data.boolean, opt_flags); - } else if (v->type == kOptValTypeNumber) { - errmsg = set_num_option(opt_idx, varp, v->data.number, errbuf, errbuflen, opt_flags); - } else if (v->type == kOptValTypeString) { - errmsg = set_string_option(opt_idx, varp, v->data.string.data, opt_flags, &value_checked, - errbuf, errbuflen); + comp_col(); // in case 'columns' or 'ls' changed + if (curwin->w_curswant != MAXCOL + && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { + curwin->w_set_curswant = true; } + check_redraw(opt->flags); +end: if (errmsg == NULL) { - did_set_option(opt_idx, opt_flags, true, value_checked); + opt->flags |= P_WAS_SET; + + // When an option is set in the sandbox, from a modeline or in secure mode set the P_INSECURE + // flag. Otherwise, if a new value is stored reset the flag. + uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); + if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) { + *p |= P_INSECURE; + } else if (new_value) { + *p &= ~P_INSECURE; + } } return errmsg; @@ -3845,7 +3791,7 @@ const char *set_option_value(const char *const name, const OptVal value, int opt OptVal v = optval_copy(value); if (v.type == kOptValTypeNil) { - v = clear_optval(name, flags, varp, curbuf, curwin); + v = optval_clear(name, flags, varp, curbuf, curwin); } else if (!optval_match_type(v, opt_idx)) { char *rep = optval_to_cstr(v); char *valid_types = option_get_valid_types(opt_idx); @@ -3857,7 +3803,7 @@ const char *set_option_value(const char *const name, const OptVal value, int opt goto end; } - errmsg = set_option(opt_idx, varp, &v, opt_flags, errbuf, sizeof(errbuf)); + errmsg = set_option(opt_idx, varp, v, opt_flags, true, errbuf, sizeof(errbuf)); end: optval_free(v); // Free the copied OptVal. @@ -4835,8 +4781,8 @@ void win_copy_options(win_T *wp_from, win_T *wp_to) static char *copy_option_val(const char *val) { - if (val == empty_option) { - return empty_option; // no need to allocate memory + if (val == empty_string_option) { + return empty_string_option; // no need to allocate memory } return xstrdup(val); } @@ -4883,7 +4829,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_cocu = copy_option_val(from->wo_cocu); to->wo_cole = from->wo_cole; to->wo_fdc = copy_option_val(from->wo_fdc); - to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_option; + to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_string_option; to->wo_fen = from->wo_fen; to->wo_fen_save = from->wo_fen_save; to->wo_fdi = copy_option_val(from->wo_fdi); @@ -4891,7 +4837,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_fdl = from->wo_fdl; to->wo_fdl_save = from->wo_fdl_save; to->wo_fdm = copy_option_val(from->wo_fdm); - to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_option; + to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_string_option; to->wo_fdn = from->wo_fdn; to->wo_fde = copy_option_val(from->wo_fde); to->wo_fdt = copy_option_val(from->wo_fdt); @@ -4913,7 +4859,7 @@ void check_win_options(win_T *win) check_winopt(&win->w_allbuf_opt); } -/// Check for NULL pointers in a winopt_T and replace them with empty_option. +/// Check for NULL pointers in a winopt_T and replace them with empty_string_option. static void check_winopt(winopt_T *wop) { check_string_option(&wop->wo_fdc); @@ -5066,8 +5012,8 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_ff = xstrdup(p_ff); break; } - buf->b_p_bh = empty_option; - buf->b_p_bt = empty_option; + buf->b_p_bh = empty_string_option; + buf->b_p_bt = empty_string_option; } else { free_buf_options(buf, false); } @@ -5128,7 +5074,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_sts_nopaste = p_sts_nopaste; buf->b_p_vsts = xstrdup(p_vsts); COPY_OPT_SCTX(buf, BV_VSTS); - if (p_vsts && p_vsts != empty_option) { + if (p_vsts && p_vsts != empty_string_option) { (void)tabstop_set(p_vsts, &buf->b_p_vsts_array); } else { buf->b_p_vsts_array = NULL; @@ -5164,7 +5110,7 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_LOP); // Don't copy 'filetype', it must be detected - buf->b_p_ft = empty_option; + buf->b_p_ft = empty_string_option; buf->b_p_pi = p_pi; COPY_OPT_SCTX(buf, BV_PI); buf->b_p_cinw = xstrdup(p_cinw); @@ -5172,10 +5118,10 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_lisp = p_lisp; COPY_OPT_SCTX(buf, BV_LISP); // Don't copy 'syntax', it must be set - buf->b_p_syn = empty_option; + buf->b_p_syn = empty_string_option; buf->b_p_smc = p_smc; COPY_OPT_SCTX(buf, BV_SMC); - buf->b_s.b_syn_isk = empty_option; + buf->b_s.b_syn_isk = empty_string_option; buf->b_s.b_p_spc = xstrdup(p_spc); COPY_OPT_SCTX(buf, BV_SPC); (void)compile_cap_prog(&buf->b_s); @@ -5189,7 +5135,7 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_INDE); buf->b_p_indk = xstrdup(p_indk); COPY_OPT_SCTX(buf, BV_INDK); - buf->b_p_fp = empty_option; + buf->b_p_fp = empty_string_option; buf->b_p_fex = xstrdup(p_fex); COPY_OPT_SCTX(buf, BV_FEX); buf->b_p_sua = xstrdup(p_sua); @@ -5208,30 +5154,30 @@ void buf_copy_options(buf_T *buf, int flags) // are not copied, start using the global value buf->b_p_ar = -1; buf->b_p_ul = NO_LOCAL_UNDOLEVEL; - buf->b_p_bkc = empty_option; + buf->b_p_bkc = empty_string_option; buf->b_bkc_flags = 0; - buf->b_p_gp = empty_option; - buf->b_p_mp = empty_option; - buf->b_p_efm = empty_option; - buf->b_p_ep = empty_option; - buf->b_p_kp = empty_option; - buf->b_p_path = empty_option; - buf->b_p_tags = empty_option; - buf->b_p_tc = empty_option; + buf->b_p_gp = empty_string_option; + buf->b_p_mp = empty_string_option; + buf->b_p_efm = empty_string_option; + buf->b_p_ep = empty_string_option; + buf->b_p_kp = empty_string_option; + buf->b_p_path = empty_string_option; + buf->b_p_tags = empty_string_option; + buf->b_p_tc = empty_string_option; buf->b_tc_flags = 0; - buf->b_p_def = empty_option; - buf->b_p_inc = empty_option; + buf->b_p_def = empty_string_option; + buf->b_p_inc = empty_string_option; buf->b_p_inex = xstrdup(p_inex); COPY_OPT_SCTX(buf, BV_INEX); - buf->b_p_dict = empty_option; - buf->b_p_tsr = empty_option; - buf->b_p_tsrfu = empty_option; + buf->b_p_dict = empty_string_option; + buf->b_p_tsr = empty_string_option; + buf->b_p_tsrfu = empty_string_option; buf->b_p_qe = xstrdup(p_qe); COPY_OPT_SCTX(buf, BV_QE); buf->b_p_udf = p_udf; COPY_OPT_SCTX(buf, BV_UDF); - buf->b_p_lw = empty_option; - buf->b_p_menc = empty_option; + buf->b_p_lw = empty_string_option; + buf->b_p_menc = empty_string_option; // Don't copy the options set by ex_help(), use the saved values, // when going from a help buffer to a non-help buffer. @@ -5239,7 +5185,7 @@ void buf_copy_options(buf_T *buf, int flags) // or to a help buffer. if (dont_do_help) { buf->b_p_isk = save_p_isk; - if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { + if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) { (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; @@ -5252,7 +5198,7 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_TS); buf->b_p_vts = xstrdup(p_vts); COPY_OPT_SCTX(buf, BV_VTS); - if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { + if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) { (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; @@ -6128,7 +6074,7 @@ char *get_showbreak_value(win_T *const win) return p_sbr; } if (strcmp(win->w_p_sbr, "NONE") == 0) { - return empty_option; + return empty_string_option; } return win->w_p_sbr; } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index a11ed9188c..970615535e 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -17,16 +17,17 @@ typedef enum { kOptValTypeString, } OptValType; +typedef union { + // boolean options are actually tri-states because they have a third "None" value. + TriState boolean; + OptInt number; + String string; +} OptValData; + /// Option value typedef struct { OptValType type; - - union { - // boolean options are actually tri-states because they have a third "None" value. - TriState boolean; - OptInt number; - String string; - } data; + OptValData data; } OptVal; /// :set operator types @@ -45,21 +46,11 @@ typedef struct { void *os_varp; int os_idx; int os_flags; - set_op_T os_op; - - /// old value of the option (can be a string, number or a boolean) - union { - const OptInt number; - const bool boolean; - const char *string; - } os_oldval; - - /// new value of the option (can be a string, number or a boolean) - union { - const OptInt number; - const bool boolean; - const char *string; - } os_newval; + + /// Old value of the option. + OptValData os_oldval; + /// New value of the option. + OptValData os_newval; /// When set by the called function: Stop processing the option further. /// Currently only used for boolean options. diff --git a/src/nvim/options.lua b/src/nvim/options.lua index ec8450ba17..8d12d860d8 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -6824,6 +6824,7 @@ return { }, { abbreviation = 'sd', + cb = 'did_set_shada', defaults = { if_true = "!,'100,<50,s10,h", doc = [[for diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 88e7ebd991..2e90b6b4ae 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -287,29 +287,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; } } @@ -385,7 +384,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; @@ -441,14 +440,13 @@ void set_string_option_direct_in_buf(buf_T *buf, const char *name, int opt_idx, /// #OPT_GLOBAL. /// /// @return NULL on success, an untranslated error message on error. -const char *set_string_option(const int opt_idx, void *varp_arg, const char *value, - const int opt_flags, bool *value_checked, char *const errbuf, +const char *set_string_option(const int opt_idx, void *varp, const char *value, const int opt_flags, + const bool new_value, bool *value_checked, char *const errbuf, const size_t errbuflen) FUNC_ATTR_WARN_UNUSED_RESULT { vimoption_T *opt = get_option(opt_idx); - void *varp = (char **)varp_arg; char *origval_l = NULL; char *origval_g = NULL; @@ -469,7 +467,7 @@ const char *set_string_option(const int opt_idx, void *varp_arg, const char *val // A global-local string option might have an empty option as value to // indicate that the global value should be used. - if (((int)opt->indir & PV_BOTH) && origval_l == empty_option) { + if (((int)opt->indir & PV_BOTH) && origval_l == empty_string_option) { origval_l = origval_g; } } @@ -483,7 +481,7 @@ const char *set_string_option(const int opt_idx, void *varp_arg, const char *val origval = oldval; } - *(char **)varp = xstrdup(value != NULL ? value : empty_option); + *(char **)varp = xstrdup(value != NULL ? value : empty_string_option); char *const saved_origval = (origval != NULL) ? xstrdup(origval) : NULL; char *const saved_oldval_l = (origval_l != NULL) ? xstrdup(origval_l) : 0; @@ -494,19 +492,17 @@ const char *set_string_option(const int opt_idx, void *varp_arg, const char *val char *const saved_newval = xstrdup(*(char **)varp); const int secure_saved = secure; + const uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); - // When an option is set in the sandbox, from a modeline or in secure - // mode, then deal with side effects in secure mode. Also when the - // value was set with the P_INSECURE flag and is not completely - // replaced. - if ((opt_flags & OPT_MODELINE) - || sandbox != 0) { + // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side + // effects in secure mode. Also when the value was set with the P_INSECURE flag and is not + // completely replaced. + if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!new_value && (*p & P_INSECURE))) { secure = 1; } const char *const errmsg = did_set_string_option(curbuf, curwin, opt_idx, varp, oldval, - errbuf, errbuflen, - opt_flags, OP_NONE, value_checked); + errbuf, errbuflen, opt_flags, value_checked); secure = secure_saved; @@ -864,7 +860,7 @@ int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches) const char *did_set_backupcopy(optset_T *args) { buf_T *buf = (buf_T *)args->os_buf; - const char *oldval = args->os_oldval.string; + const char *oldval = args->os_oldval.string.data; int opt_flags = args->os_flags; char *bkc = p_bkc; unsigned *flags = &bkc_flags; @@ -1463,7 +1459,7 @@ 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; + const char *oldval = args->os_oldval.string.data; int opt_flags = args->os_flags; if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) { return e_modifiable; @@ -1516,7 +1512,7 @@ const char *did_set_filetype_or_syntax(optset_T *args) return e_invarg; } - args->os_value_changed = strcmp(args->os_oldval.string, *varp) != 0; + 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. @@ -2107,7 +2103,7 @@ const char *did_set_sessionoptions(optset_T *args) } if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { // Don't allow both "sesdir" and "curdir". - const char *oldval = args->os_oldval.string; + const char *oldval = args->os_oldval.string.data; (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); return e_invarg; } @@ -2123,20 +2119,11 @@ int expand_set_sessionoptions(optexpand_T *args, int *numMatches, char ***matche matches); } -static const char *did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, char *errbuf, - size_t errbuflen) -{ - static int shada_idx = -1; - // 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); +const char *did_set_shada(optset_T *args) +{ + 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) { @@ -2228,7 +2215,7 @@ const char *did_set_signcolumn(optset_T *args) { win_T *win = (win_T *)args->os_win; char **varp = (char **)args->os_varp; - const char *oldval = args->os_oldval.string; + const char *oldval = args->os_oldval.string.data; if (check_signcolumn(*varp) != OK) { return e_invarg; } @@ -2582,7 +2569,7 @@ const char *did_set_virtualedit(optset_T *args) } else { if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { return e_invarg; - } else if (strcmp(ve, args->os_oldval.string) != 0) { + } 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); @@ -2743,7 +2730,7 @@ static void do_spelllang_source(win_T *win) /// /// @return NULL for success, or an untranslated error message for an error const char *did_set_string_option(buf_T *buf, win_T *win, int opt_idx, char **varp, char *oldval, - char *errbuf, size_t errbuflen, int opt_flags, set_op_T op, + char *errbuf, size_t errbuflen, int opt_flags, bool *value_checked) { const char *errmsg = NULL; @@ -2757,9 +2744,8 @@ const char *did_set_string_option(buf_T *buf, win_T *win, int opt_idx, char **va .os_varp = varp, .os_idx = opt_idx, .os_flags = opt_flags, - .os_op = op, - .os_oldval.string = oldval, - .os_newval.string = *varp, + .os_oldval.string = cstr_as_string(oldval), + .os_newval.string = cstr_as_string(*varp), .os_value_checked = false, .os_value_changed = false, .os_restore_chartab = false, @@ -2789,8 +2775,6 @@ const char *did_set_string_option(buf_T *buf, win_T *win, int opt_idx, char **va // The 'isident', 'iskeyword', 'isprint' and 'isfname' options may // change the character table. On failure, this needs to be restored. restore_chartab = args.os_restore_chartab; - } else if (varp == &p_shada) { // 'shada' - errmsg = did_set_shada(&opt, &opt_idx, &free_oldval, errbuf, errbuflen); } // If an error is detected, restore the previous value. @@ -2818,7 +2802,7 @@ const char *did_set_string_option(buf_T *buf, win_T *win, int opt_idx, char **va // the local value and make it empty char *p = get_varp_scope(opt, OPT_LOCAL); free_string_option(*(char **)p); - *(char **)p = empty_option; + *(char **)p = empty_string_option; } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { // May set global value for local option. set_string_option_global(opt, varp); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c532c08572..5616a5a048 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2596,7 +2596,7 @@ static int qf_open_new_file_win(qf_info_T *ll_ref) if (win_split(0, flags) == FAIL) { return FAIL; // not enough room for window } - p_swb = empty_option; // don't split again + p_swb = empty_string_option; // don't split again swb_flags = 0; RESET_BINDING(curwin); if (ll_ref != NULL) { @@ -3073,7 +3073,7 @@ theend: qfl->qf_ptr = qf_ptr; qfl->qf_index = qf_index; } - if (p_swb != old_swb && p_swb == empty_option) { + if (p_swb != old_swb && p_swb == empty_string_option) { // Restore old 'switchbuf' value, but not when an autocommand or // modeline has changed the value. p_swb = old_swb; @@ -7201,7 +7201,7 @@ void ex_helpgrep(exarg_T *eap) // Make 'cpoptions' empty, the 'l' flag should not be used here. char *const save_cpo = p_cpo; const bool save_cpo_allocated = is_option_allocated("cpo"); - p_cpo = empty_option; + p_cpo = empty_string_option; bool new_qi = false; if (is_loclist_cmd(eap->cmdidx)) { @@ -7232,7 +7232,7 @@ void ex_helpgrep(exarg_T *eap) updated = true; } - if (p_cpo == empty_option) { + if (p_cpo == empty_string_option) { p_cpo = save_cpo; } else { // Darn, some plugin changed the value. If it's still empty it was diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index a35352136b..8336c33275 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -729,7 +729,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) static void save_chartab(char *chartab) { - if (syn_block->b_syn_isk == empty_option) { + if (syn_block->b_syn_isk == empty_string_option) { return; } @@ -739,7 +739,7 @@ static void save_chartab(char *chartab) static void restore_chartab(char *chartab) { - if (syn_win->w_s->b_syn_isk != empty_option) { + if (syn_win->w_s->b_syn_isk != empty_string_option) { memmove(syn_buf->b_chartab, chartab, (size_t)32); } } @@ -2946,7 +2946,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) arg = skipwhite(arg); if (*arg == NUL) { msg_puts("\n"); - if (curwin->w_s->b_syn_isk != empty_option) { + if (curwin->w_s->b_syn_isk != empty_string_option) { msg_puts("syntax iskeyword "); msg_outtrans(curwin->w_s->b_syn_isk, 0); } else { @@ -4758,7 +4758,7 @@ static char *get_syn_pattern(char *arg, synpat_T *ci) // Make 'cpoptions' empty, to avoid the 'l' flag char *cpo_save = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC); p_cpo = cpo_save; @@ -4915,7 +4915,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) // Make 'cpoptions' empty, to avoid the 'l' flag cpo_save = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; curwin->w_s->b_syn_linecont_prog = vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC); p_cpo = cpo_save; @@ -5298,7 +5298,7 @@ void ex_ownsyntax(exarg_T *eap) hash_init(&curwin->w_s->b_keywtab_ic); // TODO(vim): Keep the spell checking as it was. curwin->w_p_spell = false; // No spell checking - // make sure option values are "empty_option" instead of NULL + // make sure option values are "empty_string_option" instead of NULL clear_string_option(&curwin->w_s->b_p_spc); clear_string_option(&curwin->w_s->b_p_spf); clear_string_option(&curwin->w_s->b_p_spl); diff --git a/src/nvim/types.h b/src/nvim/types.h index 7b23fe419b..86ef64a9a9 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -42,6 +42,8 @@ typedef enum { #define TRISTATE_TO_BOOL(val, \ default) ((val) == kTrue ? true : ((val) == kFalse ? false : (default))) +#define TRISTATE_FROM_INT(val) ((val) == 0 ? kFalse : ((val) >= 1 ? kTrue : kNone)) + typedef struct Decoration Decoration; #ifndef ORDER_BIG_ENDIAN diff --git a/test/functional/legacy/autocmd_option_spec.lua b/test/functional/legacy/autocmd_option_spec.lua index 2d17439a78..850c005d39 100644 --- a/test/functional/legacy/autocmd_option_spec.lua +++ b/test/functional/legacy/autocmd_option_spec.lua @@ -48,10 +48,10 @@ end local function expected_table(option, oldval, oldval_l, oldval_g, newval, scope, cmd, attr) return { option = option, - oldval = tostring(oldval), - oldval_l = tostring(oldval_l), - oldval_g = tostring(oldval_g), - newval = tostring(newval), + oldval = oldval, + oldval_l = oldval_l, + oldval_g = oldval_g, + newval = newval, scope = scope, cmd = cmd, attr = attr, @@ -129,44 +129,44 @@ describe('au OptionSet', function() it('should be called in setting number option', function() command('set nu') - expected_combination({'number', 0, 0, 0, 1, 'global', 'set'}) + expected_combination({'number', false, false, false, true, 'global', 'set'}) command('setlocal nonu') - expected_combination({'number', 1, 1, '', 0, 'local', 'setlocal'}) + expected_combination({'number', true, true, '', false, 'local', 'setlocal'}) command('setglobal nonu') - expected_combination({'number', 1, '', 1, 0, 'global', 'setglobal'}) + expected_combination({'number', true, '', true, false, 'global', 'setglobal'}) end) it('should be called in setting autoindent option',function() command('setlocal ai') - expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'autoindent', false, false, '', true, 'local', 'setlocal'}) command('setglobal ai') - expected_combination({'autoindent', 0, '', 0, 1, 'global', 'setglobal'}) + expected_combination({'autoindent', false, '', false, true, 'global', 'setglobal'}) command('set noai') - expected_combination({'autoindent', 1, 1, 1, 0, 'global', 'set'}) + expected_combination({'autoindent', true, true, true, false, 'global', 'set'}) end) it('should be called in inverting global autoindent option',function() command('set ai!') - expected_combination({'autoindent', 0, 0, 0, 1, 'global', 'set'}) + expected_combination({'autoindent', false, false, false, true, 'global', 'set'}) end) it('should be called in being unset local autoindent option',function() command('setlocal ai') - expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'autoindent', false, false, '', true, 'local', 'setlocal'}) command('setlocal ai<') - expected_combination({'autoindent', 1, 1, '', 0, 'local', 'setlocal'}) + expected_combination({'autoindent', true, true, '', false, 'local', 'setlocal'}) end) it('should be called in setting global list and number option at the same time',function() command('set list nu') expected_combination( - {'list', 0, 0, 0, 1, 'global', 'set'}, - {'number', 0, 0, 0, 1, 'global', 'set'} + {'list', false, false, false, true, 'global', 'set'}, + {'number', false, false, false, true, 'global', 'set'} ) end) @@ -177,20 +177,20 @@ describe('au OptionSet', function() it('should be called in setting local acd', function() command('setlocal acd') - expected_combination({'autochdir', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'autochdir', false, false, '', true, 'local', 'setlocal'}) end) it('should be called in setting autoread', function() command('set noar') - expected_combination({'autoread', 1, 1, 1, 0, 'global', 'set'}) + expected_combination({'autoread', true, true, true, false, 'global', 'set'}) command('setlocal ar') - expected_combination({'autoread', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'autoread', false, false, '', true, 'local', 'setlocal'}) end) it('should be called in inverting global autoread', function() command('setglobal invar') - expected_combination({'autoread', 1, '', 1, 0, 'global', 'setglobal'}) + expected_combination({'autoread', true, '', true, false, 'global', 'setglobal'}) end) it('should be called in setting backspace option through :let', function() @@ -208,7 +208,7 @@ describe('au OptionSet', function() it('should trigger using correct option name', function() command('call setbufvar(1, "&backup", 1)') - expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'backup', false, false, '', true, 'local', 'setlocal'}) end) it('should trigger if the current buffer is different from the targeted buffer', function() @@ -441,105 +441,105 @@ describe('au OptionSet', function() command('noa setglobal foldcolumn=8') command('noa setlocal foldcolumn=1') command('setglobal foldcolumn=2') - expected_combination({'foldcolumn', 8, '', 8, 2, 'global', 'setglobal'}) + expected_combination({'foldcolumn', '8', '', '8', '2', 'global', 'setglobal'}) command('noa setglobal foldcolumn=8') command('noa setlocal foldcolumn=1') command('setlocal foldcolumn=2') - expected_combination({'foldcolumn', 1, 1, '', 2, 'local', 'setlocal'}) + expected_combination({'foldcolumn', '1', '1', '', '2', 'local', 'setlocal'}) command('noa setglobal foldcolumn=8') command('noa setlocal foldcolumn=1') command('set foldcolumn=2') - expected_combination({'foldcolumn', 1, 1, 8, 2, 'global', 'set'}) + expected_combination({'foldcolumn', '1', '1', '8', '2', 'global', 'set'}) command('noa set foldcolumn=8') command('set foldcolumn=2') - expected_combination({'foldcolumn', 8, 8, 8, 2, 'global', 'set'}) + expected_combination({'foldcolumn', '8', '8', '8', '2', 'global', 'set'}) end) it('with boolean global option', function() command('noa setglobal nowrapscan') command('noa setlocal wrapscan') -- Sets the global(!) value command('setglobal nowrapscan') - expected_combination({'wrapscan', 1, '', 1, 0, 'global', 'setglobal'}) + expected_combination({'wrapscan', true, '', true, false, 'global', 'setglobal'}) command('noa setglobal nowrapscan') command('noa setlocal wrapscan') -- Sets the global(!) value command('setlocal nowrapscan') - expected_combination({'wrapscan', 1, 1, '', 0, 'local', 'setlocal'}) + expected_combination({'wrapscan', true, true, '', false, 'local', 'setlocal'}) command('noa setglobal nowrapscan') command('noa setlocal wrapscan') -- Sets the global(!) value command('set nowrapscan') - expected_combination({'wrapscan', 1, 1, 1, 0, 'global', 'set'}) + expected_combination({'wrapscan', true, true, true, false, 'global', 'set'}) command('noa set nowrapscan') command('set wrapscan') - expected_combination({'wrapscan', 0, 0, 0, 1, 'global', 'set'}) + expected_combination({'wrapscan', false, false, false, true, 'global', 'set'}) end) it('with boolean global-local (to buffer) option', function() command('noa setglobal noautoread') command('noa setlocal autoread') command('setglobal autoread') - expected_combination({'autoread', 0, '', 0, 1, 'global', 'setglobal'}) + expected_combination({'autoread', false, '', false, true, 'global', 'setglobal'}) command('noa setglobal noautoread') command('noa setlocal autoread') command('setlocal noautoread') - expected_combination({'autoread', 1, 1, '', 0, 'local', 'setlocal'}) + expected_combination({'autoread', true, true, '', false, 'local', 'setlocal'}) command('noa setglobal noautoread') command('noa setlocal autoread') command('set autoread') - expected_combination({'autoread', 1, 1, 0, 1, 'global', 'set'}) + expected_combination({'autoread', true, true, false, true, 'global', 'set'}) command('noa set noautoread') command('set autoread') - expected_combination({'autoread', 0, 0, 0, 1, 'global', 'set'}) + expected_combination({'autoread', false, false, false, true, 'global', 'set'}) end) it('with boolean local (to buffer) option', function() command('noa setglobal nocindent') command('noa setlocal cindent') command('setglobal cindent') - expected_combination({'cindent', 0, '', 0, 1, 'global', 'setglobal'}) + expected_combination({'cindent', false, '', false, true, 'global', 'setglobal'}) command('noa setglobal nocindent') command('noa setlocal cindent') command('setlocal nocindent') - expected_combination({'cindent', 1, 1, '', 0, 'local', 'setlocal'}) + expected_combination({'cindent', true, true, '', false, 'local', 'setlocal'}) command('noa setglobal nocindent') command('noa setlocal cindent') command('set cindent') - expected_combination({'cindent', 1, 1, 0, 1, 'global', 'set'}) + expected_combination({'cindent', true, true, false, true, 'global', 'set'}) command('noa set nocindent') command('set cindent') - expected_combination({'cindent', 0, 0, 0, 1, 'global', 'set'}) + expected_combination({'cindent', false, false, false, true, 'global', 'set'}) end) it('with boolean local (to window) option', function() command('noa setglobal nocursorcolumn') command('noa setlocal cursorcolumn') command('setglobal cursorcolumn') - expected_combination({'cursorcolumn', 0, '', 0, 1, 'global', 'setglobal'}) + expected_combination({'cursorcolumn', false, '', false, true, 'global', 'setglobal'}) command('noa setglobal nocursorcolumn') command('noa setlocal cursorcolumn') command('setlocal nocursorcolumn') - expected_combination({'cursorcolumn', 1, 1, '', 0, 'local', 'setlocal'}) + expected_combination({'cursorcolumn', true, true, '', false, 'local', 'setlocal'}) command('noa setglobal nocursorcolumn') command('noa setlocal cursorcolumn') command('set cursorcolumn') - expected_combination({'cursorcolumn', 1, 1, 0, 1, 'global', 'set'}) + expected_combination({'cursorcolumn', true, true, false, true, 'global', 'set'}) command('noa set nocursorcolumn') command('set cursorcolumn') - expected_combination({'cursorcolumn', 0, 0, 0, 1, 'global', 'set'}) + expected_combination({'cursorcolumn', false, false, false, true, 'global', 'set'}) end) end) @@ -559,13 +559,13 @@ describe('au OptionSet', function() expected_empty() command('setlocal ro') - expected_combination({'readonly', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'readonly', false, false, '', true, 'local', 'setlocal'}) command('setglobal ro') - expected_combination({'readonly', 0, '', 0, 1, 'global', 'setglobal'}) + expected_combination({'readonly', false, '', false, true, 'global', 'setglobal'}) command('set noro') - expected_combination({'readonly', 1, 1, 1, 0, 'global', 'set'}) + expected_combination({'readonly', true, true, true, false, 'global', 'set'}) end) describe('being set by setbufvar()', function() @@ -580,7 +580,7 @@ describe('au OptionSet', function() set_hook('backup') command('call setbufvar(1, "&backup", 1)') - expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'backup', false, false, '', true, 'local', 'setlocal'}) end) it('should trigger if the current buffer is different from the targeted buffer', function() @@ -590,7 +590,8 @@ describe('au OptionSet', function() local new_bufnr = buf.get_number(new_buffer) command('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")') - expected_combination({'buftype', '', '', '', 'nofile', 'local', 'setlocal', {bufnr = new_bufnr}}) + expected_combination({ 'buftype', '', '', '', 'nofile', 'local', 'setlocal', + { bufnr = new_bufnr } }) end) end) @@ -606,7 +607,7 @@ describe('au OptionSet', function() set_hook('backup') command('call setwinvar(1, "&backup", 1)') - expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'}) + expected_combination({'backup', false, false, '', true, 'local', 'setlocal'}) end) it('should not trigger if the current window is different from the targeted window', function() @@ -615,7 +616,7 @@ describe('au OptionSet', function() local new_winnr = get_new_window_number() command('call setwinvar(' .. new_winnr .. ', "&cursorcolumn", 1)') - -- expected_combination({'cursorcolumn', 0, 1, 'local', {winnr = new_winnr}}) + -- expected_combination({'cursorcolumn', false, true, 'local', {winnr = new_winnr}}) expected_empty() end) end) @@ -626,7 +627,7 @@ describe('au OptionSet', function() nvim.set_option_value('autochdir', true, {scope='global'}) eq(true, nvim.get_option_value('autochdir', {scope='global'})) - expected_combination({'autochdir', 0, '', 0, 1, 'global', 'setglobal'}) + expected_combination({'autochdir', false, '', false, true, 'global', 'setglobal'}) end) it('should trigger if a number option be set globally', function() |