aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval
diff options
context:
space:
mode:
authorFamiu Haque <famiuhaque@proton.me>2023-06-07 06:05:16 +0600
committerGitHub <noreply@github.com>2023-06-07 08:05:16 +0800
commitb3d5138fd0066fda26ef7724a542ae45eb42fc84 (patch)
treef9893af238d838408c81c6d4e36655865fe465b0 /src/nvim/eval
parent0370e4def0c0328f8cd09f02c1ca82ed491ecb9a (diff)
downloadrneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.tar.gz
rneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.tar.bz2
rneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.zip
refactor(options): remove `getoption_T` and introduce `OptVal` (#23850)
Removes the `getoption_T` struct and also introduces the `OptVal` struct to unify the methods of getting/setting different option value types. This is the first of many PRs to reduce code duplication in the Vim option code as well as to make options easier to maintain. It also increases the flexibility and extensibility of options. Which opens the door for things like Array and Dictionary options.
Diffstat (limited to 'src/nvim/eval')
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/eval/vars.c228
2 files changed, 143 insertions, 87 deletions
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