From 6c5bbf07d988ef55e5e8ba8d70b62c1f0885261b Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 00:33:26 -0800 Subject: eval.c: factor out eval/funcs.c #11828 close #11828 ref #5081 cf. vim patch 7.4.2063 --- src/nvim/CMakeLists.txt | 5 +- src/nvim/eval.c | 13617 +++++----------------------------------------- src/nvim/eval.h | 116 +- src/nvim/eval/funcs.c | 10920 +++++++++++++++++++++++++++++++++++++ src/nvim/eval/funcs.h | 24 + src/nvim/globals.h | 8 + src/nvim/lua/executor.c | 2 +- 7 files changed, 12359 insertions(+), 12333 deletions(-) create mode 100644 src/nvim/eval/funcs.c create mode 100644 src/nvim/eval/funcs.h (limited to 'src/nvim') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index aec258d2fc..2bfa193a63 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -150,6 +150,7 @@ set(CONV_SOURCES diff.c edit.c eval.c + eval/funcs.c ex_cmds.c ex_docmd.c fileio.c @@ -180,10 +181,10 @@ if(NOT MSVC) check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE) if(HAS_WSTATIC_IN_INLINE) set_source_files_properties( - eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion") + eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion") else() set_source_files_properties( - eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") + eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") endif() # tree-sitter: inlined external project, we don't maintain it. #10124 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 47c094f49d..0dceca671b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5,111 +5,52 @@ * eval.c: Expression evaluation. */ -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include "nvim/assert.h" -#include "nvim/vim.h" -#include "nvim/ascii.h" #ifdef HAVE_LOCALE_H # include #endif -#include "nvim/eval.h" + +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" -#include "nvim/context.h" #include "nvim/cursor.h" -#include "nvim/diff.h" #include "nvim/edit.h" -#include "nvim/ex_cmds.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/executor.h" +#include "nvim/eval/gc.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" +#include "nvim/ex_session.h" #include "nvim/fileio.h" -#include "nvim/os/fileio.h" -#include "nvim/func_attr.h" -#include "nvim/fold.h" #include "nvim/getchar.h" -#include "nvim/hashtab.h" -#include "nvim/iconv.h" -#include "nvim/if_cscope.h" -#include "nvim/indent_c.h" -#include "nvim/indent.h" +#include "nvim/lua/executor.h" #include "nvim/mark.h" -#include "nvim/math.h" -#include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/menu.h" -#include "nvim/message.h" #include "nvim/misc1.h" -#include "nvim/keymap.h" -#include "nvim/map.h" -#include "nvim/file_search.h" -#include "nvim/garray.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/os_unix.h" +#include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/shell.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" -#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/ex_session.h" -#include "nvim/sha256.h" #include "nvim/sign.h" -#include "nvim/spell.h" -#include "nvim/state.h" -#include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/tag.h" #include "nvim/ui.h" -#include "nvim/main.h" -#include "nvim/mouse.h" -#include "nvim/terminal.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" -#include "nvim/eval/encode.h" -#include "nvim/eval/decode.h" -#include "nvim/os/os.h" -#include "nvim/event/libuv_process.h" -#include "nvim/os/pty_process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" -#include "nvim/event/time.h" -#include "nvim/os/time.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/server.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" -#include "nvim/os/dl.h" -#include "nvim/os/input.h" -#include "nvim/event/loop.h" -#include "nvim/lib/kvec.h" -#include "nvim/lib/khash.h" -#include "nvim/lib/queue.h" -#include "nvim/lua/executor.h" -#include "nvim/eval/typval.h" -#include "nvim/eval/executor.h" -#include "nvim/eval/gc.h" -#include "nvim/macros.h" + // TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead @@ -118,61 +59,9 @@ // Character used as separator in autoload function/variable names. #define AUTOLOAD_CHAR '#' -/* - * Structure returned by get_lval() and used by set_var_lval(). - * For a plain name: - * "name" points to the variable name. - * "exp_name" is NULL. - * "tv" is NULL - * For a magic braces name: - * "name" points to the expanded variable name. - * "exp_name" is non-NULL, to be freed later. - * "tv" is NULL - * For an index in a list: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the (first) list item value - * "li" points to the (first) list item - * "range", "n1", "n2" and "empty2" indicate what items are used. - * For an existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the dict item value - * "newkey" is NULL - * For a non-existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the Dictionary typval_T - * "newkey" is the key for the new item. - */ -typedef struct lval_S { - const char *ll_name; ///< Start of variable name (can be NULL). - size_t ll_name_len; ///< Length of the .ll_name. - char *ll_exp_name; ///< NULL or expanded name in allocated memory. - typval_T *ll_tv; ///< Typeval of item being used. If "newkey" - ///< isn't NULL it's the Dict to which to add the item. - listitem_T *ll_li; ///< The list item or NULL. - list_T *ll_list; ///< The list or NULL. - int ll_range; ///< TRUE when a [i:j] range was used. - long ll_n1; ///< First index for list. - long ll_n2; ///< Second index for list range. - int ll_empty2; ///< Second index is empty: [i:]. - dict_T *ll_dict; ///< The Dictionary or NULL. - dictitem_T *ll_di; ///< The dictitem or NULL. - char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL. -} lval_T; - static char *e_letunexp = N_("E18: Unexpected characters in :let"); static char *e_missbrac = N_("E111: Missing ']'"); -static char *e_listarg = N_("E686: Argument of %s must be a List"); -static char *e_listdictarg = N_( - "E712: Argument of %s must be a List or Dictionary"); -static char *e_listreq = N_("E714: List required"); -static char *e_dictreq = N_("E715: Dictionary required"); -static char *e_stringreq = N_("E928: String required"); -static char *e_toomanyarg = N_("E118: Too many arguments for function: %s"); -static char *e_dictkey = N_("E716: Key not present in Dictionary: %s"); static char *e_funcexts = N_( "E122: Function %s already exists, add ! to replace it"); static char *e_funcdict = N_("E717: Dictionary entry already exists"); @@ -181,8 +70,6 @@ static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); -static const char *e_readonlyvar = N_( - "E46: Cannot change read-only variable \"%.*s\""); // TODO(ZyX-I): move to eval/executor static char *e_letwrong = N_("E734: Wrong variable type for %s="); @@ -221,32 +108,6 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ -/// Describe data to return from find_some_match() -typedef enum { - kSomeMatch, ///< Data for match(). - kSomeMatchEnd, ///< Data for matchend(). - kSomeMatchList, ///< Data for matchlist(). - kSomeMatchStr, ///< Data for matchstr(). - kSomeMatchStrPos, ///< Data for matchstrpos(). -} SomeMatchType; - -/// trans_function_name() flags -typedef enum { - TFN_INT = 1, ///< May use internal function name - TFN_QUIET = 2, ///< Do not emit error messages. - TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading. - TFN_NO_DEREF = 8, ///< Do not dereference a Funcref. - TFN_READ_ONLY = 16, ///< Will not change the variable. -} TransFunctionNameFlags; - -/// get_lval() flags -typedef enum { - GLV_QUIET = TFN_QUIET, ///< Do not emit error messages. - GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading. - GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change - ///< the value (prevents error message). -} GetLvalFlags; - // flags used in uf_flags #define FC_ABORT 0x01 // abort function on error #define FC_RANGE 0x02 // function accepts range @@ -290,13 +151,6 @@ struct funccall_S { garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". }; -///< Structure used by trans_function_name() -typedef struct { - dict_T *fd_dict; ///< Dictionary used. - char_u *fd_newkey; ///< New key in "dict" in allocated memory. - dictitem_T *fd_di; ///< Dictionary item used. -} funcdict_T; - /* * Info used by a ":for" loop. */ @@ -446,71 +300,13 @@ static partial_T *vvlua_partial; /// v: hashtab #define vimvarht vimvardict.dv_hashtab -typedef struct { - TimeWatcher tw; - int timer_id; - int repeat_count; - int refcount; - int emsg_count; ///< Errors in a repeating timer. - long timeout; - bool stopped; - bool paused; - Callback callback; -} timer_T; - -typedef void (*FunPtr)(void); - -/// Prototype of C function that implements VimL function -typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); - -/// Structure holding VimL function definition -typedef struct fst { - char *name; ///< Name of the function. - uint8_t min_argc; ///< Minimal number of arguments. - uint8_t max_argc; ///< Maximal number of arguments. - VimLFunc func; ///< Function implementation. - FunPtr data; ///< Userdata for function implementation. -} VimLFuncDef; - -KHASH_MAP_INIT_STR(functions, VimLFuncDef) - -/// Type of assert_* check being performed -typedef enum -{ - ASSERT_EQUAL, - ASSERT_NOTEQUAL, - ASSERT_MATCH, - ASSERT_NOTMATCH, - ASSERT_INRANGE, - ASSERT_OTHER, -} assert_type_T; - -/// Type for dict_list function -typedef enum { - kDictListKeys, ///< List dictionary keys. - kDictListValues, ///< List dictionary values. - kDictListItems, ///< List dictionary contents: [keys, values]. -} DictListType; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval.c.generated.h" #endif -#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */ -#define FNE_CHECK_START 2 /* find_name_end(): check name starts with - valid character */ - static uint64_t last_timer_id = 1; static PMap(uint64_t) *timers = NULL; -/// Dummy va_list for passing to vim_snprintf -/// -/// Used because: -/// - passing a NULL pointer doesn't work when va_list isn't a pointer -/// - locally in the function results in a "used before set" warning -/// - using va_start() to initialize it gives "function with fixed args" error -static va_list dummy_ap; - static const char *const msgpack_type_names[] = { [kMPNil] = "nil", [kMPBoolean] = "boolean", @@ -1018,8 +814,8 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) return ret; } -static int eval_expr_typval(const typval_T *expr, typval_T *argv, - int argc, typval_T *rettv) +int eval_expr_typval(const typval_T *expr, typval_T *argv, + int argc, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int dummy; @@ -1064,7 +860,7 @@ static int eval_expr_typval(const typval_T *expr, typval_T *argv, /// Like eval_to_bool() but using a typval_T instead of a string. /// Works for string, funcref and partial. -static bool eval_expr_to_bool(const typval_T *expr, bool *error) +bool eval_expr_to_bool(const typval_T *expr, bool *error) FUNC_ATTR_NONNULL_ARG(1, 2) { typval_T argv, rettv; @@ -1238,7 +1034,7 @@ static void restore_vimvar(int idx, typval_T *save_tv) } /// If there is a window for "curbuf", make it the current window. -static void find_win_for_curbuf(void) +void find_win_for_curbuf(void) { for (wininfo_T *wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) { if (wip->wi_win != NULL) { @@ -2276,9 +2072,9 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, /// /// @return A pointer to just after the name, including indexes. Returns NULL /// for a parsing error, but it is still needed to free items in lp. -static char_u *get_lval(char_u *const name, typval_T *const rettv, - lval_T *const lp, const bool unlet, const bool skip, - const int flags, const int fne_flags) +char_u *get_lval(char_u *const name, typval_T *const rettv, + lval_T *const lp, const bool unlet, const bool skip, + const int flags, const int fne_flags) FUNC_ATTR_NONNULL_ARG(1, 3) { dictitem_T *v; @@ -2599,7 +2395,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, /* * Clear lval "lp" that was filled by get_lval(). */ -static void clear_lval(lval_T *lp) +void clear_lval(lval_T *lp) { xfree(lp->ll_exp_name); xfree(lp->ll_newkey); @@ -3583,7 +3379,7 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) * * Return OK or FAIL. */ -static int eval1(char_u **arg, typval_T *rettv, int evaluate) +int eval1(char_u **arg, typval_T *rettv, int evaluate) { int result; typval_T var2; @@ -4887,8 +4683,8 @@ eval_index( /// @param[in] evaluate If not true, rettv is not populated. /// /// @return OK or FAIL. -static int get_option_tv(const char **const arg, typval_T *const rettv, - const bool evaluate) +int get_option_tv(const char **const arg, typval_T *const rettv, + const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { long numval; @@ -6227,88 +6023,6 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -#ifdef INCLUDE_GENERATED_DECLARATIONS - -#ifdef _MSC_VER -// This prevents MSVC from replacing the functions with intrinsics, -// and causing errors when trying to get their addresses in funcs.generated.h -#pragma function (ceil) -#pragma function (floor) -#endif - -PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES -# include "funcs.generated.h" -PRAGMA_DIAG_POP -#endif - -/* - * Function given to ExpandGeneric() to obtain the list of internal - * or user defined function names. - */ -char_u *get_function_name(expand_T *xp, int idx) -{ - static int intidx = -1; - char_u *name; - - if (idx == 0) - intidx = -1; - if (intidx < 0) { - name = get_user_func_name(xp, idx); - if (name != NULL) - return name; - } - while ( (size_t)++intidx < ARRAY_SIZE(functions) - && functions[intidx].name[0] == '\0') { - } - - if ((size_t)intidx >= ARRAY_SIZE(functions)) { - return NULL; - } - - const char *const key = functions[intidx].name; - const size_t key_len = strlen(key); - memcpy(IObuff, key, key_len); - IObuff[key_len] = '('; - if (functions[intidx].max_argc == 0) { - IObuff[key_len + 1] = ')'; - IObuff[key_len + 2] = NUL; - } else { - IObuff[key_len + 1] = NUL; - } - return IObuff; -} - -/* - * Function given to ExpandGeneric() to obtain the list of internal or - * user defined variable or function names. - */ -char_u *get_expr_name(expand_T *xp, int idx) -{ - static int intidx = -1; - char_u *name; - - if (idx == 0) - intidx = -1; - if (intidx < 0) { - name = get_function_name(xp, idx); - if (name != NULL) - return name; - } - return get_user_var_name(xp, ++intidx); -} - -/// Find internal function in hash functions -/// -/// @param[in] name Name of the function. -/// -/// Returns pointer to the function definition or NULL if not found. -static const VimLFuncDef *find_internal_func(const char *const name) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL -{ - size_t len = strlen(name); - return find_internal_func_gperf(name, len); -} - /// Return name of the function corresponding to `name` /// /// If `name` points to variable that is either a function or partial then @@ -6715,7 +6429,7 @@ call_func( emsg_funcname(N_("E933: Function was deleted: %s"), name); break; case ERROR_TOOMANY: - emsg_funcname(e_toomanyarg, name); + emsg_funcname(_(e_toomanyarg), name); break; case ERROR_TOOFEW: emsg_funcname(N_("E119: Not enough arguments for function: %s"), @@ -6762,193 +6476,9 @@ static void emsg_funcname(char *ermsg, const char_u *name) } } -/* - * Return TRUE for a non-zero Number and a non-empty String. - */ -static int non_zero_arg(typval_T *argvars) -{ - return ((argvars[0].v_type == VAR_NUMBER - && argvars[0].vval.v_number != 0) - || (argvars[0].v_type == VAR_SPECIAL - && argvars[0].vval.v_special == kSpecialVarTrue) - || (argvars[0].v_type == VAR_STRING - && argvars[0].vval.v_string != NULL - && *argvars[0].vval.v_string != NUL)); -} - -/********************************************* - * Implementation of the built-in functions - */ - - -// Apply a floating point C function on a typval with one float_T. -// -// Some versions of glibc on i386 have an optimization that makes it harder to -// call math functions indirectly from inside an inlined function, causing -// compile-time errors. Avoid `inline` in that case. #3072 -static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T f; - float_T (*function)(float_T) = (float_T (*)(float_T))fptr; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &f)) { - rettv->vval.v_float = function(f); - } else { - rettv->vval.v_float = 0.0; - } -} - -static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - Error err = ERROR_INIT; - Object result = fn(VIML_INTERNAL_CALL, args, &err); - - if (ERROR_SET(&err)) { - emsgf_multiline((const char *)e_api_error, err.msg); - goto end; - } - - if (!object_to_vim(result, rettv, &err)) { - EMSG2(_("Error converting the call result: %s"), err.msg); - } - -end: - api_free_array(args); - api_free_object(result); - api_clear_error(&err); -} - -/* - * "abs(expr)" function - */ -static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_FLOAT) { - float_op_wrapper(argvars, rettv, (FunPtr)&fabs); - } else { - varnumber_T n; - bool error = false; - - n = tv_get_number_chk(&argvars[0], &error); - if (error) { - rettv->vval.v_number = -1; - } else if (n > 0) { - rettv->vval.v_number = n; - } else { - rettv->vval.v_number = -n; - } - } -} - -/* - * "add(list, item)" function - */ -static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 1; // Default: failed. - if (argvars[0].v_type == VAR_LIST) { - list_T *const l = argvars[0].vval.v_list; - if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { - tv_list_append_tv(l, &argvars[1]); - tv_copy(&argvars[0], rettv); - } - } else { - EMSG(_(e_listreq)); - } -} - -/* - * "and(expr, expr)" function - */ -static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) - & tv_get_number_chk(&argvars[1], NULL); -} - - -/// "api_info()" function -static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - Dictionary metadata = api_metadata(); - (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); - api_free_dictionary(metadata); -} - -// "append(lnum, string/list)" function -static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(&argvars[0]); - - set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); -} - -// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, true, &argvars[2], rettv); - } -} - -static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_UNKNOWN) { - // use the current window - rettv->vval.v_number = ARGCOUNT; - } else if (argvars[0].v_type == VAR_NUMBER - && tv_get_number(&argvars[0]) == -1) { - // use the global argument list - rettv->vval.v_number = GARGCOUNT; - } else { - // use the argument list of the specified window - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) { - rettv->vval.v_number = WARGCOUNT(wp); - } else { - rettv->vval.v_number = -1; - } - } -} - -/* - * "argidx()" function - */ -static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = curwin->w_arg_idx; -} - -/// "arglistid" function -static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - win_T *wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp != NULL) { - rettv->vval.v_number = wp->w_alist->id; - } -} - /// Get the argument list for a given window -static void get_arglist_as_rettv(aentry_T *arglist, int argcount, - typval_T *rettv) +void get_arglist_as_rettv(aentry_T *arglist, int argcount, + typval_T *rettv) { tv_list_alloc_ret(rettv, argcount); if (arglist != NULL) { @@ -6959,46 +6489,8 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount, } } -/* - * "argv(nr)" function - */ -static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - aentry_T *arglist = NULL; - int argcount = -1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type == VAR_UNKNOWN) { - arglist = ARGLIST; - argcount = ARGCOUNT; - } else if (argvars[1].v_type == VAR_NUMBER - && tv_get_number(&argvars[1]) == -1) { - arglist = GARGLIST; - argcount = GARGCOUNT; - } else { - win_T *wp = find_win_by_nr_or_id(&argvars[1]); - if (wp != NULL) { - // Use the argument list of the specified window - arglist = WARGLIST(wp); - argcount = WARGCOUNT(wp); - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - int idx = tv_get_number_chk(&argvars[0], NULL); - if (arglist != NULL && idx >= 0 && idx < argcount) { - rettv->vval.v_string = (char_u *)xstrdup( - (const char *)alist_name(&arglist[idx])); - } else if (idx == -1) { - get_arglist_as_rettv(arglist, argcount, rettv); - } - } else { - get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); - } -} - // Prepare "gap" for an assert error and add the sourcing position. -static void prepare_assert_error(garray_T *gap) +void prepare_assert_error(garray_T *gap) { char buf[NUMBUFLEN]; @@ -7052,9 +6544,9 @@ static void ga_concat_esc(garray_T *gap, char_u *str) } // Fill "gap" with information about an assert error. -static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, - char_u *exp_str, typval_T *exp_tv, - typval_T *got_tv, assert_type_T atype) +void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, + char_u *exp_str, typval_T *exp_tv, + typval_T *got_tv, assert_type_T atype) { char_u *tofree; @@ -7096,7 +6588,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, } // Add an assert error to v:errors. -static void assert_error(garray_T *gap) +void assert_error(garray_T *gap) { struct vimvar *vp = &vimvars[VV_ERRORS]; @@ -7108,7 +6600,7 @@ static void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -static int assert_equal_common(typval_T *argvars, assert_type_T atype) +int assert_equal_common(typval_T *argvars, assert_type_T atype) FUNC_ATTR_NONNULL_ALL { garray_T ga; @@ -7125,7 +6617,7 @@ static int assert_equal_common(typval_T *argvars, assert_type_T atype) return 0; } -static int assert_equalfile(typval_T *argvars) +int assert_equalfile(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; @@ -7179,68 +6671,86 @@ static int assert_equalfile(typval_T *argvars) return 0; } -static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_inrange(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; + bool error = false; + const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); + const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); + const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (!called_vim_beep) { + if (error) { + return 0; + } + if (actual < lower || actual > upper) { + garray_T ga; prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not beep: "); - ga_concat(&ga, (const char_u *)cmd); + + char msg[55]; + vim_snprintf(msg, sizeof(msg), + "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", + lower, upper); + fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + ASSERT_INRANGE); assert_error(&ga); ga_clear(&ga); - ret = 1; + return 1; } - - suppress_errthrow = false; - emsg_on_display = false; - rettv->vval.v_number = ret; -} - -// "assert_equal(expected, actual[, msg])" function -static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); + return 0; } -// "assert_equalfile(fname-one, fname-two)" function -static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +// Common for assert_true() and assert_false(). +int assert_bool(typval_T *argvars, bool is_true) + FUNC_ATTR_NONNULL_ALL { - rettv->vval.v_number = assert_equalfile(argvars); -} + bool error = false; + garray_T ga; -// "assert_notequal(expected, actual[, msg])" function -static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); + if ((argvars[0].v_type != VAR_NUMBER + || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true + || error) + && (argvars[0].v_type != VAR_SPECIAL + || (argvars[0].vval.v_special + != (SpecialVarValue) (is_true + ? kSpecialVarTrue + : kSpecialVarFalse)))) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], + (char_u *)(is_true ? "True" : "False"), + NULL, &argvars[0], ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; } -/// "assert_report(msg) -static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_exception(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { - garray_T ga; + garray_T ga; + const char *const error = tv_get_string_chk(&argvars[0]); + if (vimvars[VV_EXCEPTION].vv_str == NULL) { prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); + ga_concat(&ga, (char_u *)"v:exception is not set"); assert_error(&ga); ga_clear(&ga); - rettv->vval.v_number = 1; -} - -/// "assert_exception(string[, msg])" function -static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_exception(argvars); + return 1; + } else if (error != NULL + && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; } -/// "assert_fails(cmd [, error [, msg]])" function -static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_fails(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; @@ -7289,94 +6799,10 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg_silent = false; emsg_on_display = false; set_vim_var_string(VV_ERRMSG, NULL, 0); - rettv->vval.v_number = ret; + return ret; } -static int assert_inrange(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); - const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); - const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - - if (error) { - return 0; - } - if (actual < lower || actual > upper) { - garray_T ga; - prepare_assert_error(&ga); - - char msg[55]; - vim_snprintf(msg, sizeof(msg), - "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", - lower, upper); - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], - ASSERT_INRANGE); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -// Common for assert_true() and assert_false(). -static int assert_bool(typval_T *argvars, bool is_true) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - garray_T ga; - - if ((argvars[0].v_type != VAR_NUMBER - || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true - || error) - && (argvars[0].v_type != VAR_SPECIAL - || (argvars[0].vval.v_special - != (SpecialVarValue) (is_true - ? kSpecialVarTrue - : kSpecialVarFalse)))) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], - (char_u *)(is_true ? "True" : "False"), - NULL, &argvars[0], ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -static int assert_exception(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - const char *const error = tv_get_string_chk(&argvars[0]); - if (vimvars[VV_EXCEPTION].vv_str == NULL) { - prepare_assert_error(&ga); - ga_concat(&ga, (char_u *)"v:exception is not set"); - assert_error(&ga); - ga_clear(&ga); - return 1; - } else if (error != NULL - && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -// "assert_false(actual[, msg])" function -static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, false); -} - -static int assert_match_common(typval_T *argvars, assert_type_T atype) +int assert_match_common(typval_T *argvars, assert_type_T atype) FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; @@ -7398,355 +6824,6 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype) return 0; } -/// "assert_inrange(lower, upper[, msg])" function -static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_inrange(argvars); -} - -/// "assert_match(pattern, actual[, msg])" function -static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); -} - -/// "assert_notmatch(pattern, actual[, msg])" function -static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); -} - -// "assert_true(actual[, msg])" function -static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, true); -} - -/* - * "atan2()" function - */ -static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T fx; - float_T fy; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { - rettv->vval.v_float = atan2(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "browse(save, title, initdir, default)" function - */ -static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; -} - -/* - * "browsedir(title, initdir)" function - */ -static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - f_browse(argvars, rettv, NULL); -} - - -/* - * Find a buffer by number or exact name. - */ -static buf_T *find_buffer(typval_T *avar) -{ - buf_T *buf = NULL; - - if (avar->v_type == VAR_NUMBER) - buf = buflist_findnr((int)avar->vval.v_number); - else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { - buf = buflist_findname_exp(avar->vval.v_string); - if (buf == NULL) { - /* No full path name match, try a match with a URL or a "nofile" - * buffer, these don't use the full path. */ - FOR_ALL_BUFFERS(bp) { - if (bp->b_fname != NULL - && (path_with_url((char *)bp->b_fname) - || bt_nofile(bp) - ) - && STRCMP(bp->b_fname, avar->vval.v_string) == 0) { - buf = bp; - break; - } - } - } - } - return buf; -} - -// "bufadd(expr)" function -static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *name = (char_u *)tv_get_string(&argvars[0]); - - rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); -} - -/* - * "bufexists(expr)" function - */ -static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); -} - -/* - * "buflisted(expr)" function - */ -static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_p_bl); -} - -// "bufload(expr)" function -static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - buf_T *buf = get_buf_arg(&argvars[0]); - - if (buf != NULL && buf->b_ml.ml_mfp == NULL) { - aco_save_T aco; - - aucmd_prepbuf(&aco, buf); - swap_exists_action = SEA_NONE; - open_buffer(false, NULL, 0); - aucmd_restbuf(&aco); - } -} - -/* - * "bufloaded(expr)" function - */ -static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); -} - - -/* - * Get buffer by number or pattern. - */ -static buf_T *tv_get_buf(typval_T *tv, int curtab_only) -{ - char_u *name = tv->vval.v_string; - int save_magic; - char_u *save_cpo; - buf_T *buf; - - if (tv->v_type == VAR_NUMBER) - return buflist_findnr((int)tv->vval.v_number); - if (tv->v_type != VAR_STRING) - return NULL; - if (name == NULL || *name == NUL) - return curbuf; - if (name[0] == '$' && name[1] == NUL) - return lastbuf; - - // Ignore 'magic' and 'cpoptions' here to make scripts portable - save_magic = p_magic; - p_magic = TRUE; - save_cpo = p_cpo; - p_cpo = (char_u *)""; - - buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name), - TRUE, FALSE, curtab_only)); - - p_magic = save_magic; - p_cpo = save_cpo; - - // If not found, try expanding the name, like done for bufexists(). - if (buf == NULL) { - buf = find_buffer(tv); - } - - return buf; -} - -/// Get the buffer from "arg" and give an error and return NULL if it is not -/// valid. -static buf_T * get_buf_arg(typval_T *arg) -{ - buf_T *buf; - - emsg_off++; - buf = tv_get_buf(arg, false); - emsg_off--; - if (buf == NULL) { - EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg)); - } - return buf; -} - -/* - * "bufname(expr)" function - */ -static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - if (buf != NULL && buf->b_fname != NULL) { - rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname); - } -} - -/* - * "bufnr(expr)" function - */ -static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - bool error = false; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - // If the buffer isn't found and the second argument is not zero create a - // new buffer. - const char *name; - if (buf == NULL - && argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error) != 0 - && !error - && (name = tv_get_string_chk(&argvars[0])) != NULL) { - buf = buflist_new((char_u *)name, NULL, 1, 0); - } - - if (buf != NULL) { - rettv->vval.v_number = buf->b_fnum; - } -} - -static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) -{ - if (!tv_check_str_or_nr(&argvars[0])) { - rettv->vval.v_number = -1; - return; - } - - emsg_off++; - buf_T *buf = tv_get_buf(&argvars[0], true); - if (buf == NULL) { // no need to search if buffer was not found - rettv->vval.v_number = -1; - goto end; - } - - int winnr = 0; - int winid; - bool found_buf = false; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - winnr++; - if (wp->w_buffer == buf) { - found_buf = true; - winid = wp->handle; - break; - } - } - rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); -end: - emsg_off--; -} - -/// "bufwinid(nr)" function -static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_win_common(argvars, rettv, false); -} - -/// "bufwinnr(nr)" function -static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_win_common(argvars, rettv, true); -} - -/* - * "byte2line(byte)" function - */ -static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long boff = tv_get_number(&argvars[0]) - 1; - if (boff < 0) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0, - &boff, false); - } -} - -static void byteidx(typval_T *argvars, typval_T *rettv, int comp) -{ - const char *const str = tv_get_string_chk(&argvars[0]); - varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); - rettv->vval.v_number = -1; - if (str == NULL || idx < 0) { - return; - } - - const char *t = str; - for (; idx > 0; idx--) { - if (*t == NUL) { // EOL reached. - return; - } - if (enc_utf8 && comp) { - t += utf_ptr2len((const char_u *)t); - } else { - t += (*mb_ptr2len)((const char_u *)t); - } - } - rettv->vval.v_number = (varnumber_T)(t - str); -} - -/* - * "byteidx()" function - */ -static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - byteidx(argvars, rettv, FALSE); -} - -/* - * "byteidxcomp()" function - */ -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - byteidx(argvars, rettv, TRUE); -} - int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { @@ -7778,11522 +6855,1513 @@ func_call_skip_call: return r; } -/// "call(func, arglist [, dict])" function -static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Find a window: When using a Window ID in any tab page, when using a number +/// in the current tab page. +win_T * find_win_by_nr_or_id(typval_T *vp) { - if (argvars[1].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - if (argvars[1].vval.v_list == NULL) { - return; - } - - char_u *func; - partial_T *partial = NULL; - dict_T *selfdict = NULL; - if (argvars[0].v_type == VAR_FUNC) { - func = argvars[0].vval.v_string; - } else if (argvars[0].v_type == VAR_PARTIAL) { - partial = argvars[0].vval.v_partial; - func = partial_name(partial); - } else { - func = (char_u *)tv_get_string(&argvars[0]); - } - if (*func == NUL) { - return; // type error or empty name - } + int nr = (int)tv_get_number_chk(vp, NULL); - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - selfdict = argvars[2].vval.v_dict; + if (nr >= LOWEST_WIN_ID) { + return win_id2wp(vp); } - func_call(func, &argvars[1], partial, selfdict, rettv); + return find_win_by_nr(vp, NULL); } /* - * "changenr()" function + * Implementation of map() and filter(). */ -static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = curbuf->b_u_seq_cur; -} - -// "chanclose(id[, stream])" function -static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void filter_map(typval_T *argvars, typval_T *rettv, int map) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING - && argvars[1].v_type != VAR_UNKNOWN)) { - EMSG(_(e_invarg)); - return; - } + typval_T *expr; + list_T *l = NULL; + dictitem_T *di; + hashtab_T *ht; + hashitem_T *hi; + dict_T *d = NULL; + typval_T save_val; + typval_T save_key; + int rem = false; + int todo; + char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); + const char *const arg_errmsg = (map + ? N_("map() argument") + : N_("filter() argument")); + int save_did_emsg; + int idx = 0; - ChannelPart part = kChannelPartAll; - if (argvars[1].v_type == VAR_STRING) { - char *stream = (char *)argvars[1].vval.v_string; - if (!strcmp(stream, "stdin")) { - part = kChannelPartStdin; - } else if (!strcmp(stream, "stdout")) { - part = kChannelPartStdout; - } else if (!strcmp(stream, "stderr")) { - part = kChannelPartStderr; - } else if (!strcmp(stream, "rpc")) { - part = kChannelPartRpc; - } else { - EMSG2(_("Invalid channel stream \"%s\""), stream); + if (argvars[0].v_type == VAR_LIST) { + tv_copy(&argvars[0], rettv); + if ((l = argvars[0].vval.v_list) == NULL + || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, + TV_TRANSLATE))) { return; } - } - const char *error; - rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error); - if (!rettv->vval.v_number) { - EMSG(error); - } -} - -// "chansend(id, data)" function -static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) { - // First argument is the channel id and second is the data to write - EMSG(_(e_invarg)); - return; - } - - ptrdiff_t input_len = 0; - char *input = save_tv_as_string(&argvars[1], &input_len, false); - if (!input) { - // Either the error has been handled by save_tv_as_string(), - // or there is no input to send. - return; - } - uint64_t id = argvars[0].vval.v_number; - const char *error = NULL; - rettv->vval.v_number = channel_send(id, input, input_len, &error); - if (error) { - EMSG(error); - } -} - -/* - * "char2nr(string)" function - */ -static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[1].v_type != VAR_UNKNOWN) { - if (!tv_check_num(&argvars[1])) { + } else if (argvars[0].v_type == VAR_DICT) { + tv_copy(&argvars[0], rettv); + if ((d = argvars[0].vval.v_dict) == NULL + || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; } + } else { + EMSG2(_(e_listdictarg), ermsg); + return; } - rettv->vval.v_number = utf_ptr2char( - (const char_u *)tv_get_string(&argvars[0])); -} + expr = &argvars[1]; + // On type errors, the preceding call has already displayed an error + // message. Avoid a misleading error message for an empty string that + // was not passed as argument. + if (expr->v_type != VAR_UNKNOWN) { + prepare_vimvar(VV_VAL, &save_val); -/* - * "cindent(lnum)" function - */ -static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - linenr_T lnum; + // We reset "did_emsg" to be able to detect whether an error + // occurred during evaluation of the expression. + save_did_emsg = did_emsg; + did_emsg = FALSE; - pos = curwin->w_cursor; - lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_c_indent(); - curwin->w_cursor = pos; - } else - rettv->vval.v_number = -1; -} + prepare_vimvar(VV_KEY, &save_key); + if (argvars[0].v_type == VAR_DICT) { + vimvars[VV_KEY].vv_type = VAR_STRING; -/* - * "clearmatches()" function - */ -static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - clear_matches(curwin); -} + ht = &d->dv_hashtab; + hash_lock(ht); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; -/* - * "col(string)" function - */ -static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) { - if (fp->col == MAXCOL) { - /* '> can be MAXCOL, get the length of the line then */ - if (fp->lnum <= curbuf->b_ml.ml_line_count) - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - else - col = MAXCOL; + di = TV_DICT_HI2DI(hi); + if (map + && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) + || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { + break; + } + + vimvars[VV_KEY].vv_str = vim_strsave(di->di_key); + int r = filter_map_one(&di->di_tv, expr, map, &rem); + tv_clear(&vimvars[VV_KEY].vv_tv); + if (r == FAIL || did_emsg) { + break; + } + if (!map && rem) { + if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) + || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { + break; + } + tv_dict_item_remove(d, di); + } + } + } + hash_unlock(ht); } else { - col = fp->col + 1; - /* col(".") when the cursor is on the NUL at the end of the line - * because of "coladd" can be seen as an extra column. */ - if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); - - if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, - curwin->w_virtcol - curwin->w_cursor.coladd)) { - int l; - - if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) - col += l; + assert(argvars[0].v_type == VAR_LIST); + vimvars[VV_KEY].vv_type = VAR_NUMBER; + + for (listitem_T *li = tv_list_first(l); li != NULL;) { + if (map + && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { + break; } + vimvars[VV_KEY].vv_nr = idx; + if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL + || did_emsg) { + break; + } + if (!map && rem) { + li = tv_list_item_remove(l, li); + } else { + li = TV_LIST_ITEM_NEXT(l, li); + } + idx++; } } - } - rettv->vval.v_number = col; -} - -/* - * "complete()" function - */ -static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if ((State & INSERT) == 0) { - EMSG(_("E785: complete() can only be used in Insert mode")); - return; - } - - /* Check for undo allowed here, because if something was already inserted - * the line was already saved for undo and this check isn't done. */ - if (!undo_allowed()) - return; - if (argvars[1].v_type != VAR_LIST) { - EMSG(_(e_invarg)); - return; - } + restore_vimvar(VV_KEY, &save_key); + restore_vimvar(VV_VAL, &save_val); - const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); - if (startcol <= 0) { - return; + did_emsg |= save_did_emsg; } - - set_completion(startcol - 1, argvars[1].vval.v_list); -} - -/* - * "complete_add()" function - */ -static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0); -} - -/* - * "complete_check()" function - */ -static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int saved = RedrawingDisabled; - - RedrawingDisabled = 0; - ins_compl_check_keys(0, true); - rettv->vval.v_number = compl_interrupted; - RedrawingDisabled = saved; } -// "complete_info()" function -static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) + FUNC_ATTR_NONNULL_ARG(1, 2) { - tv_dict_alloc_ret(rettv); + typval_T rettv; + typval_T argv[3]; + int retval = FAIL; - list_T *what_list = NULL; + tv_copy(tv, &vimvars[VV_VAL].vv_tv); + argv[0] = vimvars[VV_KEY].vv_tv; + argv[1] = vimvars[VV_VAL].vv_tv; + if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { + goto theend; + } + if (map) { + // map(): replace the list item value. + tv_clear(tv); + rettv.v_lock = 0; + *tv = rettv; + } else { + bool error = false; - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; + // filter(): when expr is zero remove the item + *remp = (tv_get_number_chk(&rettv, &error) == 0); + tv_clear(&rettv); + // On type error, nothing has been removed; return FAIL to stop the + // loop. The error message was given by tv_get_number_chk(). + if (error) { + goto theend; } - what_list = argvars[0].vval.v_list; } - get_complete_info(what_list, rettv->vval.v_dict); + retval = OK; +theend: + tv_clear(&vimvars[VV_VAL].vv_tv); + return retval; } -/* - * "confirm(message, buttons[, default [, type]])" function - */ -static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void common_function(typval_T *argvars, typval_T *rettv, + bool is_funcref, FunPtr fptr) { - char buf[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *message; - const char *buttons = NULL; - int def = 1; - int type = VIM_GENERIC; - const char *typestr; - bool error = false; - - message = tv_get_string_chk(&argvars[0]); - if (message == NULL) { - error = true; - } - if (argvars[1].v_type != VAR_UNKNOWN) { - buttons = tv_get_string_buf_chk(&argvars[1], buf); - if (buttons == NULL) { - error = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - def = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - typestr = tv_get_string_buf_chk(&argvars[3], buf2); - if (typestr == NULL) { - error = true; - } else { - switch (TOUPPER_ASC(*typestr)) { - case 'E': type = VIM_ERROR; break; - case 'Q': type = VIM_QUESTION; break; - case 'I': type = VIM_INFO; break; - case 'W': type = VIM_WARNING; break; - case 'G': type = VIM_GENERIC; break; - } - } - } - } - } + char_u *s; + char_u *name; + bool use_string = false; + partial_T *arg_pt = NULL; + char_u *trans_name = NULL; - if (buttons == NULL || *buttons == NUL) { - buttons = _("&Ok"); + if (argvars[0].v_type == VAR_FUNC) { + // function(MyFunc, [arg], dict) + s = argvars[0].vval.v_string; + } else if (argvars[0].v_type == VAR_PARTIAL + && argvars[0].vval.v_partial != NULL) { + // function(dict.MyFunc, [arg]) + arg_pt = argvars[0].vval.v_partial; + s = partial_name(arg_pt); + } else { + // function('MyFunc', [arg], dict) + s = (char_u *)tv_get_string(&argvars[0]); + use_string = true; } - if (!error) { - rettv->vval.v_number = do_dialog( - type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false); + if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { + name = s; + trans_name = trans_function_name(&name, false, + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD + | TFN_NO_DEREF, NULL, NULL); + if (*name != NUL) { + s = NULL; + } } -} - -/* - * "copy()" function - */ -static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - var_item_copy(NULL, &argvars[0], rettv, false, 0); -} - -/* - * "count()" function - */ -static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long n = 0; - int ic = 0; - bool error = false; - - if (argvars[2].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[2], &error); - } - - if (argvars[0].v_type == VAR_STRING) { - const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]); - const char_u *p = argvars[0].vval.v_string; - - if (!error && expr != NULL && *expr != NUL && p != NULL) { - if (ic) { - const size_t len = STRLEN(expr); + if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) + || (is_funcref && trans_name == NULL)) { + emsgf(_(e_invarg2), (use_string + ? tv_get_string(&argvars[0]) + : (const char *)s)); + // Don't check an autoload name for existence here. + } else if (trans_name != NULL + && (is_funcref ? find_func(trans_name) == NULL + : !translated_function_exists((const char *)trans_name))) { + emsgf(_("E700: Unknown function: %s"), s); + } else { + int dict_idx = 0; + int arg_idx = 0; + list_T *list = NULL; + if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "", 5) == 0) { + char sid_buf[25]; + int off = *s == 's' ? 2 : 5; - while (*p != NUL) { - if (mb_strnicmp(p, expr, len) == 0) { - n++; - p += len; - } else { - MB_PTR_ADV(p); - } - } - } else { - char_u *next; - while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { - n++; - p = next + STRLEN(expr); - } - } + // Expand s: and into nr_, so that the function can + // also be called from another script. Using trans_function_name() + // would also work, but some plugins depend on the name being + // printable text. + snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); + STRCPY(name, sid_buf); + STRCAT(name, s + off); + } else { + name = vim_strsave(s); } - } else if (argvars[0].v_type == VAR_LIST) { - listitem_T *li; - list_T *l; - long idx; - if ((l = argvars[0].vval.v_list) != NULL) { - li = tv_list_first(l); + if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - idx = tv_get_number_chk(&argvars[3], &error); - if (!error) { - li = tv_list_find(l, idx); - if (li == NULL) { - EMSGN(_(e_listidx), idx); - } - } + // function(name, [args], dict) + arg_idx = 1; + dict_idx = 2; + } else if (argvars[1].v_type == VAR_DICT) { + // function(name, dict) + dict_idx = 1; + } else { + // function(name, [args]) + arg_idx = 1; + } + if (dict_idx > 0) { + if (argvars[dict_idx].v_type != VAR_DICT) { + EMSG(_("E922: expected a dict")); + xfree(name); + goto theend; + } + if (argvars[dict_idx].vval.v_dict == NULL) { + dict_idx = 0; } - if (error) - li = NULL; } - - for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { - n++; + if (arg_idx > 0) { + if (argvars[arg_idx].v_type != VAR_LIST) { + EMSG(_("E923: Second argument of function() must be " + "a list or a dict")); + xfree(name); + goto theend; + } + list = argvars[arg_idx].vval.v_list; + if (tv_list_len(list) == 0) { + arg_idx = 0; } } } - } else if (argvars[0].v_type == VAR_DICT) { - int todo; - dict_T *d; - hashitem_T *hi; + if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) { + partial_T *const pt = xcalloc(1, sizeof(*pt)); - if ((d = argvars[0].vval.v_dict) != NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - EMSG(_(e_invarg)); + // result is a VAR_PARTIAL + if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { + const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); + const int lv_len = tv_list_len(list); + + pt->pt_argc = arg_len + lv_len; + pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); + int i = 0; + for (; i < arg_len; i++) { + tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); + } + if (lv_len > 0) { + TV_LIST_ITER(list, li, { + tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); + }); } } - todo = error ? 0 : (int)d->dv_hashtab.ht_used; - for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { - n++; - } + // For "function(dict.func, [], dict)" and "func" is a partial + // use "dict". That is backwards compatible. + if (dict_idx > 0) { + // The dict is bound explicitly, pt_auto is false + pt->pt_dict = argvars[dict_idx].vval.v_dict; + (pt->pt_dict->dv_refcount)++; + } else if (arg_pt != NULL) { + // If the dict was bound automatically the result is also + // bound automatically. + pt->pt_dict = arg_pt->pt_dict; + pt->pt_auto = arg_pt->pt_auto; + if (pt->pt_dict != NULL) { + (pt->pt_dict->dv_refcount)++; } } + + pt->pt_refcount = 1; + if (arg_pt != NULL && arg_pt->pt_func != NULL) { + pt->pt_func = arg_pt->pt_func; + func_ptr_ref(pt->pt_func); + xfree(name); + } else if (is_funcref) { + pt->pt_func = find_func(trans_name); + func_ptr_ref(pt->pt_func); + xfree(name); + } else { + pt->pt_name = name; + func_ref(name); + } + + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } else { + // result is a VAR_FUNC + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = name; + func_ref(name); } - } else { - EMSG2(_(e_listdictarg), "count()"); } - rettv->vval.v_number = n; +theend: + xfree(trans_name); } -/* - * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function - * - * Checks the existence of a cscope connection. - */ -static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Returns buffer options, variables and other attributes in a dictionary. +dict_T *get_buffer_info(buf_T *buf) { - int num = 0; - const char *dbpath = NULL; - const char *prepend = NULL; - char buf[NUMBUFLEN]; + dict_T *const dict = tv_dict_alloc(); - if (argvars[0].v_type != VAR_UNKNOWN - && argvars[1].v_type != VAR_UNKNOWN) { - num = (int)tv_get_number(&argvars[0]); - dbpath = tv_get_string(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) { - prepend = tv_get_string_buf(&argvars[2], buf); - } - } + tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); + tv_dict_add_str(dict, S_LEN("name"), + buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); + tv_dict_add_nr(dict, S_LEN("lnum"), + buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); + tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); + tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); + tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); + tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); + tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); + tv_dict_add_nr(dict, S_LEN("hidden"), + buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); - rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, - (char_u *)prepend); -} + // Get a reference to buffer variables + tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); -/// "ctxget([{index}])" function -static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - size_t index = 0; - if (argvars[0].v_type == VAR_NUMBER) { - index = argvars[0].vval.v_number; - } else if (argvars[0].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a Number as an argument"); - return; + // List of windows displaying this buffer + list_T *const windows = tv_list_alloc(kListLenMayKnow); + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + tv_list_append_number(windows, (varnumber_T)wp->handle); + } } + tv_dict_add_list(dict, S_LEN("windows"), windows); - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; + if (buf->b_signlist != NULL) { + // List of signs placed in this buffer + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } - Dictionary ctx_dict = ctx_to_dict(ctx); - Error err = ERROR_INIT; - object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); - api_free_dictionary(ctx_dict); - api_clear_error(&err); + return dict; } -/// "ctxpop()" function -static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Get the line number from VimL object +/// +/// @note Unlike tv_get_lnum(), this one supports only "$" special string. +/// +/// @param[in] tv Object to get value from. Is expected to be a number or +/// a special string "$". +/// @param[in] buf Buffer to take last line number from in case tv is "$". May +/// be NULL, in this case "$" results in zero return. +/// +/// @return Line number or 0 in case of error. +linenr_T tv_get_lnum_buf(const typval_T *const tv, + const buf_T *const buf) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { - if (!ctx_restore(NULL, kCtxAll)) { - EMSG(_("Context stack is empty")); + if (tv->v_type == VAR_STRING + && tv->vval.v_string != NULL + && tv->vval.v_string[0] == '$' + && buf != NULL) { + return buf->b_ml.ml_line_count; } + return tv_get_number_chk(tv, NULL); } -/// "ctxpush([{types}])" function -static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, + typval_T *rettv) { - int types = kCtxAll; - if (argvars[0].v_type == VAR_LIST) { - types = 0; - TV_LIST_ITER(argvars[0].vval.v_list, li, { - typval_T *tv_li = TV_LIST_ITEM_TV(li); - if (tv_li->v_type == VAR_STRING) { - if (strequal((char *)tv_li->vval.v_string, "regs")) { - types |= kCtxRegs; - } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { - types |= kCtxJumps; - } else if (strequal((char *)tv_li->vval.v_string, "bufs")) { - types |= kCtxBufs; - } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { - types |= kCtxGVars; - } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { - types |= kCtxSFuncs; - } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { - types |= kCtxFuncs; + if (what_arg->v_type == VAR_UNKNOWN) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + if (is_qf || wp != NULL) { + (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list); + } + } else { + tv_dict_alloc_ret(rettv); + if (is_qf || wp != NULL) { + if (what_arg->v_type == VAR_DICT) { + dict_T *d = what_arg->vval.v_dict; + + if (d != NULL) { + qf_get_properties(wp, d, rettv->vval.v_dict); } + } else { + EMSG(_(e_dictreq)); } - }); - } else if (argvars[0].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a List as an argument"); - return; + } } - ctx_save(NULL, types); } -/// "ctxset({context}[, {index}])" function -static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Returns information (variables, options, etc.) about a tab page +/// as a dictionary. +dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) { - if (argvars[0].v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "expected dictionary as first argument"); - return; - } + dict_T *const dict = tv_dict_alloc(); - size_t index = 0; - if (argvars[1].v_type == VAR_NUMBER) { - index = argvars[1].vval.v_number; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a Number as second argument"); - return; - } + tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; + list_T *const l = tv_list_alloc(kListLenMayKnow); + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + tv_list_append_number(l, (varnumber_T)wp->handle); } + tv_dict_add_list(dict, S_LEN("windows"), l); - int save_did_emsg = did_emsg; - did_emsg = false; - - Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; - Context tmp = CONTEXT_INIT; - ctx_from_dict(dict, &tmp); - - if (did_emsg) { - ctx_free(&tmp); - } else { - ctx_free(ctx); - *ctx = tmp; - } + // Make a reference to tabpage variables + tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); - api_free_dictionary(dict); - did_emsg = save_did_emsg; + return dict; } -/// "ctxsize()" function -static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Returns information about a window as a dictionary. +dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = ctx_size(); -} + dict_T *const dict = tv_dict_alloc(); -/// "cursor(lnum, col)" function, or -/// "cursor(list)" -/// -/// Moves the cursor to the specified line and column. -/// -/// @returns 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long line, col; - long coladd = 0; - bool set_curswant = true; + tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); + tv_dict_add_nr(dict, S_LEN("winnr"), winnr); + tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); + tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); + tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); + tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); + tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); + tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); + tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); + tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); - rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) { - pos_T pos; - colnr_T curswant = -1; + tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); + tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); + tv_dict_add_nr(dict, S_LEN("loclist"), + (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { - EMSG(_(e_invarg)); - return; - } - - line = pos.lnum; - col = pos.col; - coladd = pos.coladd; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - set_curswant = false; - } - } else { - line = tv_get_lnum(argvars); - col = (long)tv_get_number_chk(&argvars[1], NULL); - if (argvars[2].v_type != VAR_UNKNOWN) { - coladd = (long)tv_get_number_chk(&argvars[2], NULL); - } - } - if (line < 0 || col < 0 - || coladd < 0) { - return; // type error; errmsg already given - } - if (line > 0) { - curwin->w_cursor.lnum = line; - } - if (col > 0) { - curwin->w_cursor.col = col - 1; - } - curwin->w_cursor.coladd = coladd; - - // Make sure the cursor is in a valid position. - check_cursor(); - // Correct cursor for multi-byte character. - if (has_mbyte) { - mb_adjust_cursor(); - } + // Add a reference to window variables + tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); - curwin->w_set_curswant = set_curswant; - rettv->vval.v_number = 0; + return dict; } /* - * "deepcopy()" function + * Find window specified by "vp" in tabpage "tp". */ -static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int noref = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - noref = tv_get_number_chk(&argvars[1], NULL); - } - if (noref < 0 || noref > 1) { - EMSG(_(e_invarg)); - } else { - var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 - ? get_copyID() - : 0)); - } -} - -// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) { - return; - } - - const char *const name = tv_get_string(&argvars[0]); - if (*name == NUL) { - EMSG(_(e_invarg)); - return; - } - - char nbuf[NUMBUFLEN]; - const char *flags; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } else { - flags = ""; - } - - if (*flags == NUL) { - // delete a file - rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "d") == 0) { - // delete an empty directory - rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "rf") == 0) { - // delete a directory recursively - rettv->vval.v_number = delete_recursive(name); - } else { - emsgf(_(e_invexpr2), flags); - } -} - -// dictwatcheradd(dict, key, funcref) function -static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_DICT) { - emsgf(_(e_invarg2), "dict"); - return; - } else if (argvars[0].vval.v_dict == NULL) { - const char *const arg_errmsg = _("dictwatcheradd() argument"); - const size_t arg_errmsg_len = strlen(arg_errmsg); - emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg); - return; - } - - if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) { - emsgf(_(e_invarg2), "key"); - return; - } - - const char *const key_pattern = tv_get_string_chk(argvars + 1); - if (key_pattern == NULL) { - return; - } - const size_t key_pattern_len = strlen(key_pattern); - - Callback callback; - if (!callback_from_typval(&callback, &argvars[2])) { - emsgf(_(e_invarg2), "funcref"); - return; - } - - tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len, - callback); -} - -// dictwatcherdel(dict, key, funcref) function -static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +win_T * +find_win_by_nr( + typval_T *vp, + tabpage_T *tp /* NULL for current tab page */ +) { - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_DICT) { - emsgf(_(e_invarg2), "dict"); - return; - } + int nr = (int)tv_get_number_chk(vp, NULL); - if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { - emsgf(_(e_invarg2), "funcref"); - return; + if (nr < 0) { + return NULL; } - const char *const key_pattern = tv_get_string_chk(argvars + 1); - if (key_pattern == NULL) { - return; + if (nr == 0) { + return curwin; } - Callback callback; - if (!callback_from_typval(&callback, &argvars[2])) { - return; + // This method accepts NULL as an alias for curtab. + if (tp == NULL) { + tp = curtab; } - if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern, - strlen(key_pattern), callback)) { - EMSG("Couldn't find a watcher matching key and callback"); + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + if (nr >= LOWEST_WIN_ID) { + if (wp->handle == nr) { + return wp; + } + } else if (--nr <= 0) { + return wp; + } } - - callback_free(&callback); + return NULL; } -/// "deletebufline()" function -static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Find window specified by "wvp" in tabpage "tvp". +win_T *find_tabwin(typval_T *wvp, typval_T *tvp) { - linenr_T last; - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - return; - } - const bool is_curbuf = buf == curbuf; - - const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - last = tv_get_lnum_buf(&argvars[2], buf); - } else { - last = first; - } - - if (buf->b_ml.ml_mfp == NULL || first < 1 - || first > buf->b_ml.ml_line_count || last < first) { - rettv->vval.v_number = 1; // FAIL - return; - } - - if (!is_curbuf) { - curbuf_save = curbuf; - curwin_save = curwin; - curbuf = buf; - find_win_for_curbuf(); - } - if (last > curbuf->b_ml.ml_line_count) { - last = curbuf->b_ml.ml_line_count; - } - const long count = last - first + 1; - - // When coming here from Insert mode, sync undo, so that this can be - // undone separately from what was previously inserted. - if (u_sync_once == 2) { - u_sync_once = 1; // notify that u_sync() was called - u_sync(true); - } - - if (u_save(first - 1, last + 1) == FAIL) { - rettv->vval.v_number = 1; // FAIL - return; - } - - for (linenr_T lnum = first; lnum <= last; lnum++) { - ml_delete(first, true); - } + win_T *wp = NULL; + tabpage_T *tp = NULL; - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= count; - } else if (wp->w_cursor.lnum> first) { - wp->w_cursor.lnum = first; - } - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + if (wvp->v_type != VAR_UNKNOWN) { + if (tvp->v_type != VAR_UNKNOWN) { + long n = tv_get_number(tvp); + if (n >= 0) { + tp = find_tabpage(n); } + } else { + tp = curtab; } - } - check_cursor_col(); - deleted_lines_mark(first, count); - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; + if (tp != NULL) { + wp = find_win_by_nr(wvp, tp); + } + } else { + wp = curwin; } -} -/* - * "did_filetype()" function - */ -static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = did_filetype; + return wp; } /* - * "diff_filler()" function + * getwinvar() and gettabwinvar() */ -static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void +getwinvar( + typval_T *argvars, + typval_T *rettv, + int off /* 1 for gettabwinvar() */ +) { - rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); -} + win_T *win, *oldcurwin; + dictitem_T *v; + tabpage_T *tp = NULL; + tabpage_T *oldtabpage = NULL; + bool done = false; -/* - * "diff_hlID()" function - */ -static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(argvars); - static linenr_T prev_lnum = 0; - static int changedtick = 0; - static int fnum = 0; - static int change_start = 0; - static int change_end = 0; - static hlf_T hlID = (hlf_T)0; - int filler_lines; - int col; - - if (lnum < 0) /* ignore type error in {lnum} arg */ - lnum = 0; - if (lnum != prev_lnum - || changedtick != buf_get_changedtick(curbuf) - || fnum != curbuf->b_fnum) { - /* New line, buffer, change: need to get the values. */ - filler_lines = diff_check(curwin, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { - change_start = MAXCOL; - change_end = -1; - if (diff_find_change(curwin, lnum, &change_start, &change_end)) - hlID = HLF_ADD; /* added line */ - else - hlID = HLF_CHD; /* changed line */ - } else - hlID = HLF_ADD; /* added line */ - } else - hlID = (hlf_T)0; - prev_lnum = lnum; - changedtick = buf_get_changedtick(curbuf); - fnum = curbuf->b_fnum; + if (off == 1) { + tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + } else { + tp = curtab; } + win = find_win_by_nr(&argvars[off], tp); + const char *varname = tv_get_string_chk(&argvars[off + 1]); - if (hlID == HLF_CHD || hlID == HLF_TXD) { - col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. - if (col >= change_start && col <= change_end) { - hlID = HLF_TXD; // Changed text. - } else { - hlID = HLF_CHD; // Changed line. - } - } - rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); -} + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; -/* - * "empty({expr})" function - */ -static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool n = true; + emsg_off++; + if (win != NULL && varname != NULL) { + // Set curwin to be our win, temporarily. Also set the tabpage, + // otherwise the window is not valid. Only do this when needed, + // autocommands get blocked. + bool need_switch_win = tp != curtab || win != curwin; + if (!need_switch_win + || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { + if (*varname == '&') { + if (varname[1] == NUL) { + // get all window-local options in a dict + dict_T *opts = get_winbuf_options(false); - switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_FUNC: { - n = argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL; - break; - } - case VAR_PARTIAL: { - n = false; - break; - } - case VAR_NUMBER: { - n = argvars[0].vval.v_number == 0; - break; - } - case VAR_FLOAT: { - n = argvars[0].vval.v_float == 0.0; - break; - } - case VAR_LIST: { - n = (tv_list_len(argvars[0].vval.v_list) == 0); - break; - } - case VAR_DICT: { - n = (tv_dict_len(argvars[0].vval.v_dict) == 0); - break; - } - case VAR_SPECIAL: { - // Using switch to get warning if SpecialVarValue receives more values. - switch (argvars[0].vval.v_special) { - case kSpecialVarTrue: { - n = false; - break; + if (opts != NULL) { + tv_dict_set_ret(rettv, opts); + done = true; + } + } else if (get_option_tv(&varname, rettv, 1) == OK) { + // window-local-option + done = true; } - case kSpecialVarFalse: - case kSpecialVarNull: { - n = true; - break; + } else { + // Look up the variable. + // Let getwinvar({nr}, "") return the "w:" dictionary. + v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, + strlen(varname), false); + if (v != NULL) { + tv_copy(&v->di_tv, rettv); + done = true; } } - break; } - case VAR_UNKNOWN: { - internal_error("f_empty(UNKNOWN)"); - break; + + if (need_switch_win) { + // restore previous notion of curwin + restore_win(oldcurwin, oldtabpage, true); } } + emsg_off--; - rettv->vval.v_number = n; -} - -/// "environ()" function -static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - size_t env_size = os_get_fullenv_size(); - char **env = xmalloc(sizeof(*env) * (env_size + 1)); - env[env_size] = NULL; - - os_copy_fullenv(env, env_size); - - for (size_t i = 0; i < env_size; i++) { - const char * str = env[i]; - const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), - '='); - assert(end != NULL); - ptrdiff_t len = end - str; - assert(len > 0); - const char * value = str + len + 1; - tv_dict_add_str(rettv->vval.v_dict, - str, len, - value); + if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) { + // use the default return value + tv_copy(&argvars[off + 2], rettv); } - os_free_fullenv(env); } /* - * "escape({string}, {chars})" function + * This function is used by f_input() and f_inputdialog() functions. The third + * argument to f_input() specifies the type of completion to use at the + * prompt. The third argument to f_inputdialog() specifies the value to return + * when the user cancels the prompt. */ -static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - - rettv->vval.v_string = vim_strsave_escaped( - (const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], buf)); - rettv->v_type = VAR_STRING; -} - -/// "getenv()" function -static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void get_user_input(const typval_T *const argvars, + typval_T *const rettv, + const bool inputdialog, + const bool secret) + FUNC_ATTR_NONNULL_ALL { - char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); - - if (p == NULL) { - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_special = kSpecialVarNull; - return; - } - rettv->vval.v_string = p; rettv->v_type = VAR_STRING; -} + rettv->vval.v_string = NULL; -/* - * "eval()" function - */ -static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *s = tv_get_string_chk(&argvars[0]); - if (s != NULL) { - s = (const char *)skipwhite((const char_u *)s); - } - - const char *const expr_start = s; - if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) { - if (expr_start != NULL && !aborting()) { - EMSG2(_(e_invexpr2), expr_start); + const char *prompt = ""; + const char *defstr = ""; + const char *cancelreturn = NULL; + const char *xp_name = NULL; + Callback input_callback = { .type = kCallbackNone }; + char prompt_buf[NUMBUFLEN]; + char defstr_buf[NUMBUFLEN]; + char cancelreturn_buf[NUMBUFLEN]; + char xp_name_buf[NUMBUFLEN]; + char def[1] = { 0 }; + if (argvars[0].v_type == VAR_DICT) { + if (argvars[1].v_type != VAR_UNKNOWN) { + EMSG(_("E5050: {opts} must be the only argument")); + return; } - need_clr_eos = FALSE; - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - } else if (*s != NUL) { - EMSG(_(e_trailing)); - } -} - -/* - * "eventhandler()" function - */ -static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = vgetc_busy; -} - -/* - * "executable()" function - */ -static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name = tv_get_string(&argvars[0]); - - // Check in $PATH and also check directly if there is a directory name - rettv->vval.v_number = os_can_exe(name, NULL, true); -} - -typedef struct { - const list_T *const l; - const listitem_T *li; -} GetListLineCookie; - -static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) -{ - GetListLineCookie *const p = (GetListLineCookie *)cookie; - - const listitem_T *const item = p->li; - if (item == NULL) { - return NULL; - } - char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); - p->li = TV_LIST_ITEM_NEXT(p->l, item); - return (char_u *)(s == NULL ? NULL : xstrdup(s)); -} - -// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int save_msg_silent = msg_silent; - const int save_emsg_silent = emsg_silent; - const bool save_emsg_noredir = emsg_noredir; - const bool save_redir_off = redir_off; - garray_T *const save_capture_ga = capture_ga; - const int save_msg_col = msg_col; - bool echo_output = false; - - if (check_secure()) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&argvars[1], buf); - - if (s == NULL) { + dict_T *const dict = argvars[0].vval.v_dict; + prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); + if (prompt == NULL) { + return; + } + defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, ""); + if (defstr == NULL) { + return; + } + cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), + cancelreturn_buf, def); + if (cancelreturn == NULL) { // error return; } - if (*s == NUL) { - echo_output = true; + if (*cancelreturn == NUL) { + cancelreturn = NULL; + } + xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), + xp_name_buf, def); + if (xp_name == NULL) { // error + return; } - if (strncmp(s, "silent", 6) == 0) { - msg_silent++; + if (xp_name == def) { // default to NULL + xp_name = NULL; } - if (strcmp(s, "silent!") == 0) { - emsg_silent = true; - emsg_noredir = true; + if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { + return; } } else { - msg_silent++; + prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); + if (prompt == NULL) { + return; + } + if (argvars[1].v_type != VAR_UNKNOWN) { + defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf); + if (defstr == NULL) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const arg2 = tv_get_string_buf_chk(&argvars[2], + cancelreturn_buf); + if (arg2 == NULL) { + return; + } + if (inputdialog) { + cancelreturn = arg2; + } else { + xp_name = arg2; + } + } + } } - garray_T capture_local; - ga_init(&capture_local, (int)sizeof(char), 80); - capture_ga = &capture_local; - redir_off = false; - if (!echo_output) { - msg_col = 0; // prevent leading spaces + int xp_type = EXPAND_NOTHING; + char *xp_arg = NULL; + if (xp_name != NULL) { + // input() with a third argument: completion + const int xp_namelen = (int)strlen(xp_name); + + uint32_t argt; + if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, + &argt, (char_u **)&xp_arg) == FAIL) { + return; + } } - if (argvars[0].v_type != VAR_LIST) { - do_cmdline_cmd(tv_get_string(&argvars[0])); - } else if (argvars[0].vval.v_list != NULL) { - list_T *const list = argvars[0].vval.v_list; - tv_list_ref(list); - GetListLineCookie cookie = { - .l = list, - .li = tv_list_first(list), - }; - do_cmdline(NULL, get_list_line, (void *)&cookie, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); - tv_list_unref(list); - } - msg_silent = save_msg_silent; - emsg_silent = save_emsg_silent; - emsg_noredir = save_emsg_noredir; - redir_off = save_redir_off; - // "silent reg" or "silent echo x" leaves msg_col somewhere in the line. - if (echo_output) { - // When not working silently: put it in column zero. A following - // "echon" will overwrite the message, unavoidably. - msg_col = 0; - } else { - // When working silently: Put it back where it was, since nothing - // should have been written. - msg_col = save_msg_col; + const bool cmd_silent_save = cmd_silent; + + cmd_silent = false; // Want to see the prompt. + // Only the part of the message after the last NL is considered as + // prompt for the command line, unlsess cmdline is externalized + const char *p = prompt; + if (!ui_has(kUICmdline)) { + const char *lastnl = strrchr(prompt, '\n'); + if (lastnl != NULL) { + p = lastnl+1; + msg_start(); + msg_clr_eos(); + msg_puts_attr_len(prompt, p - prompt, echo_attr); + msg_didout = false; + msg_starthere(); + } } + cmdline_row = msg_row; - ga_append(capture_ga, NUL); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = capture_ga->ga_data; + stuffReadbuffSpec(defstr); - capture_ga = save_capture_ga; -} + const int save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + rettv->vval.v_string = + (char_u *)getcmdline_prompt(secret ? NUL : '@', p, echo_attr, + xp_type, xp_arg, input_callback); + ex_normal_busy = save_ex_normal_busy; + callback_free(&input_callback); -/// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *arg = tv_get_string(&argvars[0]); - char *path = NULL; + if (rettv->vval.v_string == NULL && cancelreturn != NULL) { + rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); + } - (void)os_can_exe(arg, &path, true); + xfree(xp_arg); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)path; + // Since the user typed this, no need to wait for return. + need_wait_return = false; + msg_didout = false; + cmd_silent = cmd_silent_save; } -/// Find a window: When using a Window ID in any tab page, when using a number -/// in the current tab page. -win_T * find_win_by_nr_or_id(typval_T *vp) +/// Turn a dictionary into a list +/// +/// @param[in] tv Dictionary to convert. Is checked for actually being +/// a dictionary, will give an error if not. +/// @param[out] rettv Location where result will be saved. +/// @param[in] what What to save in rettv. +void dict_list(typval_T *const tv, typval_T *const rettv, + const DictListType what) { - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr >= LOWEST_WIN_ID) { - return win_id2wp(vp); + if (tv->v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + if (tv->vval.v_dict == NULL) { + return; } - return find_win_by_nr(vp, NULL); -} + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); -/* - * "exists()" function - */ -static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = false; - int len = 0; + TV_DICT_ITER(tv->vval.v_dict, di, { + typval_T tv_item = { .v_lock = VAR_UNLOCKED }; - const char *p = tv_get_string(&argvars[0]); - if (*p == '$') { // Environment variable. - // First try "normal" environment variables (fast). - if (os_env_exists(p + 1)) { - n = true; - } else { - // Try expanding things like $VIM and ${HOME}. - char_u *const exp = expand_env_save((char_u *)p); - if (exp != NULL && *exp != '$') { - n = true; + switch (what) { + case kDictListKeys: { + tv_item.v_type = VAR_STRING; + tv_item.vval.v_string = vim_strsave(di->di_key); + break; } - xfree(exp); - } - } else if (*p == '&' || *p == '+') { // Option. - n = (get_option_tv(&p, NULL, true) == OK); - if (*skipwhite((const char_u *)p) != NUL) { - n = false; // Trailing garbage. - } - } else if (*p == '*') { // Internal or user defined function. - n = function_exists(p + 1, false); - } else if (*p == ':') { - n = cmd_exists(p + 1); - } else if (*p == '#') { - if (p[1] == '#') { - n = autocmd_supported(p + 2); - } else { - n = au_exists(p + 1); - } - } else { // Internal variable. - typval_T tv; - - // get_name_len() takes care of expanding curly braces - const char *name = p; - char *tofree; - len = get_name_len((const char **)&p, &tofree, true, false); - if (len > 0) { - if (tofree != NULL) { - name = tofree; + case kDictListValues: { + tv_copy(&di->di_tv, &tv_item); + break; } - n = (get_var_tv(name, len, &tv, NULL, false, true) == OK); - if (n) { - // Handle d.key, l[idx], f(expr). - n = (handle_subscript(&p, &tv, true, false) == OK); - if (n) { - tv_clear(&tv); - } + case kDictListItems: { + // items() + list_T *const sub_l = tv_list_alloc(2); + tv_item.v_type = VAR_LIST; + tv_item.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); + + break; } } - if (*p != NUL) - n = FALSE; - - xfree(tofree); - } - rettv->vval.v_number = n; + tv_list_append_owned_tv(rettv->vval.v_list, tv_item); + }); } -/* - * "expand()" function - */ -static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Builds a process argument vector from a VimL object (typval_T). +/// +/// @param[in] cmd_tv VimL object +/// @param[out] cmd Returns the command or executable name. +/// @param[out] executable Returns `false` if argv[0] is not executable. +/// +/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. +/// Else, string values of `cmd_tv` copied to a (char **) list with +/// argv[0] resolved to full path ($PATHEXT-resolved on Windows). +char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) { - size_t len; - char_u *errormsg; - int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; - expand_T xpc; - bool error = false; - char_u *result; - - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[2], &error) - && !error) { - tv_list_set_ret(rettv, NULL); - } - - const char *s = tv_get_string(&argvars[0]); - if (*s == '%' || *s == '#' || *s == '<') { - emsg_off++; - result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); - emsg_off--; - if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv, (result != NULL)); - if (result != NULL) { - tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); - } - } else - rettv->vval.v_string = result; - } else { - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ - if (argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) { - options += WILD_ICASE; - } - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, - WILD_ALL); - } else { - ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, - (const char *)xpc.xp_files[i], -1); - } - ExpandCleanup(&xpc); - } - } else { - rettv->vval.v_string = NULL; + if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics". + const char *cmd_str = tv_get_string(cmd_tv); + if (cmd) { + *cmd = cmd_str; } + return shell_build_argv(cmd_str, NULL); } -} - -/// "menu_get(path [, modes])" function -static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - int modes = MENU_ALL_MODES; - if (argvars[1].v_type == VAR_STRING) { - const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); - modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); + if (cmd_tv->v_type != VAR_LIST) { + EMSG2(_(e_invarg2), "expected String or List"); + return NULL; } - menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); -} -/* - * "extend(list, list [, idx])" function - * "extend(dict, dict [, action])" function - */ -static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const arg_errmsg = N_("extend() argument"); + list_T *argl = cmd_tv->vval.v_list; + int argc = tv_list_len(argl); + if (!argc) { + EMSG(_(e_invarg)); // List must have at least one item. + return NULL; + } - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { - long before; - bool error = false; - - list_T *const l1 = argvars[0].vval.v_list; - list_T *const l2 = argvars[1].vval.v_list; - if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { - listitem_T *item; - if (argvars[2].v_type != VAR_UNKNOWN) { - before = (long)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; // Type error; errmsg already given. - } - - if (before == tv_list_len(l1)) { - item = NULL; - } else { - item = tv_list_find(l1, before); - if (item == NULL) { - EMSGN(_(e_listidx), before); - return; - } - } - } else { - item = NULL; - } - tv_list_extend(l1, l2, item); - - tv_copy(&argvars[0], rettv); - } - } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == - VAR_DICT) { - dict_T *const d1 = argvars[0].vval.v_dict; - dict_T *const d2 = argvars[1].vval.v_dict; - if (d1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (d2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *action = "force"; - // Check the third argument. - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const av[] = { "keep", "force", "error" }; - - action = tv_get_string_chk(&argvars[2]); - if (action == NULL) { - return; // Type error; error message already given. - } - size_t i; - for (i = 0; i < ARRAY_SIZE(av); i++) { - if (strcmp(action, av[i]) == 0) { - break; - } - } - if (i == 3) { - EMSG2(_(e_invarg2), action); - return; - } - } - - tv_dict_extend(d1, d2, action); - - tv_copy(&argvars[0], rettv); - } - } else { - EMSG2(_(e_listdictarg), "extend()"); - } -} - -/* - * "feedkeys()" function - */ -static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is not allowed in the sandbox. If the commands would still be - // executed in the sandbox it would be OK, but it probably happens later, - // when "sandbox" is no longer set. - if (check_secure()) { - return; - } - - const char *const keys = tv_get_string(&argvars[0]); - char nbuf[NUMBUFLEN]; - const char *flags = NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } - - nvim_feedkeys(cstr_as_string((char *)keys), - cstr_as_string((char *)flags), true); -} - -/// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_number = - (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); -} - -/* - * Return 0 for not writable, 1 for writable file, 2 for a dir which we have - * rights to write into. - */ -static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *filename = tv_get_string(&argvars[0]); - rettv->vval.v_number = os_file_is_writable(filename); -} - - -static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) -{ - char_u *fresult = NULL; - char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; - int count = 1; - bool first = true; - bool error = false; - - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; - - const char *fname = tv_get_string(&argvars[0]); - - char pathbuf[NUMBUFLEN]; - if (argvars[1].v_type != VAR_UNKNOWN) { - const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); - if (p == NULL) { - error = true; - } else { - if (*p != NUL) { - path = (char_u *)p; - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - count = tv_get_number_chk(&argvars[2], &error); - } - } - } - - if (count < 0) { - tv_list_alloc_ret(rettv, kListLenUnknown); - } - - if (*fname != NUL && !error) { - do { - if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) - xfree(fresult); - fresult = find_file_in_path_option(first ? (char_u *)fname : NULL, - first ? strlen(fname) : 0, - 0, first, path, - find_what, curbuf->b_ffname, - (find_what == FINDFILE_DIR - ? (char_u *)"" - : curbuf->b_p_sua)); - first = false; - - if (fresult != NULL && rettv->v_type == VAR_LIST) { - tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1); - } - } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); - } - - if (rettv->v_type == VAR_STRING) - rettv->vval.v_string = fresult; -} - - -/* - * Implementation of map() and filter(). - */ -static void filter_map(typval_T *argvars, typval_T *rettv, int map) -{ - typval_T *expr; - list_T *l = NULL; - dictitem_T *di; - hashtab_T *ht; - hashitem_T *hi; - dict_T *d = NULL; - typval_T save_val; - typval_T save_key; - int rem = false; - int todo; - char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); - const char *const arg_errmsg = (map - ? N_("map() argument") - : N_("filter() argument")); - int save_did_emsg; - int idx = 0; - - if (argvars[0].v_type == VAR_LIST) { - tv_copy(&argvars[0], rettv); - if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, - TV_TRANSLATE))) { - return; - } - } else if (argvars[0].v_type == VAR_DICT) { - tv_copy(&argvars[0], rettv); - if ((d = argvars[0].vval.v_dict) == NULL - || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { - return; - } - } else { - EMSG2(_(e_listdictarg), ermsg); - return; - } - - expr = &argvars[1]; - // On type errors, the preceding call has already displayed an error - // message. Avoid a misleading error message for an empty string that - // was not passed as argument. - if (expr->v_type != VAR_UNKNOWN) { - prepare_vimvar(VV_VAL, &save_val); - - // We reset "did_emsg" to be able to detect whether an error - // occurred during evaluation of the expression. - save_did_emsg = did_emsg; - did_emsg = FALSE; - - prepare_vimvar(VV_KEY, &save_key); - if (argvars[0].v_type == VAR_DICT) { - vimvars[VV_KEY].vv_type = VAR_STRING; - - ht = &d->dv_hashtab; - hash_lock(ht); - todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - - di = TV_DICT_HI2DI(hi); - if (map - && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) - || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { - break; - } - - vimvars[VV_KEY].vv_str = vim_strsave(di->di_key); - int r = filter_map_one(&di->di_tv, expr, map, &rem); - tv_clear(&vimvars[VV_KEY].vv_tv); - if (r == FAIL || did_emsg) { - break; - } - if (!map && rem) { - if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) - || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { - break; - } - tv_dict_item_remove(d, di); - } - } - } - hash_unlock(ht); - } else { - assert(argvars[0].v_type == VAR_LIST); - vimvars[VV_KEY].vv_type = VAR_NUMBER; - - for (listitem_T *li = tv_list_first(l); li != NULL;) { - if (map - && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, - TV_TRANSLATE)) { - break; - } - vimvars[VV_KEY].vv_nr = idx; - if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL - || did_emsg) { - break; - } - if (!map && rem) { - li = tv_list_item_remove(l, li); - } else { - li = TV_LIST_ITEM_NEXT(l, li); - } - idx++; - } - } - - restore_vimvar(VV_KEY, &save_key); - restore_vimvar(VV_VAL, &save_val); - - did_emsg |= save_did_emsg; - } -} - -static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) - FUNC_ATTR_NONNULL_ARG(1, 2) -{ - typval_T rettv; - typval_T argv[3]; - int retval = FAIL; - - tv_copy(tv, &vimvars[VV_VAL].vv_tv); - argv[0] = vimvars[VV_KEY].vv_tv; - argv[1] = vimvars[VV_VAL].vv_tv; - if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { - goto theend; - } - if (map) { - // map(): replace the list item value. - tv_clear(tv); - rettv.v_lock = 0; - *tv = rettv; - } else { - bool error = false; - - // filter(): when expr is zero remove the item - *remp = (tv_get_number_chk(&rettv, &error) == 0); - tv_clear(&rettv); - // On type error, nothing has been removed; return FAIL to stop the - // loop. The error message was given by tv_get_number_chk(). - if (error) { - goto theend; - } - } - retval = OK; -theend: - tv_clear(&vimvars[VV_VAL].vv_tv); - return retval; -} - -/* - * "filter()" function - */ -static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - filter_map(argvars, rettv, FALSE); -} - -/* - * "finddir({fname}[, {path}[, {count}]])" function - */ -static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - findfilendir(argvars, rettv, FINDFILE_DIR); -} - -/* - * "findfile({fname}[, {path}[, {count}]])" function - */ -static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - findfilendir(argvars, rettv, FINDFILE_FILE); -} - -/* - * "float2nr({float})" function - */ -static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T f; - - if (tv_get_float_chk(argvars, &f)) { - if (f <= -VARNUMBER_MAX + DBL_EPSILON) { - rettv->vval.v_number = -VARNUMBER_MAX; - } else if (f >= VARNUMBER_MAX - DBL_EPSILON) { - rettv->vval.v_number = VARNUMBER_MAX; - } else { - rettv->vval.v_number = (varnumber_T)f; - } - } -} - -/* - * "fmod()" function - */ -static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T fx; - float_T fy; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { - rettv->vval.v_float = fmod(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "fnameescape({string})" function - */ -static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_string = (char_u *)vim_strsave_fnameescape( - tv_get_string(&argvars[0]), false); - rettv->v_type = VAR_STRING; -} - -/* - * "fnamemodify({fname}, {mods})" function - */ -static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *fbuf = NULL; - size_t len; - char buf[NUMBUFLEN]; - const char *fname = tv_get_string_chk(&argvars[0]); - const char *const mods = tv_get_string_buf_chk(&argvars[1], buf); - if (fname == NULL || mods == NULL) { - fname = NULL; - } else { - len = strlen(fname); - size_t usedlen = 0; - (void)modify_fname((char_u *)mods, false, &usedlen, - (char_u **)&fname, &fbuf, &len); - } - - rettv->v_type = VAR_STRING; - if (fname == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = (char_u *)xmemdupz(fname, len); - } - xfree(fbuf); -} - - -/* - * "foldclosed()" function - */ -static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - linenr_T first; - linenr_T last; - if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { - if (end) { - rettv->vval.v_number = (varnumber_T)last; - } else { - rettv->vval.v_number = (varnumber_T)first; - } - return; - } - } - rettv->vval.v_number = -1; -} - -/* - * "foldclosed()" function - */ -static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, FALSE); -} - -/* - * "foldclosedend()" function - */ -static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, TRUE); -} - -/* - * "foldlevel()" function - */ -static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = foldLevel(lnum); - } -} - -/* - * "foldtext()" function - */ -static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T foldstart; - linenr_T foldend; - char_u *dashes; - linenr_T lnum; - char_u *s; - char_u *r; - int len; - char *txt; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); - foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); - dashes = get_vim_var_str(VV_FOLDDASHES); - if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { - // Find first non-empty line in the fold. - for (lnum = foldstart; lnum < foldend; lnum++) { - if (!linewhite(lnum)) { - break; - } - } - - /* Find interesting text in this line. */ - s = skipwhite(ml_get(lnum)); - /* skip C comment-start */ - if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { - s = skipwhite(s + 2); - if (*skipwhite(s) == NUL && lnum + 1 < foldend) { - s = skipwhite(ml_get(lnum + 1)); - if (*s == '*') - s = skipwhite(s + 1); - } - } - unsigned long count = (unsigned long)(foldend - foldstart + 1); - txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); - r = xmalloc(STRLEN(txt) - + STRLEN(dashes) // for %s - + 20 // for %3ld - + STRLEN(s)); // concatenated - sprintf((char *)r, txt, dashes, count); - len = (int)STRLEN(r); - STRCAT(r, s); - /* remove 'foldmarker' and 'commentstring' */ - foldtext_cleanup(r + len); - rettv->vval.v_string = r; - } -} - -/* - * "foldtextresult(lnum)" function - */ -static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *text; - char_u buf[FOLD_TEXT_LEN]; - foldinfo_T foldinfo; - int fold_count; - static bool entered = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (entered) { - return; // reject recursive use - } - entered = true; - linenr_T lnum = tv_get_lnum(argvars); - // Treat illegal types and illegal string values for {lnum} the same. - if (lnum < 0) { - lnum = 0; - } - fold_count = foldedCount(curwin, lnum, &foldinfo); - if (fold_count > 0) { - text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf); - if (text == buf) { - text = vim_strsave(text); - } - rettv->vval.v_string = text; - } - - entered = false; -} - -/* - * "foreground()" function - */ -static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ -} - -static void common_function(typval_T *argvars, typval_T *rettv, - bool is_funcref, FunPtr fptr) -{ - char_u *s; - char_u *name; - bool use_string = false; - partial_T *arg_pt = NULL; - char_u *trans_name = NULL; - - if (argvars[0].v_type == VAR_FUNC) { - // function(MyFunc, [arg], dict) - s = argvars[0].vval.v_string; - } else if (argvars[0].v_type == VAR_PARTIAL - && argvars[0].vval.v_partial != NULL) { - // function(dict.MyFunc, [arg]) - arg_pt = argvars[0].vval.v_partial; - s = partial_name(arg_pt); - } else { - // function('MyFunc', [arg], dict) - s = (char_u *)tv_get_string(&argvars[0]); - use_string = true; - } - - if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { - name = s; - trans_name = trans_function_name(&name, false, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD - | TFN_NO_DEREF, NULL, NULL); - if (*name != NUL) { - s = NULL; - } - } - if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) - || (is_funcref && trans_name == NULL)) { - emsgf(_(e_invarg2), (use_string - ? tv_get_string(&argvars[0]) - : (const char *)s)); - // Don't check an autoload name for existence here. - } else if (trans_name != NULL - && (is_funcref ? find_func(trans_name) == NULL - : !translated_function_exists((const char *)trans_name))) { - emsgf(_("E700: Unknown function: %s"), s); - } else { - int dict_idx = 0; - int arg_idx = 0; - list_T *list = NULL; - if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "", 5) == 0) { - char sid_buf[25]; - int off = *s == 's' ? 2 : 5; - - // Expand s: and into nr_, so that the function can - // also be called from another script. Using trans_function_name() - // would also work, but some plugins depend on the name being - // printable text. - snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); - STRCPY(name, sid_buf); - STRCAT(name, s + off); - } else { - name = vim_strsave(s); - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_UNKNOWN) { - // function(name, [args], dict) - arg_idx = 1; - dict_idx = 2; - } else if (argvars[1].v_type == VAR_DICT) { - // function(name, dict) - dict_idx = 1; - } else { - // function(name, [args]) - arg_idx = 1; - } - if (dict_idx > 0) { - if (argvars[dict_idx].v_type != VAR_DICT) { - EMSG(_("E922: expected a dict")); - xfree(name); - goto theend; - } - if (argvars[dict_idx].vval.v_dict == NULL) { - dict_idx = 0; - } - } - if (arg_idx > 0) { - if (argvars[arg_idx].v_type != VAR_LIST) { - EMSG(_("E923: Second argument of function() must be " - "a list or a dict")); - xfree(name); - goto theend; - } - list = argvars[arg_idx].vval.v_list; - if (tv_list_len(list) == 0) { - arg_idx = 0; - } - } - } - if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) { - partial_T *const pt = xcalloc(1, sizeof(*pt)); - - // result is a VAR_PARTIAL - if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { - const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); - const int lv_len = tv_list_len(list); - - pt->pt_argc = arg_len + lv_len; - pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); - int i = 0; - for (; i < arg_len; i++) { - tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); - } - if (lv_len > 0) { - TV_LIST_ITER(list, li, { - tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); - }); - } - } - - // For "function(dict.func, [], dict)" and "func" is a partial - // use "dict". That is backwards compatible. - if (dict_idx > 0) { - // The dict is bound explicitly, pt_auto is false - pt->pt_dict = argvars[dict_idx].vval.v_dict; - (pt->pt_dict->dv_refcount)++; - } else if (arg_pt != NULL) { - // If the dict was bound automatically the result is also - // bound automatically. - pt->pt_dict = arg_pt->pt_dict; - pt->pt_auto = arg_pt->pt_auto; - if (pt->pt_dict != NULL) { - (pt->pt_dict->dv_refcount)++; - } - } - - pt->pt_refcount = 1; - if (arg_pt != NULL && arg_pt->pt_func != NULL) { - pt->pt_func = arg_pt->pt_func; - func_ptr_ref(pt->pt_func); - xfree(name); - } else if (is_funcref) { - pt->pt_func = find_func(trans_name); - func_ptr_ref(pt->pt_func); - xfree(name); - } else { - pt->pt_name = name; - func_ref(name); - } - - rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = pt; - } else { - // result is a VAR_FUNC - rettv->v_type = VAR_FUNC; - rettv->vval.v_string = name; - func_ref(name); - } - } -theend: - xfree(trans_name); -} - -static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - common_function(argvars, rettv, true, fptr); -} - -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - common_function(argvars, rettv, false, fptr); -} - -/// "garbagecollect()" function -static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is postponed until we are back at the toplevel, because we may be - // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". - want_garbage_collect = true; - - if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) { - garbage_collect_at_exit = true; - } -} - -/* - * "get()" function - */ -static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - listitem_T *li; - list_T *l; - dictitem_T *di; - dict_T *d; - typval_T *tv = NULL; - bool what_is_dict = false; - - if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL) { - bool error = false; - - li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); - if (!error && li != NULL) { - tv = TV_LIST_ITEM_TV(li); - } - } - } else if (argvars[0].v_type == VAR_DICT) { - if ((d = argvars[0].vval.v_dict) != NULL) { - di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); - if (di != NULL) { - tv = &di->di_tv; - } - } - } else if (tv_is_func(argvars[0])) { - partial_T *pt; - partial_T fref_pt; - - if (argvars[0].v_type == VAR_PARTIAL) { - pt = argvars[0].vval.v_partial; - } else { - memset(&fref_pt, 0, sizeof(fref_pt)); - fref_pt.pt_name = argvars[0].vval.v_string; - pt = &fref_pt; - } - - if (pt != NULL) { - const char *const what = tv_get_string(&argvars[1]); - - if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) { - rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); - const char *const n = (const char *)partial_name(pt); - assert(n != NULL); - rettv->vval.v_string = (char_u *)xstrdup(n); - if (rettv->v_type == VAR_FUNC) { - func_ref(rettv->vval.v_string); - } - } else if (strcmp(what, "dict") == 0) { - what_is_dict = true; - if (pt->pt_dict != NULL) { - tv_dict_set_ret(rettv, pt->pt_dict); - } - } else if (strcmp(what, "args") == 0) { - rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { - for (int i = 0; i < pt->pt_argc; i++) { - tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); - } - } - } else { - EMSG2(_(e_invarg2), what); - } - - // When {what} == "dict" and pt->pt_dict == NULL, evaluate the - // third argument - if (!what_is_dict) { - return; - } - } - } else { - EMSG2(_(e_listdictarg), "get()"); - } - - if (tv == NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - tv_copy(&argvars[2], rettv); - } - } else { - tv_copy(tv, rettv); - } -} - -/// Returns buffer options, variables and other attributes in a dictionary. -static dict_T *get_buffer_info(buf_T *buf) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); - tv_dict_add_str(dict, S_LEN("name"), - buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); - tv_dict_add_nr(dict, S_LEN("lnum"), - buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); - tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); - tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); - tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); - tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); - tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); - tv_dict_add_nr(dict, S_LEN("hidden"), - buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); - - // Get a reference to buffer variables - tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); - - // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(kListLenMayKnow); - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - tv_list_append_number(windows, (varnumber_T)wp->handle); - } - } - tv_dict_add_list(dict, S_LEN("windows"), windows); - - if (buf->b_signlist != NULL) { - // List of signs placed in this buffer - tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); - } - - return dict; -} - -/// "getbufinfo()" function -static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *argbuf = NULL; - bool filtered = false; - bool sel_buflisted = false; - bool sel_bufloaded = false; - bool sel_bufmodified = false; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - // List of all the buffers or selected buffers - if (argvars[0].v_type == VAR_DICT) { - dict_T *sel_d = argvars[0].vval.v_dict; - - if (sel_d != NULL) { - dictitem_T *di; - - filtered = true; - - di = tv_dict_find(sel_d, S_LEN("buflisted")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_buflisted = true; - } - - di = tv_dict_find(sel_d, S_LEN("bufloaded")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufloaded = true; - } - di = tv_dict_find(sel_d, S_LEN("bufmodified")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufmodified = true; - } - } - } else if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one buffer. Argument specifies the buffer - if (tv_check_num(&argvars[0])) { // issue errmsg if type error - emsg_off++; - argbuf = tv_get_buf(&argvars[0], false); - emsg_off--; - if (argbuf == NULL) { - return; - } - } - } - - // Return information about all the buffers or a specified buffer - FOR_ALL_BUFFERS(buf) { - if (argbuf != NULL && argbuf != buf) { - continue; - } - if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) - || (sel_buflisted && !buf->b_p_bl) - || (sel_bufmodified && !buf->b_changed))) { - continue; - } - - dict_T *const d = get_buffer_info(buf); - tv_list_append_dict(rettv->vval.v_list, d); - if (argbuf != NULL) { - return; - } - } -} - -/* - * Get line or list of lines from buffer "buf" into "rettv". - * Return a range (from start to end) of lines in rettv from the specified - * buffer. - * If 'retlist' is TRUE, then the lines are returned as a Vim List. - */ -static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) -{ - rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); - rettv->vval.v_string = NULL; - - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { - if (retlist) { - tv_list_alloc_ret(rettv, 0); - } - return; - } - - if (retlist) { - if (start < 1) { - start = 1; - } - if (end > buf->b_ml.ml_line_count) { - end = buf->b_ml.ml_line_count; - } - tv_list_alloc_ret(rettv, end - start + 1); - while (start <= end) { - tv_list_append_string(rettv->vval.v_list, - (const char *)ml_get_buf(buf, start++, false), -1); - } - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) - ? vim_strsave(ml_get_buf(buf, start, false)) - : NULL); - } -} - -/// Get the line number from VimL object -/// -/// @note Unlike tv_get_lnum(), this one supports only "$" special string. -/// -/// @param[in] tv Object to get value from. Is expected to be a number or -/// a special string "$". -/// @param[in] buf Buffer to take last line number from in case tv is "$". May -/// be NULL, in this case "$" results in zero return. -/// -/// @return Line number or 0 in case of error. -static linenr_T tv_get_lnum_buf(const typval_T *const tv, - const buf_T *const buf) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (tv->v_type == VAR_STRING - && tv->vval.v_string != NULL - && tv->vval.v_string[0] == '$' - && buf != NULL) { - return buf->b_ml.ml_line_count; - } - return tv_get_number_chk(tv, NULL); -} - -/* - * "getbufline()" function - */ -static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf = NULL; - - if (tv_check_str_or_nr(&argvars[0])) { - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN - ? lnum - : tv_get_lnum_buf(&argvars[2], buf)); - - get_buffer_lines(buf, lnum, end, true, rettv); -} - -/* - * "getbufvar()" function - */ -static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - if (!tv_check_str_or_nr(&argvars[0])) { - goto f_getbufvar_end; - } - - const char *varname = tv_get_string_chk(&argvars[1]); - emsg_off++; - buf_T *const buf = tv_get_buf(&argvars[0], false); - - if (buf != NULL && varname != NULL) { - // set curbuf to be our buf, temporarily - buf_T *const save_curbuf = curbuf; - curbuf = buf; - - if (*varname == '&') { // buffer-local-option - if (varname[1] == NUL) { - // get all buffer-local options in a dict - dict_T *opts = get_winbuf_options(true); - - if (opts != NULL) { - tv_dict_set_ret(rettv, opts); - done = true; - } - } else if (get_option_tv(&varname, rettv, true) == OK) { - // buffer-local-option - done = true; - } - } else { - // Look up the variable. - // Let getbufvar({nr}, "") return the "b:" dictionary. - dictitem_T *const v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, 'b', - varname, strlen(varname), false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - - // restore previous notion of curbuf - curbuf = save_curbuf; - } - emsg_off--; - -f_getbufvar_end: - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - // use the default value - tv_copy(&argvars[2], rettv); - } -} - -// "getchangelist()" function -static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error - emsg_off++; - const buf_T *const buf = tv_get_buf(&argvars[0], false); - emsg_off--; - if (buf == NULL) { - return; - } - - list_T *const l = tv_list_alloc(buf->b_changelistlen); - tv_list_append_list(rettv->vval.v_list, l); - // The current window change list index tracks only the position in the - // current buffer change list. For other buffers, use the change list - // length as the current index. - tv_list_append_number(rettv->vval.v_list, - (buf == curwin->w_buffer) - ? curwin->w_changelistidx - : buf->b_changelistlen); - - for (int i = 0; i < buf->b_changelistlen; i++) { - if (buf->b_changelist[i].mark.lnum == 0) { - continue; - } - dict_T *const d = tv_dict_alloc(); - tv_list_append_dict(l, d); - tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum); - tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col); - tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd); - } -} - -/* - * "getchar()" function - */ -static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - varnumber_T n; - bool error = false; - - no_mapping++; - for (;; ) { - // Position the cursor. Needed after a message that ends in a space, - // or if event processing caused a redraw. - ui_cursor_goto(msg_row, msg_col); - - if (argvars[0].v_type == VAR_UNKNOWN) { - // getchar(): blocking wait. - if (!(char_avail() || using_script() || input_available())) { - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); - if (!multiqueue_empty(main_loop.events)) { - multiqueue_process_events(main_loop.events); - continue; - } - } - n = safe_vgetc(); - } else if (tv_get_number_chk(&argvars[0], &error) == 1) { - // getchar(1): only check if char avail - n = vpeekc_any(); - } else if (error || vpeekc_any() == NUL) { - // illegal argument or getchar(0) and no char avail: return zero - n = 0; - } else { - // getchar(0) and char avail: return char - n = safe_vgetc(); - } - - if (n == K_IGNORE) { - continue; - } - break; - } - no_mapping--; - - vimvars[VV_MOUSE_WIN].vv_nr = 0; - vimvars[VV_MOUSE_WINID].vv_nr = 0; - vimvars[VV_MOUSE_LNUM].vv_nr = 0; - vimvars[VV_MOUSE_COL].vv_nr = 0; - - rettv->vval.v_number = n; - if (IS_SPECIAL(n) || mod_mask != 0) { - char_u temp[10]; /* modifier: 3, mbyte-char: 6, NUL: 1 */ - int i = 0; - - /* Turn a special key into three bytes, plus modifier. */ - if (mod_mask != 0) { - temp[i++] = K_SPECIAL; - temp[i++] = KS_MODIFIER; - temp[i++] = mod_mask; - } - if (IS_SPECIAL(n)) { - temp[i++] = K_SPECIAL; - temp[i++] = K_SECOND(n); - temp[i++] = K_THIRD(n); - } else { - i += utf_char2bytes(n, temp + i); - } - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(temp); - - if (is_mouse_key(n)) { - int row = mouse_row; - int col = mouse_col; - int grid = mouse_grid; - win_T *win; - linenr_T lnum; - win_T *wp; - int winnr = 1; - - if (row >= 0 && col >= 0) { - /* Find the window at the mouse coordinates and compute the - * text position. */ - win = mouse_find_win(&grid, &row, &col); - if (win == NULL) { - return; - } - (void)mouse_comp_pos(win, &row, &col, &lnum); - for (wp = firstwin; wp != win; wp = wp->w_next) - ++winnr; - vimvars[VV_MOUSE_WIN].vv_nr = winnr; - vimvars[VV_MOUSE_WINID].vv_nr = wp->handle; - vimvars[VV_MOUSE_LNUM].vv_nr = lnum; - vimvars[VV_MOUSE_COL].vv_nr = col + 1; - } - } - } -} - -/* - * "getcharmod()" function - */ -static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = mod_mask; -} - -/* - * "getcharsearch()" function - */ -static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_str(dict, S_LEN("char"), last_csearch()); - tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward()); - tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); -} - -/* - * "getcmdline()" function - */ -static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_cmdline_str(); -} - -/* - * "getcmdpos()" function - */ -static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_pos() + 1; -} - -/* - * "getcmdtype()" function - */ -static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = get_cmdline_type(); -} - -/* - * "getcmdwintype()" function - */ -static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = cmdwin_type; -} - -// "getcompletion()" function -static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *pat; - expand_T xpc; - bool filtered = false; - int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH - | WILD_NO_BEEP; - - if (argvars[2].v_type != VAR_UNKNOWN) { - filtered = (bool)tv_get_number_chk(&argvars[2], NULL); - } - - if (p_wic) { - options |= WILD_ICASE; - } - - // For filtered results, 'wildignore' is used - if (!filtered) { - options |= WILD_KEEP_ALL; - } - - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - - if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) { - set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - goto theend; - } - - ExpandInit(&xpc); - xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type( - (char_u *)tv_get_string(&argvars[1])); - if (xpc.xp_context == EXPAND_NOTHING) { - EMSG2(_(e_invarg2), argvars[1].vval.v_string); - return; - } - - if (xpc.xp_context == EXPAND_MENUS) { - set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_CSCOPE) { - set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_SIGN) { - set_context_in_sign_cmd(&xpc, xpc.xp_pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - -theend: - pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } - xfree(pat); - ExpandCleanup(&xpc); -} - -/// `getcwd([{win}[, {tab}]])` function -/// -/// Every scope not specified implies the currently selected scope object. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. - }; - - char_u *cwd = NULL; // Current working directory to print - char_u *from = NULL; // The original string to copy - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - // If there is no argument there are no more scopes after it, break out. - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - scope_number[i] = argvars[i].vval.v_number; - // It is an error for the scope number to be less than `-1`. - if (scope_number[i] < -1) { - EMSG(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); - if (!tp) { - EMSG(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { - EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - EMSG(_("E5002: Cannot find window number.")); - return; - } - } - } - - cwd = xmalloc(MAXPATHL); - - switch (scope) { - case kCdScopeWindow: - assert(win); - from = win->w_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeTab: - assert(tp); - from = tp->tp_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeGlobal: - if (globaldir) { // `globaldir` is not always set. - from = globaldir; - } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. - from = (char_u *)""; // Return empty string on failure. - } - break; - case kCdScopeInvalid: // We should never get here - assert(false); - } - - if (from) { - xstrlcpy((char *)cwd, (char *)from, MAXPATHL); - } - - rettv->vval.v_string = vim_strsave(cwd); -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(rettv->vval.v_string); -#endif - - xfree(cwd); -} - -/* - * "getfontname()" function - */ -static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; -} - -/* - * "getfperm({fname})" function - */ -static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char *perm = NULL; - char_u flags[] = "rwx"; - - const char *filename = tv_get_string(&argvars[0]); - int32_t file_perm = os_getperm(filename); - if (file_perm >= 0) { - perm = xstrdup("---------"); - for (int i = 0; i < 9; i++) { - if (file_perm & (1 << (8 - i))) { - perm[i] = flags[i % 3]; - } - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)perm; -} - -/* - * "getfsize({fname})" function - */ -static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_NUMBER; - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir((const char_u *)fname)) { - rettv->vval.v_number = 0; - } else { - rettv->vval.v_number = (varnumber_T)filesize; - - /* non-perfect check for overflow */ - if ((uint64_t)rettv->vval.v_number != filesize) { - rettv->vval.v_number = -2; - } - } - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "getftime({fname})" function - */ -static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "getftype({fname})" function - */ -static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *type = NULL; - char *t; - - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_STRING; - FileInfo file_info; - if (os_fileinfo_link(fname, &file_info)) { - uint64_t mode = file_info.stat.st_mode; -#ifdef S_ISREG - if (S_ISREG(mode)) - t = "file"; - else if (S_ISDIR(mode)) - t = "dir"; -# ifdef S_ISLNK - else if (S_ISLNK(mode)) - t = "link"; -# endif -# ifdef S_ISBLK - else if (S_ISBLK(mode)) - t = "bdev"; -# endif -# ifdef S_ISCHR - else if (S_ISCHR(mode)) - t = "cdev"; -# endif -# ifdef S_ISFIFO - else if (S_ISFIFO(mode)) - t = "fifo"; -# endif -# ifdef S_ISSOCK - else if (S_ISSOCK(mode)) - t = "socket"; -# endif - else - t = "other"; -#else -# ifdef S_IFMT - switch (mode & S_IFMT) { - case S_IFREG: t = "file"; break; - case S_IFDIR: t = "dir"; break; -# ifdef S_IFLNK - case S_IFLNK: t = "link"; break; -# endif -# ifdef S_IFBLK - case S_IFBLK: t = "bdev"; break; -# endif -# ifdef S_IFCHR - case S_IFCHR: t = "cdev"; break; -# endif -# ifdef S_IFIFO - case S_IFIFO: t = "fifo"; break; -# endif -# ifdef S_IFSOCK - case S_IFSOCK: t = "socket"; break; -# endif - default: t = "other"; - } -# else - if (os_isdir((const char_u *)fname)) { - t = "dir"; - } else { - t = "file"; - } -# endif -#endif - type = vim_strsave((char_u *)t); - } - rettv->vval.v_string = type; -} - -// "getjumplist()" function -static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp == NULL) { - return; - } - - cleanup_jumplist(wp, true); - - list_T *const l = tv_list_alloc(wp->w_jumplistlen); - tv_list_append_list(rettv->vval.v_list, l); - tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx); - - for (int i = 0; i < wp->w_jumplistlen; i++) { - if (wp->w_jumplist[i].fmark.mark.lnum == 0) { - continue; - } - dict_T *const d = tv_dict_alloc(); - tv_list_append_dict(l, d); - tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum); - tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col); - tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd); - tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum); - if (wp->w_jumplist[i].fname != NULL) { - tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname); - } - } -} - -/* - * "getline(lnum, [end])" function - */ -static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T end; - bool retlist; - - const linenr_T lnum = tv_get_lnum(argvars); - if (argvars[1].v_type == VAR_UNKNOWN) { - end = lnum; - retlist = false; - } else { - end = tv_get_lnum(&argvars[1]); - retlist = true; - } - - get_buffer_lines(curbuf, lnum, end, retlist, rettv); -} - -static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, - typval_T *rettv) -{ - if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - if (is_qf || wp != NULL) { - (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list); - } - } else { - tv_dict_alloc_ret(rettv); - if (is_qf || wp != NULL) { - if (what_arg->v_type == VAR_DICT) { - dict_T *d = what_arg->vval.v_dict; - - if (d != NULL) { - qf_get_properties(wp, d, rettv->vval.v_dict); - } - } else { - EMSG(_(e_dictreq)); - } - } - } -} - -/// "getloclist()" function -static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - get_qf_loc_list(false, wp, &argvars[1], rettv); -} - -/* - * "getmatches()" function - */ -static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - matchitem_T *cur = curwin->w_match_head; - int i; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - while (cur != NULL) { - dict_T *dict = tv_dict_alloc(); - if (cur->match.regprog == NULL) { - // match added with matchaddpos() - for (i = 0; i < MAXPOSMATCH; i++) { - llpos_T *llpos; - char buf[30]; // use 30 to avoid compiler warning - - llpos = &cur->pos.pos[i]; - if (llpos->lnum == 0) { - break; - } - list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); - tv_list_append_number(l, (varnumber_T)llpos->lnum); - if (llpos->col > 0) { - tv_list_append_number(l, (varnumber_T)llpos->col); - tv_list_append_number(l, (varnumber_T)llpos->len); - } - int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); - assert((size_t)len < sizeof(buf)); - tv_dict_add_list(dict, buf, (size_t)len, l); - } - } else { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); - } - tv_dict_add_str(dict, S_LEN("group"), - (const char *)syn_id2name(cur->hlg_id)); - tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); - tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); - - if (cur->conceal_char) { - char buf[MB_MAXBYTES + 1]; - - buf[utf_char2bytes((int)cur->conceal_char, (char_u *)buf)] = NUL; - tv_dict_add_str(dict, S_LEN("conceal"), buf); - } - - tv_list_append_dict(rettv->vval.v_list, dict); - cur = cur->next; - } -} - -/* - * "getpid()" function - */ -static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = os_get_pid(); -} - -static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) -{ - pos_T *fp; - int fnum = -1; - - if (getcurpos) { - fp = &curwin->w_cursor; - } else { - fp = var2fpos(&argvars[0], true, &fnum); - } - - list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); - tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)fp->lnum - : (varnumber_T)0)); - tv_list_append_number( - l, ((fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0)); - tv_list_append_number( - l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); - if (getcurpos) { - const int save_set_curswant = curwin->w_set_curswant; - const colnr_T save_curswant = curwin->w_curswant; - const colnr_T save_virtcol = curwin->w_virtcol; - - update_curswant(); - tv_list_append_number(l, (curwin->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL - : (varnumber_T)curwin->w_curswant + 1)); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (save_set_curswant) { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } -} - -/* - * "getcurpos(string)" function - */ -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getpos_both(argvars, rettv, true); -} - -/* - * "getpos(string)" function - */ -static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getpos_both(argvars, rettv, false); -} - -/// "getqflist()" functions -static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_qf_loc_list(true, NULL, &argvars[0], rettv); -} - -/// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *strregname; - int arg2 = false; - bool return_list = false; - bool error = false; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - error = strregname == NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - arg2 = tv_get_number_chk(&argvars[1], &error); - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - return_list = tv_get_number_chk(&argvars[2], &error); - } - } - } else { - strregname = (const char *)vimvars[VV_REG].vv_str; - } - - if (error) { - return; - } - - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; - } - - if (return_list) { - rettv->v_type = VAR_LIST; - rettv->vval.v_list = - get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); - if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(0); - } - tv_list_ref(rettv->vval.v_list); - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); - } -} - -/* - * "getregtype()" function - */ -static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *strregname; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { // Type error; errmsg already given. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - return; - } - } else { - // Default to v:register. - strregname = (const char *)vimvars[VV_REG].vv_str; - } - - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; - } - - colnr_T reglen = 0; - char buf[NUMBUFLEN + 2]; - MotionType reg_type = get_reg_type(regname, ®len); - format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)xstrdup(buf); -} - -/// Returns information (variables, options, etc.) about a tab page -/// as a dictionary. -static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - - list_T *const l = tv_list_alloc(kListLenMayKnow); - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - tv_list_append_number(l, (varnumber_T)wp->handle); - } - tv_dict_add_list(dict, S_LEN("windows"), l); - - // Make a reference to tabpage variables - tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); - - return dict; -} - -/// "gettabinfo()" function -static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tabpage_T *tparg = NULL; - - tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN - ? 1 - : kListLenMayKnow)); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one tab page - tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tparg == NULL) { - return; - } - } - - // Get information about a specific tab page or all tab pages - int tpnr = 0; - FOR_ALL_TABS(tp) { - tpnr++; - if (tparg != NULL && tp != tparg) { - continue; - } - dict_T *const d = get_tabpage_info(tp, tpnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (tparg != NULL) { - return; - } - } -} - -/* - * "gettabvar()" function - */ -static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *oldcurwin; - tabpage_T *oldtabpage; - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const varname = tv_get_string_chk(&argvars[1]); - tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tp != NULL && varname != NULL) { - // Set tp to be our tabpage, temporarily. Also set the window to the - // first window in the tabpage, otherwise the window is not valid. - win_T *const window = tp == curtab || tp->tp_firstwin == NULL - ? firstwin - : tp->tp_firstwin; - if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { - // look up the variable - // Let gettabvar({nr}, "") return the "t:" dictionary. - const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', - varname, strlen(varname), - false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - - // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); - } - - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - tv_copy(&argvars[2], rettv); - } -} - -/* - * "gettabwinvar()" function - */ -static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 1); -} - -// "gettagstack()" function -static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = curwin; // default is current window - - tv_dict_alloc_ret(rettv); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - } - - get_tagstack(wp, rettv->vval.v_dict); -} - -/// Returns information about a window as a dictionary. -static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); - tv_dict_add_nr(dict, S_LEN("winnr"), winnr); - tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); - tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); - tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); - tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); - tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); - tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); - tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); - tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); - - tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); - tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); - tv_dict_add_nr(dict, S_LEN("loclist"), - (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); - - // Add a reference to window variables - tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); - - return dict; -} - -/// "getwininfo()" function -static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wparg = NULL; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp(argvars); - if (wparg == NULL) { - return; - } - } - - // Collect information about either all the windows across all the tab - // pages or one particular window. - int16_t tabnr = 0; - FOR_ALL_TABS(tp) { - tabnr++; - int16_t winnr = 0; - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - winnr++; - if (wparg != NULL && wp != wparg) { - continue; - } - dict_T *const d = get_win_info(wp, tabnr, winnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (wparg != NULL) { - // found information about a specific window - return; - } - } - } -} - -// Dummy timer callback. Used by f_wait(). -static void dummy_timer_due_cb(TimeWatcher *tw, void *data) -{ -} - -// Dummy timer close callback. Used by f_wait(). -static void dummy_timer_close_cb(TimeWatcher *tw, void *data) -{ - xfree(tw); -} - -/// "wait(timeout, condition[, interval])" function -static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_NUMBER) { - EMSG2(_(e_invargval), "1"); - return; - } - if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN) - || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) { - EMSG2(_(e_invargval), "3"); - return; - } - - int timeout = argvars[0].vval.v_number; - typval_T expr = argvars[1]; - int interval = argvars[2].v_type == VAR_NUMBER - ? argvars[2].vval.v_number - : 200; // Default. - TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); - - // Start dummy timer. - time_watcher_init(&main_loop, tw, NULL); - tw->events = main_loop.events; - tw->blockable = true; - time_watcher_start(tw, dummy_timer_due_cb, interval, interval); - - typval_T argv = TV_INITIAL_VALUE; - typval_T exprval = TV_INITIAL_VALUE; - bool error = false; - int save_called_emsg = called_emsg; - called_emsg = false; - - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, - eval_expr_typval(&expr, &argv, 0, &exprval) != OK - || tv_get_number_chk(&exprval, &error) - || called_emsg || error || got_int); - - if (called_emsg || error) { - rettv->vval.v_number = -3; - } else if (got_int) { - got_int = false; - vgetc(); - rettv->vval.v_number = -2; - } else if (tv_get_number_chk(&exprval, &error)) { - rettv->vval.v_number = 0; - } - - called_emsg = save_called_emsg; - - // Stop dummy timer - time_watcher_stop(tw); - time_watcher_close(tw, dummy_timer_close_cb); -} - -// "win_screenpos()" function -static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); -} - -// "getwinpos({timeout})" function -static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); -} - -/* - * "getwinposx()" function - */ -static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - -/* - * "getwinposy()" function - */ -static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - -/* - * Find window specified by "vp" in tabpage "tp". - */ -static win_T * -find_win_by_nr ( - typval_T *vp, - tabpage_T *tp /* NULL for current tab page */ -) -{ - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr < 0) { - return NULL; - } - - if (nr == 0) { - return curwin; - } - - // This method accepts NULL as an alias for curtab. - if (tp == NULL) { - tp = curtab; - } - - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - if (nr >= LOWEST_WIN_ID) { - if (wp->handle == nr) { - return wp; - } - } else if (--nr <= 0) { - return wp; - } - } - return NULL; -} - -/// Find window specified by "wvp" in tabpage "tvp". -static win_T *find_tabwin(typval_T *wvp, typval_T *tvp) -{ - win_T *wp = NULL; - tabpage_T *tp = NULL; - - if (wvp->v_type != VAR_UNKNOWN) { - if (tvp->v_type != VAR_UNKNOWN) { - long n = tv_get_number(tvp); - if (n >= 0) { - tp = find_tabpage(n); - } - } else { - tp = curtab; - } - - if (tp != NULL) { - wp = find_win_by_nr(wvp, tp); - } - } else { - wp = curwin; - } - - return wp; -} - -/// "getwinvar()" function -static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 0); -} - -/* - * getwinvar() and gettabwinvar() - */ -static void -getwinvar( - typval_T *argvars, - typval_T *rettv, - int off /* 1 for gettabwinvar() */ -) -{ - win_T *win, *oldcurwin; - dictitem_T *v; - tabpage_T *tp = NULL; - tabpage_T *oldtabpage = NULL; - bool done = false; - - if (off == 1) { - tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - } else { - tp = curtab; - } - win = find_win_by_nr(&argvars[off], tp); - const char *varname = tv_get_string_chk(&argvars[off + 1]); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - emsg_off++; - if (win != NULL && varname != NULL) { - // Set curwin to be our win, temporarily. Also set the tabpage, - // otherwise the window is not valid. Only do this when needed, - // autocommands get blocked. - bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { - if (*varname == '&') { - if (varname[1] == NUL) { - // get all window-local options in a dict - dict_T *opts = get_winbuf_options(false); - - if (opts != NULL) { - tv_dict_set_ret(rettv, opts); - done = true; - } - } else if (get_option_tv(&varname, rettv, 1) == OK) { - // window-local-option - done = true; - } - } else { - // Look up the variable. - // Let getwinvar({nr}, "") return the "w:" dictionary. - v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, - strlen(varname), false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - } - - if (need_switch_win) { - // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); - } - } - emsg_off--; - - if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) { - // use the default return value - tv_copy(&argvars[off + 2], rettv); - } -} - -/* - * "glob()" function - */ -static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int options = WILD_SILENT|WILD_USE_NL; - expand_T xpc; - bool error = false; - - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[2], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[3], &error)) { - options |= WILD_ALLLINKS; - } - } - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) - options += WILD_ICASE; - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne( - &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); - } else { - ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, - WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } - ExpandCleanup(&xpc); - } - } else - rettv->vval.v_string = NULL; -} - -/// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int flags = 0; // Flags for globpath. - bool error = false; - - // Return a string, or a list if the optional third argument is non-zero. - rettv->v_type = VAR_STRING; - - if (argvars[2].v_type != VAR_UNKNOWN) { - // When the optional second argument is non-zero, don't remove matches - // for 'wildignore' and don't put matches for 'suffixes' at the end. - if (tv_get_number_chk(&argvars[2], &error)) { - flags |= WILD_KEEP_ALL; - } - - if (argvars[3].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[3], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[4].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[4], &error)) { - flags |= WILD_ALLLINKS; - } - } - } - - char buf1[NUMBUFLEN]; - const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); - if (file != NULL && !error) { - garray_T ga; - ga_init(&ga, (int)sizeof(char_u *), 10); - globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); - - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); - } else { - tv_list_alloc_ret(rettv, ga.ga_len); - for (int i = 0; i < ga.ga_len; i++) { - tv_list_append_string(rettv->vval.v_list, - ((const char **)(ga.ga_data))[i], -1); - } - } - - ga_clear_strings(&ga); - } else { - rettv->vval.v_string = NULL; - } -} - -// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = ((pat == NULL) - ? NULL - : file_pat_to_reg_pat((char_u *)pat, NULL, NULL, - false)); -} - -/// "has()" function -static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - static const char *const has_list[] = { -#if defined(BSD) && !defined(__APPLE__) - "bsd", -#endif -#ifdef UNIX - "unix", -#endif -#if defined(WIN32) - "win32", -#endif -#if defined(WIN64) || defined(_WIN64) - "win64", -#endif - "fname_case", -#ifdef HAVE_ACL - "acl", -#endif - "autochdir", - "arabic", - "autocmd", - "browsefilter", - "byte_offset", - "cindent", - "cmdline_compl", - "cmdline_hist", - "comments", - "conceal", - "cscope", - "cursorbind", - "cursorshape", -#ifdef DEBUG - "debug", -#endif - "dialog_con", - "diff", - "digraphs", - "eval", /* always present, of course! */ - "ex_extra", - "extra_search", - "file_in_path", - "filterpipe", - "find_in_path", - "float", - "folding", -#if defined(UNIX) - "fork", -#endif - "gettext", -#if defined(HAVE_ICONV) - "iconv", -#endif - "insert_expand", - "jumplist", - "keymap", - "lambda", - "langmap", - "libcall", - "linebreak", - "lispindent", - "listcmds", - "localmap", -#ifdef __APPLE__ - "mac", - "macunix", - "osx", - "osxdarwin", -#endif - "menu", - "mksession", - "modify_fname", - "mouse", - "multi_byte", - "multi_lang", - "num64", - "packages", - "path_extra", - "persistent_undo", - "postscript", - "printer", - "profile", - "pythonx", - "reltime", - "quickfix", - "rightleft", - "scrollbind", - "showcmd", - "cmdline_info", - "shada", - "signs", - "smartindent", - "startuptime", - "statusline", - "spell", - "syntax", -#if !defined(UNIX) - "system", // TODO(SplinterOfChaos): This IS defined for UNIX! -#endif - "tablineat", - "tag_binary", - "termguicolors", - "termresponse", - "textobjects", - "timers", - "title", - "user-commands", /* was accidentally included in 5.4 */ - "user_commands", - "vertsplit", - "virtualedit", - "visual", - "visualextra", - "vreplace", - "wildignore", - "wildmenu", - "windows", - "winaltkeys", - "writebackup", -#if defined(HAVE_WSL) - "wsl", -#endif - "nvim", - }; - - bool n = false; - const char *const name = tv_get_string(&argvars[0]); - for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) { - if (STRICMP(name, has_list[i]) == 0) { - n = true; - break; - } - } - - if (!n) { - if (STRNICMP(name, "patch", 5) == 0) { - if (name[5] == '-' - && strlen(name) >= 11 - && ascii_isdigit(name[6]) - && ascii_isdigit(name[8]) - && ascii_isdigit(name[10])) { - int major = atoi(name + 6); - int minor = atoi(name + 8); - - // Expect "patch-9.9.01234". - n = (major < VIM_VERSION_MAJOR - || (major == VIM_VERSION_MAJOR - && (minor < VIM_VERSION_MINOR - || (minor == VIM_VERSION_MINOR - && has_vim_patch(atoi(name + 10)))))); - } else { - n = has_vim_patch(atoi(name + 5)); - } - } else if (STRNICMP(name, "nvim-", 5) == 0) { - // Expect "nvim-x.y.z" - n = has_nvim_version(name + 5); - } else if (STRICMP(name, "vim_starting") == 0) { - n = (starting != 0); - } else if (STRICMP(name, "ttyin") == 0) { - n = stdin_isatty; - } else if (STRICMP(name, "ttyout") == 0) { - n = stdout_isatty; - } else if (STRICMP(name, "multi_byte_encoding") == 0) { - n = has_mbyte != 0; - } else if (STRICMP(name, "syntax_items") == 0) { - n = syntax_present(curwin); -#ifdef UNIX - } else if (STRICMP(name, "unnamedplus") == 0) { - n = eval_has_provider("clipboard"); -#endif - } - } - - if (!n && eval_has_provider(name)) { - n = true; - } - - rettv->vval.v_number = n; -} - -/* - * "has_key()" function - */ -static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - if (argvars[0].vval.v_dict == NULL) - return; - - rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, - tv_get_string(&argvars[1]), - -1) != NULL; -} - -/// `haslocaldir([{win}[, {tab}]])` function -/// -/// Returns `1` if the scope object has a local directory, `0` otherwise. If a -/// scope object is not specified the current one is implied. This function -/// share a lot of code with `f_getcwd`. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. - }; - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - scope_number[i] = argvars[i].vval.v_number; - if (scope_number[i] < -1) { - EMSG(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); - if (!tp) { - EMSG(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { - EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - EMSG(_("E5002: Cannot find window number.")); - return; - } - } - } - - switch (scope) { - case kCdScopeWindow: - assert(win); - rettv->vval.v_number = win->w_localdir ? 1 : 0; - break; - case kCdScopeTab: - assert(tp); - rettv->vval.v_number = tp->tp_localdir ? 1 : 0; - break; - case kCdScopeGlobal: - // The global scope never has a local directory - break; - case kCdScopeInvalid: - // We should never get here - assert(false); - } -} - -/* - * "hasmapto()" function - */ -static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *mode; - const char *const name = tv_get_string(&argvars[0]); - bool abbr = false; - char buf[NUMBUFLEN]; - if (argvars[1].v_type == VAR_UNKNOWN) { - mode = "nvo"; - } else { - mode = tv_get_string_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - abbr = tv_get_number(&argvars[2]); - } - } - - if (map_to_exists(name, mode, abbr)) { - rettv->vval.v_number = true; - } else { - rettv->vval.v_number = false; - } -} - -/* - * "histadd()" function - */ -static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType histype; - - rettv->vval.v_number = false; - if (check_restricted() || check_secure()) { - return; - } - const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error - histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; - if (histype != HIST_INVALID) { - char buf[NUMBUFLEN]; - str = tv_get_string_buf(&argvars[1], buf); - if (*str != NUL) { - init_history(); - add_to_history(histype, (char_u *)str, false, NUL); - rettv->vval.v_number = true; - return; - } - } -} - -/* - * "histdel()" function - */ -static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n; - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - n = 0; - } else if (argvars[1].v_type == VAR_UNKNOWN) { - // only one argument: clear entire history - n = clr_history(get_histtype(str, strlen(str), false)); - } else if (argvars[1].v_type == VAR_NUMBER) { - // index given: remove that entry - n = del_history_idx(get_histtype(str, strlen(str), false), - (int)tv_get_number(&argvars[1])); - } else { - // string given: remove all matching entries - char buf[NUMBUFLEN]; - n = del_history_entry(get_histtype(str, strlen(str), false), - (char_u *)tv_get_string_buf(&argvars[1], buf)); - } - rettv->vval.v_number = n; -} - -/* - * "histget()" function - */ -static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType type; - int idx; - - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - rettv->vval.v_string = NULL; - } else { - type = get_histtype(str, strlen(str), false); - if (argvars[1].v_type == VAR_UNKNOWN) { - idx = get_history_idx(type); - } else { - idx = (int)tv_get_number_chk(&argvars[1], NULL); - } - // -1 on type error - rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); - } - rettv->v_type = VAR_STRING; -} - -/* - * "histnr()" function - */ -static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const history = tv_get_string_chk(&argvars[0]); - HistoryType i = history == NULL - ? HIST_INVALID - : get_histtype(history, strlen(history), false); - if (i != HIST_INVALID) { - i = get_history_idx(i); - } - rettv->vval.v_number = i; -} - -/* - * "highlightID(name)" function - */ -static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = syn_name2id( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "highlight_exists()" function - */ -static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = highlight_exists( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "hostname()" function - */ -static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char hostname[256]; - - os_get_hostname(hostname, 256); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave((char_u *)hostname); -} - -/* - * iconv() function - */ -static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - vimconv_T vimconv; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const str = tv_get_string(&argvars[0]); - char buf1[NUMBUFLEN]; - char_u *const from = enc_canonize(enc_skip( - (char_u *)tv_get_string_buf(&argvars[1], buf1))); - char buf2[NUMBUFLEN]; - char_u *const to = enc_canonize(enc_skip( - (char_u *)tv_get_string_buf(&argvars[2], buf2))); - vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, from, to); - - // If the encodings are equal, no conversion needed. - if (vimconv.vc_type == CONV_NONE) { - rettv->vval.v_string = (char_u *)xstrdup(str); - } else { - rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL); - } - - convert_setup(&vimconv, NULL, NULL); - xfree(from); - xfree(to); -} - -/* - * "indent()" function - */ -static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = get_indent_lnum(lnum); - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "index()" function - */ -static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long idx = 0; - bool ic = false; - - rettv->vval.v_number = -1; - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - list_T *const l = argvars[0].vval.v_list; - if (l != NULL) { - listitem_T *item = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - // Start at specified item. - idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); - if (error || idx == -1) { - item = NULL; - } else { - item = tv_list_find(l, idx); - assert(item != NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = !!tv_get_number_chk(&argvars[3], &error); - if (error) { - item = NULL; - } - } - } - - for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { - if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { - rettv->vval.v_number = idx; - break; - } - } - } -} - -static int inputsecret_flag = 0; - -/* - * This function is used by f_input() and f_inputdialog() functions. The third - * argument to f_input() specifies the type of completion to use at the - * prompt. The third argument to f_inputdialog() specifies the value to return - * when the user cancels the prompt. - */ -void get_user_input(const typval_T *const argvars, - typval_T *const rettv, const bool inputdialog) - FUNC_ATTR_NONNULL_ALL -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *prompt = ""; - const char *defstr = ""; - const char *cancelreturn = NULL; - const char *xp_name = NULL; - Callback input_callback = { .type = kCallbackNone }; - char prompt_buf[NUMBUFLEN]; - char defstr_buf[NUMBUFLEN]; - char cancelreturn_buf[NUMBUFLEN]; - char xp_name_buf[NUMBUFLEN]; - char def[1] = { 0 }; - if (argvars[0].v_type == VAR_DICT) { - if (argvars[1].v_type != VAR_UNKNOWN) { - EMSG(_("E5050: {opts} must be the only argument")); - return; - } - dict_T *const dict = argvars[0].vval.v_dict; - prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); - if (prompt == NULL) { - return; - } - defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, ""); - if (defstr == NULL) { - return; - } - cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), - cancelreturn_buf, def); - if (cancelreturn == NULL) { // error - return; - } - if (*cancelreturn == NUL) { - cancelreturn = NULL; - } - xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), - xp_name_buf, def); - if (xp_name == NULL) { // error - return; - } - if (xp_name == def) { // default to NULL - xp_name = NULL; - } - if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { - return; - } - } else { - prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); - if (prompt == NULL) { - return; - } - if (argvars[1].v_type != VAR_UNKNOWN) { - defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf); - if (defstr == NULL) { - return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const arg2 = tv_get_string_buf_chk(&argvars[2], - cancelreturn_buf); - if (arg2 == NULL) { - return; - } - if (inputdialog) { - cancelreturn = arg2; - } else { - xp_name = arg2; - } - } - } - } - - int xp_type = EXPAND_NOTHING; - char *xp_arg = NULL; - if (xp_name != NULL) { - // input() with a third argument: completion - const int xp_namelen = (int)strlen(xp_name); - - uint32_t argt; - if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, - &argt, (char_u **)&xp_arg) == FAIL) { - return; - } - } - - const bool cmd_silent_save = cmd_silent; - - cmd_silent = false; // Want to see the prompt. - // Only the part of the message after the last NL is considered as - // prompt for the command line, unlsess cmdline is externalized - const char *p = prompt; - if (!ui_has(kUICmdline)) { - const char *lastnl = strrchr(prompt, '\n'); - if (lastnl != NULL) { - p = lastnl+1; - msg_start(); - msg_clr_eos(); - msg_puts_attr_len(prompt, p - prompt, echo_attr); - msg_didout = false; - msg_starthere(); - } - } - cmdline_row = msg_row; - - stuffReadbuffSpec(defstr); - - const int save_ex_normal_busy = ex_normal_busy; - ex_normal_busy = 0; - rettv->vval.v_string = - (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, - xp_type, xp_arg, input_callback); - ex_normal_busy = save_ex_normal_busy; - callback_free(&input_callback); - - if (rettv->vval.v_string == NULL && cancelreturn != NULL) { - rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); - } - - xfree(xp_arg); - - // Since the user typed this, no need to wait for return. - need_wait_return = false; - msg_didout = false; - cmd_silent = cmd_silent_save; -} - -/* - * "input()" function - * Also handles inputsecret() when inputsecret is set. - */ -static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_user_input(argvars, rettv, FALSE); -} - -/* - * "inputdialog()" function - */ -static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_user_input(argvars, rettv, TRUE); -} - -/* - * "inputlist()" function - */ -static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int selected; - int mouse_used; - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "inputlist()"); - return; - } - - msg_start(); - msg_row = Rows - 1; /* for when 'cmdheight' > 1 */ - lines_left = Rows; /* avoid more prompt */ - msg_scroll = TRUE; - msg_clr_eos(); - - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); - msg_putchar('\n'); - }); - - // Ask for choice. - selected = prompt_for_number(&mouse_used); - if (mouse_used) { - selected -= lines_left; - } - - rettv->vval.v_number = selected; -} - - -static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL}; - -/// "inputrestore()" function -static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (!GA_EMPTY(&ga_userinput)) { - ga_userinput.ga_len--; - restore_typeahead((tasave_T *)(ga_userinput.ga_data) - + ga_userinput.ga_len); - // default return is zero == OK - } else if (p_verbose > 1) { - verb_msg(_("called inputrestore() more often than inputsave()")); - rettv->vval.v_number = 1; // Failed - } -} - -/// "inputsave()" function -static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Add an entry to the stack of typeahead storage. - tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput); - save_typeahead(p); -} - -/// "inputsecret()" function -static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - cmdline_star++; - inputsecret_flag++; - f_input(argvars, rettv, NULL); - cmdline_star--; - inputsecret_flag--; -} - -/* - * "insert()" function - */ -static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - list_T *l; - bool error = false; - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "insert()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { - long before = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - before = tv_get_number_chk(&argvars[2], &error); - } - if (error) { - // type error; errmsg already given - return; - } - - listitem_T *item = NULL; - if (before != tv_list_len(l)) { - item = tv_list_find(l, before); - if (item == NULL) { - EMSGN(_(e_listidx), before); - l = NULL; - } - } - if (l != NULL) { - tv_list_insert_tv(l, &argvars[1], item); - tv_copy(&argvars[0], rettv); - } - } -} - -/* - * "invert(expr)" function - */ -static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); -} - -/* - * "isdirectory()" function - */ -static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "islocked()" function - */ -static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - lval_T lv; - dictitem_T *di; - - rettv->vval.v_number = -1; - const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]), - NULL, - &lv, false, false, - GLV_NO_AUTOLOAD|GLV_READ_ONLY, - FNE_CHECK_START); - if (end != NULL && lv.ll_name != NULL) { - if (*end != NUL) { - EMSG(_(e_trailing)); - } else { - if (lv.ll_tv == NULL) { - di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true); - if (di != NULL) { - // Consider a variable locked when: - // 1. the variable itself is locked - // 2. the value of the variable is locked. - // 3. the List or Dict value is locked. - rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) - || tv_islocked(&di->di_tv)); - } - } else if (lv.ll_range) { - EMSG(_("E786: Range not allowed")); - } else if (lv.ll_newkey != NULL) { - EMSG2(_(e_dictkey), lv.ll_newkey); - } else if (lv.ll_list != NULL) { - // List item. - rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); - } else { - // Dictionary item. - rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); - } - } - } - - clear_lval(&lv); -} - -// "isinf()" function -static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_FLOAT - && xisinf(argvars[0].vval.v_float)) { - rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1; - } -} - -// "isnan()" function -static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT - && xisnan(argvars[0].vval.v_float); -} - -/// Turn a dictionary into a list -/// -/// @param[in] tv Dictionary to convert. Is checked for actually being -/// a dictionary, will give an error if not. -/// @param[out] rettv Location where result will be saved. -/// @param[in] what What to save in rettv. -static void dict_list(typval_T *const tv, typval_T *const rettv, - const DictListType what) -{ - if (tv->v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - if (tv->vval.v_dict == NULL) { - return; - } - - tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); - - TV_DICT_ITER(tv->vval.v_dict, di, { - typval_T tv_item = { .v_lock = VAR_UNLOCKED }; - - switch (what) { - case kDictListKeys: { - tv_item.v_type = VAR_STRING; - tv_item.vval.v_string = vim_strsave(di->di_key); - break; - } - case kDictListValues: { - tv_copy(&di->di_tv, &tv_item); - break; - } - case kDictListItems: { - // items() - list_T *const sub_l = tv_list_alloc(2); - tv_item.v_type = VAR_LIST; - tv_item.vval.v_list = sub_l; - tv_list_ref(sub_l); - - tv_list_append_owned_tv(sub_l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), - }); - - tv_list_append_tv(sub_l, &di->di_tv); - - break; - } - } - - tv_list_append_owned_tv(rettv->vval.v_list, tv_item); - }); -} - -/// "id()" function -static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmalloc(len + 1); - vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p", - dummy_ap, argvars); -} - -/* - * "items(dict)" function - */ -static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 2); -} - -// "jobpid(id)" function -static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_job(argvars[0].vval.v_number, true); - if (!data) { - return; - } - - Process *proc = (Process *)&data->stream.proc; - rettv->vval.v_number = proc->pid; -} - -// "jobresize(job, width, height)" function -static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER - || argvars[2].v_type != VAR_NUMBER) { - // job id, width, height - EMSG(_(e_invarg)); - return; - } - - - Channel *data = find_job(argvars[0].vval.v_number, true); - if (!data) { - return; - } - - if (data->stream.proc.type != kProcessTypePty) { - EMSG(_(e_channotpty)); - return; - } - - pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, - argvars[2].vval.v_number); - rettv->vval.v_number = 1; -} - -/// Builds a process argument vector from a VimL object (typval_T). -/// -/// @param[in] cmd_tv VimL object -/// @param[out] cmd Returns the command or executable name. -/// @param[out] executable Returns `false` if argv[0] is not executable. -/// -/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. -/// Else, string values of `cmd_tv` copied to a (char **) list with -/// argv[0] resolved to full path ($PATHEXT-resolved on Windows). -static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) -{ - if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics". - const char *cmd_str = tv_get_string(cmd_tv); - if (cmd) { - *cmd = cmd_str; - } - return shell_build_argv(cmd_str, NULL); - } - - if (cmd_tv->v_type != VAR_LIST) { - EMSG2(_(e_invarg2), "expected String or List"); - return NULL; - } - - list_T *argl = cmd_tv->vval.v_list; - int argc = tv_list_len(argl); - if (!argc) { - EMSG(_(e_invarg)); // List must have at least one item. - return NULL; - } - - const char *arg0 = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); - char *exe_resolved = NULL; - if (!arg0 || !os_can_exe(arg0, &exe_resolved, true)) { - if (arg0 && executable) { - char buf[IOSIZE]; - snprintf(buf, sizeof(buf), "'%s' is not executable", arg0); - EMSG3(_(e_invargNval), "cmd", buf); - *executable = false; - } - return NULL; - } - - if (cmd) { - *cmd = exe_resolved; - } - - // Build the argument vector - int i = 0; - char **argv = xcalloc(argc + 1, sizeof(char *)); - TV_LIST_ITER_CONST(argl, arg, { - const char *a = tv_get_string_chk(TV_LIST_ITEM_TV(arg)); - if (!a) { - // Did emsg in tv_get_string_chk; just deallocate argv. - shell_free_argv(argv); - xfree(exe_resolved); - return NULL; - } - argv[i++] = xstrdup(a); - }); - // Replace argv[0] with absolute path. The only reason for this is to make - // $PATHEXT work on Windows with jobstart([…]). #9569 - xfree(argv[0]); - argv[0] = exe_resolved; - - return argv; -} - -// "jobstart()" function -static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - bool executable = true; - char **argv = tv_to_argv(&argvars[0], NULL, &executable); - char **env = NULL; - if (!argv) { - rettv->vval.v_number = executable ? 0 : -1; - return; // Did error message in tv_to_argv. - } - - if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) { - // Wrong argument types - EMSG2(_(e_invarg2), "expected dictionary"); - shell_free_argv(argv); - return; - } - - - dict_T *job_opts = NULL; - bool detach = false; - bool rpc = false; - bool pty = false; - bool clear_env = false; - CallbackReader on_stdout = CALLBACK_READER_INIT, - on_stderr = CALLBACK_READER_INIT; - Callback on_exit = CALLBACK_NONE; - char *cwd = NULL; - if (argvars[1].v_type == VAR_DICT) { - job_opts = argvars[1].vval.v_dict; - - detach = tv_dict_get_number(job_opts, "detach") != 0; - rpc = tv_dict_get_number(job_opts, "rpc") != 0; - pty = tv_dict_get_number(job_opts, "pty") != 0; - clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; - if (pty && rpc) { - EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); - shell_free_argv(argv); - return; - } - - char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && strlen(new_cwd) > 0) { - cwd = new_cwd; - // The new cwd must be a directory. - if (!os_isdir_executable((const char *)cwd)) { - EMSG2(_(e_invarg2), "expected valid directory"); - shell_free_argv(argv); - return; - } - } - dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); - if (job_env) { - if (job_env->di_tv.v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "env"); - shell_free_argv(argv); - return; - } - - size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); - size_t i = 0; - size_t env_size = 0; - - if (clear_env) { - // + 1 for last null entry - env = xmalloc((custom_env_size + 1) * sizeof(*env)); - env_size = 0; - } else { - env_size = os_get_fullenv_size(); - - env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); - - os_copy_fullenv(env, env_size); - i = env_size; - } - assert(env); // env must be allocated at this point - - TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { - const char *str = tv_get_string(&var->di_tv); - assert(str); - size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; - env[i] = xmalloc(len); - snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); - i++; - }); - - // must be null terminated - env[env_size + custom_env_size] = NULL; - } - - - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { - shell_free_argv(argv); - return; - } - } - - uint16_t width = 0, height = 0; - char *term_name = NULL; - - if (pty) { - width = (uint16_t)tv_dict_get_number(job_opts, "width"); - height = (uint16_t)tv_dict_get_number(job_opts, "height"); - term_name = tv_dict_get_string(job_opts, "TERM", true); - } - - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, - rpc, detach, cwd, width, height, - term_name, env, &rettv->vval.v_number); - if (chan) { - channel_create_event(chan, NULL); - } -} - -// "jobstop()" function -static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER) { - // Only argument is the job id - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_job(argvars[0].vval.v_number, false); - if (!data) { - return; - } - - const char *error = NULL; - if (data->is_rpc) { - // Ignore return code, but show error later. - (void)channel_close(data->id, kChannelPartRpc, &error); - } - process_stop((Process *)&data->stream.proc); - rettv->vval.v_number = 1; - if (error) { - EMSG(error); - } -} - -// "jobwait(ids[, timeout])" function -static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER - && argvars[1].v_type != VAR_UNKNOWN)) { - EMSG(_(e_invarg)); - return; - } - - ui_busy_start(); - list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); - MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); - - // Validate, prepare jobs for waiting. - int i = 0; - TV_LIST_ITER_CONST(args, arg, { - Channel *chan = NULL; - if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER - || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { - jobs[i] = NULL; // Invalid job. - } else { - jobs[i] = chan; - channel_incref(chan); - if (chan->stream.proc.status < 0) { - // Process any pending events on the job's queue before temporarily - // replacing it. - multiqueue_process_events(chan->events); - multiqueue_replace_parent(chan->events, waiting_jobs); - } - } - i++; - }); - - int remaining = -1; - uint64_t before = 0; - if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) { - remaining = argvars[1].vval.v_number; - before = os_hrtime(); - } - - for (i = 0; i < tv_list_len(args); i++) { - if (remaining == 0) { - break; // Timeout. - } - if (jobs[i] == NULL) { - continue; // Invalid job, will assign status=-3 below. - } - int status = process_wait(&jobs[i]->stream.proc, remaining, - waiting_jobs); - if (status < 0) { - break; // Interrupted (CTRL-C) or timeout, skip remaining jobs. - } - if (remaining > 0) { - uint64_t now = os_hrtime(); - remaining = MIN(0, remaining - (int)((now - before) / 1000000)); - before = now; - } - } - - list_T *const rv = tv_list_alloc(tv_list_len(args)); - - // For each job: - // * Restore its parent queue if the job is still alive. - // * Append its status to the output list, or: - // -3 for "invalid job id" - // -2 for "interrupted" (user hit CTRL-C) - // -1 for jobs that were skipped or timed out - for (i = 0; i < tv_list_len(args); i++) { - if (jobs[i] == NULL) { - tv_list_append_number(rv, -3); - continue; - } - multiqueue_process_events(jobs[i]->events); - multiqueue_replace_parent(jobs[i]->events, main_loop.events); - - tv_list_append_number(rv, jobs[i]->stream.proc.status); - channel_decref(jobs[i]); - } - - multiqueue_free(waiting_jobs); - xfree(jobs); - ui_busy_stop(); - tv_list_ref(rv); - rettv->v_type = VAR_LIST; - rettv->vval.v_list = rv; -} - -/* - * "join()" function - */ -static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - const char *const sep = (argvars[1].v_type == VAR_UNKNOWN - ? " " - : tv_get_string_chk(&argvars[1])); - - rettv->v_type = VAR_STRING; - - if (sep != NULL) { - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - tv_list_join(&ga, argvars[0].vval.v_list, sep); - ga_append(&ga, NUL); - rettv->vval.v_string = (char_u *)ga.ga_data; - } else { - rettv->vval.v_string = NULL; - } -} - -/// json_decode() function -static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char numbuf[NUMBUFLEN]; - const char *s = NULL; - char *tofree = NULL; - size_t len; - if (argvars[0].v_type == VAR_LIST) { - if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) { - EMSG(_("E474: Failed to convert list to string")); - return; - } - s = tofree; - if (s == NULL) { - assert(len == 0); - s = ""; - } - } else { - s = tv_get_string_buf_chk(&argvars[0], numbuf); - if (s) { - len = strlen(s); - } else { - return; - } - } - if (json_decode_string(s, len, rettv) == FAIL) { - emsgf(_("E474: Failed to parse %.*s"), (int)len, s); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - } - assert(rettv->v_type != VAR_UNKNOWN); - xfree(tofree); -} - -/// json_encode() function -static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *) encode_tv2json(&argvars[0], NULL); -} - -/* - * "keys()" function - */ -static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 0); -} - -/* - * "last_buffer_nr()" function. - */ -static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = 0; - - FOR_ALL_BUFFERS(buf) { - if (n < buf->b_fnum) { - n = buf->b_fnum; - } - } - - rettv->vval.v_number = n; -} - -/* - * "len()" function - */ -static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_NUMBER: { - rettv->vval.v_number = (varnumber_T)strlen( - tv_get_string(&argvars[0])); - break; - } - case VAR_LIST: { - rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list); - break; - } - case VAR_DICT: { - rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict); - break; - } - case VAR_UNKNOWN: - case VAR_SPECIAL: - case VAR_FLOAT: - case VAR_PARTIAL: - case VAR_FUNC: { - EMSG(_("E701: Invalid type for len()")); - break; - } - } -} - -static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) -{ - rettv->v_type = out_type; - if (out_type != VAR_NUMBER) { - rettv->vval.v_string = NULL; - } - - if (check_restricted() || check_secure()) { - return; - } - - // The first two args (libname and funcname) must be strings - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - return; - } - - const char *libname = (char *) argvars[0].vval.v_string; - const char *funcname = (char *) argvars[1].vval.v_string; - - VarType in_type = argvars[2].v_type; - - // input variables - char *str_in = (in_type == VAR_STRING) - ? (char *) argvars[2].vval.v_string : NULL; - int64_t int_in = argvars[2].vval.v_number; - - // output variables - char **str_out = (out_type == VAR_STRING) - ? (char **)&rettv->vval.v_string : NULL; - int int_out = 0; - - bool success = os_libcall(libname, funcname, - str_in, int_in, - str_out, &int_out); - - if (!success) { - EMSG2(_(e_libcall), funcname); - return; - } - - if (out_type == VAR_NUMBER) { - rettv->vval.v_number = (varnumber_T)int_out; - } -} - -/* - * "libcall()" function - */ -static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - libcall_common(argvars, rettv, VAR_STRING); -} - -/* - * "libcallnr()" function - */ -static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - libcall_common(argvars, rettv, VAR_NUMBER); -} - -/* - * "line(string)" function - */ -static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = 0; - pos_T *fp; - int fnum; - - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fp != NULL) - lnum = fp->lnum; - rettv->vval.v_number = lnum; -} - -/* - * "line2byte(lnum)" function - */ -static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false); - } - if (rettv->vval.v_number >= 0) { - rettv->vval.v_number++; - } -} - -/* - * "lispindent(lnum)" function - */ -static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const pos_T pos = curwin->w_cursor; - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_lisp_indent(); - curwin->w_cursor = pos; - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "localtime()" function - */ -static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (varnumber_T)time(NULL); -} - - -static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) -{ - char_u *keys_buf = NULL; - char_u *rhs; - int mode; - int abbr = FALSE; - int get_dict = FALSE; - mapblock_T *mp; - int buffer_local; - - // Return empty string for failure. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - char_u *keys = (char_u *)tv_get_string(&argvars[0]); - if (*keys == NUL) { - return; - } - - char buf[NUMBUFLEN]; - const char *which; - if (argvars[1].v_type != VAR_UNKNOWN) { - which = tv_get_string_buf_chk(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - abbr = tv_get_number(&argvars[2]); - if (argvars[3].v_type != VAR_UNKNOWN) { - get_dict = tv_get_number(&argvars[3]); - } - } - } else { - which = ""; - } - if (which == NULL) { - return; - } - - mode = get_map_mode((char_u **)&which, 0); - - keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, - CPO_TO_CPO_FLAGS); - rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); - xfree(keys_buf); - - if (!get_dict) { - // Return a string. - if (rhs != NULL) { - if (*rhs == NUL) { - rettv->vval.v_string = vim_strsave((char_u *)""); - } else { - rettv->vval.v_string = (char_u *)str2special_save( - (char *)rhs, false, false); - } - } - - } else { - tv_dict_alloc_ret(rettv); - if (rhs != NULL) { - // Return a dictionary. - mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); - } - } -} - -/// luaeval() function implementation -static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - const char *const str = (const char *)tv_get_string_chk(&argvars[0]); - if (str == NULL) { - return; - } - - executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); -} - -/// Fill a dictionary with all applicable maparg() like dictionaries -/// -/// @param dict The dictionary to be filled -/// @param mp The maphash that contains the mapping information -/// @param buffer_value The "buffer" value -/// @param compatible True for compatible with old maparg() dict -void mapblock_fill_dict(dict_T *const dict, - const mapblock_T *const mp, - long buffer_value, - bool compatible) - FUNC_ATTR_NONNULL_ALL -{ - char *const lhs = str2special_save((const char *)mp->m_keys, - compatible, !compatible); - char *const mapmode = map_mode_to_chars(mp->m_mode); - varnumber_T noremap_value; - - if (compatible) { - // Keep old compatible behavior - // This is unable to determine whether a mapping is a