diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/deprecated.c | 11 | ||||
-rw-r--r-- | src/nvim/api/options.c | 278 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 2 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 8 | ||||
-rw-r--r-- | src/nvim/buffer.c | 6 | ||||
-rw-r--r-- | src/nvim/context.c | 9 | ||||
-rw-r--r-- | src/nvim/diff.c | 4 | ||||
-rw-r--r-- | src/nvim/eval.c | 49 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 2 | ||||
-rw-r--r-- | src/nvim/eval/vars.c | 228 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 4 | ||||
-rw-r--r-- | src/nvim/globals.h | 2 | ||||
-rw-r--r-- | src/nvim/help.c | 2 | ||||
-rw-r--r-- | src/nvim/highlight_group.c | 2 | ||||
-rw-r--r-- | src/nvim/main.c | 22 | ||||
-rw-r--r-- | src/nvim/memline.c | 2 | ||||
-rw-r--r-- | src/nvim/option.c | 309 | ||||
-rw-r--r-- | src/nvim/option.h | 23 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 21 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 4 | ||||
-rw-r--r-- | src/nvim/popupmenu.c | 10 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 12 | ||||
-rw-r--r-- | src/nvim/runtime.c | 2 | ||||
-rw-r--r-- | src/nvim/spell.c | 10 | ||||
-rw-r--r-- | src/nvim/spellfile.c | 2 | ||||
-rw-r--r-- | src/nvim/terminal.c | 6 | ||||
-rw-r--r-- | src/nvim/ui.c | 2 |
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(); |