diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 52e028c55f..e503072e02 100644 --- 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 index 1ab577c270..4f45e2ccc4 100644 --- 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 index 5b7f5420bf..02bb37adb7 100644 --- 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 index e0b114b83a..1200660722 100644 --- 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 index c7efa66966..da04d37931 100644 --- 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 index 5f14be6f96..f4345fbe59 100644 --- 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 index 87f2f5c584..555e0d6a50 100644 --- 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 index 343e8ac6da..c9816c72a1 100644 --- 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 index b6959434f6..8fec038300 100644 --- 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 index 8794133f24..fe4e30307f 100644 --- 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 index a1b550302a..cd23f92220 100644 --- 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 index a194504c23..e371fce26f 100644 --- 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 index a57ca059b7..fc7290d843 100644 --- 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 index 723c8ff346..37e8c59e00 100644 --- 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 index 09bbe6d8f3..8cea0f1bd9 100644 --- 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 index 8e42880b02..b20ac1ba77 100644 --- 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 index 8032eb11f5..5fe5fd3f3a 100644 --- 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) {