refactor(options): remove getoption_T and introduce OptVal (#23850)

Removes the `getoption_T` struct and also introduces the `OptVal` struct
to unify the methods of getting/setting different option value types.
This is the first of many PRs to reduce code duplication in the Vim
option code as well as to make options easier to maintain. It also
increases the flexibility and extensibility of options. Which opens the
door for things like Array and Dictionary options.
This commit is contained in:
Famiu Haque
2023-06-07 06:05:16 +06:00
committed by GitHub
parent 0370e4def0
commit b3d5138fd0
29 changed files with 683 additions and 378 deletions

View File

@@ -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);
});
}

View File

@@ -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))) {
if (try_end(err)) {
return 0;
}
api_set_error(err, kErrorTypeException, "%s", errmsg);
}
return 0;
}
}
getoption_T access_option_value_for(char *key, long *numval, char **stringval, int opt_flags,
int opt_type, void *from, bool get, Error *err)
{
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)
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);
restore_win_noblock(switchwin, true);
if (try_end(err)) {
return result;
return false;
}
api_set_error(err, kErrorTypeException, "Problem while switching windows");
return result;
return false;
}
return true;
}
result = access_option_value(key, numval, stringval, opt_flags, get, err);
if (need_switch) {
restore_win_noblock(&switchwin, true);
case SREQ_BUF: {
buf_T *const buf = (buf_T *)from;
aco_save_T *const aco = (aco_save_T *)ctx;
if (buf == curbuf) {
return false;
}
aucmd_prepbuf(aco, buf);
return true;
}
case SREQ_GLOBAL:
return false;
default:
abort(); // This should never happen.
}
}
/// 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)
{
switch (opt_type) {
case SREQ_WIN:
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;
}
const char *const errmsg = set_option_value(name, value, opt_flags);
if (errmsg) {
api_set_error(err, kErrorTypeException, "%s", errmsg);
}
if (switched) {
restore_option_context(ctx, opt_type);
}
if (ERROR_SET(err)) {
return result;
}
try_end(err);
return result;
}

View File

@@ -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
///

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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,39 +3771,39 @@ 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) {
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) {
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 if (working && (opt_type == gov_hidden_bool
|| opt_type == gov_hidden_number
|| opt_type == gov_hidden_string)) {
} else {
// Value isn't being used, free it.
optval_free(value);
if (value.type == kOptValTypeNil || (working && hidden)) {
ret = FAIL;
}
}
*option_end = c; // put back for error messages
*arg = option_end;
@@ -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);
}

View File

@@ -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);
}

View File

@@ -766,72 +766,71 @@ 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;
return NULL;
}
bool hidden;
bool error;
const char c1 = *p;
*p = NUL;
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);
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;
}
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);
}
// 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 (((opt_type == gov_bool || opt_type == gov_number) && *op == '.')
|| (opt_type == gov_string && *op != '.')) {
if (!hidden && ((is_num && *op == '.') || (is_string && *op != '.'))) {
semsg(_(e_letwrong), op);
failed = true; // don't set the value
goto end;
} else {
// number or bool
if (opt_type == gov_number || opt_type == gov_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 '+':
n = numval + n; break;
new_n = cur_n + new_n; break;
case '-':
n = numval - n; break;
new_n = cur_n - new_n; break;
case '*':
n = numval * n; break;
new_n = cur_n * new_n; break;
case '/':
n = num_divide(numval, n); break;
new_n = num_divide(cur_n, new_n); break;
case '%':
n = num_modulus(numval, n); break;
new_n = num_modulus(cur_n, new_n); break;
}
s = NULL;
} else if (opt_type == gov_string && stringval != NULL && s != NULL) {
// clamp boolean values
if (newval.type == kOptValTypeBoolean && (new_n > 1 || new_n < -1)) {
new_n = (new_n > 1) ? 1 : -1;
}
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
char *const oldstringval = stringval;
stringval = concat_str(stringval, s);
xfree(oldstringval);
s = stringval;
OptVal newval_old = newval;
newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
optval_free(newval_old);
}
}
}
if (!failed) {
if (opt_type != gov_string || s != NULL) {
const char *err = set_option_value(arg, (long)n, s, scope);
// 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));
@@ -839,11 +838,12 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
} else {
emsg(_(e_stringreq));
}
}
end:
*p = c1;
xfree(stringval);
xfree(tofree);
}
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);
}
/// 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)
{
OptVal value = NIL_OPTVAL;
char nbuf[NUMBUFLEN];
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));
} else {
const char *strval = tv_get_string_buf_chk(tv, nbuf);
err = strval == NULL;
value = CSTR_TO_OPTVAL(strval);
}
} else {
abort(); // This should never happen.
}
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)
{
long numval = 0;
const char *strval;
bool error = false;
char nbuf[NUMBUFLEN];
OptVal value = tv_to_optval(varp, varname, OPT_LOCAL, &error);
if (varp->v_type == VAR_BOOL) {
if (is_string_option(varname)) {
emsg(_(e_stringreq));
return;
}
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);
}
if (!error && strval != NULL) {
set_option_value_give_err(varname, numval, strval, OPT_LOCAL);
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

View File

@@ -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;
}

View File

@@ -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--;

View File

@@ -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"));

View File

@@ -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--;
}

View File

@@ -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");
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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
@@ -3545,19 +3659,24 @@ 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] 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;
const char *errmsg = NULL;
// Copy the value so we can modify the copy.
OptVal v = optval_copy(value);
// 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
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;
}
long numval = number;
switch (v.type) {
case kOptValTypeNil:
abort(); // This will never happen.
case kOptValTypeBoolean: {
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;
v.data.boolean = kNone;
} else {
char *s = NULL;
(void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL);
v = get_option_value(name, NULL, OPT_GLOBAL, NULL);
}
}
if (flags & P_NUM) {
return set_num_option(opt_idx, varp, numval, errbuf, sizeof(errbuf), opt_flags);
errmsg = set_bool_option(opt_idx, varp, (int)v.data.boolean, opt_flags);
break;
}
return set_bool_option(opt_idx, varp, (int)numval, opt_flags);
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;
}
}
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));

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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();

View File

@@ -6997,7 +6997,10 @@ func Test_compound_assignment_operators()
call assert_equal(6, &scrolljump)
let &scrolljump %= 5
call assert_equal(1, &scrolljump)
call assert_fails('let &scrolljump .= "j"', 'E734:')
" A different error is shown due to a change in implementation of option
" values.
" call assert_fails('let &scrolljump .= "j"', 'E734:')
call assert_fails('let &scrolljump .= "j"', 'E521:')
set scrolljump&vim
let &foldlevelstart = 2