diff options
Diffstat (limited to 'src/nvim/eval/vars.c')
-rw-r--r-- | src/nvim/eval/vars.c | 228 |
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 |