From e19cc9c9b715d8171f7940632b8855104b5290b6 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 14 Oct 2023 22:19:11 +0600 Subject: [PATCH] refactor(options)!: unify `set_option` and `set_string_option` While the interfaces for setting number and boolean options are now unified by #25394, there is still a separate `set_string_option` function that is used for setting a string option. This PR removes that function and merges it with set_option. BREAKING CHANGE: `v:option_old` is now the old global value for all global-local options, instead of just string global-local options. Local value for a global-local number/boolean option is now unset when the option is set (e.g. using `:set` or `nvim_set_option_value`) without a scope, which means they now behave the same way as string options. Ref: #25672 --- runtime/doc/autocmd.txt | 9 +- runtime/doc/news.txt | 7 +- runtime/doc/vim_diff.txt | 7 +- src/nvim/api/deprecated.c | 15 - src/nvim/eval.c | 5 +- src/nvim/option.c | 667 ++++++++++-------- src/nvim/option.h | 2 + src/nvim/optionstr.c | 317 +-------- test/functional/api/vim_spec.lua | 8 +- .../functional/legacy/autocmd_option_spec.lua | 34 +- 10 files changed, 439 insertions(+), 632 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 7ca9996e15..4b36a7d4ec 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -778,11 +778,10 @@ OptionSet After setting an option (except during This does not set ||, you could use |bufnr()|. - Note that when setting a |global-local| string - option with |:set|, then |v:option_old| is the - old global value. However, for all other kinds - of options (local string options, global-local - number options, ...) it is the old local + Note that when setting a |global-local| option + with |:set|, then |v:option_old| is the old + global value. However, for all options that + are not global-local it is the old local value. OptionSet is not triggered on startup and for diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index c090cfe166..d1191bef9a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -73,7 +73,12 @@ The following changes may require adaptations in user config or plugins. • |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. + instead of always being strings. |v:option_old| is now the old global value + for all global-local options, instead of just string global-local options. + +• Local value for a global-local number/boolean option is now unset when + the option is set (e.g. using |:set| or |nvim_set_option_value()|) without a + scope, which means they now behave the same way as string options. ============================================================================== NEW FEATURES *news-features* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 7d0047bb1d..8249179187 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -289,6 +289,10 @@ Normal commands: |Q| replays the last recorded macro instead of switching to Ex mode (|gQ|). Options: + Local values for global-local number/boolean options are unset when the + option is set without a scope (e.g. by using |:set|), similarly to how + global-local string options work. + 'autoread' works in the terminal (if it supports "focus" events) 'cpoptions' flags: |cpo-_| 'diffopt' "linematch" feature @@ -381,7 +385,8 @@ Variables: |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. + instead of always being strings. |v:option_old| is now the old global value + for all global-local options, instead of just string global-local options. Vimscript: |:redir| nested in |execute()| works. diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 31ba20f627..59b7fc18d6 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -681,21 +681,6 @@ static void set_option_to(uint64_t channel_id, void *to, OptReqScope req_scope, return; }); - if (value.type == kObjectTypeNil) { - if (req_scope == kOptReqGlobal) { - api_set_error(err, kErrorTypeException, "Cannot unset option '%s'", name.data); - return; - } else if (!(flags & SOPT_GLOBAL)) { - api_set_error(err, kErrorTypeException, - "Cannot unset option '%s' because it doesn't have a global value", - name.data); - return; - } else { - unset_global_local_option(name.data, to); - return; - } - } - bool error = false; OptVal optval = object_as_optval(value, &error); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 20d76334f6..8ffc6fd179 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7229,12 +7229,11 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const 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. +/// @param[in] val Value to set to. Will be copied. 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; + tv_copy(tv, &vimvars[idx].vv_di.di_tv); } /// Set the v:argv list. diff --git a/src/nvim/option.c b/src/nvim/option.c index 2582e77662..b1b9a68198 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -574,14 +574,14 @@ void free_all_options(void) if (options[i].indir == PV_NONE) { // global option: free value and default value. if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) { - free_string_option(*(char **)options[i].var); + optval_free(optval_from_varp(i, options[i].var)); } if (options[i].flags & P_DEF_ALLOCED) { - free_string_option(options[i].def_val); + optval_free(optval_from_varp(i, &options[i].def_val)); } - } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) { + } else if (options[i].var != VAR_WIN) { // buffer-local option: free global value - clear_string_option((char **)options[i].var); + optval_free(optval_from_varp(i, options[i].var)); } } free_operatorfunc_option(); @@ -1142,8 +1142,11 @@ static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix, { assert(varp != NULL); + vimoption_T *opt = &options[opt_idx]; char *arg = *argp; - OptVal oldval = optval_from_varp(opt_idx, varp); + // When setting the local value of a global option, the old value may be the global value. + const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL); + OptVal oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp); OptVal newval = NIL_OPTVAL; switch (oldval.type) { @@ -1250,17 +1253,7 @@ static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix, break; } case kOptValTypeString: { - char *oldval_str; - vimoption_T *opt = get_option(opt_idx); - - // 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; - } - + char *oldval_str = oldval.data.string.data; // 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); @@ -1416,8 +1409,6 @@ static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char * } *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. @@ -2753,6 +2744,47 @@ static const char *did_set_wrap(optset_T *args) return NULL; } + +// When 'syntax' is set, load the syntax of that name +static void do_syntax_autocmd(buf_T *buf, bool value_changed) +{ + static int syn_recursive = 0; + + 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_spelllang_source(win_T *win) +{ + char fname[200]; + char *q = win->w_s->b_p_spl; + + // Skip the first name if it is "cjk". + if (strncmp(q, "cjk,", 4) == 0) { + q += 4; + } + + // Source the spell/LANG.{vim,lua} 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.*", (int)(p - q), q); + source_runtime_vim_lua(fname, DIP_ALL); + } +} + /// Check the bounds of numeric options. static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, char *errbuf, size_t errbuflen, const char *errmsg) @@ -3193,7 +3225,10 @@ void optval_free(OptVal o) case kOptValTypeNumber: break; case kOptValTypeString: - api_free_string(o.data.string); + // Don't free empty string option + if (o.data.string.data != empty_string_option) { + api_free_string(o.data.string); + } break; } } @@ -3271,11 +3306,19 @@ OptVal optval_from_varp(int opt_idx, void *varp) /// Set option var pointer value from Optval. /// -/// @param varp Pointer to option variable. -/// @param value Option value. -static void set_option_varp(void *varp, OptVal value) - FUNC_ATTR_NONNULL_ARG(1) +/// @param opt_idx Option index in options[] table. +/// @param[out] varp Pointer to option variable. +/// @param[in] value New option value. +/// @param free_oldval Free old value. +static void set_option_varp(int opt_idx, void *varp, OptVal value, bool free_oldval) + FUNC_ATTR_NONNULL_ARG(2) { + assert(optval_match_type(value, opt_idx)); + + if (free_oldval) { + optval_free(optval_from_varp(opt_idx, varp)); + } + switch (value.type) { case kOptValTypeNil: return; @@ -3356,43 +3399,36 @@ OptVal object_as_optval(Object o, bool *error) UNREACHABLE; } -/// Clear an option value. +/// Unset the local value of an option. The exact semantics of this depend on the option. +/// TODO(famiu): Remove this once we have a dedicated OptVal type for unset local options. /// -/// 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) +/// @param opt_idx Option index in options[] table. +/// @param[in] varp Pointer to option variable. +/// +/// @return [allocated] Option value equal to the unset value for the option. +static OptVal optval_unset_local(int opt_idx, void *varp) { - 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); + vimoption_T *opt = &options[opt_idx]; + // For global-local options, use the unset value of the local value. + if (opt->indir & PV_BOTH) { + // String global-local options always use an empty string for the unset value. + if (opt->flags & P_STRING) { + return STATIC_CSTR_TO_OPTVAL(""); } - } 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; + + if ((int *)varp == &curbuf->b_p_ar) { + return BOOLEAN_OPTVAL(kNone); + } else if ((OptInt *)varp == &curwin->w_p_so || (OptInt *)varp == &curwin->w_p_siso) { + return NUMBER_OPTVAL(-1); + } else if ((OptInt *)varp == &curbuf->b_p_ul) { + return NUMBER_OPTVAL(NO_LOCAL_UNDOLEVEL); } else { - v = get_option_value(name, NULL, OPT_GLOBAL, NULL); + // This should never happen. + abort(); } - } else if (flags & P_STRING) { - v.type = kOptValTypeString; - v.data.string.data = NULL; } - - return v; + // For options that aren't global-local, just set the local value to the global value. + return get_option_value(opt->fullname, NULL, OPT_GLOBAL, NULL); } /// Get an allocated string containing a list of valid types for an option. @@ -3485,36 +3521,254 @@ vimoption_T *get_option(int opt_idx) return &options[opt_idx]; } +/// Check if local value of global-local option is unset for current buffer / window. +/// Always returns false for options that aren't global-local. +/// +/// TODO(famiu): Remove this once we have an OptVal type to indicate an unset local value. +static bool is_option_local_value_unset(vimoption_T *opt, buf_T *buf, win_T *win) +{ + // Local value of option that isn't global-local is always considered set. + if (!((int)opt->indir & PV_BOTH)) { + return false; + } + + // Get pointer to local value in varp_local, and a pointer to the currently used value in varp. + // If the local value is the one currently being used, that indicates that it's set. + // Otherwise it indicates the local value is unset. + void *varp = get_varp_from(opt, buf, win); + void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, buf, win); + + return varp != varp_local; +} + +/// Handle side-effects of setting an option. +/// +/// @param opt_idx Index in options[] table. Must be >= 0. +/// @param[in] varp Option variable pointer, cannot be NULL. +/// @param old_value Old option value. +/// @param new_value New option value. +/// @param opt_flags Option flags. +/// @param[out] doskip Whether option should be processed further. +/// @param[out] value_checked Value was checked to be safe, no need to set P_INSECURE. +/// @param value_replaced Value was replaced completely. +/// @param[out] errbuf Buffer for error message. +/// @param errbuflen Length of error buffer. +/// +/// @return NULL on success, an untranslated error message on error. +static const char *did_set_option(int opt_idx, void *varp, OptVal old_value, OptVal new_value, + int opt_flags, bool *doskip, bool *value_checked, + bool value_replaced, char *errbuf, size_t errbuflen) +{ + vimoption_T *opt = &options[opt_idx]; + const char *errmsg = NULL; + bool restore_chartab = false; + bool free_oldval = (opt->flags & P_ALLOCED); + bool value_changed = false; + + opt_did_set_cb_T did_set_cb; + optset_T did_set_cb_args = { + .os_varp = varp, + .os_idx = opt_idx, + .os_flags = opt_flags, + .os_oldval = old_value.data, + .os_newval = new_value.data, + .os_value_checked = false, + .os_value_changed = false, + .os_restore_chartab = false, + .os_doskip = false, + .os_errbuf = errbuf, + .os_errbuflen = errbuflen, + .os_buf = curbuf, + .os_win = curwin + }; + + 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; + } + + // 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 string options. + } else if (new_value.type == kOptValTypeString + && check_illegal_path_names(*(char **)varp, opt->flags)) { + errmsg = e_invarg; + } else if (did_set_cb != NULL) { + // Invoke the option specific callback function to validate and apply the new value. + errmsg = did_set_cb(&did_set_cb_args); + // Whether option should be processed further or skipped. + *doskip = did_set_cb_args.os_doskip; + // The 'filetype' and 'syntax' option callback functions may change the os_value_changed field. + value_changed = did_set_cb_args.os_value_changed; + // The 'keymap', 'filetype' and 'syntax' option callback functions may change the + // os_value_checked field. + *value_checked = did_set_cb_args.os_value_checked; + // The 'isident', 'iskeyword', 'isprint' and 'isfname' options may change the character table. + // On failure, this needs to be restored. + restore_chartab = did_set_cb_args.os_restore_chartab; + } + + // If an error is detected, restore the previous value. + if (errmsg != NULL) { + set_option_varp(opt_idx, varp, old_value, true); + // When resetting some values, need to act on it. + if (restore_chartab) { + (void)buf_init_chartab(curbuf, true); + } + + // Unset new_value as it is no longer valid. + new_value = NIL_OPTVAL; // NOLINT(clang-analyzer-deadcode.DeadStores) + } else { + // Re-assign the new value as its value may get freed or modified by the option callback. + new_value = optval_from_varp(opt_idx, varp); + + // Remember where the option was set. + set_option_sctx_idx(opt_idx, opt_flags, current_sctx); + // Free options that are in allocated memory. + // Use "free_oldval", because recursiveness may change the flags (esp. init_highlight()). + if (free_oldval) { + optval_free(old_value); + } + opt->flags |= P_ALLOCED; + + // Check the bound for num options. + if (new_value.type == kOptValTypeNumber) { + errmsg = check_num_option_bounds((OptInt *)varp, old_value.data.number, errbuf, errbuflen, + errmsg); + // Re-assign new_value because the new value was modified by the bound check. + new_value = optval_from_varp(opt_idx, varp); + } + + 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 clear it. + void *varp_local = get_varp_scope(opt, OPT_LOCAL); + OptVal local_unset_value = optval_unset_local(opt_idx, varp_local); + set_option_varp(opt_idx, varp_local, local_unset_value, true); + } else if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + // May set global value for local option. + void *varp_global = get_varp_scope(opt, OPT_GLOBAL); + set_option_varp(opt_idx, varp_global, optval_copy(new_value), true); + } + } + + // Skip processing the option further if asked to do so. + if (*doskip) { + return errmsg; + } + + if (errmsg == NULL) { + // Trigger the autocommand only after setting the flags. + if (varp == &curbuf->b_p_syn) { + do_syntax_autocmd(curbuf, value_changed); + } else if (varp == &curbuf->b_p_ft) { + // 'filetype' is set, trigger the FileType autocommand + // Skip this when called from a modeline + // Force autocmd when the filetype was changed + if (!(opt_flags & OPT_MODELINE) || value_changed) { + do_filetype_autocmd(curbuf, value_changed); + } + } else if (varp == &curwin->w_s->b_p_spl) { + do_spelllang_source(curwin); + } + } + + // In case 'columns' or 'ls' changed. + comp_col(); + + if (varp == &p_mouse) { + setmouse(); // in case 'mouse' changed + } else if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) { + // Changing Formatlistpattern when briopt includes the list setting: + // redraw + redraw_all_later(UPD_NOT_VALID); + } else if (varp == &p_wbr || varp == &(curwin->w_p_wbr)) { + // add / remove window bars for 'winbar' + set_winbar(true); + } + + if (curwin->w_curswant != MAXCOL && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { + curwin->w_set_curswant = true; + } + + check_redraw(opt->flags); + + if (errmsg == NULL) { + uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); + 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. + if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) { + *p |= P_INSECURE; + } else if (value_replaced) { + *p &= ~P_INSECURE; + } + } + + return errmsg; +} + /// Set the value of an option using an OptVal. /// -/// @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. +/// @param opt_idx Index in options[] table. Must be >= 0. +/// @param[in] varp Option variable pointer, cannot be NULL. +/// @param value New option value. Might get freed. +/// @param opt_flags Option flags. +/// @param value_replaced 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. +/// @return NULL on success, an untranslated error message on error. 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) + const bool value_replaced, char *errbuf, size_t errbuflen) { assert(opt_idx >= 0 && varp != NULL); const char *errmsg = NULL; bool value_checked = false; + vimoption_T *opt = &options[opt_idx]; - // 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; + static const char *optval_type_names[] = { + [kOptValTypeNil] = "Nil", + [kOptValTypeBoolean] = "Boolean", + [kOptValTypeNumber] = "Number", + [kOptValTypeString] = "String" + }; + + if (value.type == kOptValTypeNil) { + // Don't try to unset local value if scope is global. + // TODO(famiu): Change this to forbid changing all non-local scopes when the API scope bug is + // fixed. + if (opt_flags == OPT_GLOBAL) { + errmsg = _("Cannot unset global option value"); + } else { + optval_free(value); + value = optval_unset_local(opt_idx, varp); + } + } else if (!optval_match_type(value, opt_idx)) { + char *rep = optval_to_cstr(value); + char *valid_types = option_get_valid_types(opt_idx); + snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"), + opt->fullname, valid_types, optval_type_names[value.type], rep); + xfree(rep); + xfree(valid_types); + errmsg = errbuf; } - // Disallow changing some options from secure mode. - if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { - return e_secure; + if (errmsg != NULL) { + goto err; + } + + // 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; } OptVal old_value = optval_from_varp(opt_idx, varp); @@ -3527,100 +3781,84 @@ static const char *set_option(const int opt_idx, void *varp, OptVal value, int o // 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(opt_idx, get_varp_scope(opt, OPT_LOCAL)); old_global_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_GLOBAL)); + old_local_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_LOCAL)); + + // If local value of global-local option is unset, use global value as local value. + if (is_option_local_value_unset(opt, curbuf, curwin)) { + old_local_value = old_global_value; + } } + // Value that's actually being used. + // For local scope of a global-local option, it is equal to the global value. + // In every other case, it is the same as old_value. + const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL); + OptVal used_old_value = oldval_is_global ? optval_from_varp(opt_idx, get_varp(opt)) : old_value; + 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; + goto err; } } - // 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); + set_option_varp(opt_idx, varp, value, false); + + OptVal saved_used_value = optval_copy(used_old_value); + OptVal saved_old_global_value = optval_copy(old_global_value); + OptVal saved_old_local_value = optval_copy(old_local_value); + // New value (and varp) may become invalid if the buffer is closed by autocommands. + OptVal saved_new_value = optval_copy(value); + + 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 || (!value_replaced && (*p & P_INSECURE))) { + secure = 1; } - // Invoke the option specific callback function to validate and apply the new value. bool doskip = false; - opt_did_set_cb_T did_set_cb; - 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_option(opt_idx, varp, old_value, value, opt_flags, &doskip, &value_checked, + value_replaced, errbuf, errbuflen); - errmsg = did_set_cb(&did_set_cb_args); - doskip = did_set_cb_args.os_doskip; - } + secure = secure_saved; + + // Stop processing option further if asked to do so. if (doskip) { - return errmsg; + goto end; } - // 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(opt_idx, varp)); - ui_call_option_set(cstr_as_string(opt->fullname), - optval_as_object(value_copy)); - } - - 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) { - 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; + if (!starting) { + apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value, + saved_old_local_value, saved_new_value, errmsg); + } + if (opt->flags & P_UI_OPTION) { + // Calculate saved_new_value again as its value might be changed by bound checks. + // NOTE: Currently there are no buffer/window local UI options, but if there ever are buffer + // or window local UI options added in the future, varp might become invalid if the buffer or + // window is closed during an autocommand, and a check would have to be added for it. + optval_free(saved_new_value); + saved_new_value = optval_copy(optval_from_varp(opt_idx, varp)); + ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value)); } } - +end: + // Free copied values as they are not needed anymore + optval_free(saved_used_value); + optval_free(saved_old_local_value); + optval_free(saved_old_global_value); + optval_free(saved_new_value); + return errmsg; +err: + optval_free(value); return errmsg; } @@ -3630,17 +3868,10 @@ end: /// @param[in] value Option value. If NIL_OPTVAL, the option value is cleared. /// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). /// -/// @return NULL on success, an untranslated error message on error. +/// @return NULL on success, an untranslated error message on error. const char *set_option_value(const char *const name, const OptVal value, int opt_flags) FUNC_ATTR_NONNULL_ARG(1) { - static const char *optval_type_names[] = { - [kOptValTypeNil] = "Nil", - [kOptValTypeBoolean] = "Boolean", - [kOptValTypeNumber] = "Number", - [kOptValTypeString] = "String" - }; - static char errbuf[IOSIZE]; if (is_tty_option(name)) { @@ -3666,26 +3897,9 @@ const char *set_option_value(const char *const name, const OptVal value, int opt } const char *errmsg = NULL; - // Copy the value so we can modify the copy. - OptVal v = optval_copy(value); - if (v.type == kOptValTypeNil) { - 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); - snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"), - name, valid_types, optval_type_names[v.type], rep); - xfree(rep); - xfree(valid_types); - errmsg = errbuf; - goto end; - } + errmsg = set_option(opt_idx, varp, optval_copy(value), opt_flags, true, errbuf, sizeof(errbuf)); - errmsg = set_option(opt_idx, varp, v, opt_flags, true, errbuf, sizeof(errbuf)); - -end: - optval_free(v); // Free the copied OptVal. return errmsg; } @@ -4143,115 +4357,6 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) return OK; } -// Unset local option value, similar to ":set opt<". -void unset_global_local_option(char *name, void *from) -{ - vimoption_T *p; - buf_T *buf = (buf_T *)from; - - int opt_idx = findoption(name); - if (opt_idx < 0) { - semsg(_("E355: Unknown option: %s"), name); - return; - } - p = &(options[opt_idx]); - - switch ((int)p->indir) { - // global option with local value: use local value if it's been set - case PV_EP: - clear_string_option(&buf->b_p_ep); - break; - case PV_KP: - clear_string_option(&buf->b_p_kp); - break; - case PV_PATH: - clear_string_option(&buf->b_p_path); - break; - case PV_AR: - buf->b_p_ar = -1; - break; - case PV_BKC: - clear_string_option(&buf->b_p_bkc); - buf->b_bkc_flags = 0; - break; - case PV_TAGS: - clear_string_option(&buf->b_p_tags); - break; - case PV_TC: - clear_string_option(&buf->b_p_tc); - buf->b_tc_flags = 0; - break; - case PV_SISO: - curwin->w_p_siso = -1; - break; - case PV_SO: - curwin->w_p_so = -1; - break; - case PV_DEF: - clear_string_option(&buf->b_p_def); - break; - case PV_INC: - clear_string_option(&buf->b_p_inc); - break; - case PV_DICT: - clear_string_option(&buf->b_p_dict); - break; - case PV_TSR: - clear_string_option(&buf->b_p_tsr); - break; - case PV_TSRFU: - clear_string_option(&buf->b_p_tsrfu); - break; - case PV_FP: - clear_string_option(&buf->b_p_fp); - break; - case PV_EFM: - clear_string_option(&buf->b_p_efm); - break; - case PV_GP: - clear_string_option(&buf->b_p_gp); - break; - case PV_MP: - clear_string_option(&buf->b_p_mp); - break; - case PV_SBR: - clear_string_option(&((win_T *)from)->w_p_sbr); - break; - case PV_STL: - clear_string_option(&((win_T *)from)->w_p_stl); - break; - case PV_WBR: - clear_string_option(&((win_T *)from)->w_p_wbr); - break; - case PV_UL: - buf->b_p_ul = NO_LOCAL_UNDOLEVEL; - break; - case PV_LW: - clear_string_option(&buf->b_p_lw); - break; - case PV_MENC: - clear_string_option(&buf->b_p_menc); - break; - case PV_LCS: - clear_string_option(&((win_T *)from)->w_p_lcs); - set_listchars_option((win_T *)from, ((win_T *)from)->w_p_lcs, true); - redraw_later((win_T *)from, UPD_NOT_VALID); - break; - case PV_FCS: - clear_string_option(&((win_T *)from)->w_p_fcs); - set_fillchars_option((win_T *)from, ((win_T *)from)->w_p_fcs, true); - redraw_later((win_T *)from, UPD_NOT_VALID); - break; - case PV_VE: - clear_string_option(&((win_T *)from)->w_p_ve); - ((win_T *)from)->w_ve_flags = 0; - break; - case PV_STC: - clear_string_option(&((win_T *)from)->w_p_stc); - break; - } -} - void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) { if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) { @@ -4634,12 +4739,6 @@ static inline void *get_varp(vimoption_T *p) return get_varp_from(p, curbuf, curwin); } -/// Return the did_set callback function for the option at 'opt_idx' -opt_did_set_cb_T get_option_did_set_cb(int opt_idx) -{ - return options[opt_idx].opt_did_set_cb; -} - /// Get the value of 'equalprg', either the buffer-local one or the global one. char *get_equalprg(void) { diff --git a/src/nvim/option.h b/src/nvim/option.h index 7bc379c843..9e936f39ca 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -56,6 +56,7 @@ typedef struct vimoption { /// cmdline. Only useful for string options. opt_expand_cb_T opt_expand_cb; + // TODO(famiu): Use OptVal for def_val. void *def_val; ///< default values for variable (neovim!!) LastSet last_set; ///< script in which the option was last set } vimoption_T; @@ -72,6 +73,7 @@ enum { /// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global /// values, get local value. typedef enum { + // TODO(famiu): See if `OPT_FREE` is really necessary and remove it if not. OPT_FREE = 0x01, ///< Free old value if it was allocated. OPT_GLOBAL = 0x02, ///< Use global value. OPT_LOCAL = 0x04, ///< Use local value. diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 63ceb07b48..b868c90108 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -173,51 +173,7 @@ void didset_string_options(void) (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 ""; @@ -340,7 +296,9 @@ 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) { @@ -431,100 +389,6 @@ void set_string_option_direct_in_buf(buf_T *buf, const char *name, int opt_idx, unblock_autocmds(); } -/// Set a string option to a new value, handling the effects -/// Must not be called with a hidden option! -/// -/// @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. -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); - - 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_string_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_from(opt, curbuf, curwin); - } else { - origval = oldval; - } - - *(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; - char *const saved_oldval_g = (origval_g != NULL) ? xstrdup(origval_g) : 0; - - // newval (and varp) may become invalid if the buffer is closed by - // autocommands. - 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 || (!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, 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_oldval_l, - saved_oldval_g, saved_newval); - } - if (opt->flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(opt->fullname), - CSTR_AS_OBJ(saved_newval)); - } - } - xfree(saved_origval); - xfree(saved_oldval_l); - xfree(saved_oldval_g); - xfree(saved_newval); - - return errmsg; -} - /// Return true if "val" is a valid 'filetype' name. /// Also used for 'syntax' and 'keymap'. static bool valid_filetype(const char *val) @@ -636,7 +500,7 @@ const 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) @@ -2676,177 +2540,6 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) return expand_set_opt_generic(args, get_highlight_name, numMatches, matches); } -// When 'syntax' is set, load the syntax of that name -static void do_syntax_autocmd(buf_T *buf, bool value_changed) -{ - static int syn_recursive = 0; - - 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_spelllang_source(win_T *win) -{ - char fname[200]; - char *q = win->w_s->b_p_spl; - - // Skip the first name if it is "cjk". - if (strncmp(q, "cjk,", 4) == 0) { - q += 4; - } - - // Source the spell/LANG.{vim,lua} 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.*", (int)(p - q), q); - source_runtime_vim_lua(fname, DIP_ALL); - } -} - -/// 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 op OP_ADDING/OP_PREPENDING/OP_REMOVING -/// @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 -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, - bool *value_checked) -{ - const char *errmsg = NULL; - int restore_chartab = false; - vimoption_T *opt = get_option(opt_idx); - bool free_oldval = (opt->flags & P_ALLOCED); - opt_did_set_cb_T did_set_cb = get_option_did_set_cb(opt_idx); - bool value_changed = false; - - optset_T args = { - .os_varp = varp, - .os_idx = opt_idx, - .os_flags = opt_flags, - .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, - .os_errbuf = errbuf, - .os_errbuflen = errbuflen, - .os_win = win, - .os_buf = buf, - }; - - // 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 (did_set_cb != NULL) { - // Invoke the option specific callback function to validate and apply - // the new option value. - errmsg = did_set_cb(&args); - - // The 'filetype' and 'syntax' option callback functions may change - // the os_value_changed field. - value_changed = args.os_value_changed; - // The 'keymap', 'filetype' and 'syntax' option callback functions - // may change the os_value_checked field. - *value_checked = args.os_value_checked; - // 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; - } - - // 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 (restore_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; - - 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_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); - } - - // 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) { - // 'filetype' is set, trigger the FileType autocommand - // Skip this when called from a modeline - // Force autocmd when the filetype was changed - if (!(opt_flags & OPT_MODELINE) || value_changed) { - do_filetype_autocmd(buf, value_changed); - } - } else if (varp == &win->w_s->b_p_spl) { - do_spelllang_source(win); - } - } - - if (varp == &p_mouse) { - setmouse(); // in case 'mouse' changed - } - - if ((varp == &p_flp || varp == &(buf->b_p_flp)) - && win->w_briopt_list) { - // Changing Formatlistpattern when briopt includes the list setting: - // redraw - redraw_all_later(UPD_NOT_VALID); - } else if (varp == &p_wbr || varp == &(win->w_p_wbr)) { - // add / remove window bars for 'winbar' - set_winbar(true); - } - - if (win->w_curswant != MAXCOL - && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { - win->w_set_curswant = true; - } - - check_redraw_for(buf, win, opt->flags); - - return errmsg; -} - /// Check an option that can be a range of string values. /// /// @param list when true: accept a list of values diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 2849fcc6af..c346dfbe6c 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1536,14 +1536,18 @@ describe('API', function() -- Now try with options with a special "local is unset" value (e.g. 'undolevels') nvim('set_option_value', 'undolevels', 1000, {}) - eq(1000, nvim('get_option_value', 'undolevels', {scope = 'local'})) + nvim('set_option_value', 'undolevels', 1200, {scope = 'local'}) + eq(1200, nvim('get_option_value', 'undolevels', {scope = 'local'})) nvim('set_option_value', 'undolevels', NIL, {scope = 'local'}) eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'})) + eq(1000, nvim('get_option_value', 'undolevels', {})) nvim('set_option_value', 'autoread', true, {}) - eq(true, nvim('get_option_value', 'autoread', {scope = 'local'})) + nvim('set_option_value', 'autoread', false, {scope = 'local'}) + eq(false, nvim('get_option_value', 'autoread', {scope = 'local'})) nvim('set_option_value', 'autoread', NIL, {scope = 'local'}) eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'})) + eq(true, nvim('get_option_value', 'autoread', {})) end) it('set window options', function() diff --git a/test/functional/legacy/autocmd_option_spec.lua b/test/functional/legacy/autocmd_option_spec.lua index 850c005d39..6034d13e2a 100644 --- a/test/functional/legacy/autocmd_option_spec.lua +++ b/test/functional/legacy/autocmd_option_spec.lua @@ -260,7 +260,7 @@ describe('au OptionSet', function() command('setlocal tags=tagpath2') expected_combination({'tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal'}) - -- Note: v:option_old is the old global value for global-local string options + -- Note: v:option_old is the old global value for global-local options. -- but the old local value for all other kinds of options. command('noa setglobal tags=tag_global') command('noa setlocal tags=tag_local') @@ -269,12 +269,12 @@ describe('au OptionSet', function() 'tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set' }) - -- Note: v:option_old is the old global value for global-local string options + -- Note: v:option_old is the old global value for global-local options. -- but the old local value for all other kinds of options. command('noa set tags=tag_global') command('noa setlocal tags=') command('set tags=tagpath') - expected_combination({'tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set'}) + expected_combination({'tags', 'tag_global', 'tag_global', 'tag_global', 'tagpath', 'global', 'set'}) end) it('with string local (to buffer) option', function() @@ -295,7 +295,7 @@ describe('au OptionSet', function() command('setlocal spelllang=klingon') expected_combination({'spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal'}) - -- Note: v:option_old is the old global value for global-local string options + -- Note: v:option_old is the old global value for global-local options. -- but the old local value for all other kinds of options. command('noa setglobal spelllang=spellglobal') command('noa setlocal spelllang=spelllocal') @@ -311,7 +311,7 @@ describe('au OptionSet', function() command('set statusline=foo') expected_combination({'statusline', oldval, oldval, '', 'foo', 'global', 'set'}) - -- Note: v:option_old is the old global value for global-local string options + -- Note: v:option_old is the old global value for global-local options. -- but the old local value for all other kinds of options. command('set statusline&') expected_combination({'statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set'}) @@ -323,7 +323,7 @@ describe('au OptionSet', function() command('setlocal statusline=baz') expected_combination({'statusline', oldval, oldval, '', 'baz', 'local', 'setlocal'}) - -- Note: v:option_old is the old global value for global-local string options + -- Note: v:option_old is the old global value for global-local options. -- but the old local value for all other kinds of options. command('noa setglobal statusline=bar') command('noa setlocal statusline=baz') @@ -364,11 +364,15 @@ describe('au OptionSet', function() command('setlocal cmdheight=2') expected_combination({'cmdheight', 1, 1, '', 2, 'local', 'setlocal'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa setglobal cmdheight=8') command('noa setlocal cmdheight=1') -- Sets the global(!) value command('set cmdheight=2') expected_combination({'cmdheight', 1, 1, 1, 2, 'global', 'set'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa set cmdheight=8') command('set cmdheight=2') expected_combination({'cmdheight', 8, 8, 8, 2, 'global', 'set'}) @@ -385,11 +389,15 @@ describe('au OptionSet', function() command('setlocal undolevels=2') expected_combination({'undolevels', 1, 1, '', 2, 'local', 'setlocal'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa setglobal undolevels=8') command('noa setlocal undolevels=1') command('set undolevels=2') - expected_combination({'undolevels', 1, 1, 8, 2, 'global', 'set'}) + expected_combination({'undolevels', 8, 1, 8, 2, 'global', 'set'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa set undolevels=8') command('set undolevels=2') expected_combination({'undolevels', 8, 8, 8, 2, 'global', 'set'}) @@ -427,11 +435,15 @@ describe('au OptionSet', function() command('setlocal scrolloff=2') expected_combination({'scrolloff', 1, 1, '', 2, 'local', 'setlocal'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa setglobal scrolloff=8') command('noa setlocal scrolloff=1') command('set scrolloff=2') - expected_combination({'scrolloff', 1, 1, 8, 2, 'global', 'set'}) + expected_combination({'scrolloff', 8, 1, 8, 2, 'global', 'set'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa set scrolloff=8') command('set scrolloff=2') expected_combination({'scrolloff', 8, 8, 8, 2, 'global', 'set'}) @@ -490,11 +502,15 @@ describe('au OptionSet', function() command('setlocal noautoread') expected_combination({'autoread', true, true, '', false, 'local', 'setlocal'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa setglobal noautoread') command('noa setlocal autoread') command('set autoread') - expected_combination({'autoread', true, true, false, true, 'global', 'set'}) + expected_combination({'autoread', false, true, false, true, 'global', 'set'}) + -- Note: v:option_old is the old global value for global-local options. + -- but the old local value for all other kinds of options. command('noa set noautoread') command('set autoread') expected_combination({'autoread', false, false, false, true, 'global', 'set'})