mirror of
https://github.com/neovim/neovim.git
synced 2025-10-12 21:06:13 +00:00

Problem: Gcc 12.1 warns for uninitialized variable.
Solution: Initialize the variable. (closes vim/vim#10476)
8be36eecdc
Co-authored-by: mityu <mityu.mail@gmail.com>
3013 lines
85 KiB
C
3013 lines
85 KiB
C
// eval/vars.c: functions for dealing with variables
|
||
|
||
#include <assert.h>
|
||
#include <ctype.h>
|
||
#include <stdbool.h>
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <uv.h>
|
||
|
||
#include "nvim/ascii_defs.h"
|
||
#include "nvim/autocmd.h"
|
||
#include "nvim/autocmd_defs.h"
|
||
#include "nvim/buffer_defs.h"
|
||
#include "nvim/charset.h"
|
||
#include "nvim/drawscreen.h"
|
||
#include "nvim/errors.h"
|
||
#include "nvim/eval.h"
|
||
#include "nvim/eval/encode.h"
|
||
#include "nvim/eval/funcs.h"
|
||
#include "nvim/eval/typval.h"
|
||
#include "nvim/eval/typval_defs.h"
|
||
#include "nvim/eval/userfunc.h"
|
||
#include "nvim/eval/vars.h"
|
||
#include "nvim/eval/window.h"
|
||
#include "nvim/eval_defs.h"
|
||
#include "nvim/ex_cmds.h"
|
||
#include "nvim/ex_cmds_defs.h"
|
||
#include "nvim/ex_docmd.h"
|
||
#include "nvim/ex_eval.h"
|
||
#include "nvim/garray.h"
|
||
#include "nvim/gettext_defs.h"
|
||
#include "nvim/globals.h"
|
||
#include "nvim/hashtab.h"
|
||
#include "nvim/macros_defs.h"
|
||
#include "nvim/mbyte.h"
|
||
#include "nvim/memory.h"
|
||
#include "nvim/message.h"
|
||
#include "nvim/option.h"
|
||
#include "nvim/option_defs.h"
|
||
#include "nvim/os/os.h"
|
||
#include "nvim/register.h"
|
||
#include "nvim/runtime.h"
|
||
#include "nvim/search.h"
|
||
#include "nvim/strings.h"
|
||
#include "nvim/types_defs.h"
|
||
#include "nvim/vim_defs.h"
|
||
#include "nvim/window.h"
|
||
|
||
typedef int (*ex_unletlock_callback)(lval_T *, char *, exarg_T *, int);
|
||
|
||
#include "eval/vars.c.generated.h"
|
||
|
||
// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead
|
||
|
||
#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
|
||
|
||
static const char *e_letunexp = N_("E18: Unexpected characters in :let");
|
||
static const char e_double_semicolon_in_list_of_variables[]
|
||
= N_("E452: Double ; in list of variables");
|
||
static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
|
||
static const char e_setting_v_str_to_value_with_wrong_type[]
|
||
= N_("E963: Setting v:%s to value with wrong type");
|
||
static const char e_missing_end_marker_str[] = N_("E990: Missing end marker '%s'");
|
||
static const char e_cannot_use_heredoc_here[] = N_("E991: Cannot use =<< here");
|
||
|
||
static dict_T globvardict; // Dict with g: variables
|
||
/// g: value
|
||
#define globvarht globvardict.dv_hashtab
|
||
|
||
static dict_T vimvardict; // Dict with v: variables
|
||
/// v: hashtab
|
||
#define vimvarht vimvardict.dv_hashtab
|
||
|
||
int garbage_collect_globvars(int copyID)
|
||
{
|
||
return set_ref_in_ht(&globvarht, copyID, NULL);
|
||
}
|
||
|
||
bool garbage_collect_vimvars(int copyID)
|
||
{
|
||
return set_ref_in_ht(&vimvarht, copyID, NULL);
|
||
}
|
||
|
||
bool garbage_collect_scriptvars(int copyID)
|
||
{
|
||
bool abort = false;
|
||
|
||
for (int i = 1; i <= script_items.ga_len; i++) {
|
||
abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
|
||
}
|
||
|
||
return abort;
|
||
}
|
||
|
||
/// Set an internal variable to a string value. Creates the variable if it does
|
||
/// not already exist.
|
||
void set_internal_string_var(const char *name, char *value) // NOLINT(readability-non-const-parameter)
|
||
FUNC_ATTR_NONNULL_ARG(1)
|
||
{
|
||
typval_T tv = {
|
||
.v_type = VAR_STRING,
|
||
.vval.v_string = value,
|
||
};
|
||
|
||
set_var(name, strlen(name), &tv, true);
|
||
}
|
||
|
||
int eval_charconvert(const char *const enc_from, const char *const enc_to,
|
||
const char *const fname_from, const char *const fname_to)
|
||
{
|
||
const sctx_T saved_sctx = current_sctx;
|
||
|
||
set_vim_var_string(VV_CC_FROM, enc_from, -1);
|
||
set_vim_var_string(VV_CC_TO, enc_to, -1);
|
||
set_vim_var_string(VV_FNAME_IN, fname_from, -1);
|
||
set_vim_var_string(VV_FNAME_OUT, fname_to, -1);
|
||
sctx_T *ctx = get_option_sctx(kOptCharconvert);
|
||
if (ctx != NULL) {
|
||
current_sctx = *ctx;
|
||
}
|
||
|
||
bool err = false;
|
||
if (eval_to_bool(p_ccv, &err, NULL, false, true)) {
|
||
err = true;
|
||
}
|
||
|
||
set_vim_var_string(VV_CC_FROM, NULL, -1);
|
||
set_vim_var_string(VV_CC_TO, NULL, -1);
|
||
set_vim_var_string(VV_FNAME_IN, NULL, -1);
|
||
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
|
||
current_sctx = saved_sctx;
|
||
|
||
if (err) {
|
||
return FAIL;
|
||
}
|
||
return OK;
|
||
}
|
||
|
||
void eval_diff(const char *const origfile, const char *const newfile, const char *const outfile)
|
||
{
|
||
const sctx_T saved_sctx = current_sctx;
|
||
set_vim_var_string(VV_FNAME_IN, origfile, -1);
|
||
set_vim_var_string(VV_FNAME_NEW, newfile, -1);
|
||
set_vim_var_string(VV_FNAME_OUT, outfile, -1);
|
||
|
||
sctx_T *ctx = get_option_sctx(kOptDiffexpr);
|
||
if (ctx != NULL) {
|
||
current_sctx = *ctx;
|
||
}
|
||
|
||
// errors are ignored
|
||
typval_T *tv = eval_expr_ext(p_dex, NULL, true);
|
||
tv_free(tv);
|
||
|
||
set_vim_var_string(VV_FNAME_IN, NULL, -1);
|
||
set_vim_var_string(VV_FNAME_NEW, NULL, -1);
|
||
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
|
||
current_sctx = saved_sctx;
|
||
}
|
||
|
||
void eval_patch(const char *const origfile, const char *const difffile, const char *const outfile)
|
||
{
|
||
const sctx_T saved_sctx = current_sctx;
|
||
set_vim_var_string(VV_FNAME_IN, origfile, -1);
|
||
set_vim_var_string(VV_FNAME_DIFF, difffile, -1);
|
||
set_vim_var_string(VV_FNAME_OUT, outfile, -1);
|
||
|
||
sctx_T *ctx = get_option_sctx(kOptPatchexpr);
|
||
if (ctx != NULL) {
|
||
current_sctx = *ctx;
|
||
}
|
||
|
||
// errors are ignored
|
||
typval_T *tv = eval_expr_ext(p_pex, NULL, true);
|
||
tv_free(tv);
|
||
|
||
set_vim_var_string(VV_FNAME_IN, NULL, -1);
|
||
set_vim_var_string(VV_FNAME_DIFF, NULL, -1);
|
||
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
|
||
current_sctx = saved_sctx;
|
||
}
|
||
|
||
/// Evaluate an expression to a list with suggestions.
|
||
/// For the "expr:" part of 'spellsuggest'.
|
||
///
|
||
/// @return NULL when there is an error.
|
||
list_T *eval_spell_expr(char *badword, char *expr)
|
||
{
|
||
typval_T save_val;
|
||
typval_T rettv;
|
||
list_T *list = NULL;
|
||
char *p = skipwhite(expr);
|
||
const sctx_T saved_sctx = current_sctx;
|
||
|
||
// Set "v:val" to the bad word.
|
||
prepare_vimvar(VV_VAL, &save_val);
|
||
set_vim_var_string(VV_VAL, badword, -1);
|
||
if (p_verbose == 0) {
|
||
emsg_off++;
|
||
}
|
||
sctx_T *ctx = get_option_sctx(kOptSpellsuggest);
|
||
if (ctx != NULL) {
|
||
current_sctx = *ctx;
|
||
}
|
||
|
||
int r = may_call_simple_func(p, &rettv);
|
||
if (r == NOTDONE) {
|
||
r = eval1(&p, &rettv, &EVALARG_EVALUATE);
|
||
}
|
||
if (r == OK) {
|
||
if (rettv.v_type != VAR_LIST) {
|
||
tv_clear(&rettv);
|
||
} else {
|
||
list = rettv.vval.v_list;
|
||
}
|
||
}
|
||
|
||
if (p_verbose == 0) {
|
||
emsg_off--;
|
||
}
|
||
tv_clear(get_vim_var_tv(VV_VAL));
|
||
restore_vimvar(VV_VAL, &save_val);
|
||
current_sctx = saved_sctx;
|
||
|
||
return list;
|
||
}
|
||
|
||
/// Get spell word from an entry from spellsuggest=expr:
|
||
///
|
||
/// Entry in question is supposed to be a list (to be checked by the caller)
|
||
/// with two items: a word and a score represented as an unsigned number
|
||
/// (whether it actually is unsigned is not checked).
|
||
///
|
||
/// Used to get the good word and score from the eval_spell_expr() result.
|
||
///
|
||
/// @param[in] list List to get values from.
|
||
/// @param[out] ret_word Suggested word. Not initialized if return value is
|
||
/// -1.
|
||
///
|
||
/// @return -1 in case of error, score otherwise.
|
||
int get_spellword(list_T *const list, const char **ret_word)
|
||
{
|
||
if (tv_list_len(list) != 2) {
|
||
emsg(_("E5700: Expression from 'spellsuggest' must yield lists with "
|
||
"exactly two values"));
|
||
return -1;
|
||
}
|
||
*ret_word = tv_list_find_str(list, 0);
|
||
if (*ret_word == NULL) {
|
||
return -1;
|
||
}
|
||
return (int)tv_list_find_nr(list, -1, NULL);
|
||
}
|
||
|
||
/// List Vim variables.
|
||
static void list_vim_vars(int *first)
|
||
{
|
||
list_hashtable_vars(&vimvarht, "v:", false, first);
|
||
}
|
||
|
||
/// List script-local variables, if there is a script.
|
||
static void list_script_vars(int *first)
|
||
{
|
||
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
|
||
list_hashtable_vars(&SCRIPT_VARS(current_sctx.sc_sid), "s:", false, first);
|
||
}
|
||
}
|
||
|
||
/// Evaluate one Vim expression {expr} in string "p" and append the
|
||
/// resulting string to "gap". "p" points to the opening "{".
|
||
/// When "evaluate" is false only skip over the expression.
|
||
/// Return a pointer to the character after "}", NULL for an error.
|
||
char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate)
|
||
{
|
||
char *block_start = skipwhite(p + 1); // skip the opening {
|
||
char *block_end = block_start;
|
||
|
||
if (*block_start == NUL) {
|
||
semsg(_(e_missing_close_curly_str), p);
|
||
return NULL;
|
||
}
|
||
if (skip_expr(&block_end, NULL) == FAIL) {
|
||
return NULL;
|
||
}
|
||
block_end = skipwhite(block_end);
|
||
if (*block_end != '}') {
|
||
semsg(_(e_missing_close_curly_str), p);
|
||
return NULL;
|
||
}
|
||
if (evaluate) {
|
||
*block_end = NUL;
|
||
char *expr_val = eval_to_string(block_start, false, false);
|
||
*block_end = '}';
|
||
if (expr_val == NULL) {
|
||
return NULL;
|
||
}
|
||
ga_concat(gap, expr_val);
|
||
xfree(expr_val);
|
||
}
|
||
|
||
return block_end + 1;
|
||
}
|
||
|
||
/// Evaluate all the Vim expressions {expr} in "str" and return the resulting
|
||
/// string in allocated memory. "{{" is reduced to "{" and "}}" to "}".
|
||
/// Used for a heredoc assignment.
|
||
/// Returns NULL for an error.
|
||
static char *eval_all_expr_in_str(char *str)
|
||
{
|
||
garray_T ga;
|
||
ga_init(&ga, 1, 80);
|
||
char *p = str;
|
||
|
||
while (*p != NUL) {
|
||
bool escaped_brace = false;
|
||
|
||
// Look for a block start.
|
||
char *lit_start = p;
|
||
while (*p != '{' && *p != '}' && *p != NUL) {
|
||
p++;
|
||
}
|
||
|
||
if (*p != NUL && *p == p[1]) {
|
||
// Escaped brace, unescape and continue.
|
||
// Include the brace in the literal string.
|
||
p++;
|
||
escaped_brace = true;
|
||
} else if (*p == '}') {
|
||
semsg(_(e_stray_closing_curly_str), str);
|
||
ga_clear(&ga);
|
||
return NULL;
|
||
}
|
||
|
||
// Append the literal part.
|
||
ga_concat_len(&ga, lit_start, (size_t)(p - lit_start));
|
||
|
||
if (*p == NUL) {
|
||
break;
|
||
}
|
||
|
||
if (escaped_brace) {
|
||
// Skip the second brace.
|
||
p++;
|
||
continue;
|
||
}
|
||
|
||
// Evaluate the expression and append the result.
|
||
p = eval_one_expr_in_str(p, &ga, true);
|
||
if (p == NULL) {
|
||
ga_clear(&ga);
|
||
return NULL;
|
||
}
|
||
}
|
||
ga_append(&ga, NUL);
|
||
|
||
return ga.ga_data;
|
||
}
|
||
|
||
/// Get a list of lines from a HERE document. The here document is a list of
|
||
/// lines surrounded by a marker.
|
||
/// cmd << {marker}
|
||
/// {line1}
|
||
/// {line2}
|
||
/// ....
|
||
/// {marker}
|
||
///
|
||
/// The {marker} is a string. If the optional 'trim' word is supplied before the
|
||
/// marker, then the leading indentation before the lines (matching the
|
||
/// indentation in the 'cmd' line) is stripped.
|
||
///
|
||
/// When getting lines for an embedded script (e.g. python, lua, perl, ruby,
|
||
/// tcl, mzscheme), "script_get" is set to true. In this case, if the marker is
|
||
/// missing, then '.' is accepted as a marker.
|
||
///
|
||
/// @return a List with {lines} or NULL on failure.
|
||
list_T *heredoc_get(exarg_T *eap, char *cmd, bool script_get)
|
||
{
|
||
char *marker;
|
||
int marker_indent_len = 0;
|
||
int text_indent_len = 0;
|
||
char *text_indent = NULL;
|
||
char dot[] = ".";
|
||
bool heredoc_in_string = false;
|
||
char *line_arg = NULL;
|
||
char *nl_ptr = vim_strchr(cmd, '\n');
|
||
|
||
if (nl_ptr != NULL) {
|
||
heredoc_in_string = true;
|
||
line_arg = nl_ptr + 1;
|
||
*nl_ptr = NUL;
|
||
} else if (eap->ea_getline == NULL) {
|
||
emsg(_(e_cannot_use_heredoc_here));
|
||
return NULL;
|
||
}
|
||
|
||
// Check for the optional 'trim' word before the marker
|
||
cmd = skipwhite(cmd);
|
||
bool evalstr = false;
|
||
bool eval_failed = false;
|
||
while (true) {
|
||
if (strncmp(cmd, "trim", 4) == 0
|
||
&& (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
|
||
cmd = skipwhite(cmd + 4);
|
||
|
||
// Trim the indentation from all the lines in the here document.
|
||
// The amount of indentation trimmed is the same as the indentation
|
||
// of the first line after the :let command line. To find the end
|
||
// marker the indent of the :let command line is trimmed.
|
||
char *p = *eap->cmdlinep;
|
||
while (ascii_iswhite(*p)) {
|
||
p++;
|
||
marker_indent_len++;
|
||
}
|
||
text_indent_len = -1;
|
||
|
||
continue;
|
||
}
|
||
if (strncmp(cmd, "eval", 4) == 0
|
||
&& (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
|
||
cmd = skipwhite(cmd + 4);
|
||
evalstr = true;
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
|
||
const char comment_char = '"';
|
||
// The marker is the next word.
|
||
if (*cmd != NUL && *cmd != comment_char) {
|
||
marker = skipwhite(cmd);
|
||
char *p = skiptowhite(marker);
|
||
if (*skipwhite(p) != NUL && *skipwhite(p) != comment_char) {
|
||
semsg(_(e_trailing_arg), p);
|
||
return NULL;
|
||
}
|
||
*p = NUL;
|
||
if (!script_get && islower((uint8_t)(*marker))) {
|
||
emsg(_("E221: Marker cannot start with lower case letter"));
|
||
return NULL;
|
||
}
|
||
} else {
|
||
// When getting lines for an embedded script, if the marker is missing,
|
||
// accept '.' as the marker.
|
||
if (script_get) {
|
||
marker = dot;
|
||
} else {
|
||
emsg(_("E172: Missing marker"));
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
char *theline = NULL;
|
||
list_T *l = tv_list_alloc(0);
|
||
while (true) {
|
||
int mi = 0;
|
||
int ti = 0;
|
||
|
||
if (heredoc_in_string) {
|
||
// heredoc in a string separated by newlines. Get the next line
|
||
// from the string.
|
||
|
||
if (*line_arg == NUL) {
|
||
if (!script_get) {
|
||
semsg(_(e_missing_end_marker_str), marker);
|
||
}
|
||
break;
|
||
}
|
||
|
||
theline = line_arg;
|
||
char *next_line = vim_strchr(theline, '\n');
|
||
if (next_line == NULL) {
|
||
line_arg += strlen(line_arg);
|
||
} else {
|
||
*next_line = NUL;
|
||
line_arg = next_line + 1;
|
||
}
|
||
} else {
|
||
xfree(theline);
|
||
theline = eap->ea_getline(NUL, eap->cookie, 0, false);
|
||
if (theline == NULL) {
|
||
if (!script_get) {
|
||
semsg(_(e_missing_end_marker_str), marker);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// with "trim": skip the indent matching the :let line to find the
|
||
// marker
|
||
if (marker_indent_len > 0
|
||
&& strncmp(theline, *eap->cmdlinep, (size_t)marker_indent_len) == 0) {
|
||
mi = marker_indent_len;
|
||
}
|
||
if (strcmp(marker, theline + mi) == 0) {
|
||
break;
|
||
}
|
||
|
||
// If expression evaluation failed in the heredoc, then skip till the
|
||
// end marker.
|
||
if (eval_failed) {
|
||
continue;
|
||
}
|
||
|
||
if (text_indent_len == -1 && *theline != NUL) {
|
||
// set the text indent from the first line.
|
||
char *p = theline;
|
||
text_indent_len = 0;
|
||
while (ascii_iswhite(*p)) {
|
||
p++;
|
||
text_indent_len++;
|
||
}
|
||
text_indent = xmemdupz(theline, (size_t)text_indent_len);
|
||
}
|
||
// with "trim": skip the indent matching the first line
|
||
if (text_indent != NULL) {
|
||
for (ti = 0; ti < text_indent_len; ti++) {
|
||
if (theline[ti] != text_indent[ti]) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
char *str = theline + ti;
|
||
if (evalstr && !eap->skip) {
|
||
str = eval_all_expr_in_str(str);
|
||
if (str == NULL) {
|
||
// expression evaluation failed
|
||
eval_failed = true;
|
||
continue;
|
||
}
|
||
tv_list_append_allocated_string(l, str);
|
||
} else {
|
||
tv_list_append_string(l, str, -1);
|
||
}
|
||
}
|
||
if (heredoc_in_string) {
|
||
// Next command follows the heredoc in the string.
|
||
eap->nextcmd = line_arg;
|
||
} else {
|
||
xfree(theline);
|
||
}
|
||
xfree(text_indent);
|
||
|
||
if (eval_failed) {
|
||
// expression evaluation in the heredoc failed
|
||
tv_list_free(l);
|
||
return NULL;
|
||
}
|
||
return l;
|
||
}
|
||
|
||
/// ":let" list all variable values
|
||
/// ":let var1 var2" list variable values
|
||
/// ":let var = expr" assignment command.
|
||
/// ":let var += expr" assignment command.
|
||
/// ":let var -= expr" assignment command.
|
||
/// ":let var *= expr" assignment command.
|
||
/// ":let var /= expr" assignment command.
|
||
/// ":let var %= expr" assignment command.
|
||
/// ":let var .= expr" assignment command.
|
||
/// ":let var ..= expr" assignment command.
|
||
/// ":let [var1, var2] = expr" unpack list.
|
||
/// ":let [name, ..., ; lastname] = expr" unpack list.
|
||
///
|
||
/// ":cons[t] var = expr1" define constant
|
||
/// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list
|
||
/// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list
|
||
void ex_let(exarg_T *eap)
|
||
{
|
||
const bool is_const = eap->cmdidx == CMD_const;
|
||
char *arg = eap->arg;
|
||
char *expr = NULL;
|
||
typval_T rettv;
|
||
int var_count = 0;
|
||
int semicolon = 0;
|
||
char op[2];
|
||
const char *argend;
|
||
int first = true;
|
||
|
||
argend = skip_var_list(arg, &var_count, &semicolon, false);
|
||
if (argend == NULL) {
|
||
return;
|
||
}
|
||
if (argend > arg && argend[-1] == '.') { // For var.='str'.
|
||
argend--;
|
||
}
|
||
expr = skipwhite(argend);
|
||
bool concat = strncmp(expr, "..=", 3) == 0;
|
||
bool has_assign = *expr == '=' || (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL
|
||
&& expr[1] == '=');
|
||
if (!has_assign && !concat) {
|
||
// ":let" without "=": list variables
|
||
if (*arg == '[') {
|
||
emsg(_(e_invarg));
|
||
} else if (!ends_excmd(*arg)) {
|
||
// ":let var1 var2"
|
||
arg = (char *)list_arg_vars(eap, arg, &first);
|
||
} else if (!eap->skip) {
|
||
// ":let"
|
||
list_glob_vars(&first);
|
||
list_buf_vars(&first);
|
||
list_win_vars(&first);
|
||
list_tab_vars(&first);
|
||
list_script_vars(&first);
|
||
list_func_vars(&first);
|
||
list_vim_vars(&first);
|
||
}
|
||
eap->nextcmd = check_nextcmd(arg);
|
||
return;
|
||
}
|
||
|
||
if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
|
||
// HERE document
|
||
list_T *l = heredoc_get(eap, expr + 3, false);
|
||
if (l != NULL) {
|
||
tv_list_set_ret(&rettv, l);
|
||
if (!eap->skip) {
|
||
op[0] = '=';
|
||
op[1] = NUL;
|
||
ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
|
||
}
|
||
tv_clear(&rettv);
|
||
}
|
||
return;
|
||
}
|
||
|
||
rettv.v_type = VAR_UNKNOWN;
|
||
|
||
op[0] = '=';
|
||
op[1] = NUL;
|
||
if (*expr != '=') {
|
||
if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) {
|
||
op[0] = *expr; // +=, -=, *=, /=, %= or .=
|
||
if (expr[0] == '.' && expr[1] == '.') { // ..=
|
||
expr++;
|
||
}
|
||
}
|
||
expr += 2;
|
||
} else {
|
||
expr += 1;
|
||
}
|
||
|
||
expr = skipwhite(expr);
|
||
|
||
if (eap->skip) {
|
||
emsg_skip++;
|
||
}
|
||
evalarg_T evalarg;
|
||
fill_evalarg_from_eap(&evalarg, eap, eap->skip);
|
||
int eval_res = eval0(expr, &rettv, eap, &evalarg);
|
||
if (eap->skip) {
|
||
emsg_skip--;
|
||
}
|
||
clear_evalarg(&evalarg, eap);
|
||
|
||
if (!eap->skip && eval_res != FAIL) {
|
||
ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
|
||
}
|
||
if (eval_res != FAIL) {
|
||
tv_clear(&rettv);
|
||
}
|
||
}
|
||
|
||
/// Assign the typevalue "tv" to the variable or variables at "arg_start".
|
||
/// Handles both "var" with any type and "[var, var; var]" with a list type.
|
||
/// When "op" is not NULL it points to a string with characters that
|
||
/// must appear after the variable(s). Use "+", "-" or "." for add, subtract
|
||
/// or concatenate.
|
||
///
|
||
/// @param copy copy values from "tv", don't move
|
||
/// @param semicolon from skip_var_list()
|
||
/// @param var_count from skip_var_list()
|
||
/// @param is_const lock variables for :const
|
||
///
|
||
/// @return OK or FAIL;
|
||
int ex_let_vars(char *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int is_const,
|
||
char *op)
|
||
{
|
||
char *arg = arg_start;
|
||
typval_T ltv;
|
||
|
||
if (*arg != '[') {
|
||
// ":let var = expr" or ":for var in list"
|
||
if (ex_let_one(arg, tv, copy, is_const, op, op) == NULL) {
|
||
return FAIL;
|
||
}
|
||
return OK;
|
||
}
|
||
|
||
// ":let [v1, v2] = list" or ":for [v1, v2] in listlist"
|
||
if (tv->v_type != VAR_LIST) {
|
||
emsg(_(e_listreq));
|
||
return FAIL;
|
||
}
|
||
list_T *const l = tv->vval.v_list;
|
||
|
||
const int len = tv_list_len(l);
|
||
if (semicolon == 0 && var_count < len) {
|
||
emsg(_("E687: Less targets than List items"));
|
||
return FAIL;
|
||
}
|
||
if (var_count - semicolon > len) {
|
||
emsg(_("E688: More targets than List items"));
|
||
return FAIL;
|
||
}
|
||
// List l may actually be NULL, but it should fail with E688 or even earlier
|
||
// if you try to do ":let [] = v:_null_list".
|
||
assert(l != NULL);
|
||
|
||
listitem_T *item = tv_list_first(l);
|
||
size_t rest_len = (size_t)tv_list_len(l);
|
||
while (*arg != ']') {
|
||
arg = skipwhite(arg + 1);
|
||
arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, ",;]", op);
|
||
if (arg == NULL) {
|
||
return FAIL;
|
||
}
|
||
rest_len--;
|
||
|
||
item = TV_LIST_ITEM_NEXT(l, item);
|
||
arg = skipwhite(arg);
|
||
if (*arg == ';') {
|
||
// Put the rest of the list (may be empty) in the var after ';'.
|
||
// Create a new list for this.
|
||
list_T *const rest_list = tv_list_alloc((ptrdiff_t)rest_len);
|
||
while (item != NULL) {
|
||
tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item));
|
||
item = TV_LIST_ITEM_NEXT(l, item);
|
||
}
|
||
|
||
ltv.v_type = VAR_LIST;
|
||
ltv.v_lock = VAR_UNLOCKED;
|
||
ltv.vval.v_list = rest_list;
|
||
tv_list_ref(rest_list);
|
||
|
||
arg = ex_let_one(skipwhite(arg + 1), <v, false, is_const, "]", op);
|
||
tv_clear(<v);
|
||
if (arg == NULL) {
|
||
return FAIL;
|
||
}
|
||
break;
|
||
} else if (*arg != ',' && *arg != ']') {
|
||
internal_error("ex_let_vars()");
|
||
return FAIL;
|
||
}
|
||
}
|
||
|
||
return OK;
|
||
}
|
||
|
||
/// Skip over assignable variable "var" or list of variables "[var, var]".
|
||
/// Used for ":let varvar = expr" and ":for varvar in expr".
|
||
/// For "[var, var]" increment "*var_count" for each variable.
|
||
/// for "[var, var; var]" set "semicolon" to 1.
|
||
/// If "silent" is true do not give an "invalid argument" error message.
|
||
///
|
||
/// @return NULL for an error.
|
||
const char *skip_var_list(const char *arg, int *var_count, int *semicolon, bool silent)
|
||
{
|
||
if (*arg == '[') {
|
||
const char *s;
|
||
// "[var, var]": find the matching ']'.
|
||
const char *p = arg;
|
||
while (true) {
|
||
p = skipwhite(p + 1); // skip whites after '[', ';' or ','
|
||
s = skip_var_one(p);
|
||
if (s == p) {
|
||
if (!silent) {
|
||
semsg(_(e_invarg2), p);
|
||
}
|
||
return NULL;
|
||
}
|
||
(*var_count)++;
|
||
|
||
p = skipwhite(s);
|
||
if (*p == ']') {
|
||
break;
|
||
} else if (*p == ';') {
|
||
if (*semicolon == 1) {
|
||
if (!silent) {
|
||
emsg(_(e_double_semicolon_in_list_of_variables));
|
||
}
|
||
return NULL;
|
||
}
|
||
*semicolon = 1;
|
||
} else if (*p != ',') {
|
||
if (!silent) {
|
||
semsg(_(e_invarg2), p);
|
||
}
|
||
return NULL;
|
||
}
|
||
}
|
||
return p + 1;
|
||
}
|
||
return skip_var_one(arg);
|
||
}
|
||
|
||
/// Skip one (assignable) variable name, including @r, $VAR, &option, d.key,
|
||
/// l[idx].
|
||
static const char *skip_var_one(const char *arg)
|
||
{
|
||
if (*arg == '@' && arg[1] != NUL) {
|
||
return arg + 2;
|
||
}
|
||
return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
|
||
NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
|
||
}
|
||
|
||
/// List variables for hashtab "ht" with prefix "prefix".
|
||
///
|
||
/// @param empty if true also list NULL strings as empty strings.
|
||
void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *first)
|
||
{
|
||
hashitem_T *hi;
|
||
dictitem_T *di;
|
||
int todo;
|
||
|
||
todo = (int)ht->ht_used;
|
||
for (hi = ht->ht_array; todo > 0 && !got_int; hi++) {
|
||
if (!HASHITEM_EMPTY(hi)) {
|
||
todo--;
|
||
di = TV_DICT_HI2DI(hi);
|
||
char buf[IOSIZE];
|
||
|
||
// apply :filter /pat/ to variable name
|
||
xstrlcpy(buf, prefix, IOSIZE);
|
||
xstrlcat(buf, di->di_key, IOSIZE);
|
||
if (message_filtered(buf)) {
|
||
continue;
|
||
}
|
||
|
||
if (empty || di->di_tv.v_type != VAR_STRING
|
||
|| di->di_tv.vval.v_string != NULL) {
|
||
list_one_var(di, prefix, first);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// List global variables.
|
||
static void list_glob_vars(int *first)
|
||
{
|
||
list_hashtable_vars(&globvarht, "", true, first);
|
||
}
|
||
|
||
/// List buffer variables.
|
||
static void list_buf_vars(int *first)
|
||
{
|
||
list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first);
|
||
}
|
||
|
||
/// List window variables.
|
||
static void list_win_vars(int *first)
|
||
{
|
||
list_hashtable_vars(&curwin->w_vars->dv_hashtab, "w:", true, first);
|
||
}
|
||
|
||
/// List tab page variables.
|
||
static void list_tab_vars(int *first)
|
||
{
|
||
list_hashtable_vars(&curtab->tp_vars->dv_hashtab, "t:", true, first);
|
||
}
|
||
|
||
/// List variables in "arg".
|
||
static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
|
||
{
|
||
bool error = false;
|
||
int len;
|
||
const char *name;
|
||
const char *name_start;
|
||
typval_T tv;
|
||
|
||
while (!ends_excmd(*arg) && !got_int) {
|
||
if (error || eap->skip) {
|
||
arg = find_name_end(arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
|
||
if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) {
|
||
emsg_severe = true;
|
||
semsg(_(e_trailing_arg), arg);
|
||
break;
|
||
}
|
||
} else {
|
||
// get_name_len() takes care of expanding curly braces
|
||
name_start = name = arg;
|
||
char *tofree;
|
||
len = get_name_len(&arg, &tofree, true, true);
|
||
if (len <= 0) {
|
||
// This is mainly to keep test 49 working: when expanding
|
||
// curly braces fails overrule the exception error message.
|
||
if (len < 0 && !aborting()) {
|
||
emsg_severe = true;
|
||
semsg(_(e_invarg2), arg);
|
||
break;
|
||
}
|
||
error = true;
|
||
} else {
|
||
if (tofree != NULL) {
|
||
name = tofree;
|
||
}
|
||
if (eval_variable(name, len, &tv, NULL, true, false) == FAIL) {
|
||
error = true;
|
||
} else {
|
||
// handle d.key, l[idx], f(expr)
|
||
const char *const arg_subsc = arg;
|
||
if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, true) == FAIL) {
|
||
error = true;
|
||
} else {
|
||
if (arg == arg_subsc && len == 2 && name[1] == ':') {
|
||
switch (*name) {
|
||
case 'g':
|
||
list_glob_vars(first); break;
|
||
case 'b':
|
||
list_buf_vars(first); break;
|
||
case 'w':
|
||
list_win_vars(first); break;
|
||
case 't':
|
||
list_tab_vars(first); break;
|
||
case 'v':
|
||
list_vim_vars(first); break;
|
||
case 's':
|
||
list_script_vars(first); break;
|
||
case 'l':
|
||
list_func_vars(first); break;
|
||
default:
|
||
semsg(_("E738: Can't list variables for %s"), name);
|
||
}
|
||
} else {
|
||
char *const s = encode_tv2echo(&tv, NULL);
|
||
const char *const used_name = (arg == arg_subsc
|
||
? name
|
||
: name_start);
|
||
assert(used_name != NULL);
|
||
const ptrdiff_t name_size = (used_name == tofree
|
||
? (ptrdiff_t)strlen(used_name)
|
||
: (arg - used_name));
|
||
list_one_var_a("", used_name, name_size,
|
||
tv.v_type, s == NULL ? "" : s, first);
|
||
xfree(s);
|
||
}
|
||
tv_clear(&tv);
|
||
}
|
||
}
|
||
}
|
||
|
||
xfree(tofree);
|
||
}
|
||
|
||
arg = skipwhite(arg);
|
||
}
|
||
|
||
return arg;
|
||
}
|
||
|
||
/// Set an environment variable, part of ex_let_one().
|
||
static char *ex_let_env(char *arg, typval_T *const tv, const bool is_const,
|
||
const char *const endchars, const char *const op)
|
||
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
|
||
{
|
||
if (is_const) {
|
||
emsg(_("E996: Cannot lock an environment variable"));
|
||
return NULL;
|
||
}
|
||
|
||
// Find the end of the name.
|
||
char *arg_end = NULL;
|
||
arg++;
|
||
char *name = arg;
|
||
int len = get_env_len((const char **)&arg);
|
||
if (len == 0) {
|
||
semsg(_(e_invarg2), name - 1);
|
||
} else {
|
||
if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
|
||
semsg(_(e_letwrong), op);
|
||
} else if (endchars != NULL
|
||
&& vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) {
|
||
emsg(_(e_letunexp));
|
||
} else if (!check_secure()) {
|
||
char *tofree = NULL;
|
||
const char c1 = name[len];
|
||
name[len] = NUL;
|
||
const char *p = tv_get_string_chk(tv);
|
||
if (p != NULL && op != NULL && *op == '.') {
|
||
char *s = vim_getenv(name);
|
||
if (s != NULL) {
|
||
tofree = concat_str(s, p);
|
||
p = tofree;
|
||
xfree(s);
|
||
}
|
||
}
|
||
if (p != NULL) {
|
||
vim_setenv_ext(name, p);
|
||
arg_end = arg;
|
||
}
|
||
name[len] = c1;
|
||
xfree(tofree);
|
||
}
|
||
}
|
||
return arg_end;
|
||
}
|
||
|
||
/// Set an option, part of ex_let_one().
|
||
static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
|
||
const char *const endchars, const char *const op)
|
||
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
|
||
{
|
||
if (is_const) {
|
||
emsg(_("E996: Cannot lock an option"));
|
||
return NULL;
|
||
}
|
||
|
||
// Find the end of the name.
|
||
char *arg_end = NULL;
|
||
OptIndex opt_idx;
|
||
int opt_flags;
|
||
|
||
char *const p = (char *)find_option_var_end((const char **)&arg, &opt_idx, &opt_flags);
|
||
|
||
if (p == NULL || (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
|
||
emsg(_(e_letunexp));
|
||
return NULL;
|
||
}
|
||
|
||
const char c1 = *p;
|
||
*p = NUL;
|
||
|
||
bool is_tty_opt = is_tty_option(arg);
|
||
bool hidden = is_option_hidden(opt_idx);
|
||
OptVal curval = is_tty_opt ? get_tty_option(arg) : get_option_value(opt_idx, opt_flags);
|
||
OptVal newval = NIL_OPTVAL;
|
||
|
||
if (curval.type == kOptValTypeNil) {
|
||
semsg(_(e_unknown_option2), arg);
|
||
goto theend;
|
||
}
|
||
if (op != NULL && *op != '='
|
||
&& ((curval.type != kOptValTypeString && *op == '.')
|
||
|| (curval.type == kOptValTypeString && *op != '.'))) {
|
||
semsg(_(e_letwrong), op);
|
||
goto theend;
|
||
}
|
||
|
||
bool error;
|
||
newval = tv_to_optval(tv, opt_idx, arg, &error);
|
||
if (error) {
|
||
goto theend;
|
||
}
|
||
|
||
// Current value and new value must have the same type.
|
||
assert(curval.type == newval.type);
|
||
const bool is_num = curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean;
|
||
const bool is_string = curval.type == kOptValTypeString;
|
||
|
||
if (op != NULL && *op != '=') {
|
||
if (!hidden && is_num) { // number or bool
|
||
OptInt cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean;
|
||
OptInt 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 (curval.type == kOptValTypeNumber) {
|
||
newval = NUMBER_OPTVAL(new_n);
|
||
} else {
|
||
newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n));
|
||
}
|
||
} else if (!hidden && is_string) { // string
|
||
const char *curval_data = curval.data.string.data;
|
||
const char *newval_data = newval.data.string.data;
|
||
|
||
if (curval_data != NULL && newval_data != NULL) {
|
||
OptVal newval_old = newval;
|
||
newval = CSTR_AS_OPTVAL(concat_str(curval_data, newval_data));
|
||
optval_free(newval_old);
|
||
}
|
||
}
|
||
}
|
||
|
||
const char *err = set_option_value_handle_tty(arg, opt_idx, newval, opt_flags);
|
||
arg_end = p;
|
||
if (err != NULL) {
|
||
emsg(_(err));
|
||
}
|
||
|
||
theend:
|
||
*p = c1;
|
||
optval_free(curval);
|
||
optval_free(newval);
|
||
return arg_end;
|
||
}
|
||
|
||
/// Set a register, part of ex_let_one().
|
||
static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
|
||
const char *const endchars, const char *const op)
|
||
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
|
||
{
|
||
if (is_const) {
|
||
emsg(_("E996: Cannot lock a register"));
|
||
return NULL;
|
||
}
|
||
|
||
char *arg_end = NULL;
|
||
arg++;
|
||
if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
|
||
semsg(_(e_letwrong), op);
|
||
} else if (endchars != NULL
|
||
&& vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
|
||
emsg(_(e_letunexp));
|
||
} else {
|
||
char *ptofree = NULL;
|
||
const char *p = tv_get_string_chk(tv);
|
||
if (p != NULL && op != NULL && *op == '.') {
|
||
char *s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
|
||
if (s != NULL) {
|
||
ptofree = concat_str(s, p);
|
||
p = ptofree;
|
||
xfree(s);
|
||
}
|
||
}
|
||
if (p != NULL) {
|
||
write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
|
||
arg_end = arg + 1;
|
||
}
|
||
xfree(ptofree);
|
||
}
|
||
return arg_end;
|
||
}
|
||
|
||
/// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value
|
||
///
|
||
/// @param[in] arg Start of the variable name.
|
||
/// @param[in] tv Value to assign to the variable.
|
||
/// @param[in] copy If true, copy value from `tv`.
|
||
/// @param[in] endchars Valid characters after variable name or NULL.
|
||
/// @param[in] op Operation performed: *op is `+`, `-`, `.` for `+=`, etc.
|
||
/// NULL for `=`.
|
||
///
|
||
/// @return a pointer to the char just after the var name or NULL in case of
|
||
/// error.
|
||
static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bool is_const,
|
||
const char *const endchars, const char *const op)
|
||
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
|
||
{
|
||
char *arg_end = NULL;
|
||
|
||
if (*arg == '$') {
|
||
// ":let $VAR = expr": Set environment variable.
|
||
return ex_let_env(arg, tv, is_const, endchars, op);
|
||
} else if (*arg == '&') {
|
||
// ":let &option = expr": Set option value.
|
||
// ":let &l:option = expr": Set local option value.
|
||
// ":let &g:option = expr": Set global option value.
|
||
return ex_let_option(arg, tv, is_const, endchars, op);
|
||
} else if (*arg == '@') {
|
||
// ":let @r = expr": Set register contents.
|
||
return ex_let_register(arg, tv, is_const, endchars, op);
|
||
} else if (eval_isnamec1(*arg) || *arg == '{') {
|
||
// ":let var = expr": Set internal variable.
|
||
// ":let {expr} = expr": Idem, name made with curly braces
|
||
lval_T lv;
|
||
char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START);
|
||
if (p != NULL && lv.ll_name != NULL) {
|
||
if (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL) {
|
||
emsg(_(e_letunexp));
|
||
} else {
|
||
set_var_lval(&lv, p, tv, copy, is_const, op);
|
||
arg_end = p;
|
||
}
|
||
}
|
||
clear_lval(&lv);
|
||
} else {
|
||
semsg(_(e_invarg2), arg);
|
||
}
|
||
|
||
return arg_end;
|
||
}
|
||
|
||
/// ":unlet[!] var1 ... " command.
|
||
void ex_unlet(exarg_T *eap)
|
||
{
|
||
ex_unletlock(eap, eap->arg, 0, do_unlet_var);
|
||
}
|
||
|
||
/// ":lockvar" and ":unlockvar" commands
|
||
void ex_lockvar(exarg_T *eap)
|
||
{
|
||
char *arg = eap->arg;
|
||
int deep = 2;
|
||
|
||
if (eap->forceit) {
|
||
deep = -1;
|
||
} else if (ascii_isdigit(*arg)) {
|
||
deep = getdigits_int(&arg, false, -1);
|
||
arg = skipwhite(arg);
|
||
}
|
||
|
||
ex_unletlock(eap, arg, deep, do_lock_var);
|
||
}
|
||
|
||
/// Common parsing logic for :unlet, :lockvar and :unlockvar.
|
||
///
|
||
/// Invokes `callback` afterwards if successful and `eap->skip == false`.
|
||
///
|
||
/// @param[in] eap Ex command arguments for the command.
|
||
/// @param[in] argstart Start of the string argument for the command.
|
||
/// @param[in] deep Levels to (un)lock for :(un)lockvar, -1 to (un)lock
|
||
/// everything.
|
||
/// @param[in] callback Appropriate handler for the command.
|
||
static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_callback callback)
|
||
FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
char *arg = argstart;
|
||
char *name_end;
|
||
bool error = false;
|
||
lval_T lv;
|
||
|
||
do {
|
||
if (*arg == '$') {
|
||
lv.ll_name = arg;
|
||
lv.ll_tv = NULL;
|
||
arg++;
|
||
if (get_env_len((const char **)&arg) == 0) {
|
||
semsg(_(e_invarg2), arg - 1);
|
||
return;
|
||
}
|
||
assert(*lv.ll_name == '$'); // suppress clang "Uninitialized argument value"
|
||
if (!error && !eap->skip && callback(&lv, arg, eap, deep) == FAIL) {
|
||
error = true;
|
||
}
|
||
name_end = arg;
|
||
} else {
|
||
// Parse the name and find the end.
|
||
name_end = get_lval(arg, NULL, &lv, true, eap->skip || error,
|
||
0, FNE_CHECK_START);
|
||
if (lv.ll_name == NULL) {
|
||
error = true; // error, but continue parsing.
|
||
}
|
||
if (name_end == NULL
|
||
|| (!ascii_iswhite(*name_end) && !ends_excmd(*name_end))) {
|
||
if (name_end != NULL) {
|
||
emsg_severe = true;
|
||
semsg(_(e_trailing_arg), name_end);
|
||
}
|
||
if (!(eap->skip || error)) {
|
||
clear_lval(&lv);
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (!error && !eap->skip && callback(&lv, name_end, eap, deep) == FAIL) {
|
||
error = true;
|
||
}
|
||
|
||
if (!eap->skip) {
|
||
clear_lval(&lv);
|
||
}
|
||
}
|
||
arg = skipwhite(name_end);
|
||
} while (!ends_excmd(*arg));
|
||
|
||
eap->nextcmd = check_nextcmd(arg);
|
||
}
|
||
|
||
/// Unlet a variable indicated by `lp`.
|
||
///
|
||
/// @param[in] lp The lvalue.
|
||
/// @param[in] name_end End of the string argument for the command.
|
||
/// @param[in] eap Ex command arguments for :unlet.
|
||
/// @param[in] deep Unused.
|
||
///
|
||
/// @return OK on success, or FAIL on failure.
|
||
static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ATTR_UNUSED)
|
||
FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
int forceit = eap->forceit;
|
||
int ret = OK;
|
||
|
||
if (lp->ll_tv == NULL) {
|
||
int cc = (uint8_t)(*name_end);
|
||
*name_end = NUL;
|
||
|
||
// Environment variable, normal name or expanded name.
|
||
if (*lp->ll_name == '$') {
|
||
vim_unsetenv_ext(lp->ll_name + 1);
|
||
} else if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) {
|
||
ret = FAIL;
|
||
}
|
||
*name_end = (char)cc;
|
||
} else if ((lp->ll_list != NULL
|
||
// ll_list is not NULL when lvalue is not in a list, NULL lists
|
||
// yield E689.
|
||
&& value_check_lock(tv_list_locked(lp->ll_list),
|
||
lp->ll_name,
|
||
lp->ll_name_len))
|
||
|| (lp->ll_dict != NULL
|
||
&& value_check_lock(lp->ll_dict->dv_lock,
|
||
lp->ll_name,
|
||
lp->ll_name_len))) {
|
||
return FAIL;
|
||
} else if (lp->ll_range) {
|
||
tv_list_unlet_range(lp->ll_list, lp->ll_li, lp->ll_n1, !lp->ll_empty2, lp->ll_n2);
|
||
} else if (lp->ll_list != NULL) {
|
||
// unlet a List item.
|
||
tv_list_item_remove(lp->ll_list, lp->ll_li);
|
||
} else {
|
||
// unlet a Dict item.
|
||
dict_T *d = lp->ll_dict;
|
||
assert(d != NULL);
|
||
dictitem_T *di = lp->ll_di;
|
||
bool watched = tv_dict_is_watched(d);
|
||
char *key = NULL;
|
||
typval_T oldtv;
|
||
|
||
if (watched) {
|
||
tv_copy(&di->di_tv, &oldtv);
|
||
// need to save key because dictitem_remove will free it
|
||
key = xstrdup(di->di_key);
|
||
}
|
||
|
||
tv_dict_item_remove(d, di);
|
||
|
||
if (watched) {
|
||
tv_dict_watcher_notify(d, key, NULL, &oldtv);
|
||
tv_clear(&oldtv);
|
||
xfree(key);
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
/// Unlet one item or a range of items from a list.
|
||
/// Return OK or FAIL.
|
||
static void tv_list_unlet_range(list_T *const l, listitem_T *const li_first, const int n1_arg,
|
||
const bool has_n2, const int n2)
|
||
{
|
||
assert(l != NULL);
|
||
// Delete a range of List items.
|
||
listitem_T *li_last = li_first;
|
||
int n1 = n1_arg;
|
||
while (true) {
|
||
listitem_T *const li = TV_LIST_ITEM_NEXT(l, li_last);
|
||
n1++;
|
||
if (li == NULL || (has_n2 && n2 < n1)) {
|
||
break;
|
||
}
|
||
li_last = li;
|
||
}
|
||
tv_list_remove_items(l, li_first, li_last);
|
||
}
|
||
|
||
/// unlet a variable
|
||
///
|
||
/// @param[in] name Variable name to unlet.
|
||
/// @param[in] name_len Variable name length.
|
||
/// @param[in] forceit If true, do not complain if variable doesn’t exist.
|
||
///
|
||
/// @return OK if it existed, FAIL otherwise.
|
||
int do_unlet(const char *const name, const size_t name_len, const bool forceit)
|
||
FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
const char *varname = NULL; // init to shut up gcc
|
||
dict_T *dict;
|
||
hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict);
|
||
|
||
if (ht != NULL && *varname != NUL) {
|
||
dict_T *d = get_current_funccal_dict(ht);
|
||
if (d == NULL) {
|
||
if (ht == &globvarht) {
|
||
d = &globvardict;
|
||
} else if (is_compatht(ht)) {
|
||
d = &vimvardict;
|
||
} else {
|
||
dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false);
|
||
d = di->di_tv.vval.v_dict;
|
||
}
|
||
if (d == NULL) {
|
||
internal_error("do_unlet()");
|
||
return FAIL;
|
||
}
|
||
}
|
||
|
||
hashitem_T *hi = hash_find(ht, varname);
|
||
if (HASHITEM_EMPTY(hi)) {
|
||
hi = find_hi_in_scoped_ht(name, &ht);
|
||
}
|
||
if (hi != NULL && !HASHITEM_EMPTY(hi)) {
|
||
dictitem_T *const di = TV_DICT_HI2DI(hi);
|
||
if (var_check_fixed(di->di_flags, name, TV_CSTRING)
|
||
|| var_check_ro(di->di_flags, name, TV_CSTRING)
|
||
|| value_check_lock(d->dv_lock, name, TV_CSTRING)) {
|
||
return FAIL;
|
||
}
|
||
|
||
if (value_check_lock(d->dv_lock, name, TV_CSTRING)) {
|
||
return FAIL;
|
||
}
|
||
|
||
typval_T oldtv;
|
||
bool watched = tv_dict_is_watched(dict);
|
||
|
||
if (watched) {
|
||
tv_copy(&di->di_tv, &oldtv);
|
||
}
|
||
|
||
delete_var(ht, hi);
|
||
|
||
if (watched) {
|
||
tv_dict_watcher_notify(dict, varname, NULL, &oldtv);
|
||
tv_clear(&oldtv);
|
||
}
|
||
return OK;
|
||
}
|
||
}
|
||
if (forceit) {
|
||
return OK;
|
||
}
|
||
semsg(_("E108: No such variable: \"%s\""), name);
|
||
return FAIL;
|
||
}
|
||
|
||
/// Lock or unlock variable indicated by `lp`.
|
||
///
|
||
/// Locks if `eap->cmdidx == CMD_lockvar`, unlocks otherwise.
|
||
///
|
||
/// @param[in] lp The lvalue.
|
||
/// @param[in] name_end Unused.
|
||
/// @param[in] eap Ex command arguments for :(un)lockvar.
|
||
/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything.
|
||
///
|
||
/// @return OK on success, or FAIL on failure.
|
||
static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap, int deep)
|
||
FUNC_ATTR_NONNULL_ARG(1, 3)
|
||
{
|
||
bool lock = eap->cmdidx == CMD_lockvar;
|
||
int ret = OK;
|
||
|
||
if (lp->ll_tv == NULL) {
|
||
if (*lp->ll_name == '$') {
|
||
semsg(_(e_lock_unlock), lp->ll_name);
|
||
ret = FAIL;
|
||
} else {
|
||
// Normal name or expanded name.
|
||
dictitem_T *const di = find_var(lp->ll_name, lp->ll_name_len, NULL,
|
||
true);
|
||
if (di == NULL) {
|
||
ret = FAIL;
|
||
} else if ((di->di_flags & DI_FLAGS_FIX)
|
||
&& di->di_tv.v_type != VAR_DICT
|
||
&& di->di_tv.v_type != VAR_LIST) {
|
||
// For historical reasons this error is not given for Lists and
|
||
// Dictionaries. E.g. b: dictionary may be locked/unlocked.
|
||
semsg(_(e_lock_unlock), lp->ll_name);
|
||
ret = FAIL;
|
||
} else {
|
||
if (lock) {
|
||
di->di_flags |= DI_FLAGS_LOCK;
|
||
} else {
|
||
di->di_flags &= (uint8_t)(~DI_FLAGS_LOCK);
|
||
}
|
||
if (deep != 0) {
|
||
tv_item_lock(&di->di_tv, deep, lock, false);
|
||
}
|
||
}
|
||
}
|
||
} else if (deep == 0) {
|
||
// nothing to do
|
||
} else if (lp->ll_range) {
|
||
listitem_T *li = lp->ll_li;
|
||
|
||
// (un)lock a range of List items.
|
||
while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
|
||
tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock, false);
|
||
li = TV_LIST_ITEM_NEXT(lp->ll_list, li);
|
||
lp->ll_n1++;
|
||
}
|
||
} else if (lp->ll_list != NULL) {
|
||
// (un)lock a List item.
|
||
tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false);
|
||
} else {
|
||
// (un)lock a Dict item.
|
||
tv_item_lock(&lp->ll_di->di_tv, deep, lock, false);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
/// Delete all "menutrans_" variables.
|
||
void del_menutrans_vars(void)
|
||
{
|
||
hash_lock(&globvarht);
|
||
HASHTAB_ITER(&globvarht, hi, {
|
||
if (strncmp(hi->hi_key, "menutrans_", 10) == 0) {
|
||
delete_var(&globvarht, hi);
|
||
}
|
||
});
|
||
hash_unlock(&globvarht);
|
||
}
|
||
|
||
/// @return global variable dictionary
|
||
dict_T *get_globvar_dict(void)
|
||
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
|
||
{
|
||
return &globvardict;
|
||
}
|
||
|
||
/// @return global variable hash table
|
||
hashtab_T *get_globvar_ht(void)
|
||
{
|
||
return &globvarht;
|
||
}
|
||
|
||
/// @return v: variable dictionary
|
||
dict_T *get_vimvar_dict(void)
|
||
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
|
||
{
|
||
return &vimvardict;
|
||
}
|
||
|
||
/// Set v:variable to tv.
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in] val Value to set to. Will be copied.
|
||
void set_vim_var_tv(const VimVarIndex idx, typval_T *const tv)
|
||
{
|
||
typval_T *vv_tv = get_vim_var_tv(idx);
|
||
tv_clear(vv_tv);
|
||
tv_copy(tv, vv_tv);
|
||
}
|
||
|
||
/// Get number v: variable value.
|
||
varnumber_T get_vim_var_nr(const VimVarIndex idx) FUNC_ATTR_PURE
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
return tv->vval.v_number;
|
||
}
|
||
|
||
/// Get List v: variable value. Caller must take care of reference count when
|
||
/// needed.
|
||
list_T *get_vim_var_list(const VimVarIndex idx) FUNC_ATTR_PURE
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
return tv->vval.v_list;
|
||
}
|
||
|
||
/// Get Dictionary v: variable value. Caller must take care of reference count
|
||
/// when needed.
|
||
dict_T *get_vim_var_dict(const VimVarIndex idx) FUNC_ATTR_PURE
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
return tv->vval.v_dict;
|
||
}
|
||
|
||
/// Get string v: variable value. Uses a static buffer, can only be used once.
|
||
/// If the String variable has never been set, return an empty string.
|
||
/// Never returns NULL.
|
||
char *get_vim_var_str(const VimVarIndex idx)
|
||
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
|
||
{
|
||
return (char *)tv_get_string(get_vim_var_tv(idx));
|
||
}
|
||
|
||
/// Set type of v: variable to the given type.
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in] type Type to set to.
|
||
void set_vim_var_type(const VimVarIndex idx, const VarType type)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
tv->v_type = type;
|
||
}
|
||
|
||
/// Set number v: variable to the given value
|
||
/// Note that this does not set the type, use set_vim_var_type() for that.
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in] val Value to set to.
|
||
void set_vim_var_nr(const VimVarIndex idx, const varnumber_T val)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
tv_clear(tv);
|
||
tv->vval.v_number = val;
|
||
}
|
||
|
||
/// Set boolean v: {true, false} to the given value
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in] val Value to set to.
|
||
void set_vim_var_bool(const VimVarIndex idx, const BoolVarValue val)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
tv_clear(tv);
|
||
tv->v_type = VAR_BOOL;
|
||
tv->vval.v_bool = val;
|
||
}
|
||
|
||
/// Set special v: variable to the given value
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in] val Value to set to.
|
||
void set_vim_var_special(const VimVarIndex idx, const SpecialVarValue val)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
tv_clear(tv);
|
||
tv->v_type = VAR_SPECIAL;
|
||
tv->vval.v_special = val;
|
||
}
|
||
|
||
/// Set v:char to character "c".
|
||
void set_vim_var_char(int c)
|
||
{
|
||
char buf[MB_MAXCHAR + 1];
|
||
|
||
buf[utf_char2bytes(c, buf)] = NUL;
|
||
set_vim_var_string(VV_CHAR, buf, -1);
|
||
}
|
||
|
||
/// Set string v: variable to the given string
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in] val Value to set to. Will be copied.
|
||
/// @param[in] len Length of that value or -1 in which case strlen() will be
|
||
/// used.
|
||
void set_vim_var_string(const VimVarIndex idx, const char *const val, const ptrdiff_t len)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
tv_clear(tv);
|
||
tv->v_type = VAR_STRING;
|
||
if (val == NULL) {
|
||
tv->vval.v_string = NULL;
|
||
} else if (len == -1) {
|
||
tv->vval.v_string = xstrdup(val);
|
||
} else {
|
||
tv->vval.v_string = xstrndup(val, (size_t)len);
|
||
}
|
||
}
|
||
|
||
/// Set list v: variable to the given list
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in,out] val Value to set to. Reference count will be incremented.
|
||
void set_vim_var_list(const VimVarIndex idx, list_T *const val)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
tv_clear(tv);
|
||
tv->v_type = VAR_LIST;
|
||
tv->vval.v_list = val;
|
||
if (val != NULL) {
|
||
tv_list_ref(val);
|
||
}
|
||
}
|
||
|
||
/// Set Dictionary v: variable to the given dictionary
|
||
///
|
||
/// @param[in] idx Index of variable to set.
|
||
/// @param[in,out] val Value to set to. Reference count will be incremented.
|
||
/// Also keys of the dictionary will be made read-only.
|
||
void set_vim_var_dict(const VimVarIndex idx, dict_T *const val)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(idx);
|
||
tv_clear(tv);
|
||
tv->v_type = VAR_DICT;
|
||
tv->vval.v_dict = val;
|
||
if (val == NULL) {
|
||
return;
|
||
}
|
||
|
||
val->dv_refcount++;
|
||
// Set readonly
|
||
tv_dict_set_keys_readonly(val);
|
||
}
|
||
|
||
/// Set v:register if needed.
|
||
void set_reg_var(int c)
|
||
{
|
||
char regname;
|
||
|
||
if (c == 0 || c == ' ') {
|
||
regname = '"';
|
||
} else {
|
||
regname = (char)c;
|
||
}
|
||
// Avoid free/alloc when the value is already right.
|
||
typval_T *tv = get_vim_var_tv(VV_REG);
|
||
if (tv->vval.v_string == NULL || tv->vval.v_string[0] != c) {
|
||
set_vim_var_string(VV_REG, ®name, 1);
|
||
}
|
||
}
|
||
|
||
/// Get or set v:exception. If "oldval" == NULL, return the current value.
|
||
/// Otherwise, restore the value to "oldval" and return NULL.
|
||
/// Must always be called in pairs to save and restore v:exception! Does not
|
||
/// take care of memory allocations.
|
||
char *v_exception(char *oldval)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(VV_EXCEPTION);
|
||
if (oldval == NULL) {
|
||
return tv->vval.v_string;
|
||
}
|
||
|
||
tv->vval.v_string = oldval;
|
||
return NULL;
|
||
}
|
||
|
||
/// Set v:cmdarg.
|
||
/// If "eap" != NULL, use "eap" to generate the value and return the old value.
|
||
/// If "oldarg" != NULL, restore the value to "oldarg" and return NULL.
|
||
/// Must always be called in pairs!
|
||
char *set_cmdarg(exarg_T *eap, char *oldarg)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(VV_CMDARG);
|
||
char *oldval = tv->vval.v_string;
|
||
if (eap == NULL) {
|
||
goto error;
|
||
}
|
||
|
||
size_t len = 0;
|
||
if (eap->force_bin == FORCE_BIN) {
|
||
len += 6; // " ++bin"
|
||
} else if (eap->force_bin == FORCE_NOBIN) {
|
||
len += 8; // " ++nobin"
|
||
}
|
||
|
||
if (eap->read_edit) {
|
||
len += 7; // " ++edit"
|
||
}
|
||
|
||
if (eap->force_ff != 0) {
|
||
len += 10; // " ++ff=unix"
|
||
}
|
||
if (eap->force_enc != 0) {
|
||
len += strlen(eap->cmd + eap->force_enc) + 7;
|
||
}
|
||
if (eap->bad_char != 0) {
|
||
len += 7 + 4; // " ++bad=" + "keep" or "drop"
|
||
}
|
||
if (eap->mkdir_p != 0) {
|
||
len += 4; // " ++p"
|
||
}
|
||
|
||
const size_t newval_len = len + 1;
|
||
char *newval = xmalloc(newval_len);
|
||
size_t xlen = 0;
|
||
int rc = 0;
|
||
|
||
if (eap->force_bin == FORCE_BIN) {
|
||
rc = snprintf(newval, newval_len, " ++bin");
|
||
} else if (eap->force_bin == FORCE_NOBIN) {
|
||
rc = snprintf(newval, newval_len, " ++nobin");
|
||
} else {
|
||
*newval = NUL;
|
||
}
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
|
||
if (eap->read_edit) {
|
||
rc = snprintf(newval + xlen, newval_len - xlen, " ++edit");
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
}
|
||
|
||
if (eap->force_ff != 0) {
|
||
rc = snprintf(newval + xlen,
|
||
newval_len - xlen,
|
||
" ++ff=%s",
|
||
eap->force_ff == 'u' ? "unix"
|
||
: eap->force_ff == 'd' ? "dos" : "mac");
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
}
|
||
if (eap->force_enc != 0) {
|
||
rc = snprintf(newval + (xlen), newval_len - xlen, " ++enc=%s", eap->cmd + eap->force_enc);
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
}
|
||
|
||
if (eap->bad_char == BAD_KEEP) {
|
||
rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=keep");
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
} else if (eap->bad_char == BAD_DROP) {
|
||
rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=drop");
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
} else if (eap->bad_char != 0) {
|
||
rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=%c", eap->bad_char);
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
}
|
||
|
||
if (eap->mkdir_p != 0) {
|
||
rc = snprintf(newval + xlen, newval_len - xlen, " ++p");
|
||
if (rc < 0) {
|
||
goto error;
|
||
}
|
||
xlen += (size_t)rc;
|
||
}
|
||
assert(xlen <= newval_len);
|
||
|
||
tv->vval.v_string = newval;
|
||
return oldval;
|
||
|
||
error:
|
||
xfree(oldval);
|
||
tv->vval.v_string = oldarg;
|
||
return NULL;
|
||
}
|
||
|
||
/// Get or set v:throwpoint. If "oldval" == NULL, return the current value.
|
||
/// Otherwise, restore the value to "oldval" and return NULL.
|
||
/// Must always be called in pairs to save and restore v:throwpoint! Does not
|
||
/// take care of memory allocations.
|
||
char *v_throwpoint(char *oldval)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(VV_THROWPOINT);
|
||
if (oldval == NULL) {
|
||
return tv->vval.v_string;
|
||
}
|
||
|
||
tv->vval.v_string = oldval;
|
||
return NULL;
|
||
}
|
||
|
||
/// Set v:count to "count" and v:count1 to "count1".
|
||
///
|
||
/// @param set_prevcount if true, first set v:prevcount from v:count.
|
||
void set_vcount(int64_t count, int64_t count1, bool set_prevcount)
|
||
{
|
||
if (set_prevcount) {
|
||
get_vim_var_tv(VV_PREVCOUNT)->vval.v_number = get_vim_var_nr(VV_COUNT);
|
||
}
|
||
get_vim_var_tv(VV_COUNT)->vval.v_number = count;
|
||
get_vim_var_tv(VV_COUNT1)->vval.v_number = count1;
|
||
}
|
||
|
||
/// Get the value of internal variable "name".
|
||
/// Return OK or FAIL. If OK is returned "rettv" must be cleared.
|
||
///
|
||
/// @param len length of "name"
|
||
/// @param rettv NULL when only checking existence
|
||
/// @param dip non-NULL when typval's dict item is needed
|
||
/// @param verbose may give error message
|
||
/// @param no_autoload do not use script autoloading
|
||
int eval_variable(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose,
|
||
bool no_autoload)
|
||
{
|
||
int ret = OK;
|
||
typval_T *tv = NULL;
|
||
dictitem_T *v;
|
||
|
||
v = find_var(name, (size_t)len, NULL, no_autoload);
|
||
if (v != NULL) {
|
||
tv = &v->di_tv;
|
||
if (dip != NULL) {
|
||
*dip = v;
|
||
}
|
||
}
|
||
|
||
if (tv == NULL) {
|
||
if (rettv != NULL && verbose) {
|
||
semsg(_("E121: Undefined variable: %.*s"), len, name);
|
||
}
|
||
ret = FAIL;
|
||
} else if (rettv != NULL) {
|
||
tv_copy(tv, rettv);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
/// Check if variable "name[len]" is a local variable or an argument.
|
||
/// If so, "*eval_lavars_used" is set to true.
|
||
void check_vars(const char *name, size_t len)
|
||
{
|
||
if (eval_lavars_used == NULL) {
|
||
return;
|
||
}
|
||
|
||
const char *varname;
|
||
hashtab_T *ht = find_var_ht(name, len, &varname);
|
||
|
||
if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) {
|
||
if (find_var(name, len, NULL, true) != NULL) {
|
||
*eval_lavars_used = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Find variable "name" in the list of variables.
|
||
/// Careful: "a:0" variables don't have a name.
|
||
/// When "htp" is not NULL we are writing to the variable, set "htp" to the
|
||
/// hashtab_T used.
|
||
///
|
||
/// @return a pointer to it if found, NULL if not found.
|
||
dictitem_T *find_var(const char *const name, const size_t name_len, hashtab_T **htp,
|
||
int no_autoload)
|
||
{
|
||
const char *varname;
|
||
hashtab_T *const ht = find_var_ht(name, name_len, &varname);
|
||
if (htp != NULL) {
|
||
*htp = ht;
|
||
}
|
||
if (ht == NULL) {
|
||
return NULL;
|
||
}
|
||
dictitem_T *const ret = find_var_in_ht(ht, *name,
|
||
varname,
|
||
name_len - (size_t)(varname - name),
|
||
no_autoload || htp != NULL);
|
||
if (ret != NULL) {
|
||
return ret;
|
||
}
|
||
|
||
// Search in parent scope for lambda
|
||
return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL);
|
||
}
|
||
|
||
/// Find the hashtable used for a variable
|
||
///
|
||
/// @param[in] name Variable name, possibly with scope prefix.
|
||
/// @param[in] name_len Variable name length.
|
||
/// @param[out] varname Will be set to the start of the name without scope
|
||
/// prefix.
|
||
///
|
||
/// @return Scope hashtab, NULL if name is not valid.
|
||
hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **varname)
|
||
{
|
||
dict_T *d;
|
||
return find_var_ht_dict(name, name_len, varname, &d);
|
||
}
|
||
|
||
/// @return the string value of a (global/local) variable or
|
||
/// NULL when it doesn't exist.
|
||
///
|
||
/// @see tv_get_string() for how long the pointer remains valid.
|
||
char *get_var_value(const char *const name)
|
||
{
|
||
dictitem_T *v;
|
||
|
||
v = find_var(name, strlen(name), NULL, false);
|
||
if (v == NULL) {
|
||
return NULL;
|
||
}
|
||
return (char *)tv_get_string(&v->di_tv);
|
||
}
|
||
|
||
/// Allocate a new hashtab for a sourced script. It will be used while
|
||
/// sourcing this script and when executing functions defined in the script.
|
||
void new_script_vars(scid_T id)
|
||
{
|
||
scriptvar_T *sv = xcalloc(1, sizeof(scriptvar_T));
|
||
init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
|
||
SCRIPT_ITEM(id)->sn_vars = sv;
|
||
}
|
||
|
||
/// Initialize dictionary "dict" as a scope and set variable "dict_var" to
|
||
/// point to it.
|
||
void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, ScopeType scope)
|
||
{
|
||
hash_init(&dict->dv_hashtab);
|
||
dict->dv_lock = VAR_UNLOCKED;
|
||
dict->dv_scope = scope;
|
||
dict->dv_refcount = DO_NOT_FREE_CNT;
|
||
dict->dv_copyID = 0;
|
||
dict_var->di_tv.vval.v_dict = dict;
|
||
dict_var->di_tv.v_type = VAR_DICT;
|
||
dict_var->di_tv.v_lock = VAR_FIXED;
|
||
dict_var->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
|
||
dict_var->di_key[0] = NUL;
|
||
QUEUE_INIT(&dict->watchers);
|
||
}
|
||
|
||
/// Unreference a dictionary initialized by init_var_dict().
|
||
void unref_var_dict(dict_T *dict)
|
||
{
|
||
// Now the dict needs to be freed if no one else is using it, go back to
|
||
// normal reference counting.
|
||
dict->dv_refcount -= DO_NOT_FREE_CNT - 1;
|
||
tv_dict_unref(dict);
|
||
}
|
||
|
||
/// Clean up a list of internal variables.
|
||
/// Frees all allocated variables and the value they contain.
|
||
/// Clears hashtab "ht", does not free it.
|
||
void vars_clear(hashtab_T *ht)
|
||
{
|
||
vars_clear_ext(ht, true);
|
||
}
|
||
|
||
/// Like vars_clear(), but only free the value if "free_val" is true.
|
||
void vars_clear_ext(hashtab_T *ht, bool free_val)
|
||
{
|
||
int todo;
|
||
hashitem_T *hi;
|
||
dictitem_T *v;
|
||
|
||
hash_lock(ht);
|
||
todo = (int)ht->ht_used;
|
||
for (hi = ht->ht_array; todo > 0; hi++) {
|
||
if (!HASHITEM_EMPTY(hi)) {
|
||
todo--;
|
||
|
||
// Free the variable. Don't remove it from the hashtab,
|
||
// ht_array might change then. hash_clear() takes care of it
|
||
// later.
|
||
v = TV_DICT_HI2DI(hi);
|
||
if (free_val) {
|
||
tv_clear(&v->di_tv);
|
||
}
|
||
if (v->di_flags & DI_FLAGS_ALLOC) {
|
||
xfree(v);
|
||
}
|
||
}
|
||
}
|
||
hash_clear(ht);
|
||
hash_init(ht);
|
||
}
|
||
|
||
/// Delete a variable from hashtab "ht" at item "hi".
|
||
/// Clear the variable value and free the dictitem.
|
||
static void delete_var(hashtab_T *ht, hashitem_T *hi)
|
||
{
|
||
dictitem_T *di = TV_DICT_HI2DI(hi);
|
||
|
||
hash_remove(ht, hi);
|
||
tv_clear(&di->di_tv);
|
||
xfree(di);
|
||
}
|
||
|
||
/// List the value of one internal variable.
|
||
static void list_one_var(dictitem_T *v, const char *prefix, int *first)
|
||
{
|
||
char *const s = encode_tv2echo(&v->di_tv, NULL);
|
||
list_one_var_a(prefix, v->di_key, (ptrdiff_t)strlen(v->di_key),
|
||
v->di_tv.v_type, (s == NULL ? "" : s), first);
|
||
xfree(s);
|
||
}
|
||
|
||
/// @param[in] name_len Length of the name. May be -1, in this case strlen()
|
||
/// will be used.
|
||
/// @param[in,out] first When true clear rest of screen and set to false.
|
||
static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len,
|
||
const VarType type, const char *string, int *first)
|
||
{
|
||
if (*first) {
|
||
msg_ext_set_kind("list_cmd");
|
||
msg_start();
|
||
} else {
|
||
msg_putchar('\n');
|
||
}
|
||
// don't use msg() to avoid overwriting "v:statusmsg"
|
||
if (*prefix != NUL) {
|
||
msg_puts(prefix);
|
||
}
|
||
if (name != NULL) { // "a:" vars don't have a name stored
|
||
msg_puts_len(name, name_len, 0, false);
|
||
}
|
||
msg_putchar(' ');
|
||
msg_advance(22);
|
||
if (type == VAR_NUMBER) {
|
||
msg_putchar('#');
|
||
} else if (type == VAR_FUNC || type == VAR_PARTIAL) {
|
||
msg_putchar('*');
|
||
} else if (type == VAR_LIST) {
|
||
msg_putchar('[');
|
||
if (*string == '[') {
|
||
string++;
|
||
}
|
||
} else if (type == VAR_DICT) {
|
||
msg_putchar('{');
|
||
if (*string == '{') {
|
||
string++;
|
||
}
|
||
} else {
|
||
msg_putchar(' ');
|
||
}
|
||
|
||
msg_outtrans(string, 0, false);
|
||
|
||
if (type == VAR_FUNC || type == VAR_PARTIAL) {
|
||
msg_puts("()");
|
||
}
|
||
if (*first) {
|
||
msg_clr_eos();
|
||
*first = false;
|
||
}
|
||
}
|
||
|
||
/// Additional handling for setting a v: variable.
|
||
///
|
||
/// @return true if the variable should be set normally,
|
||
/// false if nothing else needs to be done.
|
||
bool before_set_vvar(const char *const varname, dictitem_T *const di, typval_T *const tv,
|
||
const bool copy, const bool watched, bool *const type_error)
|
||
{
|
||
if (di->di_tv.v_type == VAR_STRING) {
|
||
typval_T oldtv = TV_INITIAL_VALUE;
|
||
if (watched) {
|
||
tv_copy(&di->di_tv, &oldtv);
|
||
}
|
||
XFREE_CLEAR(di->di_tv.vval.v_string);
|
||
if (copy || tv->v_type != VAR_STRING) {
|
||
const char *const val = tv_get_string(tv);
|
||
// Careful: when assigning to v:errmsg and tv_get_string()
|
||
// causes an error message the variable will already be set.
|
||
if (di->di_tv.vval.v_string == NULL) {
|
||
di->di_tv.vval.v_string = xstrdup(val);
|
||
}
|
||
} else {
|
||
// Take over the string to avoid an extra alloc/free.
|
||
di->di_tv.vval.v_string = tv->vval.v_string;
|
||
tv->vval.v_string = NULL;
|
||
}
|
||
// Notify watchers
|
||
if (watched) {
|
||
tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
|
||
tv_clear(&oldtv);
|
||
}
|
||
return false;
|
||
} else if (di->di_tv.v_type == VAR_NUMBER) {
|
||
typval_T oldtv = TV_INITIAL_VALUE;
|
||
if (watched) {
|
||
tv_copy(&di->di_tv, &oldtv);
|
||
}
|
||
di->di_tv.vval.v_number = tv_get_number(tv);
|
||
if (strcmp(varname, "searchforward") == 0) {
|
||
set_search_direction(di->di_tv.vval.v_number ? '/' : '?');
|
||
} else if (strcmp(varname, "hlsearch") == 0) {
|
||
no_hlsearch = !di->di_tv.vval.v_number;
|
||
redraw_all_later(UPD_SOME_VALID);
|
||
}
|
||
// Notify watchers
|
||
if (watched) {
|
||
tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
|
||
tv_clear(&oldtv);
|
||
}
|
||
return false;
|
||
} else if (di->di_tv.v_type != tv->v_type) {
|
||
*type_error = true;
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// Set variable to the given value
|
||
///
|
||
/// If the variable already exists, the value is updated. Otherwise the variable
|
||
/// is created.
|
||
///
|
||
/// @param[in] name Variable name to set.
|
||
/// @param[in] name_len Length of the variable name.
|
||
/// @param tv Variable value.
|
||
/// @param[in] copy True if value in tv is to be copied.
|
||
void set_var(const char *name, const size_t name_len, typval_T *const tv, const bool copy)
|
||
FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
set_var_const(name, name_len, tv, copy, false);
|
||
}
|
||
|
||
/// Set variable to the given value
|
||
///
|
||
/// If the variable already exists, the value is updated. Otherwise the variable
|
||
/// is created.
|
||
///
|
||
/// @param[in] name Variable name to set.
|
||
/// @param[in] name_len Length of the variable name.
|
||
/// @param tv Variable value.
|
||
/// @param[in] copy True if value in tv is to be copied.
|
||
/// @param[in] is_const True if value in tv is to be locked.
|
||
void set_var_const(const char *name, const size_t name_len, typval_T *const tv, const bool copy,
|
||
const bool is_const)
|
||
FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
const char *varname;
|
||
dict_T *dict;
|
||
hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict);
|
||
const bool watched = tv_dict_is_watched(dict);
|
||
|
||
if (ht == NULL || *varname == NUL) {
|
||
semsg(_(e_illvar), name);
|
||
return;
|
||
}
|
||
const size_t varname_len = name_len - (size_t)(varname - name);
|
||
dictitem_T *di = find_var_in_ht(ht, 0, varname, varname_len, true);
|
||
|
||
// Search in parent scope which is possible to reference from lambda
|
||
if (di == NULL) {
|
||
di = find_var_in_scoped_ht(name, name_len, true);
|
||
}
|
||
|
||
if (tv_is_func(*tv) && var_wrong_func_name(name, di == NULL)) {
|
||
return;
|
||
}
|
||
|
||
typval_T oldtv = TV_INITIAL_VALUE;
|
||
if (di != NULL) {
|
||
if (is_const) {
|
||
emsg(_(e_cannot_mod));
|
||
return;
|
||
}
|
||
|
||
// Check in this order for backwards compatibility:
|
||
// - Whether the variable is read-only
|
||
// - Whether the variable value is locked
|
||
// - Whether the variable is locked
|
||
if (var_check_ro(di->di_flags, name, name_len)
|
||
|| value_check_lock(di->di_tv.v_lock, name, name_len)
|
||
|| var_check_lock(di->di_flags, name, name_len)) {
|
||
return;
|
||
}
|
||
|
||
// existing variable, need to clear the value
|
||
|
||
// Handle setting internal v: variables separately where needed to
|
||
// prevent changing the type.
|
||
bool type_error = false;
|
||
if (ht == &vimvarht
|
||
&& !before_set_vvar(varname, di, tv, copy, watched, &type_error)) {
|
||
if (type_error) {
|
||
semsg(_(e_setting_v_str_to_value_with_wrong_type), varname);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (watched) {
|
||
tv_copy(&di->di_tv, &oldtv);
|
||
}
|
||
tv_clear(&di->di_tv);
|
||
} else { // Add a new variable.
|
||
// Can't add "v:" or "a:" variable.
|
||
if (ht == &vimvarht || ht == get_funccal_args_ht()) {
|
||
semsg(_(e_illvar), name);
|
||
return;
|
||
}
|
||
|
||
// Make sure the variable name is valid.
|
||
if (!valid_varname(varname)) {
|
||
return;
|
||
}
|
||
|
||
// Make sure dict is valid
|
||
assert(dict != NULL);
|
||
|
||
di = xmalloc(offsetof(dictitem_T, di_key) + varname_len + 1);
|
||
memcpy(di->di_key, varname, varname_len + 1);
|
||
if (hash_add(ht, di->di_key) == FAIL) {
|
||
xfree(di);
|
||
return;
|
||
}
|
||
di->di_flags = DI_FLAGS_ALLOC;
|
||
if (is_const) {
|
||
di->di_flags |= DI_FLAGS_LOCK;
|
||
}
|
||
}
|
||
|
||
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
|
||
tv_copy(tv, &di->di_tv);
|
||
} else {
|
||
di->di_tv = *tv;
|
||
di->di_tv.v_lock = VAR_UNLOCKED;
|
||
tv_init(tv);
|
||
}
|
||
|
||
if (watched) {
|
||
tv_dict_watcher_notify(dict, di->di_key, &di->di_tv, &oldtv);
|
||
tv_clear(&oldtv);
|
||
}
|
||
|
||
if (is_const) {
|
||
// Like :lockvar! name: lock the value and what it contains, but only
|
||
// if the reference count is up to one. That locks only literal
|
||
// values.
|
||
tv_item_lock(&di->di_tv, DICT_MAXNEST, true, true);
|
||
}
|
||
}
|
||
|
||
/// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX)
|
||
///
|
||
/// Also gives an error message.
|
||
///
|
||
/// @param[in] flags di_flags attribute value.
|
||
/// @param[in] name Variable name, for use in error message.
|
||
/// @param[in] name_len Variable name length. Use #TV_TRANSLATE to translate
|
||
/// variable name and compute the length. Use #TV_CSTRING
|
||
/// to compute the length with strlen() without
|
||
/// translating.
|
||
///
|
||
/// Both #TV_… values are used for optimization purposes:
|
||
/// variable name with its length is needed only in case
|
||
/// of error, when no error occurs computing them is
|
||
/// a waste of CPU resources. This especially applies to
|
||
/// gettext.
|
||
///
|
||
/// @return True if variable is read-only: either always or in sandbox when
|
||
/// sandbox is enabled, false otherwise.
|
||
bool var_check_ro(const int flags, const char *name, size_t name_len)
|
||
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
const char *error_message = NULL;
|
||
if (flags & DI_FLAGS_RO) {
|
||
error_message = _(e_readonlyvar);
|
||
} else if ((flags & DI_FLAGS_RO_SBX) && sandbox) {
|
||
error_message = N_("E794: Cannot set variable in the sandbox: \"%.*s\"");
|
||
}
|
||
|
||
if (error_message == NULL) {
|
||
return false;
|
||
}
|
||
if (name_len == TV_TRANSLATE) {
|
||
name = _(name);
|
||
name_len = strlen(name);
|
||
} else if (name_len == TV_CSTRING) {
|
||
name_len = strlen(name);
|
||
}
|
||
|
||
semsg(_(error_message), (int)name_len, name);
|
||
|
||
return true;
|
||
}
|
||
|
||
/// Return true if di_flags "flags" indicates variable "name" is locked.
|
||
/// Also give an error message.
|
||
bool var_check_lock(const int flags, const char *name, size_t name_len)
|
||
{
|
||
if (!(flags & DI_FLAGS_LOCK)) {
|
||
return false;
|
||
}
|
||
|
||
if (name_len == TV_TRANSLATE) {
|
||
name = _(name);
|
||
name_len = strlen(name);
|
||
} else if (name_len == TV_CSTRING) {
|
||
name_len = strlen(name);
|
||
}
|
||
|
||
semsg(_("E1122: Variable is locked: %*s"), (int)name_len, name);
|
||
|
||
return true;
|
||
}
|
||
|
||
/// Check whether variable is fixed (DI_FLAGS_FIX)
|
||
///
|
||
/// Also gives an error message.
|
||
///
|
||
/// @param[in] flags di_flags attribute value.
|
||
/// @param[in] name Variable name, for use in error message.
|
||
/// @param[in] name_len Variable name length. Use #TV_TRANSLATE to translate
|
||
/// variable name and compute the length. Use #TV_CSTRING
|
||
/// to compute the length with strlen() without
|
||
/// translating.
|
||
///
|
||
/// Both #TV_… values are used for optimization purposes:
|
||
/// variable name with its length is needed only in case
|
||
/// of error, when no error occurs computing them is
|
||
/// a waste of CPU resources. This especially applies to
|
||
/// gettext.
|
||
///
|
||
/// @return True if variable is fixed, false otherwise.
|
||
bool var_check_fixed(const int flags, const char *name, size_t name_len)
|
||
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
if (flags & DI_FLAGS_FIX) {
|
||
if (name_len == TV_TRANSLATE) {
|
||
name = _(name);
|
||
name_len = strlen(name);
|
||
} else if (name_len == TV_CSTRING) {
|
||
name_len = strlen(name);
|
||
}
|
||
semsg(_("E795: Cannot delete variable %.*s"), (int)name_len, name);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// Check if name is a valid name to assign funcref to
|
||
///
|
||
/// @param[in] name Possible function/funcref name.
|
||
/// @param[in] new_var True if it is a name for a variable.
|
||
///
|
||
/// @return false in case of success, true in case of failure. Also gives an
|
||
/// error message if appropriate.
|
||
bool var_wrong_func_name(const char *const name, const bool new_var)
|
||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
||
{
|
||
// Allow for w: b: s: and t:.
|
||
// Allow autoload variable.
|
||
if (!(vim_strchr("wbst", (uint8_t)name[0]) != NULL && name[1] == ':')
|
||
&& !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') ? name[2] : name[0])
|
||
&& vim_strchr(name, '#') == NULL) {
|
||
semsg(_("E704: Funcref variable name must start with a capital: %s"), name);
|
||
return true;
|
||
}
|
||
// Don't allow hiding a function. When "v" is not NULL we might be
|
||
// assigning another function to the same var, the type is checked
|
||
// below.
|
||
if (new_var && function_exists(name, false)) {
|
||
semsg(_("E705: Variable name conflicts with existing function: %s"), name);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// Check if a variable name is valid
|
||
///
|
||
/// @param[in] varname Variable name to check.
|
||
///
|
||
/// @return false when variable name is not valid, true when it is. Also gives
|
||
/// an error message if appropriate.
|
||
bool valid_varname(const char *varname)
|
||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
||
{
|
||
for (const char *p = varname; *p != NUL; p++) {
|
||
if (!eval_isnamec1((int)(uint8_t)(*p))
|
||
&& (p == varname || !ascii_isdigit(*p))
|
||
&& *p != AUTOLOAD_CHAR) {
|
||
semsg(_(e_illvar), varname);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// Implements the logic to retrieve local variable and option values.
|
||
/// Used by "getwinvar()" "gettabvar()" "gettabwinvar()" "getbufvar()".
|
||
///
|
||
/// @param deftv default value if not found
|
||
/// @param htname 't'ab, 'w'indow or 'b'uffer local
|
||
/// @param tp can be NULL
|
||
/// @param buf ignored if htname is not 'b'
|
||
static void get_var_from(const char *varname, typval_T *rettv, typval_T *deftv, int htname,
|
||
tabpage_T *tp, win_T *win, buf_T *buf)
|
||
{
|
||
bool done = false;
|
||
const bool do_change_curbuf = buf != NULL && htname == 'b';
|
||
|
||
emsg_off++;
|
||
|
||
rettv->v_type = VAR_STRING;
|
||
rettv->vval.v_string = NULL;
|
||
|
||
if (varname != NULL && tp != NULL && win != NULL && (htname != 'b' || buf != NULL)) {
|
||
// Set curwin to be our win, temporarily. Also set the tabpage,
|
||
// otherwise the window is not valid. Only do this when needed,
|
||
// autocommands get blocked.
|
||
// If we have a buffer reference avoid the switching, we're saving and
|
||
// restoring curbuf directly.
|
||
const bool need_switch_win = !(tp == curtab && win == curwin) && !do_change_curbuf;
|
||
switchwin_T switchwin;
|
||
if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) {
|
||
if (*varname == '&' && htname != 't') {
|
||
buf_T *const save_curbuf = curbuf;
|
||
|
||
// Change curbuf so the option is read from the correct buffer.
|
||
if (do_change_curbuf) {
|
||
curbuf = buf;
|
||
}
|
||
|
||
if (varname[1] == NUL) {
|
||
// get all window-local or buffer-local options in a dict
|
||
dict_T *opts = get_winbuf_options(htname == 'b');
|
||
|
||
if (opts != NULL) {
|
||
tv_dict_set_ret(rettv, opts);
|
||
done = true;
|
||
}
|
||
} else if (eval_option(&varname, rettv, true) == OK) {
|
||
// Local option
|
||
done = true;
|
||
}
|
||
|
||
curbuf = save_curbuf;
|
||
} else if (*varname == NUL) {
|
||
const ScopeDictDictItem *v;
|
||
// Empty string: return a dict with all the local variables.
|
||
if (htname == 'b') {
|
||
v = &buf->b_bufvar;
|
||
} else if (htname == 'w') {
|
||
v = &win->w_winvar;
|
||
} else {
|
||
v = &tp->tp_winvar;
|
||
}
|
||
tv_copy(&v->di_tv, rettv);
|
||
done = true;
|
||
} else {
|
||
hashtab_T *ht;
|
||
|
||
if (htname == 'b') {
|
||
ht = &buf->b_vars->dv_hashtab;
|
||
} else if (htname == 'w') {
|
||
ht = &win->w_vars->dv_hashtab;
|
||
} else {
|
||
ht = &tp->tp_vars->dv_hashtab;
|
||
}
|
||
|
||
// Look up the variable.
|
||
const dictitem_T *const v = find_var_in_ht(ht, htname, varname, strlen(varname), false);
|
||
if (v != NULL) {
|
||
tv_copy(&v->di_tv, rettv);
|
||
done = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (need_switch_win) {
|
||
// restore previous notion of curwin
|
||
restore_win(&switchwin, true);
|
||
}
|
||
}
|
||
|
||
if (!done && deftv->v_type != VAR_UNKNOWN) {
|
||
// use the default value
|
||
tv_copy(deftv, rettv);
|
||
}
|
||
|
||
emsg_off--;
|
||
}
|
||
|
||
/// getwinvar() and gettabwinvar()
|
||
///
|
||
/// @param off 1 for gettabwinvar()
|
||
static void getwinvar(typval_T *argvars, typval_T *rettv, int off)
|
||
{
|
||
tabpage_T *tp;
|
||
|
||
if (off == 1) {
|
||
tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
|
||
} else {
|
||
tp = curtab;
|
||
}
|
||
win_T *const win = find_win_by_nr(&argvars[off], tp);
|
||
const char *const varname = tv_get_string_chk(&argvars[off + 1]);
|
||
|
||
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] flags Option flags.
|
||
/// @param[out] error Whether an error occurred.
|
||
///
|
||
/// @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, OptIndex opt_idx, const char *option, bool *error)
|
||
{
|
||
OptVal value = NIL_OPTVAL;
|
||
char nbuf[NUMBUFLEN];
|
||
bool err = false;
|
||
const bool is_tty_opt = is_tty_option(option);
|
||
const bool option_has_bool = !is_tty_opt && option_has_type(opt_idx, kOptValTypeBoolean);
|
||
const bool option_has_num = !is_tty_opt && option_has_type(opt_idx, kOptValTypeNumber);
|
||
const bool option_has_str = is_tty_opt || option_has_type(opt_idx, kOptValTypeString);
|
||
|
||
if (!is_tty_opt && (get_option(opt_idx)->flags & kOptFlagFunc) && 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 (option_has_bool || option_has_num) {
|
||
varnumber_T n = option_has_num ? tv_get_number_chk(tv, &err) : tv_get_bool_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.
|
||
err = true;
|
||
semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string);
|
||
}
|
||
}
|
||
value = option_has_num ? NUMBER_OPTVAL((OptInt)n) : BOOLEAN_OPTVAL(TRISTATE_FROM_INT(n));
|
||
} else if (option_has_str) {
|
||
// Avoid setting string option to a boolean or a special value.
|
||
if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
|
||
const char *strval = tv_get_string_buf_chk(tv, nbuf);
|
||
err = strval == NULL;
|
||
value = CSTR_TO_OPTVAL(strval);
|
||
} else if (!is_tty_opt) {
|
||
err = true;
|
||
emsg(_(e_stringreq));
|
||
}
|
||
} else {
|
||
abort(); // This should never happen.
|
||
}
|
||
|
||
if (error != NULL) {
|
||
*error = err;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
/// Convert an option value to typval.
|
||
///
|
||
/// @param[in] value Option value to convert.
|
||
/// @param numbool Whether to convert boolean values to number.
|
||
/// Used for backwards compatibility.
|
||
///
|
||
/// @return OptVal converted to typval.
|
||
typval_T optval_as_tv(OptVal value, bool numbool)
|
||
{
|
||
typval_T rettv = { .v_type = VAR_SPECIAL, .vval = { .v_special = kSpecialVarNull } };
|
||
|
||
switch (value.type) {
|
||
case kOptValTypeNil:
|
||
break;
|
||
case kOptValTypeBoolean:
|
||
if (numbool) {
|
||
rettv.v_type = VAR_NUMBER;
|
||
rettv.vval.v_number = value.data.boolean;
|
||
} else if (value.data.boolean != kNone) {
|
||
rettv.v_type = VAR_BOOL;
|
||
rettv.vval.v_bool = value.data.boolean == kTrue;
|
||
}
|
||
break; // return v:null for None boolean value.
|
||
case kOptValTypeNumber:
|
||
rettv.v_type = VAR_NUMBER;
|
||
rettv.vval.v_number = value.data.number;
|
||
break;
|
||
case kOptValTypeString:
|
||
rettv.v_type = VAR_STRING;
|
||
rettv.vval.v_string = value.data.string.data;
|
||
break;
|
||
}
|
||
|
||
return rettv;
|
||
}
|
||
|
||
/// 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)
|
||
{
|
||
OptIndex opt_idx = find_option(varname);
|
||
if (opt_idx == kOptInvalid) {
|
||
semsg(_(e_unknown_option2), varname);
|
||
return;
|
||
}
|
||
|
||
bool error = false;
|
||
OptVal value = tv_to_optval(varp, opt_idx, varname, &error);
|
||
|
||
if (!error) {
|
||
const char *errmsg = set_option_value_handle_tty(varname, opt_idx, value, OPT_LOCAL);
|
||
|
||
if (errmsg) {
|
||
emsg(errmsg);
|
||
}
|
||
}
|
||
optval_free(value);
|
||
}
|
||
|
||
/// "setwinvar()" and "settabwinvar()" functions
|
||
static void setwinvar(typval_T *argvars, int off)
|
||
{
|
||
if (check_secure()) {
|
||
return;
|
||
}
|
||
|
||
tabpage_T *tp = NULL;
|
||
if (off == 1) {
|
||
tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
|
||
} else {
|
||
tp = curtab;
|
||
}
|
||
win_T *const win = find_win_by_nr(&argvars[off], tp);
|
||
const char *varname = tv_get_string_chk(&argvars[off + 1]);
|
||
typval_T *varp = &argvars[off + 2];
|
||
|
||
if (win == NULL || varname == NULL) {
|
||
return;
|
||
}
|
||
|
||
bool need_switch_win = !(tp == curtab && win == curwin);
|
||
switchwin_T switchwin;
|
||
if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) {
|
||
if (*varname == '&') {
|
||
set_option_from_tv(varname + 1, varp);
|
||
} else {
|
||
const size_t varname_len = strlen(varname);
|
||
char *const winvarname = xmalloc(varname_len + 3);
|
||
memcpy(winvarname, "w:", 2);
|
||
memcpy(winvarname + 2, varname, varname_len + 1);
|
||
set_var(winvarname, varname_len + 2, varp, true);
|
||
xfree(winvarname);
|
||
}
|
||
}
|
||
if (need_switch_win) {
|
||
restore_win(&switchwin, true);
|
||
}
|
||
}
|
||
|
||
// reset v:option_new, v:option_old, v:option_oldlocal, v:option_oldglobal,
|
||
// v:option_type, and v:option_command.
|
||
void reset_v_option_vars(void)
|
||
{
|
||
set_vim_var_string(VV_OPTION_NEW, NULL, -1);
|
||
set_vim_var_string(VV_OPTION_OLD, NULL, -1);
|
||
set_vim_var_string(VV_OPTION_OLDLOCAL, NULL, -1);
|
||
set_vim_var_string(VV_OPTION_OLDGLOBAL, NULL, -1);
|
||
set_vim_var_string(VV_OPTION_COMMAND, NULL, -1);
|
||
set_vim_var_string(VV_OPTION_TYPE, NULL, -1);
|
||
}
|
||
|
||
/// Add an assert error to v:errors.
|
||
void assert_error(garray_T *gap)
|
||
{
|
||
typval_T *tv = get_vim_var_tv(VV_ERRORS);
|
||
|
||
if (tv->v_type != VAR_LIST || tv->vval.v_list == NULL) {
|
||
// Make sure v:errors is a list.
|
||
set_vim_var_list(VV_ERRORS, tv_list_alloc(1));
|
||
}
|
||
tv_list_append_string(get_vim_var_list(VV_ERRORS), gap->ga_data, (ptrdiff_t)gap->ga_len);
|
||
}
|
||
|
||
bool var_exists(const char *var)
|
||
FUNC_ATTR_NONNULL_ALL
|
||
{
|
||
char *tofree;
|
||
bool n = false;
|
||
|
||
// get_name_len() takes care of expanding curly braces
|
||
const char *name = var;
|
||
const int len = get_name_len(&var, &tofree, true, false);
|
||
if (len > 0) {
|
||
typval_T tv;
|
||
|
||
if (tofree != NULL) {
|
||
name = tofree;
|
||
}
|
||
n = eval_variable(name, len, &tv, NULL, false, true) == OK;
|
||
if (n) {
|
||
// Handle d.key, l[idx], f(expr).
|
||
n = handle_subscript(&var, &tv, &EVALARG_EVALUATE, false) == OK;
|
||
if (n) {
|
||
tv_clear(&tv);
|
||
}
|
||
}
|
||
}
|
||
if (*var != NUL) {
|
||
n = false;
|
||
}
|
||
|
||
xfree(tofree);
|
||
return n;
|
||
}
|
||
|
||
static lval_T *redir_lval = NULL;
|
||
static garray_T redir_ga; // Only valid when redir_lval is not NULL.
|
||
static char *redir_endp = NULL;
|
||
static char *redir_varname = NULL;
|
||
|
||
/// Start recording command output to a variable
|
||
///
|
||
/// @param append append to an existing variable
|
||
///
|
||
/// @return OK if successfully completed the setup. FAIL otherwise.
|
||
int var_redir_start(char *name, bool append)
|
||
{
|
||
// Catch a bad name early.
|
||
if (!eval_isnamec1(*name)) {
|
||
emsg(_(e_invarg));
|
||
return FAIL;
|
||
}
|
||
|
||
// Make a copy of the name, it is used in redir_lval until redir ends.
|
||
redir_varname = xstrdup(name);
|
||
|
||
redir_lval = xcalloc(1, sizeof(lval_T));
|
||
|
||
// The output is stored in growarray "redir_ga" until redirection ends.
|
||
ga_init(&redir_ga, (int)sizeof(char), 500);
|
||
|
||
// Parse the variable name (can be a dict or list entry).
|
||
redir_endp = get_lval(redir_varname, NULL, redir_lval, false, false,
|
||
0, FNE_CHECK_START);
|
||
if (redir_endp == NULL || redir_lval->ll_name == NULL
|
||
|| *redir_endp != NUL) {
|
||
clear_lval(redir_lval);
|
||
if (redir_endp != NULL && *redir_endp != NUL) {
|
||
// Trailing characters are present after the variable name
|
||
semsg(_(e_trailing_arg), redir_endp);
|
||
} else {
|
||
semsg(_(e_invarg2), name);
|
||
}
|
||
redir_endp = NULL; // don't store a value, only cleanup
|
||
var_redir_stop();
|
||
return FAIL;
|
||
}
|
||
|
||
// check if we can write to the variable: set it to or append an empty
|
||
// string
|
||
const int called_emsg_before = called_emsg;
|
||
did_emsg = false;
|
||
typval_T tv;
|
||
tv.v_type = VAR_STRING;
|
||
tv.vval.v_string = "";
|
||
if (append) {
|
||
set_var_lval(redir_lval, redir_endp, &tv, true, false, ".");
|
||
} else {
|
||
set_var_lval(redir_lval, redir_endp, &tv, true, false, "=");
|
||
}
|
||
clear_lval(redir_lval);
|
||
if (called_emsg > called_emsg_before) {
|
||
redir_endp = NULL; // don't store a value, only cleanup
|
||
var_redir_stop();
|
||
return FAIL;
|
||
}
|
||
|
||
return OK;
|
||
}
|
||
|
||
/// Append "value[value_len]" to the variable set by var_redir_start().
|
||
/// The actual appending is postponed until redirection ends, because the value
|
||
/// appended may in fact be the string we write to, changing it may cause freed
|
||
/// memory to be used:
|
||
/// :redir => foo
|
||
/// :let foo
|
||
/// :redir END
|
||
void var_redir_str(const char *value, int value_len)
|
||
{
|
||
if (redir_lval == NULL) {
|
||
return;
|
||
}
|
||
|
||
int len;
|
||
if (value_len == -1) {
|
||
len = (int)strlen(value); // Append the entire string
|
||
} else {
|
||
len = value_len; // Append only "value_len" characters
|
||
}
|
||
|
||
ga_grow(&redir_ga, len);
|
||
memmove((char *)redir_ga.ga_data + redir_ga.ga_len, value, (size_t)len);
|
||
redir_ga.ga_len += len;
|
||
}
|
||
|
||
/// Stop redirecting command output to a variable.
|
||
/// Frees the allocated memory.
|
||
void var_redir_stop(void)
|
||
{
|
||
if (redir_lval != NULL) {
|
||
// If there was no error: assign the text to the variable.
|
||
if (redir_endp != NULL) {
|
||
ga_append(&redir_ga, NUL); // Append the trailing NUL.
|
||
typval_T tv;
|
||
tv.v_type = VAR_STRING;
|
||
tv.vval.v_string = redir_ga.ga_data;
|
||
// Call get_lval() again, if it's inside a Dict or List it may
|
||
// have changed.
|
||
redir_endp = get_lval(redir_varname, NULL, redir_lval,
|
||
false, false, 0, FNE_CHECK_START);
|
||
if (redir_endp != NULL && redir_lval->ll_name != NULL) {
|
||
set_var_lval(redir_lval, redir_endp, &tv, false, false, ".");
|
||
}
|
||
clear_lval(redir_lval);
|
||
}
|
||
|
||
// free the collected output
|
||
XFREE_CLEAR(redir_ga.ga_data);
|
||
|
||
XFREE_CLEAR(redir_lval);
|
||
}
|
||
XFREE_CLEAR(redir_varname);
|
||
}
|
||
|
||
/// "gettabvar()" function
|
||
void f_gettabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
const char *const varname = tv_get_string_chk(&argvars[1]);
|
||
tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
|
||
win_T *win = NULL;
|
||
|
||
if (tp != NULL) {
|
||
win = tp == curtab || tp->tp_firstwin == NULL ? firstwin : tp->tp_firstwin;
|
||
}
|
||
|
||
get_var_from(varname, rettv, &argvars[2], 't', tp, win, NULL);
|
||
}
|
||
|
||
/// "gettabwinvar()" function
|
||
void f_gettabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
getwinvar(argvars, rettv, 1);
|
||
}
|
||
|
||
/// "getwinvar()" function
|
||
void f_getwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
getwinvar(argvars, rettv, 0);
|
||
}
|
||
|
||
/// "getbufvar()" function
|
||
void f_getbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
const char *const varname = tv_get_string_chk(&argvars[1]);
|
||
buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
|
||
|
||
get_var_from(varname, rettv, &argvars[2], 'b', curtab, curwin, buf);
|
||
}
|
||
|
||
/// "settabvar()" function
|
||
void f_settabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
if (check_secure()) {
|
||
return;
|
||
}
|
||
|
||
tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
|
||
const char *const varname = tv_get_string_chk(&argvars[1]);
|
||
typval_T *const varp = &argvars[2];
|
||
|
||
if (varname == NULL || tp == NULL) {
|
||
return;
|
||
}
|
||
|
||
tabpage_T *const save_curtab = curtab;
|
||
tabpage_T *const save_lu_tp = lastused_tabpage;
|
||
goto_tabpage_tp(tp, false, false);
|
||
|
||
const size_t varname_len = strlen(varname);
|
||
char *const tabvarname = xmalloc(varname_len + 3);
|
||
memcpy(tabvarname, "t:", 2);
|
||
memcpy(tabvarname + 2, varname, varname_len + 1);
|
||
set_var(tabvarname, varname_len + 2, varp, true);
|
||
xfree(tabvarname);
|
||
|
||
// Restore current tabpage and last accessed tabpage.
|
||
if (valid_tabpage(save_curtab)) {
|
||
goto_tabpage_tp(save_curtab, false, false);
|
||
if (valid_tabpage(save_lu_tp)) {
|
||
lastused_tabpage = save_lu_tp;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// "settabwinvar()" function
|
||
void f_settabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
setwinvar(argvars, 1);
|
||
}
|
||
|
||
/// "setwinvar()" function
|
||
void f_setwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
setwinvar(argvars, 0);
|
||
}
|
||
|
||
/// "setbufvar()" function
|
||
void f_setbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||
{
|
||
if (check_secure()
|
||
|| !tv_check_str_or_nr(&argvars[0])) {
|
||
return;
|
||
}
|
||
const char *varname = tv_get_string_chk(&argvars[1]);
|
||
buf_T *const buf = tv_get_buf(&argvars[0], false);
|
||
typval_T *varp = &argvars[2];
|
||
|
||
if (buf == NULL || varname == NULL) {
|
||
return;
|
||
}
|
||
|
||
if (*varname == '&') {
|
||
aco_save_T aco;
|
||
|
||
// Set curbuf to be our buf, temporarily.
|
||
aucmd_prepbuf(&aco, buf);
|
||
|
||
set_option_from_tv(varname + 1, varp);
|
||
|
||
// reset notion of buffer
|
||
aucmd_restbuf(&aco);
|
||
} else {
|
||
const size_t varname_len = strlen(varname);
|
||
char *const bufvarname = xmalloc(varname_len + 3);
|
||
buf_T *const save_curbuf = curbuf;
|
||
curbuf = buf;
|
||
memcpy(bufvarname, "b:", 2);
|
||
memcpy(bufvarname + 2, varname, varname_len + 1);
|
||
set_var(bufvarname, varname_len + 2, varp, true);
|
||
xfree(bufvarname);
|
||
curbuf = save_curbuf;
|
||
}
|
||
}
|