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
This commit is contained in:
Famiu Haque 2023-10-14 22:19:11 +06:00
parent 8405649f92
commit e19cc9c9b7
10 changed files with 439 additions and 632 deletions

View File

@ -778,11 +778,10 @@ OptionSet After setting an option (except during
This does not set |<abuf>|, you could use This does not set |<abuf>|, you could use
|bufnr()|. |bufnr()|.
Note that when setting a |global-local| string Note that when setting a |global-local| option
option with |:set|, then |v:option_old| is the with |:set|, then |v:option_old| is the old
old global value. However, for all other kinds global value. However, for all options that
of options (local string options, global-local are not global-local it is the old local
number options, ...) it is the old local
value. value.
OptionSet is not triggered on startup and for OptionSet is not triggered on startup and for

View File

@ -73,7 +73,12 @@ The following changes may require adaptations in user config or plugins.
• |OptionSet| autocommand args |v:option_new|, |v:option_old|, • |OptionSet| autocommand args |v:option_new|, |v:option_old|,
|v:option_oldlocal|, |v:option_oldglobal| now have the type of the option |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* NEW FEATURES *news-features*

View File

@ -289,6 +289,10 @@ Normal commands:
|Q| replays the last recorded macro instead of switching to Ex mode (|gQ|). |Q| replays the last recorded macro instead of switching to Ex mode (|gQ|).
Options: 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) 'autoread' works in the terminal (if it supports "focus" events)
'cpoptions' flags: |cpo-_| 'cpoptions' flags: |cpo-_|
'diffopt' "linematch" feature 'diffopt' "linematch" feature
@ -381,7 +385,8 @@ Variables:
|v:windowid| is always available (for use by external UIs) |v:windowid| is always available (for use by external UIs)
|OptionSet| autocommand args |v:option_new|, |v:option_old|, |OptionSet| autocommand args |v:option_new|, |v:option_old|,
|v:option_oldlocal|, |v:option_oldglobal| have the type of the option |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: Vimscript:
|:redir| nested in |execute()| works. |:redir| nested in |execute()| works.

View File

@ -681,21 +681,6 @@ static void set_option_to(uint64_t channel_id, void *to, OptReqScope req_scope,
return; 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; bool error = false;
OptVal optval = object_as_optval(value, &error); OptVal optval = object_as_optval(value, &error);

View File

@ -7229,12 +7229,11 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val)
/// Set v:variable to tv. /// Set v:variable to tv.
/// ///
/// @param[in] idx Index of variable to set. /// @param[in] idx Index of variable to set.
/// @param[in,out] val Value to set to. Reference count will be incremented. /// @param[in] val Value to set to. Will be copied.
/// Also keys of the dictionary will be made read-only.
void set_vim_var_tv(const VimVarIndex idx, typval_T *const tv) void set_vim_var_tv(const VimVarIndex idx, typval_T *const tv)
{ {
tv_clear(&vimvars[idx].vv_di.di_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. /// Set the v:argv list.

View File

@ -574,14 +574,14 @@ void free_all_options(void)
if (options[i].indir == PV_NONE) { if (options[i].indir == PV_NONE) {
// global option: free value and default value. // global option: free value and default value.
if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) { 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) { 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 // 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(); 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); assert(varp != NULL);
vimoption_T *opt = &options[opt_idx];
char *arg = *argp; 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; OptVal newval = NIL_OPTVAL;
switch (oldval.type) { switch (oldval.type) {
@ -1250,17 +1253,7 @@ static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix,
break; break;
} }
case kOptValTypeString: { case kOptValTypeString: {
char *oldval_str; char *oldval_str = oldval.data.string.data;
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;
}
// Get the new value for the option // Get the new value for the option
char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op, flags); char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op, flags);
newval = CSTR_AS_OPTVAL(newval_str); 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); *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. /// Parse 'arg' for option settings.
@ -2753,6 +2744,47 @@ static const char *did_set_wrap(optset_T *args)
return NULL; 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. /// Check the bounds of numeric options.
static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, char *errbuf, static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, char *errbuf,
size_t errbuflen, const char *errmsg) size_t errbuflen, const char *errmsg)
@ -3193,7 +3225,10 @@ void optval_free(OptVal o)
case kOptValTypeNumber: case kOptValTypeNumber:
break; break;
case kOptValTypeString: case kOptValTypeString:
// Don't free empty string option
if (o.data.string.data != empty_string_option) {
api_free_string(o.data.string); api_free_string(o.data.string);
}
break; break;
} }
} }
@ -3271,11 +3306,19 @@ OptVal optval_from_varp(int opt_idx, void *varp)
/// Set option var pointer value from Optval. /// Set option var pointer value from Optval.
/// ///
/// @param varp Pointer to option variable. /// @param opt_idx Option index in options[] table.
/// @param value Option value. /// @param[out] varp Pointer to option variable.
static void set_option_varp(void *varp, OptVal value) /// @param[in] value New option value.
FUNC_ATTR_NONNULL_ARG(1) /// @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) { switch (value.type) {
case kOptValTypeNil: case kOptValTypeNil:
return; return;
@ -3356,43 +3399,36 @@ OptVal object_as_optval(Object o, bool *error)
UNREACHABLE; 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. /// @param opt_idx Option index in options[] table.
static OptVal optval_clear(const char *name, uint32_t flags, void *varp, buf_T *buf, win_T *win) /// @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; 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("");
}
// Change the type of the OptVal to the type used by the option so that it can be cleared. if ((int *)varp == &curbuf->b_p_ar) {
// TODO(famiu): Clean up all of this after set_(num|bool|string)_option() is unified. return BOOLEAN_OPTVAL(kNone);
} else if ((OptInt *)varp == &curwin->w_p_so || (OptInt *)varp == &curwin->w_p_siso) {
if (flags & P_BOOL) { return NUMBER_OPTVAL(-1);
v.type = kOptValTypeBoolean; } else if ((OptInt *)varp == &curbuf->b_p_ul) {
if ((int *)varp == &buf->b_p_ar) { return NUMBER_OPTVAL(NO_LOCAL_UNDOLEVEL);
// 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 { } else {
v = get_option_value(name, NULL, OPT_GLOBAL, NULL); // This should never happen.
abort();
} }
} 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) { // For options that aren't global-local, just set the local value to the global value.
v.type = kOptValTypeString; return get_option_value(opt->fullname, NULL, OPT_GLOBAL, NULL);
v.data.string.data = NULL;
}
return v;
} }
/// Get an allocated string containing a list of valid types for an option. /// 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]; return &options[opt_idx];
} }
/// Set the value of an option using an OptVal. /// Check if local value of global-local option is unset for current buffer / window.
/// Always returns false for options that aren't global-local.
/// ///
/// @param opt_idx Option index. Must be >=0. /// 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[in] varp Option variable pointer, cannot be NULL.
/// @param value New option value. /// @param old_value Old option value.
/// @param new_value New option value.
/// @param opt_flags Option flags. /// @param opt_flags Option flags.
/// @param new_value Whether value was replaced completely. /// @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[out] errbuf Buffer for error message.
/// @param errbuflen Length of error buffer. /// @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 *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 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 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, 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); assert(opt_idx >= 0 && varp != NULL);
const char *errmsg = NULL; const char *errmsg = NULL;
bool value_checked = false; bool value_checked = false;
vimoption_T *opt = &options[opt_idx]; vimoption_T *opt = &options[opt_idx];
// TODO(famiu): Unify set_string_option with set_option. static const char *optval_type_names[] = {
if (value.type == kOptValTypeString) { [kOptValTypeNil] = "Nil",
errmsg = set_string_option(opt_idx, varp, value.data.string.data, opt_flags, true, [kOptValTypeBoolean] = "Boolean",
&value_checked, errbuf, errbuflen); [kOptValTypeNumber] = "Number",
goto end; [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 (errmsg != NULL) {
if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { goto err;
return e_secure; }
// 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); 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 // 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. // value.type, when multi-type options are added.
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { 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_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) { if (value.type == kOptValTypeNumber) {
errmsg = validate_num_option((OptInt *)varp, &value.data.number); errmsg = validate_num_option((OptInt *)varp, &value.data.number);
// Don't change the value and return early if validation failed. // Don't change the value and return early if validation failed.
if (errmsg != NULL) { if (errmsg != NULL) {
return errmsg; goto err;
} }
} }
// Set the new option value. set_option_varp(opt_idx, varp, value, false);
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);
}
// Invoke the option specific callback function to validate and apply the new value. OptVal saved_used_value = optval_copy(used_old_value);
bool doskip = false; OptVal saved_old_global_value = optval_copy(old_global_value);
opt_did_set_cb_T did_set_cb; 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);
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(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); uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) { const int secure_saved = secure;
*p |= P_INSECURE;
} else if (new_value) { // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side
*p &= ~P_INSECURE; // 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;
} }
bool doskip = false;
errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, &doskip, &value_checked,
value_replaced, errbuf, errbuflen);
secure = secure_saved;
// Stop processing option further if asked to do so.
if (doskip) {
goto end;
}
if (errmsg == NULL) {
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; return errmsg;
} }
@ -3634,13 +3872,6 @@ end:
const char *set_option_value(const char *const name, const OptVal value, int opt_flags) const char *set_option_value(const char *const name, const OptVal value, int opt_flags)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(1)
{ {
static const char *optval_type_names[] = {
[kOptValTypeNil] = "Nil",
[kOptValTypeBoolean] = "Boolean",
[kOptValTypeNumber] = "Number",
[kOptValTypeString] = "String"
};
static char errbuf[IOSIZE]; static char errbuf[IOSIZE];
if (is_tty_option(name)) { 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; const char *errmsg = NULL;
// Copy the value so we can modify the copy.
OptVal v = optval_copy(value);
if (v.type == kOptValTypeNil) { errmsg = set_option(opt_idx, varp, optval_copy(value), opt_flags, true, errbuf, sizeof(errbuf));
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, v, opt_flags, true, errbuf, sizeof(errbuf));
end:
optval_free(v); // Free the copied OptVal.
return errmsg; return errmsg;
} }
@ -4143,115 +4357,6 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value)
return OK; 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) void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win)
{ {
if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) { 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 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. /// Get the value of 'equalprg', either the buffer-local one or the global one.
char *get_equalprg(void) char *get_equalprg(void)
{ {

View File

@ -56,6 +56,7 @@ typedef struct vimoption {
/// cmdline. Only useful for string options. /// cmdline. Only useful for string options.
opt_expand_cb_T opt_expand_cb; opt_expand_cb_T opt_expand_cb;
// TODO(famiu): Use OptVal for def_val.
void *def_val; ///< default values for variable (neovim!!) void *def_val; ///< default values for variable (neovim!!)
LastSet last_set; ///< script in which the option was last set LastSet last_set; ///< script in which the option was last set
} vimoption_T; } vimoption_T;
@ -72,6 +73,7 @@ enum {
/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global /// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global
/// values, get local value. /// values, get local value.
typedef enum { 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_FREE = 0x01, ///< Free old value if it was allocated.
OPT_GLOBAL = 0x02, ///< Use global value. OPT_GLOBAL = 0x02, ///< Use global value.
OPT_LOCAL = 0x04, ///< Use local value. OPT_LOCAL = 0x04, ///< Use local value.

View File

@ -173,51 +173,7 @@ void didset_string_options(void)
(void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true);
} }
/// Trigger the OptionSet autocommand. char *illegal_char(char *errbuf, size_t errbuflen, int c)
/// "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)
{ {
if (errbuf == NULL) { if (errbuf == NULL) {
return ""; 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" is SID_NONE don't set the scriptID. Otherwise set the scriptID to
/// "set_sid". /// "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, void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags,
int set_sid) 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(); 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. /// Return true if "val" is a valid 'filetype' name.
/// Also used for 'syntax' and 'keymap'. /// Also used for 'syntax' and 'keymap'.
static bool valid_filetype(const char *val) 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 /// Check for a "normal" directory or file name in some options. Disallow a
/// path separator (slash and/or backslash), wildcards and characters that are /// path separator (slash and/or backslash), wildcards and characters that are
/// often illegal in a file name. Be more permissive if "secure" is off. /// 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) return (((flags & P_NFNAME)
&& strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) && 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); 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. /// Check an option that can be a range of string values.
/// ///
/// @param list when true: accept a list of values /// @param list when true: accept a list of values

View File

@ -1536,14 +1536,18 @@ describe('API', function()
-- Now try with options with a special "local is unset" value (e.g. 'undolevels') -- Now try with options with a special "local is unset" value (e.g. 'undolevels')
nvim('set_option_value', 'undolevels', 1000, {}) 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'}) nvim('set_option_value', 'undolevels', NIL, {scope = 'local'})
eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'})) eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'}))
eq(1000, nvim('get_option_value', 'undolevels', {}))
nvim('set_option_value', 'autoread', true, {}) 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'}) nvim('set_option_value', 'autoread', NIL, {scope = 'local'})
eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'})) eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'}))
eq(true, nvim('get_option_value', 'autoread', {}))
end) end)
it('set window options', function() it('set window options', function()

View File

@ -260,7 +260,7 @@ describe('au OptionSet', function()
command('setlocal tags=tagpath2') command('setlocal tags=tagpath2')
expected_combination({'tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal'}) 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. -- but the old local value for all other kinds of options.
command('noa setglobal tags=tag_global') command('noa setglobal tags=tag_global')
command('noa setlocal tags=tag_local') command('noa setlocal tags=tag_local')
@ -269,12 +269,12 @@ describe('au OptionSet', function()
'tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set' '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. -- but the old local value for all other kinds of options.
command('noa set tags=tag_global') command('noa set tags=tag_global')
command('noa setlocal tags=') command('noa setlocal tags=')
command('set tags=tagpath') 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) end)
it('with string local (to buffer) option', function() it('with string local (to buffer) option', function()
@ -295,7 +295,7 @@ describe('au OptionSet', function()
command('setlocal spelllang=klingon') command('setlocal spelllang=klingon')
expected_combination({'spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal'}) 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. -- but the old local value for all other kinds of options.
command('noa setglobal spelllang=spellglobal') command('noa setglobal spelllang=spellglobal')
command('noa setlocal spelllang=spelllocal') command('noa setlocal spelllang=spelllocal')
@ -311,7 +311,7 @@ describe('au OptionSet', function()
command('set statusline=foo') command('set statusline=foo')
expected_combination({'statusline', oldval, oldval, '', 'foo', 'global', 'set'}) 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. -- but the old local value for all other kinds of options.
command('set statusline&') command('set statusline&')
expected_combination({'statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set'}) expected_combination({'statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set'})
@ -323,7 +323,7 @@ describe('au OptionSet', function()
command('setlocal statusline=baz') command('setlocal statusline=baz')
expected_combination({'statusline', oldval, oldval, '', 'baz', 'local', 'setlocal'}) 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. -- but the old local value for all other kinds of options.
command('noa setglobal statusline=bar') command('noa setglobal statusline=bar')
command('noa setlocal statusline=baz') command('noa setlocal statusline=baz')
@ -364,11 +364,15 @@ describe('au OptionSet', function()
command('setlocal cmdheight=2') command('setlocal cmdheight=2')
expected_combination({'cmdheight', 1, 1, '', 2, 'local', 'setlocal'}) 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 setglobal cmdheight=8')
command('noa setlocal cmdheight=1') -- Sets the global(!) value command('noa setlocal cmdheight=1') -- Sets the global(!) value
command('set cmdheight=2') command('set cmdheight=2')
expected_combination({'cmdheight', 1, 1, 1, 2, 'global', 'set'}) 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('noa set cmdheight=8')
command('set cmdheight=2') command('set cmdheight=2')
expected_combination({'cmdheight', 8, 8, 8, 2, 'global', 'set'}) expected_combination({'cmdheight', 8, 8, 8, 2, 'global', 'set'})
@ -385,11 +389,15 @@ describe('au OptionSet', function()
command('setlocal undolevels=2') command('setlocal undolevels=2')
expected_combination({'undolevels', 1, 1, '', 2, 'local', 'setlocal'}) 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 setglobal undolevels=8')
command('noa setlocal undolevels=1') command('noa setlocal undolevels=1')
command('set undolevels=2') 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('noa set undolevels=8')
command('set undolevels=2') command('set undolevels=2')
expected_combination({'undolevels', 8, 8, 8, 2, 'global', 'set'}) expected_combination({'undolevels', 8, 8, 8, 2, 'global', 'set'})
@ -427,11 +435,15 @@ describe('au OptionSet', function()
command('setlocal scrolloff=2') command('setlocal scrolloff=2')
expected_combination({'scrolloff', 1, 1, '', 2, 'local', 'setlocal'}) 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 setglobal scrolloff=8')
command('noa setlocal scrolloff=1') command('noa setlocal scrolloff=1')
command('set scrolloff=2') 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('noa set scrolloff=8')
command('set scrolloff=2') command('set scrolloff=2')
expected_combination({'scrolloff', 8, 8, 8, 2, 'global', 'set'}) expected_combination({'scrolloff', 8, 8, 8, 2, 'global', 'set'})
@ -490,11 +502,15 @@ describe('au OptionSet', function()
command('setlocal noautoread') command('setlocal noautoread')
expected_combination({'autoread', true, true, '', false, 'local', 'setlocal'}) 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 setglobal noautoread')
command('noa setlocal autoread') command('noa setlocal autoread')
command('set 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('noa set noautoread')
command('set autoread') command('set autoread')
expected_combination({'autoread', false, false, false, true, 'global', 'set'}) expected_combination({'autoread', false, false, false, true, 'global', 'set'})