From b38ce04283e3c166fb6aa44c60f0490cbe657c40 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 25 Oct 2025 18:20:33 -0400 Subject: [PATCH] vim-patch:8.1.1939: code for handling v: variables in generic eval file (#36312) Problem: Code for handling v: variables in generic eval file. Solution: Move v: variables to evalvars.c. (Yegappan Lakshmanan, closes vim/vim#4872) https://github.com/vim/vim/commit/e5cdf153bcb348c68011b308c8988cea42d6ddeb Cherry-pick get_vim_var_name() from 8.2.0149. Cherry-pick evalvars.c changes from 8.2.1788. Co-authored-by: Bram Moolenaar --- src/nvim/cmdexpand.c | 1 + src/nvim/eval.c | 607 +------------------------------ src/nvim/eval.h | 3 - src/nvim/eval/decode.c | 1 + src/nvim/eval/typval_encode.c.h | 1 + src/nvim/eval/vars.c | 621 +++++++++++++++++++++++++++++++- src/nvim/eval/vars.h | 6 +- 7 files changed, 629 insertions(+), 611 deletions(-) diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 728d3227d1..4cc4413380 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -25,6 +25,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.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 f2f2e4a109..dd4ebf7be1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -80,7 +80,6 @@ #include "nvim/tag.h" #include "nvim/types_defs.h" #include "nvim/undo.h" -#include "nvim/version.h" #include "nvim/vim_defs.h" #include "nvim/window.h" @@ -113,13 +112,6 @@ static const char e_cannot_use_partial_here[] static char * const namespace_char = "abglstvw"; -/// Variable used for g: -static ScopeDictDictItem globvars_var; - -/// Old Vim variables such as "v:version" are also available without the "v:". -/// Also in functions. We need a special hashtable for them. -static hashtab_T compat_hashtab; - /// Used for checking if local variables or arguments used in a lambda. bool *eval_lavars_used = NULL; @@ -143,186 +135,11 @@ typedef enum { GLV_STOP, } glv_status_T; -// values for vv_flags: -#define VV_COMPAT 1 // compatible, also used without "v:" -#define VV_RO 2 // read-only -#define VV_RO_SBX 4 // read-only in the sandbox - -#define VV(idx, name, type, flags) \ - [idx] = { \ - .vv_name = (name), \ - .vv_di = { \ - .di_tv = { .v_type = (type) }, \ - .di_flags = 0, \ - .di_key = { 0 }, \ - }, \ - .vv_flags = (flags), \ - } - -#define VIMVAR_KEY_LEN 16 // Maximum length of the key of v:variables - -// Array to hold the value of v: variables. -// The value is in a dictitem, so that it can also be used in the v: scope. -// The reason to use this table anyway is for very quick access to the -// variables with the VV_ defines. -static struct vimvar { - char *vv_name; ///< Name of the variable, without v:. - TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di; ///< Value and name for key (max 16 chars). - char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. -} vimvars[] = { - // VV_ tails differing from upcased string literals: - // VV_CC_FROM "charconvert_from" - // VV_CC_TO "charconvert_to" - // VV_SEND_SERVER "servername" - // VV_REG "register" - // VV_OP "operator" - VV(VV_COUNT, "count", VAR_NUMBER, VV_RO), - VV(VV_COUNT1, "count1", VAR_NUMBER, VV_RO), - VV(VV_PREVCOUNT, "prevcount", VAR_NUMBER, VV_RO), - VV(VV_ERRMSG, "errmsg", VAR_STRING, 0), - VV(VV_WARNINGMSG, "warningmsg", VAR_STRING, 0), - VV(VV_STATUSMSG, "statusmsg", VAR_STRING, 0), - VV(VV_SHELL_ERROR, "shell_error", VAR_NUMBER, VV_RO), - VV(VV_THIS_SESSION, "this_session", VAR_STRING, 0), - VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT + VV_RO), - VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX), - VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO), - VV(VV_TERMREQUEST, "termrequest", VAR_STRING, VV_RO), - VV(VV_FNAME, "fname", VAR_STRING, VV_RO), - VV(VV_LANG, "lang", VAR_STRING, VV_RO), - VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO), - VV(VV_CTYPE, "ctype", VAR_STRING, VV_RO), - VV(VV_CC_FROM, "charconvert_from", VAR_STRING, VV_RO), - VV(VV_CC_TO, "charconvert_to", VAR_STRING, VV_RO), - VV(VV_FNAME_IN, "fname_in", VAR_STRING, VV_RO), - VV(VV_FNAME_OUT, "fname_out", VAR_STRING, VV_RO), - VV(VV_FNAME_NEW, "fname_new", VAR_STRING, VV_RO), - VV(VV_FNAME_DIFF, "fname_diff", VAR_STRING, VV_RO), - VV(VV_CMDARG, "cmdarg", VAR_STRING, VV_RO), - VV(VV_FOLDSTART, "foldstart", VAR_NUMBER, VV_RO_SBX), - VV(VV_FOLDEND, "foldend", VAR_NUMBER, VV_RO_SBX), - VV(VV_FOLDDASHES, "folddashes", VAR_STRING, VV_RO_SBX), - VV(VV_FOLDLEVEL, "foldlevel", VAR_NUMBER, VV_RO_SBX), - VV(VV_PROGNAME, "progname", VAR_STRING, VV_RO), - VV(VV_SEND_SERVER, "servername", VAR_STRING, VV_RO), - VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), - VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), - VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), - VV(VV_REG, "register", VAR_STRING, VV_RO), - VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), - VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), - VV(VV_VAL, "val", VAR_UNKNOWN, VV_RO), - VV(VV_KEY, "key", VAR_UNKNOWN, VV_RO), - VV(VV_PROFILING, "profiling", VAR_NUMBER, VV_RO), - VV(VV_FCS_REASON, "fcs_reason", VAR_STRING, VV_RO), - VV(VV_FCS_CHOICE, "fcs_choice", VAR_STRING, 0), - VV(VV_BEVAL_BUFNR, "beval_bufnr", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_WINNR, "beval_winnr", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_WINID, "beval_winid", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_LNUM, "beval_lnum", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_COL, "beval_col", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_TEXT, "beval_text", VAR_STRING, VV_RO), - VV(VV_SCROLLSTART, "scrollstart", VAR_STRING, 0), - VV(VV_SWAPNAME, "swapname", VAR_STRING, VV_RO), - VV(VV_SWAPCHOICE, "swapchoice", VAR_STRING, 0), - VV(VV_SWAPCOMMAND, "swapcommand", VAR_STRING, VV_RO), - VV(VV_CHAR, "char", VAR_STRING, 0), - VV(VV_MOUSE_WIN, "mouse_win", VAR_NUMBER, 0), - VV(VV_MOUSE_WINID, "mouse_winid", VAR_NUMBER, 0), - VV(VV_MOUSE_LNUM, "mouse_lnum", VAR_NUMBER, 0), - VV(VV_MOUSE_COL, "mouse_col", VAR_NUMBER, 0), - VV(VV_OP, "operator", VAR_STRING, VV_RO), - VV(VV_SEARCHFORWARD, "searchforward", VAR_NUMBER, 0), - VV(VV_HLSEARCH, "hlsearch", VAR_NUMBER, 0), - VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), - VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), - VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, 0), - VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), - VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), - VV(VV_OPTION_OLDLOCAL, "option_oldlocal", VAR_STRING, VV_RO), - VV(VV_OPTION_OLDGLOBAL, "option_oldglobal", VAR_STRING, VV_RO), - VV(VV_OPTION_COMMAND, "option_command", VAR_STRING, VV_RO), - VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), - VV(VV_ERRORS, "errors", VAR_LIST, 0), - VV(VV_FALSE, "false", VAR_BOOL, VV_RO), - VV(VV_TRUE, "true", VAR_BOOL, VV_RO), - VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), - VV(VV_NUMBERMAX, "numbermax", VAR_NUMBER, VV_RO), - VV(VV_NUMBERMIN, "numbermin", VAR_NUMBER, VV_RO), - VV(VV_NUMBERSIZE, "numbersize", VAR_NUMBER, VV_RO), - VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), - VV(VV_TESTING, "testing", VAR_NUMBER, 0), - VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), - VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), - VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), - VV(VV_TYPE_LIST, "t_list", VAR_NUMBER, VV_RO), - VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), - VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), - VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), - VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO), - VV(VV_EVENT, "event", VAR_DICT, VV_RO), - VV(VV_VERSIONLONG, "versionlong", VAR_NUMBER, VV_RO), - VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), - VV(VV_ARGV, "argv", VAR_LIST, VV_RO), - VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), - VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), - VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO), - VV(VV_STACKTRACE, "stacktrace", VAR_LIST, VV_RO), - // Neovim - VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), - VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), - VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), - VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), - VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), - VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), - VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), - VV(VV_RELNUM, "relnum", VAR_NUMBER, VV_RO), - VV(VV_VIRTNUM, "virtnum", VAR_NUMBER, VV_RO), -}; -#undef VV - -// shorthand -#define vv_type vv_di.di_tv.v_type -#define vv_nr vv_di.di_tv.vval.v_number -#define vv_str vv_di.di_tv.vval.v_string -#define vv_list vv_di.di_tv.vval.v_list -#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; - -static partial_T *vvlua_partial; - #include "eval.c.generated.h" static uint64_t last_timer_id = 1; static PMap(uint64_t) timers = MAP_INIT; -static const char *const msgpack_type_names[] = { - [kMPNil] = "nil", - [kMPBoolean] = "boolean", - [kMPInteger] = "integer", - [kMPFloat] = "float", - [kMPString] = "string", - [kMPArray] = "array", - [kMPMap] = "map", - [kMPExt] = "ext", -}; -const list_T *eval_msgpack_type_lists[] = { - [kMPNil] = NULL, - [kMPBoolean] = NULL, - [kMPInteger] = NULL, - [kMPFloat] = NULL, - [kMPString] = NULL, - [kMPArray] = NULL, - [kMPMap] = NULL, - [kMPExt] = NULL, -}; - dict_T *get_v_event(save_v_event_T *sve) { dict_T *v_event = get_vim_var_dict(VV_EVENT); @@ -384,130 +201,11 @@ 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(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); + evalvars_init(); func_init(); - - for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { - struct vimvar *p = &vimvars[i]; - assert(strlen(p->vv_name) <= VIMVAR_KEY_LEN); - STRCPY(p->vv_di.di_key, p->vv_name); - if (p->vv_flags & VV_RO) { - p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - } else if (p->vv_flags & VV_RO_SBX) { - p->vv_di.di_flags = DI_FLAGS_RO_SBX | DI_FLAGS_FIX; - } else { - p->vv_di.di_flags = DI_FLAGS_FIX; - } - - // add to v: scope dict, unless the value is not always available - if (p->vv_type != VAR_UNKNOWN) { - hash_add(&vimvarht, p->vv_di.di_key); - } - if (p->vv_flags & VV_COMPAT) { - // add to compat scope dict - hash_add(&compat_hashtab, p->vv_di.di_key); - } - } - vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; - vimvars[VV_VERSIONLONG].vv_nr = VIM_VERSION_100 * 10000 + highest_patch(); - - dict_T *const msgpack_types_dict = tv_dict_alloc(); - for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { - list_T *const type_list = tv_list_alloc(0); - tv_list_set_lock(type_list, VAR_FIXED); - tv_list_ref(type_list); - dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); - di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; - di->di_tv = (typval_T) { - .v_type = VAR_LIST, - .vval = { .v_list = type_list, }, - }; - eval_msgpack_type_lists[i] = type_list; - if (tv_dict_add(msgpack_types_dict, di) == FAIL) { - // There must not be duplicate items in this dictionary by definition. - abort(); - } - } - msgpack_types_dict->dv_lock = VAR_FIXED; - - set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict); - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); - - set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED)); - set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); - set_vim_var_nr(VV_STDERR, CHAN_STDERR); - set_vim_var_nr(VV_SEARCHFORWARD, 1); - set_vim_var_nr(VV_HLSEARCH, 1); - set_vim_var_nr(VV_COUNT1, 1); - set_vim_var_special(VV_EXITING, kSpecialVarNull); - - set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER); - set_vim_var_nr(VV_TYPE_STRING, VAR_TYPE_STRING); - set_vim_var_nr(VV_TYPE_FUNC, VAR_TYPE_FUNC); - set_vim_var_nr(VV_TYPE_LIST, VAR_TYPE_LIST); - set_vim_var_nr(VV_TYPE_DICT, VAR_TYPE_DICT); - set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT); - set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL); - set_vim_var_nr(VV_TYPE_BLOB, VAR_TYPE_BLOB); - - set_vim_var_bool(VV_FALSE, kBoolVarFalse); - set_vim_var_bool(VV_TRUE, kBoolVarTrue); - set_vim_var_special(VV_NULL, kSpecialVarNull); - set_vim_var_nr(VV_NUMBERMAX, VARNUMBER_MAX); - set_vim_var_nr(VV_NUMBERMIN, VARNUMBER_MIN); - set_vim_var_nr(VV_NUMBERSIZE, sizeof(varnumber_T) * 8); - set_vim_var_nr(VV_MAXCOL, MAXCOL); - - set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); - - vimvars[VV_LUA].vv_type = VAR_PARTIAL; - vvlua_partial = xcalloc(1, sizeof(partial_T)); - vimvars[VV_LUA].vv_partial = vvlua_partial; - // this value shouldn't be printed, but if it is, do not crash - vvlua_partial->pt_name = xmallocz(0); - vvlua_partial->pt_refcount++; - - set_reg_var(0); // default for v:register is not 0 but '"' } #if defined(EXITFREE) -static void evalvars_clear(void) -{ - for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { - struct vimvar *p = &vimvars[i]; - if (p->vv_di.di_tv.v_type == VAR_STRING) { - XFREE_CLEAR(p->vv_str); - } else if (p->vv_di.di_tv.v_type == VAR_LIST) { - tv_list_unref(p->vv_list); - p->vv_list = NULL; - } - } - - partial_unref(vvlua_partial); - vimvars[VV_LUA].vv_partial = vvlua_partial = NULL; - - hash_clear(&vimvarht); - hash_init(&vimvarht); // garbage_collect() will access it - hash_clear(&compat_hashtab); - - // global variables - vars_clear(get_globvar_ht()); - - // Script-local variables. Clear all the variables here. - // The scriptvar_T is cleared later in free_scriptnames(), because a - // variable in one script might hold a reference to the whole scope of - // another script. - for (int i = 1; i <= script_items.ga_len; i++) { - vars_clear(&SCRIPT_VARS(i)); - } -} - void eval_clear(void) { evalvars_clear(); @@ -921,41 +619,6 @@ typval_T *eval_expr_ext(char *arg, exarg_T *eap, const bool use_simple_function) return tv; } -bool is_compatht(const hashtab_T *ht) -{ - return ht == &compat_hashtab; -} - -/// Prepare v: variable "idx" to be used. -/// Save the current typeval in "save_tv" and clear it. -/// When not used yet add the variable to the v: hashtable. -void prepare_vimvar(int idx, typval_T *save_tv) -{ - *save_tv = vimvars[idx].vv_tv; - vimvars[idx].vv_str = NULL; // don't free it now - if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hash_add(&vimvarht, vimvars[idx].vv_di.di_key); - } -} - -/// Restore v: variable "idx" to typeval "save_tv". -/// Note that the v: variable must have been cleared already. -/// When no longer defined, remove the variable from the v: hashtable. -void restore_vimvar(int idx, typval_T *save_tv) -{ - vimvars[idx].vv_tv = *save_tv; - if (vimvars[idx].vv_type != VAR_UNKNOWN) { - return; - } - - hashitem_T *hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); - if (HASHITEM_EMPTY(hi)) { - internal_error("restore_vimvar()"); - } else { - hash_remove(&vimvarht, hi); - } -} - /// 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. @@ -975,7 +638,7 @@ int call_vim_function(const char *func, int argc, typval_T *argv, typval_T *rett ret = FAIL; goto fail; } - pt = vvlua_partial; + pt = get_vim_var_partial(VV_LUA); } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. @@ -2000,116 +1663,6 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) xp->xp_pattern = arg; } -/// 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(). - -static char *varnamebuf = NULL; -static size_t varnamebuflen = 0; - -/// Function to concatenate a prefix and a variable name. -char *cat_prefix_varname(int prefix, const char *name) - FUNC_ATTR_NONNULL_ALL -{ - size_t len = strlen(name) + 3; - - if (len > varnamebuflen) { - xfree(varnamebuf); - len += 10; // some additional space - varnamebuf = xmalloc(len); - varnamebuflen = len; - } - *varnamebuf = (char)prefix; - varnamebuf[1] = ':'; - STRCPY(varnamebuf + 2, name); - return varnamebuf; -} - -/// Function given to ExpandGeneric() to obtain the list of user defined -/// (global/buffer/window/built-in) variable names. -char *get_user_var_name(expand_T *xp, int idx) -{ - static size_t gdone; - static size_t bdone; - static size_t wdone; - static size_t tdone; - static size_t vidx; - static hashitem_T *hi; - - if (idx == 0) { - gdone = bdone = wdone = vidx = 0; - tdone = 0; - } - - // Global variables - hashtab_T *globvarht = get_globvar_ht(); - if (gdone < globvarht->ht_used) { - if (gdone++ == 0) { - hi = globvarht->ht_array; - } else { - hi++; - } - while (HASHITEM_EMPTY(hi)) { - hi++; - } - if (strncmp("g:", xp->xp_pattern, 2) == 0) { - return cat_prefix_varname('g', hi->hi_key); - } - return hi->hi_key; - } - - // b: variables - const hashtab_T *ht = &prevwin_curwin()->w_buffer->b_vars->dv_hashtab; - if (bdone < ht->ht_used) { - if (bdone++ == 0) { - hi = ht->ht_array; - } else { - hi++; - } - while (HASHITEM_EMPTY(hi)) { - hi++; - } - return cat_prefix_varname('b', hi->hi_key); - } - - // w: variables - ht = &prevwin_curwin()->w_vars->dv_hashtab; - if (wdone < ht->ht_used) { - if (wdone++ == 0) { - hi = ht->ht_array; - } else { - hi++; - } - while (HASHITEM_EMPTY(hi)) { - hi++; - } - return cat_prefix_varname('w', hi->hi_key); - } - - // t: variables - ht = &curtab->tp_vars->dv_hashtab; - if (tdone < ht->ht_used) { - if (tdone++ == 0) { - hi = ht->ht_array; - } else { - hi++; - } - while (HASHITEM_EMPTY(hi)) { - hi++; - } - return cat_prefix_varname('t', hi->hi_key); - } - - // v: variables - if (vidx < ARRAY_SIZE(vimvars)) { - return cat_prefix_varname('v', vimvars[vidx++].vv_name); - } - - XFREE_CLEAR(varnamebuf); - varnamebuflen = 0; - return NULL; -} - /// Does not use 'cpo' and always uses 'magic'. /// /// @return true if "pat" matches "text". @@ -3172,7 +2725,7 @@ static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan // in handle_subscript() to parse v:lua, so set it here. if (rettv->v_type == VAR_UNKNOWN && !evaluate && strnequal(s, "v:lua.", 6)) { rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = vvlua_partial; + rettv->vval.v_partial = get_vim_var_partial(VV_LUA); rettv->vval.v_partial->pt_refcount++; } ret = OK; @@ -3470,7 +3023,7 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const } else if (lua_funcname != NULL) { if (evaluate) { rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = vvlua_partial; + rettv->vval.v_partial = get_vim_var_partial(VV_LUA); rettv->vval.v_partial->pt_refcount++; } ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname); @@ -5322,7 +4875,7 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co if (len == 0) { return false; } - partial = vvlua_partial; + partial = get_vim_var_partial(VV_LUA); } else { partial = NULL; } @@ -6181,12 +5734,6 @@ bool eval_isdictc(int c) return ASCII_ISALNUM(c) || c == '_'; } -/// Get typval_T v: variable value. -typval_T *get_vim_var_tv(const VimVarIndex idx) -{ - return &vimvars[idx].vv_tv; -} - /// Set the v:argv list. void set_argv_var(char **argv, int argc) { @@ -6204,7 +5751,7 @@ void set_argv_var(char **argv, int argc) bool is_luafunc(partial_T *partial) FUNC_ATTR_PURE { - return partial == vvlua_partial; + return partial == get_vim_var_partial(VV_LUA); } /// check if special v:lua value for calling lua functions @@ -6439,148 +5986,6 @@ void set_selfdict(typval_T *const rettv, dict_T *const selfdict) make_partial(selfdict, rettv); } -/// Find variable in hashtab. -/// When "varname" is empty returns curwin/curtab/etc vars dictionary. -/// -/// @param[in] ht Hashtab to find variable in. -/// @param[in] htname Hashtab name (first character). -/// @param[in] varname Variable name. -/// @param[in] varname_len Variable name length. -/// @param[in] no_autoload If true then autoload scripts will not be sourced -/// if autoload variable was not found. -/// -/// @return pointer to the dictionary item with the found variable or NULL if it -/// was not found. -dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const varname, - const size_t varname_len, int no_autoload) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -{ - if (varname_len == 0) { - // Must be something like "s:", otherwise "ht" would be NULL. - switch (htname) { - case 's': - return (dictitem_T *)&SCRIPT_SV(current_sctx.sc_sid)->sv_var; - case 'g': - return (dictitem_T *)&globvars_var; - case 'v': - return (dictitem_T *)&vimvars_var; - case 'b': - return (dictitem_T *)&curbuf->b_bufvar; - case 'w': - return (dictitem_T *)&curwin->w_winvar; - case 't': - return (dictitem_T *)&curtab->tp_winvar; - case 'l': - return get_funccal_local_var(); - case 'a': - return get_funccal_args_var(); - } - return NULL; - } - - hashitem_T *hi = hash_find_len(ht, varname, varname_len); - if (HASHITEM_EMPTY(hi)) { - // For global variables we may try auto-loading the script. If it - // 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 == 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()) { - return NULL; - } - hi = hash_find_len(ht, varname, varname_len); - } - if (HASHITEM_EMPTY(hi)) { - return NULL; - } - } - return TV_DICT_HI2DI(hi); -} - -/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. -/// -/// Assigns SID if s: scope is accessed from Lua or anonymous Vimscript. #15994 -/// -/// @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. -/// @param[out] d Scope dictionary. -/// -/// @return Scope hashtab, NULL if name is not valid. -hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char **varname, - dict_T **d) -{ - funccall_T *funccal = get_funccal(); - *d = NULL; - - if (name_len == 0) { - return NULL; - } - if (name_len == 1 || name[1] != ':') { - // name has implicit scope - if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) { - // The name must not start with a colon or #. - return NULL; - } - *varname = name; - - // "version" is "v:version" in all scopes - hashitem_T *hi = hash_find_len(&compat_hashtab, name, name_len); - if (!HASHITEM_EMPTY(hi)) { - return &compat_hashtab; - } - - if (funccal == NULL) { // global variable - *d = get_globvar_dict(); - } else { // l: variable - *d = &funccal->fc_l_vars; - } - goto end; - } - - *varname = name + 2; - if (*name == 'g') { // global variable - *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)) { - // There must be no ':' or '#' in the rest of the name if g: was not used - return NULL; - } - - if (*name == 'b') { // buffer variable - *d = curbuf->b_vars; - } else if (*name == 'w') { // window variable - *d = curwin->w_vars; - } else if (*name == 't') { // tab page variable - *d = curtab->tp_vars; - } else if (*name == 'v') { // v: variable - *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 - *d = &funccal->fc_l_vars; - } else if (*name == 's' // script variable - && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR - || current_sctx.sc_sid == SID_LUA) - && current_sctx.sc_sid <= script_items.ga_len) { - // For anonymous scripts without a script item, create one now so script vars can be used - // Try to resolve lua filename & linenr so it can be shown in last-set messages. - nlua_set_sctx(¤t_sctx); - if (current_sctx.sc_sid == SID_STR || current_sctx.sc_sid == SID_LUA) { - // Create SID if s: scope is accessed from Lua or anon Vimscript. #15994 - new_script_item(NULL, ¤t_sctx.sc_sid); - } - *d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict; - } - -end: - return *d ? &(*d)->dv_hashtab : NULL; -} - /// Make a copy of an item /// /// Lists and Dictionaries are also copied. diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 27069e757e..6490e5662b 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -74,9 +74,6 @@ typedef enum { VAR_FLAVOUR_SHADA = 4, // all uppercase } var_flavour_T; -/// Array mapping values from MessagePackType to corresponding list pointers -extern const list_T *eval_msgpack_type_lists[NUM_MSGPACK_TYPES]; - // Struct passed to get_v_event() and restore_v_event(). typedef struct { bool sve_did_save; diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index cc57a8841c..dcd2de2160 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -16,6 +16,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/eval/vars.h" #include "nvim/eval_defs.h" #include "nvim/garray.h" #include "nvim/gettext_defs.h" diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index c0e994562c..99b740f7cb 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -255,6 +255,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_encode.h" +#include "nvim/eval/vars.h" #include "nvim/func_attr.h" /// Dummy variable used because some macros need lvalue diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 3ccb2fb437..bb6e58acee 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -33,6 +33,7 @@ #include "nvim/gettext_defs.h" #include "nvim/globals.h" #include "nvim/hashtab.h" +#include "nvim/lua/executor.h" #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" @@ -45,6 +46,7 @@ #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/types_defs.h" +#include "nvim/version.h" #include "nvim/vim_defs.h" #include "nvim/window.h" @@ -65,14 +67,313 @@ 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"); +/// Variable used for g: +static ScopeDictDictItem globvars_var; static dict_T globvardict; // Dict with g: variables /// g: value #define globvarht globvardict.dv_hashtab +/// Old Vim variables such as "v:version" are also available without the "v:". +/// Also in functions. We need a special hashtable for them. +static hashtab_T compat_hashtab; + +// values for vv_flags: +#define VV_COMPAT 1 // compatible, also used without "v:" +#define VV_RO 2 // read-only +#define VV_RO_SBX 4 // read-only in the sandbox + +#define VV(idx, name, type, flags) \ + [idx] = { \ + .vv_name = (name), \ + .vv_di = { \ + .di_tv = { .v_type = (type) }, \ + .di_flags = 0, \ + .di_key = { 0 }, \ + }, \ + .vv_flags = (flags), \ + } + +#define VIMVAR_KEY_LEN 16 // Maximum length of the key of v:variables + +// Array to hold the value of v: variables. +// The value is in a dictitem, so that it can also be used in the v: scope. +// The reason to use this table anyway is for very quick access to the +// variables with the VV_ defines. +static struct vimvar { + char *vv_name; ///< Name of the variable, without v:. + TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di; ///< Value and name for key (max 16 chars). + char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. +} vimvars[] = { + // VV_ tails differing from upcased string literals: + // VV_CC_FROM "charconvert_from" + // VV_CC_TO "charconvert_to" + // VV_SEND_SERVER "servername" + // VV_REG "register" + // VV_OP "operator" + VV(VV_COUNT, "count", VAR_NUMBER, VV_RO), + VV(VV_COUNT1, "count1", VAR_NUMBER, VV_RO), + VV(VV_PREVCOUNT, "prevcount", VAR_NUMBER, VV_RO), + VV(VV_ERRMSG, "errmsg", VAR_STRING, 0), + VV(VV_WARNINGMSG, "warningmsg", VAR_STRING, 0), + VV(VV_STATUSMSG, "statusmsg", VAR_STRING, 0), + VV(VV_SHELL_ERROR, "shell_error", VAR_NUMBER, VV_RO), + VV(VV_THIS_SESSION, "this_session", VAR_STRING, 0), + VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT + VV_RO), + VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX), + VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO), + VV(VV_TERMREQUEST, "termrequest", VAR_STRING, VV_RO), + VV(VV_FNAME, "fname", VAR_STRING, VV_RO), + VV(VV_LANG, "lang", VAR_STRING, VV_RO), + VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO), + VV(VV_CTYPE, "ctype", VAR_STRING, VV_RO), + VV(VV_CC_FROM, "charconvert_from", VAR_STRING, VV_RO), + VV(VV_CC_TO, "charconvert_to", VAR_STRING, VV_RO), + VV(VV_FNAME_IN, "fname_in", VAR_STRING, VV_RO), + VV(VV_FNAME_OUT, "fname_out", VAR_STRING, VV_RO), + VV(VV_FNAME_NEW, "fname_new", VAR_STRING, VV_RO), + VV(VV_FNAME_DIFF, "fname_diff", VAR_STRING, VV_RO), + VV(VV_CMDARG, "cmdarg", VAR_STRING, VV_RO), + VV(VV_FOLDSTART, "foldstart", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDEND, "foldend", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDDASHES, "folddashes", VAR_STRING, VV_RO_SBX), + VV(VV_FOLDLEVEL, "foldlevel", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGNAME, "progname", VAR_STRING, VV_RO), + VV(VV_SEND_SERVER, "servername", VAR_STRING, VV_RO), + VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), + VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), + VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), + VV(VV_REG, "register", VAR_STRING, VV_RO), + VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), + VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), + VV(VV_VAL, "val", VAR_UNKNOWN, VV_RO), + VV(VV_KEY, "key", VAR_UNKNOWN, VV_RO), + VV(VV_PROFILING, "profiling", VAR_NUMBER, VV_RO), + VV(VV_FCS_REASON, "fcs_reason", VAR_STRING, VV_RO), + VV(VV_FCS_CHOICE, "fcs_choice", VAR_STRING, 0), + VV(VV_BEVAL_BUFNR, "beval_bufnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_WINNR, "beval_winnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_WINID, "beval_winid", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_LNUM, "beval_lnum", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_COL, "beval_col", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_TEXT, "beval_text", VAR_STRING, VV_RO), + VV(VV_SCROLLSTART, "scrollstart", VAR_STRING, 0), + VV(VV_SWAPNAME, "swapname", VAR_STRING, VV_RO), + VV(VV_SWAPCHOICE, "swapchoice", VAR_STRING, 0), + VV(VV_SWAPCOMMAND, "swapcommand", VAR_STRING, VV_RO), + VV(VV_CHAR, "char", VAR_STRING, 0), + VV(VV_MOUSE_WIN, "mouse_win", VAR_NUMBER, 0), + VV(VV_MOUSE_WINID, "mouse_winid", VAR_NUMBER, 0), + VV(VV_MOUSE_LNUM, "mouse_lnum", VAR_NUMBER, 0), + VV(VV_MOUSE_COL, "mouse_col", VAR_NUMBER, 0), + VV(VV_OP, "operator", VAR_STRING, VV_RO), + VV(VV_SEARCHFORWARD, "searchforward", VAR_NUMBER, 0), + VV(VV_HLSEARCH, "hlsearch", VAR_NUMBER, 0), + VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), + VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), + VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, 0), + VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), + VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), + VV(VV_OPTION_OLDLOCAL, "option_oldlocal", VAR_STRING, VV_RO), + VV(VV_OPTION_OLDGLOBAL, "option_oldglobal", VAR_STRING, VV_RO), + VV(VV_OPTION_COMMAND, "option_command", VAR_STRING, VV_RO), + VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), + VV(VV_ERRORS, "errors", VAR_LIST, 0), + VV(VV_FALSE, "false", VAR_BOOL, VV_RO), + VV(VV_TRUE, "true", VAR_BOOL, VV_RO), + VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), + VV(VV_NUMBERMAX, "numbermax", VAR_NUMBER, VV_RO), + VV(VV_NUMBERMIN, "numbermin", VAR_NUMBER, VV_RO), + VV(VV_NUMBERSIZE, "numbersize", VAR_NUMBER, VV_RO), + VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), + VV(VV_TESTING, "testing", VAR_NUMBER, 0), + VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), + VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), + VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), + VV(VV_TYPE_LIST, "t_list", VAR_NUMBER, VV_RO), + VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), + VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), + VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), + VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO), + VV(VV_EVENT, "event", VAR_DICT, VV_RO), + VV(VV_VERSIONLONG, "versionlong", VAR_NUMBER, VV_RO), + VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), + VV(VV_ARGV, "argv", VAR_LIST, VV_RO), + VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), + VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO), + VV(VV_STACKTRACE, "stacktrace", VAR_LIST, VV_RO), + // Neovim + VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), + VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), + VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), + VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), + VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), + VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), + VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), + VV(VV_RELNUM, "relnum", VAR_NUMBER, VV_RO), + VV(VV_VIRTNUM, "virtnum", VAR_NUMBER, VV_RO), +}; +#undef VV + +// shorthand +#define vv_type vv_di.di_tv.v_type +#define vv_str vv_di.di_tv.vval.v_string +#define vv_list vv_di.di_tv.vval.v_list +#define vv_tv vv_di.di_tv + +/// Variable used for v: +static ScopeDictDictItem vimvars_var; static dict_T vimvardict; // Dict with v: variables /// v: hashtab #define vimvarht vimvardict.dv_hashtab +static const char *const msgpack_type_names[] = { + [kMPNil] = "nil", + [kMPBoolean] = "boolean", + [kMPInteger] = "integer", + [kMPFloat] = "float", + [kMPString] = "string", + [kMPArray] = "array", + [kMPMap] = "map", + [kMPExt] = "ext", +}; +const list_T *eval_msgpack_type_lists[] = { + [kMPNil] = NULL, + [kMPBoolean] = NULL, + [kMPInteger] = NULL, + [kMPFloat] = NULL, + [kMPString] = NULL, + [kMPArray] = NULL, + [kMPMap] = NULL, + [kMPExt] = NULL, +}; + +#define SCRIPT_SV(id) (SCRIPT_ITEM(id)->sn_vars) +#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab) + +void evalvars_init(void) +{ + 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); + + for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { + struct vimvar *p = &vimvars[i]; + assert(strlen(p->vv_name) <= VIMVAR_KEY_LEN); + STRCPY(p->vv_di.di_key, p->vv_name); + if (p->vv_flags & VV_RO) { + p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + } else if (p->vv_flags & VV_RO_SBX) { + p->vv_di.di_flags = DI_FLAGS_RO_SBX | DI_FLAGS_FIX; + } else { + p->vv_di.di_flags = DI_FLAGS_FIX; + } + + // add to v: scope dict, unless the value is not always available + if (p->vv_type != VAR_UNKNOWN) { + hash_add(&vimvarht, p->vv_di.di_key); + } + if (p->vv_flags & VV_COMPAT) { + // add to compat scope dict + hash_add(&compat_hashtab, p->vv_di.di_key); + } + } + set_vim_var_nr(VV_VERSION, VIM_VERSION_100); + set_vim_var_nr(VV_VERSIONLONG, VIM_VERSION_100 * 10000 + highest_patch()); + + dict_T *const msgpack_types_dict = tv_dict_alloc(); + for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { + list_T *const type_list = tv_list_alloc(0); + tv_list_set_lock(type_list, VAR_FIXED); + tv_list_ref(type_list); + dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); + di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; + di->di_tv = (typval_T) { + .v_type = VAR_LIST, + .vval = { .v_list = type_list, }, + }; + eval_msgpack_type_lists[i] = type_list; + if (tv_dict_add(msgpack_types_dict, di) == FAIL) { + // There must not be duplicate items in this dictionary by definition. + abort(); + } + } + msgpack_types_dict->dv_lock = VAR_FIXED; + + set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict); + set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); + + set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED)); + set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); + set_vim_var_nr(VV_STDERR, CHAN_STDERR); + set_vim_var_nr(VV_SEARCHFORWARD, 1); + set_vim_var_nr(VV_HLSEARCH, 1); + set_vim_var_nr(VV_COUNT1, 1); + set_vim_var_special(VV_EXITING, kSpecialVarNull); + + set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER); + set_vim_var_nr(VV_TYPE_STRING, VAR_TYPE_STRING); + set_vim_var_nr(VV_TYPE_FUNC, VAR_TYPE_FUNC); + set_vim_var_nr(VV_TYPE_LIST, VAR_TYPE_LIST); + set_vim_var_nr(VV_TYPE_DICT, VAR_TYPE_DICT); + set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT); + set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL); + set_vim_var_nr(VV_TYPE_BLOB, VAR_TYPE_BLOB); + + set_vim_var_bool(VV_FALSE, kBoolVarFalse); + set_vim_var_bool(VV_TRUE, kBoolVarTrue); + set_vim_var_special(VV_NULL, kSpecialVarNull); + set_vim_var_nr(VV_NUMBERMAX, VARNUMBER_MAX); + set_vim_var_nr(VV_NUMBERMIN, VARNUMBER_MIN); + set_vim_var_nr(VV_NUMBERSIZE, sizeof(varnumber_T) * 8); + set_vim_var_nr(VV_MAXCOL, MAXCOL); + + set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); + + // vimvars[VV_LUA].vv_type = VAR_PARTIAL; + partial_T *vvlua_partial = xcalloc(1, sizeof(partial_T)); + // this value shouldn't be printed, but if it is, do not crash + vvlua_partial->pt_name = xmallocz(0); + vvlua_partial->pt_refcount++; + set_vim_var_partial(VV_LUA, vvlua_partial); + + set_reg_var(0); // default for v:register is not 0 but '"' +} + +#if defined(EXITFREE) +void evalvars_clear(void) +{ + for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { + struct vimvar *p = &vimvars[i]; + if (p->vv_di.di_tv.v_type == VAR_STRING) { + XFREE_CLEAR(p->vv_str); + } else if (p->vv_di.di_tv.v_type == VAR_LIST) { + tv_list_unref(p->vv_list); + p->vv_list = NULL; + } + } + + partial_unref(get_vim_var_partial(VV_LUA)); + set_vim_var_partial(VV_LUA, NULL); + hash_clear(&vimvarht); + hash_init(&vimvarht); // garbage_collect() will access it + hash_clear(&compat_hashtab); + + // global variables + vars_clear(get_globvar_ht()); + + // Script-local variables. Clear all the variables here. + // The scriptvar_T is cleared later in free_scriptnames(), because a + // variable in one script might hold a reference to the whole scope of + // another script. + for (int i = 1; i <= script_items.ga_len; i++) { + vars_clear(&SCRIPT_VARS(i)); + } +} +#endif + int garbage_collect_globvars(int copyID) { return set_ref_in_ht(&globvarht, copyID, NULL); @@ -254,6 +555,36 @@ int get_spellword(list_T *const list, const char **ret_word) return (int)tv_list_find_nr(list, -1, NULL); } +/// Prepare v: variable "idx" to be used. +/// Save the current typeval in "save_tv" and clear it. +/// When not used yet add the variable to the v: hashtable. +void prepare_vimvar(int idx, typval_T *save_tv) +{ + *save_tv = vimvars[idx].vv_tv; + vimvars[idx].vv_str = NULL; // don't free it now + if (vimvars[idx].vv_type == VAR_UNKNOWN) { + hash_add(&vimvarht, vimvars[idx].vv_di.di_key); + } +} + +/// Restore v: variable "idx" to typeval "save_tv". +/// Note that the v: variable must have been cleared already. +/// When no longer defined, remove the variable from the v: hashtable. +void restore_vimvar(int idx, typval_T *save_tv) +{ + vimvars[idx].vv_tv = *save_tv; + if (vimvars[idx].vv_type != VAR_UNKNOWN) { + return; + } + + hashitem_T *hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); + if (HASHITEM_EMPTY(hi)) { + internal_error("restore_vimvar()"); + } else { + hash_remove(&vimvarht, hi); + } +} + /// List Vim variables. static void list_vim_vars(int *first) { @@ -1378,7 +1709,7 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit) if (d == NULL) { if (ht == &globvarht) { d = &globvardict; - } else if (is_compatht(ht)) { + } else if (ht == &compat_hashtab) { d = &vimvardict; } else { dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false); @@ -1533,9 +1864,21 @@ dict_T *get_vimvar_dict(void) /// @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); + typval_T *tv_out = get_vim_var_tv(idx); + tv_clear(tv_out); + tv_copy(tv, tv_out); +} + +char *get_vim_var_name(const VimVarIndex idx) + FUNC_ATTR_NONNULL_RET +{ + return vimvars[idx].vv_name; +} + +/// Get typval_T v: variable value. +typval_T *get_vim_var_tv(const VimVarIndex idx) +{ + return &vimvars[idx].vv_tv; } /// Get number v: variable value. @@ -1570,6 +1913,123 @@ char *get_vim_var_str(const VimVarIndex idx) return (char *)tv_get_string(get_vim_var_tv(idx)); } +/// Get Partial v: variable value. Caller must take care of reference count +/// when needed. +partial_T *get_vim_var_partial(const VimVarIndex idx) FUNC_ATTR_PURE +{ + typval_T *tv = get_vim_var_tv(idx); + return tv->vval.v_partial; +} + +/// 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(). + +static char *varnamebuf = NULL; +static size_t varnamebuflen = 0; + +/// Function to concatenate a prefix and a variable name. +char *cat_prefix_varname(int prefix, const char *name) + FUNC_ATTR_NONNULL_ALL +{ + size_t len = strlen(name) + 3; + + if (len > varnamebuflen) { + xfree(varnamebuf); + len += 10; // some additional space + varnamebuf = xmalloc(len); + varnamebuflen = len; + } + *varnamebuf = (char)prefix; + varnamebuf[1] = ':'; + STRCPY(varnamebuf + 2, name); + return varnamebuf; +} + +/// Function given to ExpandGeneric() to obtain the list of user defined +/// (global/buffer/window/built-in) variable names. +char *get_user_var_name(expand_T *xp, int idx) +{ + static size_t gdone; + static size_t bdone; + static size_t wdone; + static size_t tdone; + static size_t vidx; + static hashitem_T *hi; + + if (idx == 0) { + gdone = bdone = wdone = vidx = 0; + tdone = 0; + } + + // Global variables + if (gdone < globvarht.ht_used) { + if (gdone++ == 0) { + hi = globvarht.ht_array; + } else { + hi++; + } + while (HASHITEM_EMPTY(hi)) { + hi++; + } + if (strncmp("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', hi->hi_key); + } + return hi->hi_key; + } + + // b: variables + const hashtab_T *ht = &prevwin_curwin()->w_buffer->b_vars->dv_hashtab; + if (bdone < ht->ht_used) { + if (bdone++ == 0) { + hi = ht->ht_array; + } else { + hi++; + } + while (HASHITEM_EMPTY(hi)) { + hi++; + } + return cat_prefix_varname('b', hi->hi_key); + } + + // w: variables + ht = &prevwin_curwin()->w_vars->dv_hashtab; + if (wdone < ht->ht_used) { + if (wdone++ == 0) { + hi = ht->ht_array; + } else { + hi++; + } + while (HASHITEM_EMPTY(hi)) { + hi++; + } + return cat_prefix_varname('w', hi->hi_key); + } + + // t: variables + ht = &curtab->tp_vars->dv_hashtab; + if (tdone < ht->ht_used) { + if (tdone++ == 0) { + hi = ht->ht_array; + } else { + hi++; + } + while (HASHITEM_EMPTY(hi)) { + hi++; + } + return cat_prefix_varname('t', hi->hi_key); + } + + // v: variables + if (vidx < ARRAY_SIZE(vimvars)) { + return cat_prefix_varname('v', get_vim_var_name((VimVarIndex)vidx++)); + } + + XFREE_CLEAR(varnamebuf); + varnamebuflen = 0; + return NULL; +} + /// Set type of v: variable to the given type. /// /// @param[in] idx Index of variable to set. @@ -1680,6 +2140,17 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val) tv_dict_set_keys_readonly(val); } +/// Set partial 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_partial(const VimVarIndex idx, partial_T *val) +{ + typval_T *tv = get_vim_var_tv(idx); + tv->vval.v_partial = val; +} + /// Set v:register if needed. void set_reg_var(int c) { @@ -1939,6 +2410,148 @@ dictitem_T *find_var(const char *const name, const size_t name_len, hashtab_T ** return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL); } +/// Find variable in hashtab. +/// When "varname" is empty returns curwin/curtab/etc vars dictionary. +/// +/// @param[in] ht Hashtab to find variable in. +/// @param[in] htname Hashtab name (first character). +/// @param[in] varname Variable name. +/// @param[in] varname_len Variable name length. +/// @param[in] no_autoload If true then autoload scripts will not be sourced +/// if autoload variable was not found. +/// +/// @return pointer to the dictionary item with the found variable or NULL if it +/// was not found. +dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const varname, + const size_t varname_len, int no_autoload) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + if (varname_len == 0) { + // Must be something like "s:", otherwise "ht" would be NULL. + switch (htname) { + case 's': + return (dictitem_T *)&SCRIPT_SV(current_sctx.sc_sid)->sv_var; + case 'g': + return (dictitem_T *)&globvars_var; + case 'v': + return (dictitem_T *)&vimvars_var; + case 'b': + return (dictitem_T *)&curbuf->b_bufvar; + case 'w': + return (dictitem_T *)&curwin->w_winvar; + case 't': + return (dictitem_T *)&curtab->tp_winvar; + case 'l': + return get_funccal_local_var(); + case 'a': + return get_funccal_args_var(); + } + return NULL; + } + + hashitem_T *hi = hash_find_len(ht, varname, varname_len); + if (HASHITEM_EMPTY(hi)) { + // For global variables we may try auto-loading the script. If it + // 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 == 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()) { + return NULL; + } + hi = hash_find_len(ht, varname, varname_len); + } + if (HASHITEM_EMPTY(hi)) { + return NULL; + } + } + return TV_DICT_HI2DI(hi); +} + +/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. +/// +/// Assigns SID if s: scope is accessed from Lua or anonymous Vimscript. #15994 +/// +/// @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. +/// @param[out] d Scope dictionary. +/// +/// @return Scope hashtab, NULL if name is not valid. +static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char **varname, + dict_T **d) +{ + funccall_T *funccal = get_funccal(); + *d = NULL; + + if (name_len == 0) { + return NULL; + } + if (name_len == 1 || name[1] != ':') { + // name has implicit scope + if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) { + // The name must not start with a colon or #. + return NULL; + } + *varname = name; + + // "version" is "v:version" in all scopes + hashitem_T *hi = hash_find_len(&compat_hashtab, name, name_len); + if (!HASHITEM_EMPTY(hi)) { + return &compat_hashtab; + } + + if (funccal == NULL) { // global variable + *d = get_globvar_dict(); + } else { // l: variable + *d = &funccal->fc_l_vars; + } + goto end; + } + + *varname = name + 2; + if (*name == 'g') { // global variable + *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)) { + // There must be no ':' or '#' in the rest of the name if g: was not used + return NULL; + } + + if (*name == 'b') { // buffer variable + *d = curbuf->b_vars; + } else if (*name == 'w') { // window variable + *d = curwin->w_vars; + } else if (*name == 't') { // tab page variable + *d = curtab->tp_vars; + } else if (*name == 'v') { // v: variable + *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 + *d = &funccal->fc_l_vars; + } else if (*name == 's' // script variable + && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR + || current_sctx.sc_sid == SID_LUA) + && current_sctx.sc_sid <= script_items.ga_len) { + // For anonymous scripts without a script item, create one now so script vars can be used + // Try to resolve lua filename & linenr so it can be shown in last-set messages. + nlua_set_sctx(¤t_sctx); + if (current_sctx.sc_sid == SID_STR || current_sctx.sc_sid == SID_LUA) { + // Create SID if s: scope is accessed from Lua or anon Vimscript. #15994 + new_script_item(NULL, ¤t_sctx.sc_sid); + } + *d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict; + } + +end: + return *d ? &(*d)->dv_hashtab : NULL; +} + /// Find the hashtable used for a variable /// /// @param[in] name Variable name, possibly with scope prefix. diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h index 8fec038300..59f6afd0a1 100644 --- a/src/nvim/eval/vars.h +++ b/src/nvim/eval/vars.h @@ -9,7 +9,7 @@ #include "nvim/option_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep -#include "eval/vars.h.generated.h" +/// Array mapping values from MessagePackType to corresponding list pointers +extern const list_T *eval_msgpack_type_lists[NUM_MSGPACK_TYPES]; -#define SCRIPT_SV(id) (SCRIPT_ITEM(id)->sn_vars) -#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab) +#include "eval/vars.h.generated.h"