aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval/vars.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/eval/vars.c')
-rw-r--r--src/nvim/eval/vars.c228
1 files changed, 142 insertions, 86 deletions
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