commit 6fefdd6b5308781749ea6ba0c8bc6f797fc1ac98
parent 729e0acd269fad50bceb0381afffacc1468c2a61
Author: zeertzjq <zeertzjq@outlook.com>
Date: Mon, 6 Oct 2025 06:05:43 +0800
Merge pull request #36035 from janlazo/vim-8.1.1957
vim-patch:8.1.1957,8.2.0200
Diffstat:
17 files changed, 367 insertions(+), 334 deletions(-)
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
@@ -16,6 +16,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/decoration_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/extmark.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
@@ -578,7 +579,7 @@ Object tabpage_del_var(Tabpage tabpage, String name, Arena *arena, Error *err)
Object vim_set_var(String name, Object value, Arena *arena, Error *err)
FUNC_API_DEPRECATED_SINCE(1)
{
- return dict_set_var(&globvardict, name, value, false, true, arena, err);
+ return dict_set_var(get_globvar_dict(), name, value, false, true, arena, err);
}
/// @deprecated
@@ -586,7 +587,7 @@ Object vim_set_var(String name, Object value, Arena *arena, Error *err)
Object vim_del_var(String name, Arena *arena, Error *err)
FUNC_API_DEPRECATED_SINCE(1)
{
- return dict_set_var(&globvardict, name, NIL, true, true, arena, err);
+ return dict_set_var(get_globvar_dict(), name, NIL, true, true, arena, err);
}
static int64_t convert_index(int64_t index)
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
@@ -230,7 +230,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
rv = vim_to_object(&di->di_tv, arena, false);
}
bool type_error = false;
- if (dict == &vimvardict
+ if (dict == get_vimvar_dict()
&& !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
tv_clear(&tv);
if (type_error) {
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
@@ -691,13 +691,13 @@ void nvim_del_current_line(Arena *arena, Error *err)
Object nvim_get_var(String name, Arena *arena, Error *err)
FUNC_API_SINCE(1)
{
- dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ dictitem_T *di = tv_dict_find(get_globvar_dict(), name.data, (ptrdiff_t)name.size);
if (di == NULL) { // try to autoload script
bool found = script_autoload(name.data, name.size, false) && !aborting();
VALIDATE(found, "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
});
- di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ di = tv_dict_find(get_globvar_dict(), name.data, (ptrdiff_t)name.size);
}
VALIDATE((di != NULL), "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
@@ -713,7 +713,7 @@ Object nvim_get_var(String name, Arena *arena, Error *err)
void nvim_set_var(String name, Object value, Error *err)
FUNC_API_SINCE(1)
{
- dict_set_var(&globvardict, name, value, false, false, NULL, err);
+ dict_set_var(get_globvar_dict(), name, value, false, false, NULL, err);
}
/// Removes a global (g:) variable.
@@ -723,7 +723,7 @@ void nvim_set_var(String name, Object value, Error *err)
void nvim_del_var(String name, Error *err)
FUNC_API_SINCE(1)
{
- dict_set_var(&globvardict, name, NIL, true, false, NULL, err);
+ dict_set_var(get_globvar_dict(), name, NIL, true, false, NULL, err);
}
/// Gets a v: variable.
@@ -734,7 +734,7 @@ void nvim_del_var(String name, Error *err)
Object nvim_get_vvar(String name, Arena *arena, Error *err)
FUNC_API_SINCE(1)
{
- return dict_get_value(&vimvardict, name, arena, err);
+ return dict_get_value(get_vimvar_dict(), name, arena, err);
}
/// Sets a v: variable, if it is not readonly.
@@ -745,7 +745,7 @@ Object nvim_get_vvar(String name, Arena *arena, Error *err)
void nvim_set_vvar(String name, Object value, Error *err)
FUNC_API_SINCE(6)
{
- dict_set_var(&vimvardict, name, value, false, false, NULL, err);
+ dict_set_var(get_vimvar_dict(), name, value, false, false, NULL, err);
}
/// Prints a message given by a list of `[text, hl_group]` "chunks".
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
@@ -19,8 +19,8 @@
#include "nvim/change.h"
#include "nvim/drawscreen.h"
#include "nvim/errors.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval.h"
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
@@ -28,8 +28,8 @@
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/errors.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -292,6 +292,8 @@ static struct vimvar {
#define vv_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv
+#define vimvarht get_vimvar_dict()->dv_hashtab
+
/// Variable used for v:
static ScopeDictDictItem vimvars_var;
@@ -392,11 +394,12 @@ varnumber_T num_modulus(varnumber_T n1, varnumber_T n2)
/// Initialize the global and v: variables.
void eval_init(void)
{
+ dict_T *vimvardict = get_vimvar_dict();
vimvars[VV_VERSION].vv_nr = VIM_VERSION_100;
- init_var_dict(&globvardict, &globvars_var, VAR_DEF_SCOPE);
- init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE);
- vimvardict.dv_lock = VAR_FIXED;
+ init_var_dict(get_globvar_dict(), &globvars_var, VAR_DEF_SCOPE);
+ init_var_dict(vimvardict, &vimvars_var, VAR_SCOPE);
+ vimvardict->dv_lock = VAR_FIXED;
hash_init(&compat_hashtab);
func_init();
@@ -504,7 +507,7 @@ static void evalvars_clear(void)
hash_clear(&compat_hashtab);
// global variables
- vars_clear(&globvarht);
+ vars_clear(get_globvar_ht());
// Script-local variables. Clear all the variables here.
// The scriptvar_T is cleared later in free_scriptnames(), because a
@@ -535,200 +538,6 @@ void eval_clear(void)
#endif
-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);
-}
-
-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;
-}
-
void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip)
{
*evalarg = (evalarg_T){ .eval_flags = skip ? 0 : EVAL_EVALUATE };
@@ -1098,7 +907,7 @@ typval_T *eval_expr(char *arg, exarg_T *eap)
return eval_expr_ext(arg, eap, false);
}
-static typval_T *eval_expr_ext(char *arg, exarg_T *eap, const bool use_simple_function)
+typval_T *eval_expr_ext(char *arg, exarg_T *eap, const bool use_simple_function)
{
typval_T *tv = xmalloc(sizeof(*tv));
evalarg_T evalarg;
@@ -1157,78 +966,6 @@ void restore_vimvar(int idx, typval_T *save_tv)
}
}
-/// 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);
-}
-
/// Call some Vim script function and return the result in "*rettv".
/// Uses argv[0] to argv[argc - 1] for the function arguments. argv[argc]
/// should have type VAR_UNKNOWN.
@@ -1499,7 +1236,7 @@ static glv_status_T get_lval_dict_item(lval_T *lp, char *name, char *key, int le
if (lp->ll_di == NULL) {
// Can't add "v:" or "a:" variable.
- if (lp->ll_dict == &vimvardict
+ if (lp->ll_dict == get_vimvar_dict()
|| &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) {
semsg(_(e_illvar), name);
return GLV_FAIL;
@@ -2277,18 +2014,6 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
xp->xp_pattern = arg;
}
-/// 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);
-}
-
/// Local string buffer for the next two functions to store a variable name
/// with its prefix. Allocated in cat_prefix_varname(), freed later in
/// get_user_var_name().
@@ -2331,9 +2056,10 @@ char *get_user_var_name(expand_T *xp, int idx)
}
// Global variables
- if (gdone < globvarht.ht_used) {
+ hashtab_T *globvarht = get_globvar_ht();
+ if (gdone < globvarht->ht_used) {
if (gdone++ == 0) {
- hi = globvarht.ht_array;
+ hi = globvarht->ht_array;
} else {
hi++;
}
@@ -2566,7 +2292,7 @@ int eval0(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg)
/// If "arg" is a simple function call without arguments then call it and return
/// the result. Otherwise return NOTDONE.
-static int may_call_simple_func(const char *arg, typval_T *rettv)
+int may_call_simple_func(const char *arg, typval_T *rettv)
{
const char *parens = strstr(arg, "()");
int r = NOTDONE;
@@ -4803,7 +4529,7 @@ bool garbage_collect(bool testing)
}
// global variables
- ABORTING(set_ref_in_ht)(&globvarht, copyID, NULL);
+ ABORTING(garbage_collect_globvars)(copyID);
// function-local variables
ABORTING(set_ref_in_call_stack)(copyID);
@@ -6871,16 +6597,6 @@ typval_T *get_vim_var_tv(const VimVarIndex idx)
return &vimvars[idx].vv_tv;
}
-/// 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)
-{
- tv_clear(&vimvars[idx].vv_di.di_tv);
- tv_copy(tv, &vimvars[idx].vv_di.di_tv);
-}
-
/// Set the v:argv list.
void set_argv_var(char **argv, int argc)
{
@@ -7178,7 +6894,7 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va
// worked find the variable again. Don't auto-load a script if it was
// loaded already, otherwise it would be loaded every time when
// checking if a function name is a Funcref variable.
- if (ht == &globvarht && !no_autoload) {
+ if (ht == get_globvar_ht() && !no_autoload) {
// Note: script_autoload() may make "hi" invalid. It must either
// be obtained again or not used.
if (!script_autoload(varname, varname_len, false) || aborting()) {
@@ -7228,7 +6944,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
}
if (funccal == NULL) { // global variable
- *d = &globvardict;
+ *d = get_globvar_dict();
} else { // l: variable
*d = &funccal->fc_l_vars;
}
@@ -7237,7 +6953,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
*varname = name + 2;
if (*name == 'g') { // global variable
- *d = &globvardict;
+ *d = get_globvar_dict();
} else if (name_len > 2
&& (memchr(name + 2, ':', name_len - 2) != NULL
|| memchr(name + 2, AUTOLOAD_CHAR, name_len - 2) != NULL)) {
@@ -7252,7 +6968,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
} else if (*name == 't') { // tab page variable
*d = curtab->tp_vars;
} else if (*name == 'v') { // v: variable
- *d = &vimvardict;
+ *d = get_vimvar_dict();
} else if (*name == 'a' && funccal != NULL) { // function argument
*d = &funccal->fc_l_avars;
} else if (*name == 'l' && funccal != NULL) { // local variable
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
@@ -2427,7 +2427,7 @@ bool tv_dict_get_callback(dict_T *const d, const char *const key, const ptrdiff_
/// If the name is wrong give an error message and return true.
int tv_dict_wrong_func_name(dict_T *d, typval_T *tv, const char *name)
{
- return (d == &globvardict || &d->dv_hashtab == get_funccal_local_ht())
+ return (d == get_globvar_dict() || &d->dv_hashtab == get_funccal_local_ht())
&& tv_is_func(*tv)
&& var_wrong_func_name(name, true);
}
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
@@ -65,6 +65,19 @@ static const char e_setting_v_str_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);
@@ -94,6 +107,153 @@ void set_internal_string_var(const char *name, char *value) // NOLINT(readabili
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)
{
@@ -1335,6 +1495,49 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap
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
{
@@ -1839,7 +2042,7 @@ void vars_clear_ext(hashtab_T *ht, bool free_val)
/// Delete a variable from hashtab "ht" at item "hi".
/// Clear the variable value and free the dictitem.
-void delete_var(hashtab_T *ht, hashitem_T *hi)
+static void delete_var(hashtab_T *ht, hashitem_T *hi)
{
dictitem_T *di = TV_DICT_HI2DI(hi);
@@ -2569,6 +2772,125 @@ bool var_exists(const char *var)
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)
{
diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h
@@ -13,6 +13,3 @@
#define SCRIPT_SV(id) (SCRIPT_ITEM(id)->sn_vars)
#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab)
-
-/// v: hashtab
-#define vimvarht vimvardict.dv_hashtab
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
@@ -3374,7 +3374,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
} else if (colored_ccline->cmdfirstc == ':') {
TRY_WRAP(&err, {
err_errmsg = N_("E5408: Unable to get g:Nvim_color_cmdline callback: %s");
- dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"),
+ dgc_ret = tv_dict_get_callback(get_globvar_dict(), S_LEN("Nvim_color_cmdline"),
&color_cb);
});
can_free_cb = true;
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
@@ -533,7 +533,7 @@ static int put_view(FILE *fd, win_T *wp, tabpage_T *tp, bool add_edit, unsigned
static int store_session_globals(FILE *fd)
{
- TV_DICT_ITER(&globvardict, this_var, {
+ TV_DICT_ITER(get_globvar_dict(), this_var, {
if ((this_var->di_tv.v_type == VAR_NUMBER
|| this_var->di_tv.v_type == VAR_STRING)
&& var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) {
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
@@ -178,10 +178,6 @@ EXTERN long emsg_assert_fails_lnum INIT( = 0);
EXTERN char *emsg_assert_fails_context INIT( = NULL);
EXTERN bool did_endif INIT( = false); // just had ":endif"
-EXTERN dict_T vimvardict; // Dict with v: variables
-EXTERN dict_T globvardict; // Dict with g: variables
-/// g: value
-#define globvarht globvardict.dv_hashtab
EXTERN int did_emsg; // incremented by emsg() when a
// message is displayed or thrown
EXTERN bool called_vim_beep; // set if vim_beep() is called
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
@@ -327,9 +327,9 @@ static dict_T *nlua_get_var_scope(lua_State *lstate)
dict_T *dict = NULL;
Error err = ERROR_INIT;
if (strequal(scope, "g")) {
- dict = &globvardict;
+ dict = get_globvar_dict();
} else if (strequal(scope, "v")) {
- dict = &vimvardict;
+ dict = get_vimvar_dict();
} else if (strequal(scope, "b")) {
buf_T *buf = find_buffer_by_handle(handle, &err);
if (buf) {
@@ -410,7 +410,7 @@ int nlua_setvar(lua_State *lstate)
tv_dict_add(dict, di);
} else {
bool type_error = false;
- if (dict == &vimvardict
+ if (dict == get_vimvar_dict()
&& !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
tv_clear(&tv);
if (type_error) {
@@ -448,7 +448,7 @@ int nlua_getvar(lua_State *lstate)
const char *name = luaL_checklstring(lstate, 3, &len);
dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
- if (di == NULL && dict == &globvardict) { // try to autoload script
+ if (di == NULL && dict == get_globvar_dict()) { // try to autoload script
if (!script_autoload(name, len, false) || aborting()) {
return 0; // nil
}
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
@@ -14,9 +14,9 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/errors.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/garray.h"
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
@@ -803,11 +803,12 @@ static const void *var_shada_iter(const void *const iter, const char **const nam
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
{
const hashitem_T *hi;
- const hashitem_T *hifirst = globvarht.ht_array;
- const size_t hinum = (size_t)globvarht.ht_mask + 1;
+ hashtab_T *globvarht = get_globvar_ht();
+ const hashitem_T *hifirst = globvarht->ht_array;
+ const size_t hinum = (size_t)globvarht->ht_mask + 1;
*name = NULL;
if (iter == NULL) {
- hi = globvarht.ht_array;
+ hi = globvarht->ht_array;
while ((size_t)(hi - hifirst) < hinum
&& (HASHITEM_EMPTY(hi)
|| !(var_flavour(hi->hi_key) & flavour))) {
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
@@ -15,9 +15,9 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/errors.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
@@ -2358,7 +2358,7 @@ static char *get_config_string(char *key)
Object obj = dict_get_value(curbuf->b_vars, cstr_as_string(key), NULL, &err);
api_clear_error(&err);
if (obj.type == kObjectTypeNil) {
- obj = dict_get_value(&globvardict, cstr_as_string(key), NULL, &err);
+ obj = dict_get_value(get_globvar_dict(), cstr_as_string(key), NULL, &err);
api_clear_error(&err);
}
if (obj.type == kObjectTypeString) {