mirror of
https://github.com/neovim/neovim.git
synced 2026-10-26 07:30:20 -05:00
refactor(options): memory management
Problem:
Numerous callers have to manually check optval ownership (i.e. whether,
and how, to release) via `is_callback_option`, `option_is_global_local`,
etc. This is fragile, hard to use correctly; and if we introduce another
optval variant in the future, we'll have to redo all of these careful
checks and boilerplate again.
Solution:
Provide a unified system and use it everywhere:
optval_free_owned
optval_is_owned
optval_own
This commit is contained in:
1 parent
726d1a92d2
commit
3e01dcddbe
11 files changed
+208
-100
No files matched your search
@@ -395,7 +395,7 @@ Note: In the future more global options can be made |global-local|. Using
|
||||
*option-value-function*
|
||||
Some options ('completefunc', 'findfunc', 'omnifunc', 'operatorfunc',
|
||||
'quickfixtextfunc', 'tagfunc' and 'thesaurusfunc') can be set to a function
|
||||
name, a |Funcref|, a |lambda|, or (from Lua) a Lua function.
|
||||
name, a |Funcref|, a |lambda|, or a Lua function.
|
||||
Examples:
|
||||
>
|
||||
set opfunc=MyOpFunc
|
||||
@@ -428,6 +428,8 @@ The value read back from the option reflects how it was set:
|
||||
- A |script-local| ("s:") name is expanded to `<SNR>` form.
|
||||
- A Lua function is returned as itself from Lua, or as a `"<Lua …>"` hint
|
||||
from Vimscript/RPC.
|
||||
- Note: Lua functions cannot be serialized, so it cannot be saved/restored
|
||||
by |:mksession| |:mkview| |:mkvimrc|.
|
||||
|
||||
Calling a function in an expr option *expr-option-function*
|
||||
|
||||
@@ -5488,7 +5490,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
localoptions options and mappings local to a window or buffer (not
|
||||
global values for local options)
|
||||
options all options and mappings (also global values for local
|
||||
options)
|
||||
options), except Lua functions |option-value-function|.
|
||||
skiprtp exclude 'runtimepath' and 'packpath' from the options
|
||||
resize size of the Vim window: 'lines' and 'columns'
|
||||
sesdir the directory in which the session file is located
|
||||
@@ -7307,7 +7309,8 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
folds manually created folds, opened/closed folds and local
|
||||
fold options
|
||||
options options and mappings local to a window or buffer (not
|
||||
global values for local options)
|
||||
global values for local options), except Lua functions
|
||||
|option-value-function|.
|
||||
localoptions same as "options"
|
||||
slash |deprecated| Always enabled. Uses "/" in filenames.
|
||||
unix |deprecated| Always enabled. Uses "\n" line endings.
|
||||
|
||||
@@ -800,7 +800,7 @@ The output of ":mksession" is like ":mkvimrc", but additional commands are
|
||||
added to the file. Which ones depends on the 'sessionoptions' option. The
|
||||
resulting file, when executed with a ":source" command:
|
||||
1. Restores global mappings and options, if 'sessionoptions' contains
|
||||
"options". Script-local mappings will not be written.
|
||||
"options". Lua functions, and script-local mappings, will not be written.
|
||||
2. Restores global variables that start with an uppercase letter and contain
|
||||
at least one lowercase letter, if 'sessionoptions' contains "globals".
|
||||
3. Closes all windows in the current tabpage, except the current one; closes
|
||||
@@ -872,6 +872,7 @@ The output of ":mkview" contains these items:
|
||||
that are local to the current buffer and the current window are restored.
|
||||
When storing the view as part of a session and "options" is in
|
||||
'sessionoptions', global values for local options will be stored too.
|
||||
Lua functions are not restored.
|
||||
4. Restore folds when using manual folding and 'viewoptions' contains
|
||||
"folds". Restore manually opened and closed folds.
|
||||
5. The scroll position and the cursor position in the file. Doesn't work very
|
||||
|
||||
Generated
+3
-2
@@ -5718,7 +5718,7 @@ vim.go.slm = vim.go.selectmode
|
||||
--- localoptions options and mappings local to a window or buffer (not
|
||||
--- global values for local options)
|
||||
--- options all options and mappings (also global values for local
|
||||
--- options)
|
||||
--- options), except Lua functions `option-value-function`.
|
||||
--- skiprtp exclude 'runtimepath' and 'packpath' from the options
|
||||
--- resize size of the Vim window: 'lines' and 'columns'
|
||||
--- sesdir the directory in which the session file is located
|
||||
@@ -7885,7 +7885,8 @@ vim.go.vdir = vim.go.viewdir
|
||||
--- folds manually created folds, opened/closed folds and local
|
||||
--- fold options
|
||||
--- options options and mappings local to a window or buffer (not
|
||||
--- global values for local options)
|
||||
--- global values for local options), except Lua functions
|
||||
--- `option-value-function`.
|
||||
--- localoptions same as "options"
|
||||
--- slash `deprecated` Always enabled. Uses "/" in filenames.
|
||||
--- unix `deprecated` Always enabled. Uses "\n" line endings.
|
||||
|
||||
@@ -402,9 +402,10 @@ Object nvim_set_option_value(uint64_t channel_id, String name, Object value, Dic
|
||||
optval_free(optval_right);
|
||||
|
||||
if (optval_right.type == kObjectTypeInteger || optval_right.type == kObjectTypeString) {
|
||||
Object oldval = opt_from_varp(opt_idx, varp);
|
||||
Object oldval = optval_own(option->flags, opt_from_varp(opt_idx, varp));
|
||||
merged_val = get_option_newval(opt_idx, opt_flags, PREFIX_NONE, &argp, 0, operation,
|
||||
option->flags, varp, &oldval, NULL, 0, &errmsg);
|
||||
optval_free(oldval);
|
||||
VALIDATE(errmsg == NULL, "%s", errmsg, {
|
||||
return NIL;
|
||||
});
|
||||
|
||||
@@ -3443,6 +3443,7 @@ int eval_option(const char **const arg, typval_T *const rettv, const bool evalua
|
||||
assert(value.type != kObjectTypeNil);
|
||||
|
||||
*rettv = opt_to_tv(value, true);
|
||||
optval_free(value);
|
||||
} else if (working && !is_tty_opt && is_option_hidden(opt_idx)) {
|
||||
ret = FAIL;
|
||||
}
|
||||
|
||||
@@ -3260,7 +3260,7 @@ static Object opt_from_tv(typval_T *tv, OptIndex opt_idx, const char *option, bo
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Converts an option value to typval.
|
||||
/// Converts an option value to typval. Caller must tv_clear() the result.
|
||||
///
|
||||
/// @param[in] value Option value to convert.
|
||||
/// @param numbool Whether to convert boolean values to number.
|
||||
@@ -3297,7 +3297,7 @@ typval_T opt_to_tv(Object value, bool numbool)
|
||||
break;
|
||||
case kObjectTypeString:
|
||||
rettv.v_type = VAR_STRING;
|
||||
rettv.vval.v_string = value.data.string.data;
|
||||
rettv.vval.v_string = value.data.string.data != NULL ? xstrdup(value.data.string.data) : NULL;
|
||||
break;
|
||||
case kObjectTypeLuaRef:
|
||||
// Lua callback option (e.g. 'operatorfunc'): show a human-readable hint (same as `:map` does).
|
||||
|
||||
+172
-86
@@ -316,8 +316,9 @@ static void set_init_expand_env(void)
|
||||
p = option_expand(opt_idx, NULL);
|
||||
}
|
||||
if (p != NULL) {
|
||||
set_option_varp(opt_idx, opt->var, CSTR_TO_OBJ(p), true);
|
||||
change_option_default(opt_idx, CSTR_TO_OBJ(p));
|
||||
Object ov = CSTR_TO_OBJ(p);
|
||||
set_option_varp(opt_idx, opt->var, ov); // Copies `ov`.
|
||||
change_option_default(opt_idx, ov); // Consumes `ov`.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,7 +443,7 @@ void set_init_1(bool clean_arg)
|
||||
/// @param opt_idx Option index in options[] table.
|
||||
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
|
||||
///
|
||||
/// @return Default value of option for the scope specified in opt_flags.
|
||||
/// @return Default value for the scope specified by opt_flags. Release with optval_free_read().
|
||||
Object get_option_default(const OptIndex opt_idx, int opt_flags)
|
||||
{
|
||||
vimoption_T *opt = &options[opt_idx];
|
||||
@@ -458,14 +459,18 @@ Object get_option_default(const OptIndex opt_idx, int opt_flags)
|
||||
if ((opt_flags & OPT_LOCAL) && is_global_local_option) {
|
||||
// Use unset local value instead of default value for local scope of global-local options.
|
||||
return get_option_unset_value(opt_idx);
|
||||
} else if (option_has_type(opt_idx, kObjectTypeString) && !(opt->flags & kOptFlagNoDefExp)) {
|
||||
}
|
||||
|
||||
Object def_val = opt->def_val;
|
||||
if (option_has_type(opt_idx, kObjectTypeString) && !(opt->flags & kOptFlagNoDefExp)) {
|
||||
// For string options, expand environment variables and ~ since the default value was already
|
||||
// expanded, only required when an environment variable was set later.
|
||||
char *s = option_expand(opt_idx, opt->def_val.data.string.data);
|
||||
return s == NULL ? opt->def_val : CSTR_AS_OBJ(s);
|
||||
} else {
|
||||
return opt->def_val;
|
||||
char *s = option_expand(opt_idx, def_val.data.string.data);
|
||||
if (s != NULL) {
|
||||
def_val = CSTR_AS_OBJ(s);
|
||||
}
|
||||
}
|
||||
return optval_is_owned(opt->flags) ? copy_object(def_val, NULL) : def_val;
|
||||
}
|
||||
|
||||
/// Allocate the default values for all options by copying them from the stack.
|
||||
@@ -497,6 +502,7 @@ static void set_option_default(const OptIndex opt_idx, int opt_flags)
|
||||
bool both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0;
|
||||
Object def_val = get_option_default(opt_idx, opt_flags);
|
||||
set_option_direct(opt_idx, def_val, opt_flags, current_sctx.sc_sid);
|
||||
optval_free_read(options[opt_idx].flags, def_val);
|
||||
|
||||
if (opt_idx == kOptScroll) {
|
||||
win_comp_scroll(curwin);
|
||||
@@ -1337,25 +1343,13 @@ Object get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T prefix, c
|
||||
vimoption_T *opt = &options[opt_idx];
|
||||
char *arg = *argp;
|
||||
|
||||
Object oldval;
|
||||
if (oldval_override != NULL) {
|
||||
// Allow overriding the oldval. This is needed to handle the case where
|
||||
// options for buffers/windows other than curbuf/curwin are updated. It can
|
||||
// also support merging arbitrary values if necessary down the road.
|
||||
oldval = *oldval_override;
|
||||
} else {
|
||||
// When setting the local value of a global option, the old value may be the global value.
|
||||
const bool oldval_is_global = option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL);
|
||||
oldval = opt_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp);
|
||||
}
|
||||
|
||||
Object newval = NIL;
|
||||
|
||||
if (nextchar == '&') {
|
||||
// ":set opt&": Reset to default value.
|
||||
// NOTE: Use OPT_GLOBAL instead of opt_flags to ensure we don't use the unset local value for
|
||||
// global-local options when OPT_LOCAL is used.
|
||||
return copy_object(get_option_default(opt_idx, OPT_GLOBAL), NULL);
|
||||
return optval_own(flags, get_option_default(opt_idx, OPT_GLOBAL));
|
||||
} else if (nextchar == '<') {
|
||||
// ":set opt<": Reset to global value.
|
||||
// ":setlocal opt<": Copy global value to local value.
|
||||
@@ -1365,6 +1359,19 @@ Object get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T prefix, c
|
||||
return get_option_value(opt_idx, OPT_GLOBAL);
|
||||
}
|
||||
|
||||
// Computed lazily: the ":set opt&"/"opt<" resets above return without needing the old value.
|
||||
Object oldval;
|
||||
if (oldval_override != NULL) {
|
||||
// Allow overriding the oldval. This is needed to handle the case where options for
|
||||
// buffers/windows other than curbuf/curwin are updated. It can also support merging arbitrary
|
||||
// values if necessary down the road.
|
||||
oldval = *oldval_override;
|
||||
} else {
|
||||
// When setting the local value of a global option, the old value may be the global value.
|
||||
const bool oldval_is_global = option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL);
|
||||
oldval = optval_own(flags, opt_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp));
|
||||
}
|
||||
|
||||
switch (oldval.type) {
|
||||
case kObjectTypeNil:
|
||||
abort();
|
||||
@@ -1454,6 +1461,10 @@ Object get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T prefix, c
|
||||
abort();
|
||||
}
|
||||
|
||||
// oldval (computed above, not overridden) is owned via optval_own(); an override is borrowed.
|
||||
if (oldval_override == NULL) {
|
||||
optval_free(oldval);
|
||||
}
|
||||
return newval;
|
||||
}
|
||||
|
||||
@@ -2121,6 +2132,11 @@ void apply_optionset_autocmd_now(OptIndex opt_idx, int opt_flags, Object oldval,
|
||||
}
|
||||
apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL);
|
||||
reset_v_option_vars();
|
||||
|
||||
tv_clear(&oldval_tv);
|
||||
tv_clear(&oldval_g_tv);
|
||||
tv_clear(&oldval_l_tv);
|
||||
tv_clear(&newval_tv);
|
||||
}
|
||||
|
||||
/// For 'modified', the event is deferred.
|
||||
@@ -2401,7 +2417,7 @@ static const char *did_set_lines_or_columns(optset_T *args)
|
||||
// Changing the screen size is not allowed while updating the screen.
|
||||
if (updating_screen) {
|
||||
Object oldval = INTEGER_OBJ(args->os_oldval.data.integer);
|
||||
set_option_varp(args->os_idx, args->os_varp, oldval, false);
|
||||
set_option_varp(args->os_idx, args->os_varp, oldval);
|
||||
} else if (full_screen) {
|
||||
screen_resize((int)p_columns, (int)p_lines);
|
||||
} else {
|
||||
@@ -3348,6 +3364,14 @@ bool is_dict_option(OptIndex opt_idx)
|
||||
return opt_dict_schema(opt_idx) != NULL;
|
||||
}
|
||||
|
||||
/// Release an option value read from opt_from_varp() (and similar).
|
||||
void optval_free_read(uint32_t flags, Object value)
|
||||
{
|
||||
if (optval_is_owned(flags)) {
|
||||
optval_free(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Free an allocated option value.
|
||||
void optval_free(Object o)
|
||||
{
|
||||
@@ -3431,9 +3455,41 @@ static bool is_callback_option(uint32_t flags)
|
||||
return (flags & (kOptFlagFunc | kOptFlagExpr)) != 0;
|
||||
}
|
||||
|
||||
/// Returns true if _reading_ the option value (opt_from_varp(), etc.) returns an owned Object
|
||||
/// (release with optval_free_read()), or false if it borrows the stored value.
|
||||
static bool optval_is_owned(uint32_t flags)
|
||||
{
|
||||
return is_callback_option(flags);
|
||||
}
|
||||
|
||||
/// Ensures an option value is "owned", i.e. the caller decides when to free it.
|
||||
Object optval_own(uint32_t flags, Object value)
|
||||
{
|
||||
return optval_is_owned(flags) ? value : copy_object(value, NULL);
|
||||
}
|
||||
|
||||
/// Copies an option value for OptionSet/UI reporting. Lua function is reported as "<Lua N>".
|
||||
static Object optval_snapshot(Object value)
|
||||
{
|
||||
if (value.type == kObjectTypeLuaRef) {
|
||||
return CSTR_AS_OBJ(nlua_funcref_str(value.data.luaref, NULL));
|
||||
}
|
||||
return copy_object(value, NULL);
|
||||
}
|
||||
|
||||
/// Reads the option value at `varp` and returns a snapshot (see optval_snapshot()).
|
||||
static Object optval_snapshot_varp(OptIndex opt_idx, void *varp)
|
||||
{
|
||||
Object value = opt_from_varp(opt_idx, varp);
|
||||
Object snapshot = optval_snapshot(value);
|
||||
optval_free_read(options[opt_idx].flags, value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// Converts a callback option's stored Callback to an option value Object.
|
||||
/// Always OWNED (a copied name/expr string, or a fresh LuaRef): free with optval_free().
|
||||
///
|
||||
/// - Lua callback => `LuaRef` (owned by the caller)
|
||||
/// - Lua callback => `LuaRef`
|
||||
/// - named funcref => name
|
||||
/// - partial => name
|
||||
/// - expr option => expression string
|
||||
@@ -3490,7 +3546,7 @@ static Callback opt_to_callback(Object value, bool is_expr)
|
||||
return cb;
|
||||
}
|
||||
|
||||
/// Creates Object from var pointer.
|
||||
/// Creates Object from var pointer. Release with optval_free_read(), or promote with optval_own().
|
||||
///
|
||||
/// @param opt_idx Option index in options[] table.
|
||||
/// @param[out] varp Pointer to option variable.
|
||||
@@ -3523,29 +3579,19 @@ Object opt_from_varp(OptIndex opt_idx, void *varp)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an option var pointer (the legacy/scalar value) from a structured value.
|
||||
///
|
||||
/// @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(OptIndex opt_idx, void *varp, Object value, bool free_oldval)
|
||||
/// Stores an option value at its "varp", freeing the previously-stored value. `value` is copied
|
||||
/// in (caller retains ownership).
|
||||
static void set_option_varp(OptIndex opt_idx, void *varp, Object value)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
// Callback options (e.g. 'operatorfunc', 'foldexpr') store a Callback, not a scalar.
|
||||
if (is_callback_option(options[opt_idx].flags)) {
|
||||
Callback *cb = (Callback *)varp;
|
||||
if (free_oldval) {
|
||||
callback_free(cb);
|
||||
}
|
||||
callback_free(cb);
|
||||
*cb = opt_to_callback(value, options[opt_idx].flags & kOptFlagExpr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (free_oldval) {
|
||||
optval_free(opt_from_varp(opt_idx, varp));
|
||||
}
|
||||
|
||||
switch (value.type) {
|
||||
case kObjectTypeUnset:
|
||||
// Unset local value of a global-local boolean: store the kNone sentinel.
|
||||
@@ -3562,7 +3608,8 @@ static void set_option_varp(OptIndex opt_idx, void *varp, Object value, bool fre
|
||||
return;
|
||||
case kObjectTypeString:
|
||||
assert(option_has_type(opt_idx, kObjectTypeString));
|
||||
*(char **)varp = value.data.string.data;
|
||||
free_string_option(*(char **)varp);
|
||||
*(char **)varp = copy_option_val(value.data.string.data);
|
||||
return;
|
||||
default:
|
||||
abort();
|
||||
@@ -3909,9 +3956,7 @@ Object get_option_value(OptIndex opt_idx, int opt_flags)
|
||||
}
|
||||
|
||||
vimoption_T *opt = &options[opt_idx];
|
||||
void *varp = get_varp_scope(opt, opt_flags);
|
||||
|
||||
return copy_object(opt_from_varp(opt_idx, varp), NULL);
|
||||
return optval_own(opt->flags, opt_from_varp(opt_idx, get_varp_scope(opt, opt_flags)));
|
||||
}
|
||||
|
||||
/// Return information for option at 'opt_idx'
|
||||
@@ -3921,11 +3966,11 @@ vimoption_T *get_option(OptIndex opt_idx)
|
||||
return &options[opt_idx];
|
||||
}
|
||||
|
||||
/// Get option value that represents an unset local value for an option.
|
||||
/// Get option value representing an unset local option. Release with optval_free_read().
|
||||
/// TODO(famiu): Remove this once we have a dedicated Object type for unset local options.
|
||||
///
|
||||
/// @param opt_idx Option index in options[] table.
|
||||
/// @param[in] varp Pointer to option variable.
|
||||
///
|
||||
/// @param opt_idx Option index in options[] table.
|
||||
///
|
||||
/// @return Option value equal to the unset value for the option.
|
||||
static Object get_option_unset_value(OptIndex opt_idx)
|
||||
@@ -3937,7 +3982,7 @@ static Object get_option_unset_value(OptIndex opt_idx)
|
||||
if (option_is_global_local(opt_idx)) {
|
||||
// String global-local options always use an empty string for the unset value.
|
||||
if (option_has_type(opt_idx, kObjectTypeString)) {
|
||||
return STATIC_CSTR_AS_OBJ("");
|
||||
return optval_is_owned(opt->flags) ? STATIC_CSTR_TO_OBJ("") : STATIC_CSTR_AS_OBJ("");
|
||||
}
|
||||
|
||||
switch (opt_idx) {
|
||||
@@ -3977,7 +4022,10 @@ static bool is_option_local_value_unset(OptIndex opt_idx)
|
||||
Object local_value = opt_from_varp(opt_idx, varp_local);
|
||||
Object unset_local_value = get_option_unset_value(opt_idx);
|
||||
|
||||
return option_equal(local_value, unset_local_value);
|
||||
bool is_unset = option_equal(local_value, unset_local_value);
|
||||
optval_free_read(opt->flags, local_value);
|
||||
optval_free_read(opt->flags, unset_local_value);
|
||||
return is_unset;
|
||||
}
|
||||
|
||||
/// Handle side-effects of setting an option.
|
||||
@@ -4051,7 +4099,8 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, Object old_value
|
||||
// If option is hidden or if an error is detected, restore the previous value and don't do any
|
||||
// further processing.
|
||||
if (errmsg != NULL) {
|
||||
set_option_varp(opt_idx, varp, old_value, true);
|
||||
set_option_varp(opt_idx, varp, old_value);
|
||||
optval_free(old_value);
|
||||
// When resetting some values, need to act on it.
|
||||
if (restore_chartab) {
|
||||
buf_init_chartab(curbuf, true);
|
||||
@@ -4061,7 +4110,7 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, Object old_value
|
||||
}
|
||||
|
||||
// Re-assign the new value as its value may get freed or modified by the option callback.
|
||||
new_value = opt_from_varp(opt_idx, varp);
|
||||
new_value = optval_own(opt->flags, opt_from_varp(opt_idx, varp));
|
||||
|
||||
if (set_sid != SID_NONE) {
|
||||
sctx_T script_ctx = set_sid == 0 ? current_sctx : (sctx_T){ .sc_sid = set_sid };
|
||||
@@ -4078,19 +4127,22 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, Object old_value
|
||||
// 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);
|
||||
Object local_unset_value = get_option_unset_value(opt_idx);
|
||||
set_option_varp(opt_idx, varp_local, copy_object(local_unset_value, NULL), true);
|
||||
Object unset = get_option_unset_value(opt_idx);
|
||||
set_option_varp(opt_idx, varp_local, unset);
|
||||
optval_free_read(opt->flags, unset);
|
||||
} else {
|
||||
// May set global value for a buffer/window-local option. Skip when varp already is the
|
||||
// global (a purely-global option): re-storing there is redundant, and for a callback option
|
||||
// it would free the callback and then fail to re-parse an anonymous lambda's `<lambda>N`.
|
||||
void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
|
||||
if (varp_global != varp) {
|
||||
set_option_varp(opt_idx, varp_global, copy_object(new_value, NULL), true);
|
||||
set_option_varp(opt_idx, varp_global, new_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optval_free(new_value); // Owned via optval_own() above.
|
||||
|
||||
// Don't do anything else if setting the option directly.
|
||||
if (direct) {
|
||||
return errmsg;
|
||||
@@ -4170,9 +4222,13 @@ static const char *validate_option_value(const OptIndex opt_idx, Object *newval,
|
||||
vimoption_T *opt = &options[opt_idx];
|
||||
|
||||
// Always allow unsetting local value of global-local option.
|
||||
if (option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL)
|
||||
&& option_equal(*newval, get_option_unset_value(opt_idx))) {
|
||||
return NULL;
|
||||
if (option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL)) {
|
||||
Object unset = get_option_unset_value(opt_idx);
|
||||
bool is_unset = option_equal(*newval, unset);
|
||||
optval_free_read(opt->flags, unset);
|
||||
if (is_unset) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (newval->type == kObjectTypeNil) {
|
||||
@@ -4182,7 +4238,9 @@ static const char *validate_option_value(const OptIndex opt_idx, Object *newval,
|
||||
if (opt_flags == OPT_GLOBAL) {
|
||||
errmsg = _("Cannot unset global option value");
|
||||
} else {
|
||||
*newval = copy_object(get_option_unset_value(opt_idx), NULL);
|
||||
Object unset = get_option_unset_value(opt_idx);
|
||||
*newval = copy_object(unset, NULL);
|
||||
optval_free_read(opt->flags, unset);
|
||||
}
|
||||
} else if (newval->type == kObjectTypeLuaRef) {
|
||||
// A callback option accepts a funcref; scalar validation doesn't apply.
|
||||
@@ -4290,26 +4348,29 @@ static const char *set_option(const OptIndex opt_idx, Object value, int opt_flag
|
||||
void *varp_local = get_varp_scope(opt, OPT_LOCAL);
|
||||
void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
|
||||
|
||||
Object old_value = opt_from_varp(opt_idx, varp);
|
||||
Object old_global_value = opt_from_varp(opt_idx, varp_global);
|
||||
// If local value of global-local option is unset, use global value as local value.
|
||||
Object old_local_value = is_opt_local_unset
|
||||
? old_global_value
|
||||
: opt_from_varp(opt_idx, varp_local);
|
||||
// Value that's actually being used.
|
||||
// For local scope of a global-local option, it's equal to the global value if the local value is
|
||||
// unset. In every other case, it is the same as old_value.
|
||||
// This value is used instead of old_value when triggering the OptionSet autocommand.
|
||||
Object used_old_value = (scope_local && is_opt_local_unset)
|
||||
? opt_from_varp(opt_idx, get_varp(opt))
|
||||
: old_value;
|
||||
// All values on the set path are owned.
|
||||
Object old_value = optval_own(opt->flags, opt_from_varp(opt_idx, varp));
|
||||
|
||||
// Save the old values and the new value in case they get changed.
|
||||
Object saved_used_value = copy_object(used_old_value, NULL);
|
||||
Object saved_old_global_value = copy_object(old_global_value, NULL);
|
||||
Object saved_old_local_value = copy_object(old_local_value, NULL);
|
||||
// New value (and varp) may become invalid if the buffer is closed by autocommands.
|
||||
Object saved_new_value = copy_object(value, NULL);
|
||||
// Snapshots for OptionSet/UI reporting, taken before the store since it changes varp (and
|
||||
// autocommands may even close the buffer). Skipped when not reporting (direct set).
|
||||
Object saved_used_value = NIL;
|
||||
Object saved_old_global_value = NIL;
|
||||
Object saved_old_local_value = NIL;
|
||||
Object saved_new_value = NIL;
|
||||
if (!direct) {
|
||||
saved_old_global_value = optval_snapshot_varp(opt_idx, varp_global);
|
||||
// If local value of global-local option is unset, use global value as local value.
|
||||
saved_old_local_value = is_opt_local_unset
|
||||
? copy_object(saved_old_global_value, NULL)
|
||||
: optval_snapshot_varp(opt_idx, varp_local);
|
||||
// Value that's actually being used: for local scope of a global-local option, the global
|
||||
// value if the local value is unset; in every other case, the same as old_value. Used instead
|
||||
// of old_value when triggering the OptionSet autocommand.
|
||||
saved_used_value = (scope_local && is_opt_local_unset)
|
||||
? optval_snapshot_varp(opt_idx, get_varp(opt))
|
||||
: optval_snapshot(old_value);
|
||||
saved_new_value = optval_snapshot(value);
|
||||
}
|
||||
|
||||
uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
|
||||
const int secure_saved = secure;
|
||||
@@ -4321,8 +4382,8 @@ static const char *set_option(const OptIndex opt_idx, Object value, int opt_flag
|
||||
secure = 1;
|
||||
}
|
||||
|
||||
// Set option through its variable pointer.
|
||||
set_option_varp(opt_idx, varp, value, false);
|
||||
// Store the new value (copied) at varp.
|
||||
set_option_varp(opt_idx, varp, value);
|
||||
// Process any side effects.
|
||||
errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, set_sid, direct,
|
||||
value_replaced, errbuf, errbuflen);
|
||||
@@ -4345,6 +4406,9 @@ static const char *set_option(const OptIndex opt_idx, Object value, int opt_flag
|
||||
optval_free(saved_old_global_value);
|
||||
optval_free(saved_new_value);
|
||||
|
||||
// `old_value` is freed by did_set_option().
|
||||
optval_free(value);
|
||||
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
@@ -4442,7 +4506,10 @@ const char *set_option_value(const OptIndex opt_idx, const Object value, int opt
|
||||
static inline const char *unset_option_local_value(const OptIndex opt_idx)
|
||||
{
|
||||
assert(option_is_global_local(opt_idx));
|
||||
return set_option_value(opt_idx, get_option_unset_value(opt_idx), OPT_LOCAL);
|
||||
Object unset = get_option_unset_value(opt_idx);
|
||||
const char *errmsg = set_option_value(opt_idx, unset, OPT_LOCAL);
|
||||
optval_free_read(options[opt_idx].flags, unset);
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
/// Set the value of an option. Supports TTY options, unlike set_option_value().
|
||||
@@ -4758,7 +4825,9 @@ static int optval_default(OptIndex opt_idx, void *varp)
|
||||
Object current_val = opt_from_varp(opt_idx, varp);
|
||||
Object default_val = opt->def_val;
|
||||
|
||||
return option_equal(current_val, default_val);
|
||||
bool is_default = option_equal(current_val, default_val);
|
||||
optval_free_read(opt->flags, current_val);
|
||||
return is_default;
|
||||
}
|
||||
|
||||
/// Send update to UIs with values of UI relevant options
|
||||
@@ -4958,17 +5027,20 @@ int makefoldset(FILE *fd)
|
||||
/// @param varp Pointer to option variable.
|
||||
///
|
||||
/// @return FAIL on error, OK otherwise.
|
||||
static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp)
|
||||
static int put_set_value(FILE *fd, char *cmd, OptIndex opt_idx, void *varp, Object value)
|
||||
{
|
||||
Object value = opt_from_varp(opt_idx, varp);
|
||||
vimoption_T *opt = &options[opt_idx];
|
||||
char *name = opt->fullname;
|
||||
uint64_t flags = opt->flags;
|
||||
|
||||
if (option_is_global_local(opt_idx) && varp != opt->var
|
||||
&& option_equal(value, get_option_unset_value(opt_idx))) {
|
||||
// Processing unset local value of global-local option. Do nothing.
|
||||
return OK;
|
||||
if (option_is_global_local(opt_idx) && varp != opt->var) {
|
||||
Object unset = get_option_unset_value(opt_idx);
|
||||
bool is_unset = option_equal(value, unset);
|
||||
optval_free_read(opt->flags, unset);
|
||||
if (is_unset) {
|
||||
// Processing unset local value of global-local option. Do nothing.
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
switch (value.type) {
|
||||
@@ -5067,6 +5139,17 @@ static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp)
|
||||
return OK;
|
||||
}
|
||||
|
||||
/// Print the ":set" command for one option to a file, then release the read value (owned for a
|
||||
/// callback option; see optval_free_read()).
|
||||
static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp)
|
||||
{
|
||||
Object value = opt_from_varp(opt_idx, varp);
|
||||
// Skip func-option set to a Lua function (omit from :mksession/etc.).
|
||||
int ret = value.type == kObjectTypeLuaRef ? OK : put_set_value(fd, cmd, opt_idx, varp, value);
|
||||
optval_free_read(options[opt_idx].flags, value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win)
|
||||
{
|
||||
OptIndex opt_idx = get_opt_idx(p);
|
||||
@@ -7057,8 +7140,11 @@ dict_T *get_winbuf_options(const int bufopt)
|
||||
void *varp = get_varp(opt);
|
||||
|
||||
if (varp != NULL) {
|
||||
typval_T opt_tv = opt_to_tv(opt_from_varp(opt_idx, varp), true);
|
||||
Object ov = opt_from_varp(opt_idx, varp);
|
||||
typval_T opt_tv = opt_to_tv(ov, true);
|
||||
tv_dict_add_tv(d, opt->fullname, strlen(opt->fullname), &opt_tv);
|
||||
tv_clear(&opt_tv);
|
||||
optval_free_read(opt->flags, ov);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7741,7 +7741,7 @@ local options = {
|
||||
localoptions options and mappings local to a window or buffer (not
|
||||
global values for local options)
|
||||
options all options and mappings (also global values for local
|
||||
options)
|
||||
options), except Lua functions |option-value-function|.
|
||||
skiprtp exclude 'runtimepath' and 'packpath' from the options
|
||||
resize size of the Vim window: 'lines' and 'columns'
|
||||
sesdir the directory in which the session file is located
|
||||
@@ -10419,7 +10419,8 @@ local options = {
|
||||
folds manually created folds, opened/closed folds and local
|
||||
fold options
|
||||
options options and mappings local to a window or buffer (not
|
||||
global values for local options)
|
||||
global values for local options), except Lua functions
|
||||
|option-value-function|.
|
||||
localoptions same as "options"
|
||||
slash |deprecated| Always enabled. Uses "/" in filenames.
|
||||
unix |deprecated| Always enabled. Uses "\n" line endings.
|
||||
|
||||
@@ -1843,7 +1843,9 @@ static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerf
|
||||
&& ((args->os_flags & OPT_GLOBAL) || !(args->os_flags & OPT_LOCAL))
|
||||
&& s[0] == NUL) {
|
||||
xfree(*varp);
|
||||
*varp = xstrdup(get_option_default(args->os_idx, args->os_flags).data.string.data);
|
||||
Object def = get_option_default(args->os_idx, args->os_flags);
|
||||
*varp = xstrdup(def.data.string.data);
|
||||
optval_free_read(get_option_flags(args->os_idx), def);
|
||||
s = *varp;
|
||||
}
|
||||
|
||||
|
||||
@@ -2172,7 +2172,9 @@ stcsign:
|
||||
// matter?
|
||||
// if (called_emsg > called_emsg_before)
|
||||
if (opt_idx != kOptInvalid && did_emsg > did_emsg_before) {
|
||||
set_option_direct(opt_idx, get_option_default(opt_idx, opt_scope), opt_scope, SID_ERROR);
|
||||
Object def = get_option_default(opt_idx, opt_scope);
|
||||
set_option_direct(opt_idx, def, opt_scope, SID_ERROR);
|
||||
optval_free_read(get_option_flags(opt_idx), def);
|
||||
}
|
||||
|
||||
// A user function may reset KeyTyped, restore it.
|
||||
|
||||
@@ -129,7 +129,7 @@ describe('callback (func/expr) options', function()
|
||||
it('accept a Lua function, preserving identity and captured upvalues', function()
|
||||
-- 'operatorfunc' is global; 'tagfunc' is buffer-local: exercise both storage paths.
|
||||
eq(
|
||||
{ identity = true, captured = 'CAP', kind = 'function' },
|
||||
{ identity = true, captured = 'CAP', kind = 'function', optset_old = '', optset_new = true },
|
||||
exec_lua(function()
|
||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, { 'abcdef' })
|
||||
local captured = 'CAP'
|
||||
@@ -137,12 +137,22 @@ describe('callback (func/expr) options', function()
|
||||
local fn = function(_)
|
||||
_G.seen = captured
|
||||
end
|
||||
-- OptionSet emits with Lua function as "<Lua …>".
|
||||
local optset
|
||||
vim.api.nvim_create_autocmd('OptionSet', {
|
||||
pattern = 'operatorfunc',
|
||||
callback = function()
|
||||
optset = { old = vim.v.option_old, new = vim.v.option_new }
|
||||
end,
|
||||
})
|
||||
vim.o.operatorfunc = fn
|
||||
vim.cmd('normal! g@l')
|
||||
return {
|
||||
identity = vim.o.operatorfunc == fn,
|
||||
captured = _G.seen,
|
||||
kind = type(vim.o.operatorfunc),
|
||||
optset_old = optset.old,
|
||||
optset_new = tostring(optset.new):match('^<Lua ') ~= nil,
|
||||
}
|
||||
end)
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user