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

@@ -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;
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;
}
}
}
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));
}
} else {
emsg(_(e_stringreq));
}
}
*p = c1;
xfree(stringval);
xfree(tofree);
return NULL;
}
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;
}
// 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
OptVal newval_old = newval;
newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
optval_free(newval_old);
}
}
}
// 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);
}
/// 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