aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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,
+ 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();