aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFamiu Haque <famiuhaque@proton.me>2023-06-07 06:05:16 +0600
committerGitHub <noreply@github.com>2023-06-07 08:05:16 +0800
commitb3d5138fd0066fda26ef7724a542ae45eb42fc84 (patch)
treef9893af238d838408c81c6d4e36655865fe465b0 /src
parent0370e4def0c0328f8cd09f02c1ca82ed491ecb9a (diff)
downloadrneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.tar.gz
rneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.tar.bz2
rneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.zip
refactor(options): remove `getoption_T` and introduce `OptVal` (#23850)
Removes the `getoption_T` struct and also introduces the `OptVal` struct to unify the methods of getting/setting different option value types. This is the first of many PRs to reduce code duplication in the Vim option code as well as to make options easier to maintain. It also increases the flexibility and extensibility of options. Which opens the door for things like Array and Dictionary options.
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/deprecated.c11
-rw-r--r--src/nvim/api/options.c278
-rw-r--r--src/nvim/api/private/helpers.h2
-rw-r--r--src/nvim/api/vim.c8
-rw-r--r--src/nvim/buffer.c6
-rw-r--r--src/nvim/context.c9
-rw-r--r--src/nvim/diff.c4
-rw-r--r--src/nvim/eval.c49
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/eval/vars.c228
-rw-r--r--src/nvim/ex_docmd.c2
-rw-r--r--src/nvim/ex_getln.c4
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/help.c2
-rw-r--r--src/nvim/highlight_group.c2
-rw-r--r--src/nvim/main.c22
-rw-r--r--src/nvim/memline.c2
-rw-r--r--src/nvim/option.c309
-rw-r--r--src/nvim/option.h23
-rw-r--r--src/nvim/option_defs.h21
-rw-r--r--src/nvim/optionstr.c4
-rw-r--r--src/nvim/popupmenu.c10
-rw-r--r--src/nvim/quickfix.c12
-rw-r--r--src/nvim/runtime.c2
-rw-r--r--src/nvim/spell.c10
-rw-r--r--src/nvim/spellfile.c2
-rw-r--r--src/nvim/terminal.c6
-rw-r--r--src/nvim/ui.c2
28 files changed, 668 insertions, 366 deletions
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index a7e5b32d49..83c33db5d3 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -709,14 +709,13 @@ static void set_option_to(uint64_t channel_id, void *to, int type, String name,
}
}
- long numval = 0;
- char *stringval = NULL;
+ OptVal optval = NIL_OPTVAL;
if (flags & SOPT_BOOL) {
VALIDATE(value.type == kObjectTypeBoolean, "Option '%s' value must be Boolean", name.data, {
return;
});
- numval = value.data.boolean;
+ optval = BOOLEAN_OPTVAL(value.data.boolean);
} else if (flags & SOPT_NUM) {
VALIDATE(value.type == kObjectTypeInteger, "Option '%s' value must be Integer", name.data, {
return;
@@ -725,12 +724,12 @@ static void set_option_to(uint64_t channel_id, void *to, int type, String name,
"Option '%s' value is out of range", name.data, {
return;
});
- numval = (int)value.data.integer;
+ optval = NUMBER_OPTVAL(value.data.integer);
} else {
VALIDATE(value.type == kObjectTypeString, "Option '%s' value must be String", name.data, {
return;
});
- stringval = value.data.string.data;
+ optval = STRING_OPTVAL(value.data.string);
}
// For global-win-local options -> setlocal
@@ -739,6 +738,6 @@ static void set_option_to(uint64_t channel_id, void *to, int type, String name,
(type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL;
WITH_SCRIPT_CONTEXT(channel_id, {
- access_option_value_for(name.data, &numval, &stringval, opt_flags, type, to, false, err);
+ set_option_value_for(name.data, optval, opt_flags, type, to, err);
});
}
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index e18312a6dc..ed13e51e90 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -113,10 +113,10 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
aucmd_prepbuf(aco, ftbuf);
TRY_WRAP(err, {
- set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
- set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
- set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
- set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
+ set_option_value("bufhidden", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value("swapfile", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("modeline", BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline'
ftbuf->b_p_ft = xstrdup(filetype);
do_filetype_autocmd(ftbuf, false);
@@ -125,6 +125,54 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
return ftbuf;
}
+/// Consume an OptVal and convert it to an API Object.
+static Object optval_as_object(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ return NIL;
+ case kOptValTypeBoolean:
+ switch (o.data.boolean) {
+ case kFalse:
+ case kTrue:
+ return BOOLEAN_OBJ(o.data.boolean);
+ case kNone:
+ return NIL;
+ default:
+ abort();
+ }
+ case kOptValTypeNumber:
+ return INTEGER_OBJ(o.data.number);
+ case kOptValTypeString:
+ return STRING_OBJ(o.data.string);
+ default:
+ abort();
+ }
+}
+
+/// Consume an API Object and convert it to an OptVal.
+static OptVal object_as_optval(Object o, Error *err)
+{
+ switch (o.type) {
+ case kObjectTypeNil:
+ return NIL_OPTVAL;
+ break;
+ case kObjectTypeBoolean:
+ return BOOLEAN_OPTVAL(o.data.boolean);
+ break;
+ case kObjectTypeInteger:
+ return NUMBER_OPTVAL(o.data.integer);
+ break;
+ case kObjectTypeString:
+ return STRING_OPTVAL(o.data.string);
+ break;
+ default:
+ // Some Object types don't have an OptVal equivalent. Error out in those cases.
+ api_set_error(err, kErrorTypeException, "Invalid option value");
+ return NIL_OPTVAL;
+ }
+}
+
/// Gets the value of an option. The behavior of this function matches that of
/// |:set|: the local value of an option is returned if it exists; otherwise,
/// the global value is returned. Local values always correspond to the current
@@ -147,6 +195,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
FUNC_API_SINCE(9)
{
Object rv = OBJECT_INIT;
+ OptVal value = NIL_OPTVAL;
int scope = 0;
int opt_type = SREQ_GLOBAL;
@@ -154,14 +203,14 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
char *filetype = NULL;
if (!validate_option_value_args(opts, &scope, &opt_type, &from, &filetype, err)) {
- return rv;
+ goto err;
}
aco_save_T aco;
buf_T *ftbuf = do_ft_buf(filetype, &aco, err);
if (ERROR_SET(err)) {
- return rv;
+ goto err;
}
if (ftbuf != NULL) {
@@ -169,10 +218,8 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
from = ftbuf;
}
- long numval = 0;
- char *stringval = NULL;
- getoption_T result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type,
- from, true, err);
+ bool hidden;
+ value = get_option_value_for(name.data, NULL, scope, &hidden, opt_type, from, err);
if (ftbuf != NULL) {
// restore curwin/curbuf and a few other things
@@ -183,35 +230,16 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
}
if (ERROR_SET(err)) {
- return rv;
+ goto err;
}
- switch (result) {
- case gov_string:
- rv = CSTR_AS_OBJ(stringval);
- break;
- case gov_number:
- rv = INTEGER_OBJ(numval);
- break;
- case gov_bool:
- switch (numval) {
- case 0:
- case 1:
- rv = BOOLEAN_OBJ(numval);
- break;
- default:
- // Boolean options that return something other than 0 or 1 should return nil. Currently this
- // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
- rv = NIL;
- break;
- }
- break;
- default:
- VALIDATE_S(false, "option", name.data, {
- return rv;
- });
- }
+ VALIDATE_S(!hidden && value.type != kOptValTypeNil, "option", name.data, {
+ goto err;
+ });
+ return optval_as_object(value);
+err:
+ optval_free(value);
return rv;
}
@@ -253,30 +281,19 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(
}
}
- long numval = 0;
- char *stringval = NULL;
+ OptVal optval = object_as_optval(value, err);
+
+ // Handle invalid option value type.
+ if (ERROR_SET(err)) {
+ api_clear_error(err);
- switch (value.type) {
- case kObjectTypeInteger:
- numval = (long)value.data.integer;
- break;
- case kObjectTypeBoolean:
- numval = value.data.boolean ? 1 : 0;
- break;
- case kObjectTypeString:
- stringval = value.data.string.data;
- break;
- case kObjectTypeNil:
- scope |= OPT_CLEAR;
- break;
- default:
VALIDATE_EXP(false, name.data, "Integer/Boolean/String", api_typename(value.type), {
return;
});
}
WITH_SCRIPT_CONTEXT(channel_id, {
- access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err);
+ set_option_value_for(name.data, optval, scope, opt_type, to, err);
});
}
@@ -341,72 +358,135 @@ Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err)
return get_vimoption(name, scope, buf, win, err);
}
-static getoption_T access_option_value(char *key, long *numval, char **stringval, int opt_flags,
- bool get, Error *err)
+/// Switch current context to get/set option value for window/buffer.
+///
+/// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer.
+/// @param[in] opt_type Option type. See SREQ_* in option_defs.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+///
+/// @return true if context was switched, false otherwise.
+static bool switch_option_context(void *const ctx, int opt_type, void *const from, Error *err)
{
- if (get) {
- return get_option_value(key, numval, stringval, NULL, opt_flags);
- } else {
- const char *errmsg;
- if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) {
+ switch (opt_type) {
+ case SREQ_WIN: {
+ win_T *const win = (win_T *)from;
+ switchwin_T *const switchwin = (switchwin_T *)ctx;
+
+ if (win == curwin) {
+ return false;
+ }
+
+ if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true)
+ == FAIL) {
+ restore_win_noblock(switchwin, true);
+
if (try_end(err)) {
- return 0;
+ return false;
}
+ api_set_error(err, kErrorTypeException, "Problem while switching windows");
+ return false;
+ }
+ return true;
+ }
+ case SREQ_BUF: {
+ buf_T *const buf = (buf_T *)from;
+ aco_save_T *const aco = (aco_save_T *)ctx;
- api_set_error(err, kErrorTypeException, "%s", errmsg);
+ if (buf == curbuf) {
+ return false;
}
- return 0;
+ aucmd_prepbuf(aco, buf);
+ return true;
+ }
+ case SREQ_GLOBAL:
+ return false;
+ default:
+ abort(); // This should never happen.
}
}
-getoption_T access_option_value_for(char *key, long *numval, char **stringval, int opt_flags,
- int opt_type, void *from, bool get, Error *err)
+/// Restore context after getting/setting option for window/buffer. See switch_option_context() for
+/// params.
+static void restore_option_context(void *const ctx, const int opt_type)
{
- bool need_switch = false;
- switchwin_T switchwin;
- aco_save_T aco;
- getoption_T result = 0;
-
- try_start();
switch (opt_type) {
case SREQ_WIN:
- need_switch = (win_T *)from != curwin;
- if (need_switch) {
- if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
- == FAIL) {
- restore_win_noblock(&switchwin, true);
- if (try_end(err)) {
- return result;
- }
- api_set_error(err, kErrorTypeException, "Problem while switching windows");
- return result;
- }
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- restore_win_noblock(&switchwin, true);
- }
+ restore_win_noblock((switchwin_T *)ctx, true);
break;
case SREQ_BUF:
- need_switch = (buf_T *)from != curbuf;
- if (need_switch) {
- aucmd_prepbuf(&aco, (buf_T *)from);
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- aucmd_restbuf(&aco);
- }
+ aucmd_restbuf((aco_save_T *)ctx);
break;
case SREQ_GLOBAL:
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
break;
+ default:
+ abort(); // This should never happen.
+ }
+}
+
+/// Get option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL).
+/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param[out] hidden Whether option is hidden.
+/// @param[in] opt_type Option type. See SREQ_* in option_defs.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+///
+/// @return Option value. Must be freed by caller.
+OptVal get_option_value_for(const char *const name, uint32_t *flagsp, int scope, bool *hidden,
+ const int opt_type, void *const from, Error *err)
+{
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = opt_type == SREQ_WIN ? (void *)&switchwin
+ : (opt_type == SREQ_BUF ? (void *)&aco : NULL);
+
+ bool switched = switch_option_context(ctx, opt_type, from, err);
+ if (ERROR_SET(err)) {
+ return NIL_OPTVAL;
+ }
+
+ OptVal retv = get_option_value(name, flagsp, scope, hidden);
+
+ if (switched) {
+ restore_option_context(ctx, opt_type);
}
+ return retv;
+}
+
+/// Set option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[in] value Option value.
+/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
+/// If OPT_CLEAR is set, the value of the option
+/// is cleared (the exact semantics of this depend
+/// on the option).
+/// @param[in] opt_type Option type. See SREQ_* in option_defs.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+void set_option_value_for(const char *const name, OptVal value, const int opt_flags,
+ const int opt_type, void *const from, Error *err)
+{
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = opt_type == SREQ_WIN ? (void *)&switchwin
+ : (opt_type == SREQ_BUF ? (void *)&aco : NULL);
+
+ bool switched = switch_option_context(ctx, opt_type, from, err);
if (ERROR_SET(err)) {
- return result;
+ return;
}
- try_end(err);
+ const char *const errmsg = set_option_value(name, value, opt_flags);
+ if (errmsg) {
+ api_set_error(err, kErrorTypeException, "%s", errmsg);
+ }
- return result;
+ if (switched) {
+ restore_option_context(ctx, opt_type);
+ }
}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 8410251514..3887a11dd1 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -94,7 +94,7 @@
#define cbuf_as_string(d, s) ((String) { .data = d, .size = s })
-#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 })
+#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof("" s) - 1 })
/// Create a new String instance, putting data in allocated memory
///
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 4722195fe4..d93af6a2a4 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -910,10 +910,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
if (scratch) {
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
- set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
- set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
- set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
- set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
+ set_option_value("bufhidden", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value("swapfile", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("modeline", BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline'
aucmd_restbuf(&aco);
}
return buf->b_fnum;
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index bc52ab0771..e3d375e2df 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -4304,9 +4304,9 @@ int buf_open_scratch(handle_T bufnr, char *bufname)
apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
(void)setfname(curbuf, bufname, NULL, true);
apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
- set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
RESET_BINDING(curwin);
return OK;
}
diff --git a/src/nvim/context.c b/src/nvim/context.c
index b13a331eff..f7b3491be7 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -143,9 +143,8 @@ bool ctx_restore(Context *ctx, const int flags)
free_ctx = true;
}
- char *op_shada;
- get_option_value("shada", NULL, &op_shada, NULL, OPT_GLOBAL);
- set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
+ OptVal op_shada = get_option_value("shada", NULL, OPT_GLOBAL, NULL);
+ set_option_value("shada", STATIC_CSTR_AS_OPTVAL("!,'100,%"), OPT_GLOBAL);
if (flags & kCtxRegs) {
ctx_restore_regs(ctx);
@@ -171,8 +170,8 @@ bool ctx_restore(Context *ctx, const int flags)
ctx_free(ctx);
}
- set_option_value("shada", 0L, op_shada, OPT_GLOBAL);
- xfree(op_shada);
+ set_option_value("shada", op_shada, OPT_GLOBAL);
+ optval_free(op_shada);
return true;
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index f1a3a679be..810f631a02 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -1389,14 +1389,14 @@ void ex_diffthis(exarg_T *eap)
diff_win_options(curwin, true);
}
-static void set_diff_option(win_T *wp, int value)
+static void set_diff_option(win_T *wp, bool value)
{
win_T *old_curwin = curwin;
curwin = wp;
curbuf = curwin->w_buffer;
curbuf->b_ro_locked++;
- set_option_value_give_err("diff", (long)value, NULL, OPT_LOCAL);
+ set_option_value_give_err("diff", BOOLEAN_OPTVAL(value), OPT_LOCAL);
curbuf->b_ro_locked--;
curwin = old_curwin;
curbuf = curwin->w_buffer;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 118c1d3012..72005b1f35 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -58,6 +58,7 @@
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
#include "nvim/optionstr.h"
#include "nvim/os/fileio.h"
#include "nvim/os/fs_defs.h"
@@ -3770,38 +3771,38 @@ int eval_option(const char **const arg, typval_T *const rettv, const bool evalua
return OK;
}
- long numval;
- char *stringval;
int ret = OK;
-
+ bool hidden;
char c = *option_end;
*option_end = NUL;
- getoption_T opt_type = get_option_value(*arg, &numval,
- rettv == NULL ? NULL : &stringval, NULL, scope);
+ OptVal value = get_option_value(*arg, NULL, scope, &hidden);
- if (opt_type == gov_unknown) {
- if (rettv != NULL) {
+ if (rettv != NULL) {
+ switch (value.type) {
+ case kOptValTypeNil:
semsg(_("E113: Unknown option: %s"), *arg);
- }
- ret = FAIL;
- } else if (rettv != NULL) {
- if (opt_type == gov_hidden_string) {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- } else if (opt_type == gov_hidden_bool || opt_type == gov_hidden_number) {
+ ret = FAIL;
+ break;
+ case kOptValTypeBoolean:
rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- } else if (opt_type == gov_bool || opt_type == gov_number) {
+ rettv->vval.v_number = value.data.boolean;
+ break;
+ case kOptValTypeNumber:
rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = numval;
- } else { // string option
+ rettv->vval.v_number = value.data.number;
+ break;
+ case kOptValTypeString:
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = stringval;
+ rettv->vval.v_string = value.data.string.data;
+ break;
+ }
+ } else {
+ // Value isn't being used, free it.
+ optval_free(value);
+
+ if (value.type == kOptValTypeNil || (working && hidden)) {
+ ret = FAIL;
}
- } else if (working && (opt_type == gov_hidden_bool
- || opt_type == gov_hidden_number
- || opt_type == gov_hidden_string)) {
- ret = FAIL;
}
*option_end = c; // put back for error messages
@@ -8516,7 +8517,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
// If it's still empty it was changed and restored, need to restore in
// the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
free_string_option(save_cpo);
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 04fd81c713..d465cade3e 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -7101,7 +7101,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
// If it's still empty it was changed and restored, need to restore in
// the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
free_string_option(save_cpo);
}
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 21b25b64f4..0663c3c54c 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -766,84 +766,84 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
|| (endchars != NULL
&& vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
emsg(_(e_letunexp));
- } else {
- varnumber_T n = 0;
- getoption_T opt_type;
- long numval;
- char *stringval = NULL;
- const char *s = NULL;
- bool failed = false;
- uint32_t opt_p_flags;
- char *tofree = NULL;
-
- const char c1 = *p;
- *p = NUL;
+ return NULL;
+ }
- opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope);
- if (opt_type == gov_bool
- || opt_type == gov_number
- || opt_type == gov_hidden_bool
- || opt_type == gov_hidden_number) {
- // number, possibly hidden
- n = (long)tv_get_number(tv);
- }
-
- if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) {
- // If the option can be set to a function reference or a lambda
- // and the passed value is a function reference, then convert it to
- // the name (string) of the function reference.
- s = tofree = encode_tv2string(tv, NULL);
- } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
- // Avoid setting a string option to the text "v:false" or similar.
- s = tv_get_string_chk(tv);
- }
-
- if (op != NULL && *op != '=') {
- if (((opt_type == gov_bool || opt_type == gov_number) && *op == '.')
- || (opt_type == gov_string && *op != '.')) {
- semsg(_(e_letwrong), op);
- failed = true; // don't set the value
- } else {
- // number or bool
- if (opt_type == gov_number || opt_type == gov_bool) {
- switch (*op) {
- case '+':
- n = numval + n; break;
- case '-':
- n = numval - n; break;
- case '*':
- n = numval * n; break;
- case '/':
- n = num_divide(numval, n); break;
- case '%':
- n = num_modulus(numval, n); break;
- }
- s = NULL;
- } else if (opt_type == gov_string && stringval != NULL && s != NULL) {
- // string
- char *const oldstringval = stringval;
- stringval = concat_str(stringval, s);
- xfree(oldstringval);
- s = stringval;
+ bool hidden;
+ bool error;
+ const char c1 = *p;
+ *p = NUL;
+
+ OptVal curval = get_option_value(arg, NULL, scope, &hidden);
+ OptVal newval = tv_to_optval(tv, arg, scope, &error);
+
+ // Ignore errors for num types
+ if (newval.type != kOptValTypeNumber && newval.type != kOptValTypeBoolean && error) {
+ goto end;
+ }
+
+ // Don't assume current and new values are of the same type in order to future-proof the code for
+ // when an option can have multiple types.
+ const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean)
+ && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean));
+ const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString;
+
+ if (op != NULL && *op != '=') {
+ if (!hidden && ((is_num && *op == '.') || (is_string && *op != '.'))) {
+ semsg(_(e_letwrong), op);
+ goto end;
+ } else {
+ // number or bool
+ if (!hidden && is_num) {
+ Integer cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean;
+ Integer new_n = newval.type == kOptValTypeNumber ? newval.data.number : newval.data.boolean;
+
+ switch (*op) {
+ case '+':
+ new_n = cur_n + new_n; break;
+ case '-':
+ new_n = cur_n - new_n; break;
+ case '*':
+ new_n = cur_n * new_n; break;
+ case '/':
+ new_n = num_divide(cur_n, new_n); break;
+ case '%':
+ new_n = num_modulus(cur_n, new_n); break;
}
- }
- }
- if (!failed) {
- if (opt_type != gov_string || s != NULL) {
- const char *err = set_option_value(arg, (long)n, s, scope);
- arg_end = p;
- if (err != NULL) {
- emsg(_(err));
+ // clamp boolean values
+ if (newval.type == kOptValTypeBoolean && (new_n > 1 || new_n < -1)) {
+ new_n = (new_n > 1) ? 1 : -1;
}
- } else {
- emsg(_(e_stringreq));
+
+ newval = kOptValTypeNumber ? NUMBER_OPTVAL(new_n) : BOOLEAN_OPTVAL((TriState)new_n);
+ } else if (!hidden && is_string && curval.data.string.data != NULL
+ && newval.data.string.data != NULL) {
+ // string
+ OptVal newval_old = newval;
+ newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
+ optval_free(newval_old);
}
}
- *p = c1;
- xfree(stringval);
- xfree(tofree);
}
+
+ // If new value is a string and is NULL, show an error if it's not a hidden option.
+ // For hidden options, just pass the value to `set_option_value` and let it fail silently.
+ if (hidden || newval.type != kOptValTypeString || newval.data.string.data != NULL) {
+ const char *err = set_option_value(arg, newval, scope);
+ arg_end = p;
+ if (err != NULL) {
+ emsg(_(err));
+ }
+ } else {
+ emsg(_(e_stringreq));
+ }
+
+end:
+ *p = c1;
+ optval_free(curval);
+ optval_free(newval);
+
return arg_end;
}
@@ -1809,28 +1809,84 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off)
get_var_from(varname, rettv, &argvars[off + 2], 'w', tp, win, NULL);
}
-/// Set option "varname" to the value of "varp" for the current buffer/window.
-static void set_option_from_tv(const char *varname, typval_T *varp)
+/// Convert typval to option value for a particular option.
+///
+/// @param[in] tv typval to convert.
+/// @param[in] option Option name.
+/// @param[in] scope Option scope.
+/// @param[out] error Whether an error occured.
+///
+/// @return Typval converted to OptVal. Must be freed by caller.
+/// Returns NIL_OPTVAL for invalid option name.
+static OptVal tv_to_optval(typval_T *tv, const char *option, int scope, bool *error)
{
- long numval = 0;
- const char *strval;
- bool error = false;
+ OptVal value = NIL_OPTVAL;
char nbuf[NUMBUFLEN];
-
- if (varp->v_type == VAR_BOOL) {
- if (is_string_option(varname)) {
+ uint32_t flags;
+ bool err = false;
+
+ OptVal curval = get_option_value(option, &flags, scope, NULL);
+
+ // TODO(famiu): Delegate all of these type-checks to set_option_value()
+ if (curval.type == kOptValTypeNil) {
+ // Invalid option name,
+