aboutsummaryrefslogtreecommitdiff
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
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.
-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
-rw-r--r--test/old/testdir/test_vimscript.vim5
29 files changed, 672 insertions, 367 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,
+ value = NIL_OPTVAL;
+ } else if ((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.
+ char *strval = encode_tv2string(tv, NULL);
+ err = strval == NULL;
+ value = CSTR_AS_OPTVAL(strval);
+ } else if (flags & (P_NUM | P_BOOL)) {
+ varnumber_T n = tv_get_number_chk(tv, &err);
+ // This could be either "0" or a string that's not a number. So we need to check if it's
+ // actually a number.
+ if (!err && tv->v_type == VAR_STRING && n == 0) {
+ unsigned idx;
+ for (idx = 0; tv->vval.v_string[idx] == '0'; idx++) {}
+ if (tv->vval.v_string[idx] != NUL || idx == 0) {
+ // There's another character after zeros or the string is empty.
+ // In both cases, we are trying to set a num option using a string.
+ semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string);
+ }
+ }
+ value = (flags & P_NUM) ? NUMBER_OPTVAL(n)
+ : BOOLEAN_OPTVAL(n == 0 ? kFalse : (n >= 1 ? kTrue : kNone));
+ } else if (flags & P_STRING || is_tty_option(option)) {
+ // Avoid setting string option to a boolean.
+ if (tv->v_type == VAR_BOOL) {
+ err = true;
emsg(_(e_stringreq));
- return;
+ } else {
+ const char *strval = tv_get_string_buf_chk(tv, nbuf);
+ err = strval == NULL;
+ value = CSTR_TO_OPTVAL(strval);
}
- numval = (long)varp->vval.v_number;
- strval = "0"; // avoid using "false"
} else {
- numval = (long)tv_get_number_chk(varp, &error);
- strval = tv_get_string_buf_chk(varp, nbuf);
+ abort(); // This should never happen.
}
- if (!error && strval != NULL) {
- set_option_value_give_err(varname, numval, strval, OPT_LOCAL);
+
+ if (error != NULL) {
+ *error = err;
}
+ optval_free(curval);
+ return value;
+}
+
+/// 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)
+{
+ bool error = false;
+ OptVal value = tv_to_optval(varp, varname, OPT_LOCAL, &error);
+
+ if (!error && value.type == kOptValTypeNil) {
+ semsg(_(e_unknown_option2), varname);
+ } else if (!error) {
+ set_option_value_give_err(varname, value, OPT_LOCAL);
+ }
+
+ optval_free(value);
}
/// "setwinvar()" and "settabwinvar()" functions
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 39a54fa236..19ee1d841b 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -7216,7 +7216,7 @@ static void ex_setfiletype(exarg_T *eap)
arg += 9;
}
- set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL);
+ set_option_value_give_err("filetype", CSTR_AS_OPTVAL(arg), OPT_LOCAL);
if (arg != eap->arg) {
did_filetype = false;
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index b2acc561be..77b09a5f73 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -4357,7 +4357,7 @@ static int open_cmdwin(void)
return Ctrl_C;
}
// Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer.
- set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL);
curbuf->b_p_ma = true;
curwin->w_p_fen = false;
curwin->w_p_rl = cmdmsg_rl;
@@ -4375,7 +4375,7 @@ static int open_cmdwin(void)
add_map("<Tab>", "<C-X><C-V>", MODE_INSERT, true);
add_map("<Tab>", "a<C-X><C-V>", MODE_NORMAL, true);
}
- set_option_value_give_err("ft", 0L, "vim", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("vim"), OPT_LOCAL);
}
curbuf->b_ro_locked--;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index bedb529d04..174dd6e13a 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1033,6 +1033,8 @@ INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
+EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s"));
+
EXTERN const char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
EXTERN const char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
diff --git a/src/nvim/help.c b/src/nvim/help.c
index d412f3a098..46310cb681 100644
--- a/src/nvim/help.c
+++ b/src/nvim/help.c
@@ -653,7 +653,7 @@ void fix_help_buffer(void)
// Set filetype to "help".
if (strcmp(curbuf->b_p_ft, "help") != 0) {
curbuf->b_ro_locked++;
- set_option_value_give_err("ft", 0L, "help", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("help"), OPT_LOCAL);
curbuf->b_ro_locked--;
}
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 09e7d04e02..48e4479c58 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1280,7 +1280,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (dark != -1
&& dark != (*p_bg == 'd')
&& !option_was_set("bg")) {
- set_option_value_give_err("bg", 0L, (dark ? "dark" : "light"), 0);
+ set_option_value_give_err("bg", CSTR_AS_OPTVAL(dark ? "dark" : "light"), 0);
reset_option_was_set("bg");
}
}
diff --git a/src/nvim/main.c b/src/nvim/main.c
index f27ebb2f67..88eedf80d3 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1112,7 +1112,7 @@ static void command_line_scan(mparm_T *parmp)
} else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) {
parmp->use_vimrc = "NONE";
parmp->clean = true;
- set_option_value_give_err("shadafile", 0L, "NONE", 0);
+ set_option_value_give_err("shadafile", STATIC_CSTR_AS_OPTVAL("NONE"), 0);
} else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) {
nlua_disable_preload = true;
} else {
@@ -1126,7 +1126,7 @@ static void command_line_scan(mparm_T *parmp)
}
break;
case 'A': // "-A" start in Arabic mode.
- set_option_value_give_err("arabic", 1L, NULL, 0);
+ set_option_value_give_err("arabic", BOOLEAN_OPTVAL(true), 0);
break;
case 'b': // "-b" binary mode.
// Needs to be effective before expanding file names, because
@@ -1156,8 +1156,8 @@ static void command_line_scan(mparm_T *parmp)
usage();
os_exit(0);
case 'H': // "-H" start in Hebrew mode: rl + keymap=hebrew set.
- set_option_value_give_err("keymap", 0L, "hebrew", 0);
- set_option_value_give_err("rl", 1L, NULL, 0);
+ set_option_value_give_err("keymap", STATIC_CSTR_AS_OPTVAL("hebrew"), 0);
+ set_option_value_give_err("rl", BOOLEAN_OPTVAL(true), 0);
break;
case 'M': // "-M" no changes or writing of files
reset_modifiable();
@@ -1237,7 +1237,7 @@ static void command_line_scan(mparm_T *parmp)
// default is 10: a little bit verbose
p_verbose = get_number_arg(argv[0], &argv_idx, 10);
if (argv[0][argv_idx] != NUL) {
- set_option_value_give_err("verbosefile", 0L, argv[0] + argv_idx, 0);
+ set_option_value_give_err("verbosefile", CSTR_AS_OPTVAL(argv[0] + argv_idx), 0);
argv_idx = (int)strlen(argv[0]);
}
break;
@@ -1245,7 +1245,7 @@ static void command_line_scan(mparm_T *parmp)
// "-w {scriptout}" write to script
if (ascii_isdigit((argv[0])[argv_idx])) {
n = get_number_arg(argv[0], &argv_idx, 10);
- set_option_value_give_err("window", n, NULL, 0);
+ set_option_value_give_err("window", NUMBER_OPTVAL(n), 0);
break;
}
want_argument = true;
@@ -1341,7 +1341,7 @@ static void command_line_scan(mparm_T *parmp)
break;
case 'i': // "-i {shada}" use for shada
- set_option_value_give_err("shadafile", 0L, argv[0], 0);
+ set_option_value_give_err("shadafile", CSTR_AS_OPTVAL(argv[0]), 0);
break;
case 'l': // "-l" Lua script: args after "-l".
@@ -1351,7 +1351,7 @@ static void command_line_scan(mparm_T *parmp)
parmp->no_swap_file = true;
parmp->use_vimrc = parmp->use_vimrc ? parmp->use_vimrc : "NONE";
if (p_shadafile == NULL || *p_shadafile == NUL) {
- set_option_value_give_err("shadafile", 0L, "NONE", 0);
+ set_option_value_give_err("shadafile", STATIC_CSTR_AS_OPTVAL("NONE"), 0);
}
parmp->luaf = argv[0];
argc--;
@@ -1387,7 +1387,7 @@ scripterror:
if (ascii_isdigit(*(argv[0]))) {
argv_idx = 0;
n = get_number_arg(argv[0], &argv_idx, 10);
- set_option_value_give_err("window", n, NULL, 0);
+ set_option_value_give_err("window", NUMBER_OPTVAL(n), 0);
argv_idx = -1;
break;
}
@@ -1782,7 +1782,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
p_shm_save = xstrdup(p_shm);
snprintf(buf, sizeof(buf), "F%s", p_shm);
- set_option_value_give_err("shm", 0L, buf, 0);
+ set_option_value_give_err("shm", CSTR_AS_OPTVAL(buf), 0);
}
} else {
if (curwin->w_next == NULL) { // just checking
@@ -1826,7 +1826,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
}
if (p_shm_save != NULL) {
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
+ set_option_value_give_err("shm", CSTR_AS_OPTVAL(p_shm_save), 0);
xfree(p_shm_save);
}
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index eb2afc60b2..8f5a957846 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -977,7 +977,7 @@ void ml_recover(bool checkext)
set_fileformat(b0_ff - 1, OPT_LOCAL);
}
if (b0_fenc != NULL) {
- set_option_value_give_err("fenc", 0L, b0_fenc, OPT_LOCAL);
+ set_option_value_give_err("fenc", CSTR_AS_OPTVAL(b0_fenc), OPT_LOCAL);
xfree(b0_fenc);
}
unchanged(curbuf, true, true);
diff --git a/src/nvim/option.c b/src/nvim/option.c
index e69c017b08..bec0f12d68 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -394,7 +394,7 @@ void set_init_1(bool clean_arg)
// NOTE: mlterm's author is being asked to 'set' a variable
// instead of an environment variable due to inheritance.
if (os_env_exists("MLTERM")) {
- set_option_value_give_err("tbidi", 1L, NULL, 0);
+ set_option_value_give_err("tbidi", BOOLEAN_OPTVAL(true), 0);
}
didset_options2();
@@ -2424,7 +2424,7 @@ static const char *did_set_arabic(optset_T *args)
p_deco = true;
// Force-set the necessary keymap for arabic.
- errmsg = set_option_value("keymap", 0L, "arabic", OPT_LOCAL);
+ errmsg = set_option_value("keymap", STATIC_CSTR_AS_OPTVAL("arabic"), OPT_LOCAL);
} else {
// 'arabic' is reset, handle various sub-settings.
if (!p_tbidi) {
@@ -3339,7 +3339,7 @@ void set_tty_background(const char *value)
? "autocmd VimEnter * ++once ++nested :lua if not vim.api.nvim_get_option_info2('bg', {}).was_set then vim.o.bg = 'light' end"
: "autocmd VimEnter * ++once ++nested :lua if not vim.api.nvim_get_option_info2('bg', {}).was_set then vim.o.bg = 'dark' end");
} else {
- set_option_value_give_err("bg", 0L, value, 0);
+ set_option_value_give_err("bg", CSTR_AS_OPTVAL((char *)value), 0);
reset_option_was_set("bg");
}
}
@@ -3355,32 +3355,153 @@ int findoption(const char *const arg)
return findoption_len(arg, strlen(arg));
}
+void optval_free(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ break;
+ case kOptValTypeString:
+ api_free_string(o.data.string);
+ break;
+ }
+}
+
+OptVal optval_copy(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ return o;
+ case kOptValTypeString:
+ return STRING_OPTVAL(copy_string(o.data.string, NULL));
+ default:
+ abort();
+ }
+}
+
+// Match type of OptVal with the type of the target option. Returns true if the types match and
+// false otherwise.
+static bool optval_match_type(OptVal o, int opt_idx)
+{
+ assert(opt_idx >= 0);
+ uint32_t flags = options[opt_idx].flags;
+
+ switch (o.type) {
+ case kOptValTypeNil:
+ return false;
+ case kOptValTypeBoolean:
+ return flags & P_BOOL;
+ case kOptValTypeNumber:
+ return flags & P_NUM;
+ case kOptValTypeString:
+ return flags & P_STRING;
+ default:
+ abort();
+ }
+}
+
+// Return C-string representation of OptVal. Caller must free the returned C-string.
+static char *optval_to_cstr(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ return xstrdup("");
+ case kOptValTypeBoolean:
+ return xstrdup(o.data.boolean ? "true" : "false");
+ case kOptValTypeNumber: {
+ char *buf = xmalloc(NUMBUFLEN);
+ snprintf(buf, NUMBUFLEN, "%" PRId64, o.data.number);
+ return buf;
+ }
+ case kOptValTypeString: {
+ char *buf = xmalloc(o.data.string.size + 3);
+ snprintf(buf, o.data.string.size + 3, "\"%s\"", o.data.string.data);
+ return buf;
+ }
+ default:
+ abort();
+ }
+}
+
+// Get an allocated string containing a list of valid types for an option.
+// For options with a singular type, it returns the name of the type. For options with multiple
+// possible types, it returns a comma separated list of types. For example, if an option can be a
+// number, boolean or string, the function returns "Number, Boolean, String"
+static char *option_get_valid_types(int opt_idx)
+{
+ uint32_t flags = options[opt_idx].flags;
+ uint32_t type_count = 0;
+
+ StringBuilder str = KV_INITIAL_VALUE;
+ kv_resize(str, 32);
+
+#define OPTION_ADD_TYPE(typename) \
+ do { \
+ if (type_count == 0) { \
+ kv_concat(str, typename); \
+ } else { \
+ kv_printf(str, ", %s", typename); \
+ } \
+ type_count++; \
+ } while (0);
+
+ if (flags & P_NUM) {
+ OPTION_ADD_TYPE("Number");
+ }
+ if (flags & P_BOOL) {
+ OPTION_ADD_TYPE("Boolean");
+ }
+ if (flags & P_STRING) {
+ OPTION_ADD_TYPE("String");
+ }
+
+ if (type_count == 0) {
+ abort();
+ }
+
+ // Ensure that the string is NUL-terminated.
+ kv_push(str, NUL);
+ return str.items;
+
+#undef OPTION_ADD_TYPE
+}
+
/// Gets the value for an option.
///
-/// @param stringval NULL when only checking existence
-/// @param flagsp set to the option flags (P_xxxx) (if not NULL)
+/// @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.
///
-/// @returns:
-/// Number option: gov_number, *numval gets value.
-/// Tottle option: gov_bool, *numval gets value.
-/// String option: gov_string, *stringval gets allocated string.
-/// Hidden Number option: gov_hidden_number.
-/// Hidden Toggle option: gov_hidden_bool.
-/// Hidden String option: gov_hidden_string.
-/// Unknown option: gov_unknown.
-getoption_T get_option_value(const char *name, long *numval, char **stringval, uint32_t *flagsp,
- int scope)
+/// @return Option value. Returns NIL_OPTVAL for invalid options. Return value must be freed by
+/// caller.
+OptVal get_option_value(const char *name, uint32_t *flagsp, int scope, bool *hidden)
{
- if (get_tty_option(name, stringval)) {
- return gov_string;
+ // Make sure that hidden and flagsp are never returned uninitialized
+ if (hidden != NULL) {
+ *hidden = false;
+ }
+ if (flagsp != NULL) {
+ *flagsp = 0;
+ }
+
+ char *str;
+ if (get_tty_option(name, &str)) {
+ return CSTR_AS_OPTVAL(str);
}
int opt_idx = findoption(name);
if (opt_idx < 0) { // option not in the table
- return gov_unknown;
+ return NIL_OPTVAL;
}
char *varp = get_varp_scope(&(options[opt_idx]), scope);
+ if (hidden != NULL) {
+ *hidden = varp == NULL;
+ }
if (flagsp != NULL) {
// Return the P_xxxx option flags.
@@ -3388,30 +3509,23 @@ getoption_T get_option_value(const char *name, long *numval, char **stringval, u
}
if (options[opt_idx].flags & P_STRING) {
- if (varp == NULL) { // hidden option
- return gov_hidden_string;
- }
- if (stringval != NULL) {
- *stringval = xstrdup(*(char **)(varp));
- }
- return gov_string;
+ return varp == NULL ? STRING_OPTVAL(STRING_INIT) : CSTR_TO_OPTVAL(*(char **)(varp));
}
- if (varp == NULL) { // hidden option
- return (options[opt_idx].flags & P_NUM) ? gov_hidden_number : gov_hidden_bool;
- }
if (options[opt_idx].flags & P_NUM) {
- *numval = *(long *)varp;
+ return NUMBER_OPTVAL(varp == NULL ? 0 : (*(long *)varp));
} else {
// Special case: 'modified' is b_changed, but we also want to consider
// it set when 'ff' or 'fenc' changed.
- if ((int *)varp == &curbuf->b_changed) {
- *numval = curbufIsChanged();
+ if (varp == NULL) {
+ return BOOLEAN_OPTVAL(false);
+ } else if ((int *)varp == &curbuf->b_changed) {
+ return BOOLEAN_OPTVAL(curbufIsChanged());
} else {
- *numval = (long)(*(int *)varp);
+ int n = *(int *)varp;
+ return BOOLEAN_OPTVAL(n == 0 ? kFalse : (n >= 1 ? kTrue : kNone));
}
}
- return (options[opt_idx].flags & P_NUM) ? gov_number : gov_bool;
}
// Returns the option attributes and its value. Unlike the above function it
@@ -3544,20 +3658,25 @@ vimoption_T *get_option(int opt_idx)
/// Set the value of an option
///
-/// @param[in] name Option name.
-/// @param[in] number New value for the number or boolean option.
-/// @param[in] string New value for string option.
+/// @param[in] name Option name.
+/// @param[in] value Option value. If NIL_OPTVAL, the option value is cleared.
/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
/// If OPT_CLEAR is set, the value of the option
/// is cleared (the exact semantics of this depend
/// on the option).
///
/// @return NULL on success, an untranslated error message on error.
-const char *set_option_value(const char *const name, const long number, const char *const string,
- const int opt_flags)
+const char *set_option_value(const char *const name, const OptVal value, int opt_flags)
FUNC_ATTR_NONNULL_ARG(1)
{
- static char errbuf[80];
+ static const char *optval_type_names[] = {
+ [kOptValTypeNil] = "Nil",
+ [kOptValTypeBoolean] = "Boolean",
+ [kOptValTypeNumber] = "Number",
+ [kOptValTypeString] = "String"
+ };
+
+ static char errbuf[IOSIZE];
if (is_tty_option(name)) {
return NULL; // Fail silently; many old vimrcs set t_xx options.
@@ -3565,23 +3684,14 @@ const char *set_option_value(const char *const name, const long number, const ch
int opt_idx = findoption(name);
if (opt_idx < 0) {
- semsg(_("E355: Unknown option: %s"), name);
- return NULL;
+ snprintf(errbuf, IOSIZE, _(e_unknown_option2), name);
+ return errbuf;
}
uint32_t flags = options[opt_idx].flags;
// Disallow changing some options in the sandbox
if (sandbox > 0 && (flags & P_SECURE)) {
- emsg(_(e_sandbox));
- return NULL;
- }
-
- if (flags & P_STRING) {
- const char *s = string;
- if (s == NULL || opt_flags & OPT_CLEAR) {
- s = "";
- }
- return set_string_option(opt_idx, s, opt_flags, errbuf, sizeof(errbuf));
+ return _(e_sandbox);
}
char *varp = get_varp_scope(&(options[opt_idx]), opt_flags);
@@ -3590,46 +3700,81 @@ const char *set_option_value(const char *const name, const long number, const ch
return NULL;
}
- if (number == 0 && string != NULL) {
- int idx;
-
- // Either we are given a string or we are setting option
- // to zero.
- for (idx = 0; string[idx] == '0'; idx++) {}
- if (string[idx] != NUL || idx == 0) {
- // There's another character after zeros or the string
- // is empty. In both cases, we are trying to set a
- // num option using a string.
- semsg(_("E521: Number required: &%s = '%s'"),
- name, string);
- return NULL; // do nothing as we hit an error
- }
- }
- long numval = number;
- if (opt_flags & OPT_CLEAR) {
- if ((int *)varp == &curbuf->b_p_ar) {
- numval = -1;
- } else if ((long *)varp == &curbuf->b_p_ul) {
- numval = NO_LOCAL_UNDOLEVEL;
- } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) {
- numval = -1;
- } else {
- char *s = NULL;
- (void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL);
+ const char *errmsg = NULL;
+ // Copy the value so we can modify the copy.
+ OptVal v = optval_copy(value);
+
+ if (v.type == kOptValTypeNil) {
+ opt_flags |= OPT_CLEAR;
+
+ // Change the type of the OptVal to the type used by the option so that it can be cleared.
+ // TODO(famiu): Clean up all of this after set_(num|bool|string)_option() is unified.
+ if (flags & P_BOOL) {
+ v.type = kOptValTypeBoolean;
+ } else if (flags & P_NUM) {
+ v.type = kOptValTypeNumber;
+ } else if (flags & P_STRING) {
+ v.type = kOptValTypeString;
+ }
+ } 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, _("E5383: Allowed types for option '%s': %s. Got %s value: %s"),
+ name, valid_types, optval_type_names[v.type], rep);
+ xfree(rep);
+ xfree(valid_types);
+ errmsg = errbuf;
+ goto end;
+ }
+
+ switch (v.type) {
+ case kOptValTypeNil:
+ abort(); // This will never happen.
+ case kOptValTypeBoolean: {
+ if (opt_flags & OPT_CLEAR) {
+ if ((int *)varp == &curbuf->b_p_ar) {
+ v.data.boolean = kNone;
+ } else {
+ v = get_option_value(name, NULL, OPT_GLOBAL, NULL);
+ }
}
+ errmsg = set_bool_option(opt_idx, varp, (int)v.data.boolean, opt_flags);
+ break;
+ }
+ case kOptValTypeNumber: {
+ if (opt_flags & OPT_CLEAR) {
+ if ((long *)varp == &curbuf->b_p_ul) {
+ v.data.number = NO_LOCAL_UNDOLEVEL;
+ } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) {
+ v.data.number = -1;
+ } else {
+ v = get_option_value(name, NULL, OPT_GLOBAL, NULL);
+ }
+ }
+ errmsg = set_num_option(opt_idx, varp, (long)v.data.number, errbuf, sizeof(errbuf), opt_flags);
+ break;
+ }
+ case kOptValTypeString: {
+ const char *s = v.data.string.data;
+ if (s == NULL || opt_flags & OPT_CLEAR) {
+ s = "";
+ }
+ errmsg = set_string_option(opt_idx, s, opt_flags, errbuf, sizeof(errbuf));
+ break;
}
- if (flags & P_NUM) {
- return set_num_option(opt_idx, varp, numval, errbuf, sizeof(errbuf), opt_flags);
}
- return set_bool_option(opt_idx, varp, (int)numval, opt_flags);
+
+end:
+ optval_free(v); // Free the copied OptVal.
+ return errmsg;
}
/// Call set_option_value() and when an error is returned report it.
///
/// @param opt_flags OPT_LOCAL or 0 (both)
-void set_option_value_give_err(const char *name, long number, const char *string, int opt_flags)
+void set_option_value_give_err(const char *name, OptVal value, int opt_flags)
{
- const char *errmsg = set_option_value(name, number, string, opt_flags);
+ const char *errmsg = set_option_value(name, value, opt_flags);
if (errmsg != NULL) {
emsg(_(errmsg));
diff --git a/src/nvim/option.h b/src/nvim/option.h
index 636257bfe8..4b8329ad1c 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -1,19 +1,9 @@
#ifndef NVIM_OPTION_H
#define NVIM_OPTION_H
+#include "nvim/api/private/helpers.h"
#include "nvim/ex_cmds_defs.h"
-/// Returned by get_option_value().
-typedef enum {
- gov_unknown,
- gov_bool,
- gov_number,
- gov_string,
- gov_hidden_bool,
- gov_hidden_number,
- gov_hidden_string,
-} getoption_T;
-
// flags for buf_copy_options()
#define BCO_ENTER 1 // going to enter the buffer
#define BCO_ALWAYS 2 // always copy the options
@@ -21,6 +11,17 @@ typedef enum {
#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn'
+// OptVal helper macros.
+#define NIL_OPTVAL ((OptVal) { .type = kOptValTypeNil })
+#define BOOLEAN_OPTVAL(b) ((OptVal) { .type = kOptValTypeBoolean, .data.boolean = b })
+#define NUMBER_OPTVAL(n) ((OptVal) { .type = kOptValTypeNumber, .data.number = n })
+#define STRING_OPTVAL(s) ((OptVal) { .type = kOptValTypeString, .data.string = s })
+
+#define CSTR_AS_OPTVAL(s) STRING_OPTVAL(cstr_as_string(s))
+#define CSTR_TO_OPTVAL(s) STRING_OPTVAL(cstr_to_string(s))
+#define STATIC_CSTR_AS_OPTVAL(s) STRING_OPTVAL(STATIC_CSTR_AS_STRING(s))
+#define STATIC_CSTR_TO_OPTVAL(s) STRING_OPTVAL(STATIC_CSTR_TO_STRING(s))
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "option.h.generated.h"
#endif
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index bbf009213d..e42654bf5d 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -1,6 +1,7 @@
#ifndef NVIM_OPTION_DEFS_H
#define NVIM_OPTION_DEFS_H
+#include "nvim/api/private/defs.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/macros.h"
#include "nvim/types.h"
@@ -1080,4 +1081,24 @@ typedef struct vimoption {
// buffers. Indicate this by setting "var" to VAR_WIN.
#define VAR_WIN ((char *)-1)
+// Option value type
+typedef enum {
+ kOptValTypeNil = 0,
+ kOptValTypeBoolean,
+ kOptValTypeNumber,
+ kOptValTypeString,
+} OptValType;
+
+// Option value
+typedef struct {
+ OptValType type;
+
+ union {
+ // Vim boolean options are actually tri-states because they have a third "None" value.
+ TriState boolean;
+ Integer number;
+ String string;
+ } data;
+} OptVal;
+
#endif // NVIM_OPTION_DEFS_H
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index d3f676379d..53d58017f9 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -2250,7 +2250,7 @@ void save_clear_shm_value(void)
if (++set_shm_recursive == 1) {
STRCPY(shm_buf, p_shm);
- set_option_value_give_err("shm", 0L, "", 0);
+ set_option_value_give_err("shm", STATIC_CSTR_AS_OPTVAL(""), 0);
}
}
@@ -2258,7 +2258,7 @@ void save_clear_shm_value(void)
void restore_shm_value(void)
{
if (--set_shm_recursive == 0) {
- set_option_value_give_err("shm", 0L, shm_buf, 0);
+ set_option_value_give_err("shm", CSTR_AS_OPTVAL(shm_buf), 0);
memset(shm_buf, 0, SHM_LEN);
}
}
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 15d0372c6f..fe31c70d5d 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -780,11 +780,11 @@ static bool pum_set_selected(int n, int repeat)
if (res == OK) {
// Edit a new, empty buffer. Set options for a "wipeout"
// buffer.
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bl", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL);
- set_option_value_give_err("diff", 0L, NULL, OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bl", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL);
+ set_option_value_give_err("diff", BOOLEAN_OPTVAL(false), OPT_LOCAL);
}
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index d6bbcbc80d..8a78a3cd80 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -3612,12 +3612,12 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp
static void qf_set_cwindow_options(void)
{
// switch off 'swapfile'
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "quickfix", OPT_LOCAL);
- set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("quickfix"), OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
RESET_BINDING(curwin);
curwin->w_p_diff = false;
- set_option_value_give_err("fdm", 0L, "manual", OPT_LOCAL);
+ set_option_value_give_err("fdm", STATIC_CSTR_AS_OPTVAL("manual"), OPT_LOCAL);
}
// Open a new quickfix or location list window, load the quickfix buffer and
@@ -4176,7 +4176,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
// resembles reading a file into a buffer, it's more logical when using
// autocommands.
curbuf->b_ro_locked++;
- set_option_value_give_err("ft", 0L, "qf", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("qf"), OPT_LOCAL);
curbuf->b_p_ma = false;
keep_filetype = true; // don't detect 'filetype'
@@ -7183,7 +7183,7 @@ void ex_helpgrep(exarg_T *eap)
// Darn, some plugin changed the value. 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);
}
if (save_cpo_allocated) {
free_string_option(save_cpo);
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 964e2d0933..70d7261973 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -1006,7 +1006,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
xstrlcat(new_rtp, afterdir, new_rtp_capacity);
}
- set_option_value_give_err("rtp", 0L, new_rtp, 0);
+ set_option_value_give_err("rtp", CSTR_AS_OPTVAL(new_rtp), 0);
xfree(new_rtp);
retval = OK;
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 5a1b3f1965..b337504bd9 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -3171,17 +3171,15 @@ void ex_spelldump(exarg_T *eap)
if (no_spell_checking(curwin)) {
return;
}
- char *spl;
- long dummy;
- (void)get_option_value("spl", &dummy, &spl, NULL, OPT_LOCAL);
+ OptVal spl = get_option_value("spl", NULL, OPT_LOCAL, NULL);
// Create a new empty buffer in a new window.
do_cmdline_cmd("new");
// enable spelling locally in the new window
- set_option_value_give_err("spell", true, "", OPT_LOCAL);
- set_option_value_give_err("spl", dummy, spl, OPT_LOCAL);
- xfree(spl);
+ set_option_value_give_err("spell", BOOLEAN_OPTVAL(true), OPT_LOCAL);
+ set_option_value_give_err("spl", spl, OPT_LOCAL);
+ optval_free(spl);
if (!buf_is_empty(curbuf)) {
return;
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index e81cebe18a..330dcdad05 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -5720,7 +5720,7 @@ static void init_spellfile(void)
&& strstr(path_tail(fname), ".ascii.") != NULL)
? "ascii"
: spell_enc()));
- set_option_value_give_err("spellfile", 0L, buf, OPT_LOCAL);
+ set_option_value_give_err("spellfile", CSTR_AS_OPTVAL(buf), OPT_LOCAL);
break;
}
aspath = false;
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 792071963d..676ac954f7 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -235,7 +235,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
aucmd_prepbuf(&aco, buf);
refresh_screen(rv, buf);
- set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("terminal"), OPT_LOCAL); // -V666
// Default settings for terminal buffers
buf->b_p_ma = false; // 'nomodifiable'
@@ -243,8 +243,8 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
buf->b_p_scbk = // 'scrollback' (initialize local from global)
(p_scbk < 0) ? 10000 : MAX(1, p_scbk);
buf->b_p_tw = 0; // 'textwidth'
- set_option_value("wrap", false, NULL, OPT_LOCAL);
- set_option_value("list", false, NULL, OPT_LOCAL);
+ set_option_value("wrap", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("list", BOOLEAN_OPTVAL(false), OPT_LOCAL);
if (buf->b_ffname != NULL) {
buf_set_term_title(buf, buf->b_ffname, strlen(buf->b_ffname));
}
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 87a0271f3d..7d8328d913 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -229,7 +229,7 @@ void ui_refresh(void)
p_lz = save_p_lz;
if (ext_widgets[kUIMessages]) {
- set_option_value("cmdheight", 0L, NULL, 0);
+ set_option_value("cmdheight", NUMBER_OPTVAL(0), 0);
command_height();
}
ui_mode_info_set();
diff --git a/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim
index c8085cc396..598b359f1f 100644
--- a/test/old/testdir/test_vimscript.vim
+++ b/test/old/testdir/test_vimscript.vim
@@ -6997,7 +6997,10 @@ func Test_compound_assignment_operators()
call assert_equal(6, &scrolljump)
let &scrolljump %= 5
call assert_equal(1, &scrolljump)
- call assert_fails('let &scrolljump .= "j"', 'E734:')
+ " A different error is shown due to a change in implementation of option
+ " values.
+ " call assert_fails('let &scrolljump .= "j"', 'E734:')
+ call assert_fails('let &scrolljump .= "j"', 'E521:')
set scrolljump&vim
let &foldlevelstart = 2