diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/nvim/eval.c | 11391 | ||||
-rw-r--r-- | src/nvim/eval.h | 116 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 10920 | ||||
-rw-r--r-- | src/nvim/eval/funcs.h | 24 | ||||
-rw-r--r-- | src/nvim/globals.h | 8 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 2 |
7 files changed, 11246 insertions, 11220 deletions
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 <assert.h> -#include <float.h> -#include <inttypes.h> -#include <stdarg.h> -#include <string.h> -#include <stdlib.h> -#include <stdbool.h> #include <math.h> -#include <limits.h> -#include <msgpack.h> -#include "nvim/assert.h" -#include "nvim/vim.h" -#include "nvim/ascii.h" #ifdef HAVE_LOCALE_H # include <locale.h> #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,120 +6671,7 @@ static int assert_equalfile(typval_T *argvars) return 0; } -static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (!called_vim_beep) { - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not beep: "); - ga_concat(&ga, (const char_u *)cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 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); -} - -// "assert_equalfile(fname-one, fname-two)" function -static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equalfile(argvars); -} - -// "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); -} - -/// "assert_report(msg) -static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - garray_T ga; - - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); - 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); -} - -/// "assert_fails(cmd [, error [, msg]])" function -static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - int save_trylevel = trylevel; - - // trylevel must be zero for a ":throw" command to be considered failed - trylevel = 0; - called_emsg = false; - suppress_errthrow = true; - emsg_silent = true; - - do_cmdline_cmd(cmd); - if (!called_emsg) { - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not fail: "); - if (argvars[1].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(&ga, (char_u *)tofree); - xfree(tofree); - } else { - ga_concat(&ga, (const char_u *)cmd); - } - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); - - if (error == NULL - || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - } - - trylevel = save_trylevel; - called_emsg = false; - suppress_errthrow = false; - emsg_silent = false; - emsg_on_display = false; - set_vim_var_string(VV_ERRMSG, NULL, 0); - rettv->vval.v_number = ret; -} - -static int assert_inrange(typval_T *argvars) +int assert_inrange(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { bool error = false; @@ -7321,7 +6700,7 @@ static int assert_inrange(typval_T *argvars) } // Common for assert_true() and assert_false(). -static int assert_bool(typval_T *argvars, bool is_true) +int assert_bool(typval_T *argvars, bool is_true) FUNC_ATTR_NONNULL_ALL { bool error = false; @@ -7346,7 +6725,7 @@ static int assert_bool(typval_T *argvars, bool is_true) return 0; } -static int assert_exception(typval_T *argvars) +int assert_exception(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { garray_T ga; @@ -7370,13 +6749,60 @@ static int assert_exception(typval_T *argvars) return 0; } -// "assert_false(actual[, msg])" function -static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_fails(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { - rettv->vval.v_number = assert_bool(argvars, false); + const char *const cmd = tv_get_string_chk(&argvars[0]); + garray_T ga; + int ret = 0; + int save_trylevel = trylevel; + + // trylevel must be zero for a ":throw" command to be considered failed + trylevel = 0; + called_emsg = false; + suppress_errthrow = true; + emsg_silent = true; + + do_cmdline_cmd(cmd); + if (!called_emsg) { + prepare_assert_error(&ga); + ga_concat(&ga, (const char_u *)"command did not fail: "); + if (argvars[1].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, (char_u *)tofree); + xfree(tofree); + } else { + ga_concat(&ga, (const char_u *)cmd); + } + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } else if (argvars[1].v_type != VAR_UNKNOWN) { + char buf[NUMBUFLEN]; + const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + + if (error == NULL + || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], + &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + } + + trylevel = save_trylevel; + called_emsg = false; + suppress_errthrow = false; + emsg_silent = false; + emsg_on_display = false; + set_vim_var_string(VV_ERRMSG, NULL, 0); + return ret; } -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,1143 +6855,6 @@ func_call_skip_call: return r; } -/// "call(func, arglist [, dict])" function -static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - 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 - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - selfdict = argvars[2].vval.v_dict; - } - - func_call(func, &argvars[1], partial, selfdict, rettv); -} - -/* - * "changenr()" function - */ -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) -{ - 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; - } - - 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); - 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])) { - return; - } - } - - rettv->vval.v_number = utf_ptr2char( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "cindent(lnum)" function - */ -static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - linenr_T lnum; - - 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; -} - -/* - * "clearmatches()" function - */ -static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - clear_matches(curwin); -} - -/* - * "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; - } 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; - } - } - } - } - 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; - } - - const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); - if (startcol <= 0) { - return; - } - - 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) -{ - tv_dict_alloc_ret(rettv); - - list_T *what_list = NULL; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - what_list = argvars[0].vval.v_list; - } - get_complete_info(what_list, rettv->vval.v_dict); -} - -/* - * "confirm(message, buttons[, default [, type]])" function - */ -static void f_confirm(typval_T *argvars, typval_T *rettv, 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; - } - } - } - } - } - - if (buttons == NULL || *buttons == NUL) { - buttons = _("&Ok"); - } - - if (!error) { - rettv->vval.v_number = do_dialog( - type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false); - } -} - -/* - * "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); - - 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); - } - } - } - } 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[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); - } - } - } - 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++; - } - } - } - } else if (argvars[0].v_type == VAR_DICT) { - int todo; - dict_T *d; - hashitem_T *hi; - - 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)); - } - } - - 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++; - } - } - } - } - } else { - EMSG2(_(e_listdictarg), "count()"); - } - rettv->vval.v_number = n; -} - -/* - * "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) -{ - int num = 0; - const char *dbpath = NULL; - const char *prepend = NULL; - char buf[NUMBUFLEN]; - - 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); - } - } - - rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, - (char_u *)prepend); -} - -/// "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; - } - - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; - } - - 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); -} - -/// "ctxpop()" function -static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (!ctx_restore(NULL, kCtxAll)) { - EMSG(_("Context stack is empty")); - } -} - -/// "ctxpush([{types}])" function -static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - 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; - } - } - }); - } 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) -{ - if (argvars[0].v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "expected dictionary as first argument"); - return; - } - - 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; - } - - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; - } - - 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; - } - - api_free_dictionary(dict); - did_emsg = save_did_emsg; -} - -/// "ctxsize()" function -static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = ctx_size(); -} - -/// "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; - - rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) { - pos_T pos; - colnr_T curswant = -1; - - 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(); - } - - curwin->w_set_curswant = set_curswant; - rettv->vval.v_number = 0; -} - -/* - * "deepcopy()" function - */ -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) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_DICT) { - emsgf(_(e_invarg2), "dict"); - return; - } - - if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { - emsgf(_(e_invarg2), "funcref"); - return; - } - - const char *const key_pattern = tv_get_string_chk(argvars + 1); - if (key_pattern == NULL) { - return; - } - - Callback callback; - if (!callback_from_typval(&callback, &argvars[2])) { - return; - } - - 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"); - } - - callback_free(&callback); -} - -/// "deletebufline()" function -static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - 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); - } - - 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; - } - } - } - check_cursor_col(); - deleted_lines_mark(first, count); - - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; - } -} - -/* - * "did_filetype()" function - */ -static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = did_filetype; -} - -/* - * "diff_filler()" function - */ -static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); -} - -/* - * "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 (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); -} - -/* - * "empty({expr})" function - */ -static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool n = true; - - 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; - } - case kSpecialVarFalse: - case kSpecialVarNull: { - n = true; - break; - } - } - break; - } - case VAR_UNKNOWN: { - internal_error("f_empty(UNKNOWN)"); - break; - } - } - - 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); - } - os_free_fullenv(env); -} - -/* - * "escape({string}, {chars})" function - */ -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) -{ - 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; -} - -/* - * "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); - } - 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) { - return; - } - if (*s == NUL) { - echo_output = true; - } - if (strncmp(s, "silent", 6) == 0) { - msg_silent++; - } - if (strcmp(s, "silent!") == 0) { - emsg_silent = true; - emsg_noredir = true; - } - } else { - msg_silent++; - } - - 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 - } - - 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; - } - - ga_append(capture_ga, NUL); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = capture_ga->ga_data; - - capture_ga = save_capture_ga; -} - -/// "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; - - (void)os_can_exe(arg, &path, true); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)path; -} - /// 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) @@ -8929,329 +6869,9 @@ win_T * find_win_by_nr_or_id(typval_T *vp) } /* - * "exists()" function - */ -static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = false; - int len = 0; - - 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; - } - 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; - } - 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); - } - } - } - if (*p != NUL) - n = FALSE; - - xfree(tofree); - } - - rettv->vval.v_number = n; -} - -/* - * "expand()" function - */ -static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - 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; - } - } -} - - -/// "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); - } - 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"); - - 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) +void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; list_T *l = NULL; @@ -9401,248 +7021,8 @@ theend: 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) +void common_function(typval_T *argvars, typval_T *rettv, + bool is_funcref, FunPtr fptr) { char_u *s; char_u *name; @@ -9803,116 +7183,8 @@ 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 *get_buffer_info(buf_T *buf) { dict_T *const dict = tv_dict_alloc(); @@ -9949,109 +7221,6 @@ static dict_T *get_buffer_info(buf_T *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. @@ -10062,8 +7231,8 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli /// 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) +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 @@ -10075,675 +7244,8 @@ static linenr_T tv_get_lnum_buf(const typval_T *const tv, 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) +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); @@ -10766,217 +7268,9 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, } } -/// "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 *get_tabpage_info(tabpage_T *tp, int tp_idx) { dict_T *const dict = tv_dict_alloc(); @@ -10994,106 +7288,8 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) 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 *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) { dict_T *const dict = tv_dict_alloc(); @@ -11119,147 +7315,11 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) 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 ( +win_T * +find_win_by_nr( typval_T *vp, tabpage_T *tp /* NULL for current tab page */ ) @@ -11292,7 +7352,7 @@ find_win_by_nr ( } /// Find window specified by "wvp" in tabpage "tvp". -static win_T *find_tabwin(typval_T *wvp, typval_T *tvp) +win_T *find_tabwin(typval_T *wvp, typval_T *tvp) { win_T *wp = NULL; tabpage_T *tp = NULL; @@ -11317,16 +7377,10 @@ static win_T *find_tabwin(typval_T *wvp, typval_T *tvp) 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 +void getwinvar( typval_T *argvars, typval_T *rettv, @@ -11397,655 +7451,15 @@ getwinvar( } /* - * "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) + typval_T *const rettv, + const bool inputdialog, + const bool secret) FUNC_ATTR_NONNULL_ALL { rettv->v_type = VAR_STRING; @@ -12156,7 +7570,7 @@ void get_user_input(const typval_T *const argvars, 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, + (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); @@ -12173,212 +7587,14 @@ void get_user_input(const typval_T *const argvars, 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) +void dict_list(typval_T *const tv, typval_T *const rettv, + const DictListType what) { if (tv->v_type != VAR_DICT) { EMSG(_(e_dictreq)); @@ -12426,82 +7642,6 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, }); } -/// "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 @@ -12511,7 +7651,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @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) +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); @@ -12570,568 +7710,6 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) 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 *)"<Nop>"); - } 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 @@ -13177,260 +7755,8 @@ void mapblock_fill_dict(dict_T *const dict, tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode); } -/* - * "map()" function - */ -static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - filter_map(argvars, rettv, TRUE); -} - -/* - * "maparg()" function - */ -static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_maparg(argvars, rettv, TRUE); -} - -/* - * "mapcheck()" function - */ -static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_maparg(argvars, rettv, FALSE); -} - - -static void find_some_match(typval_T *const argvars, typval_T *const rettv, - const SomeMatchType type) -{ - char_u *str = NULL; - long len = 0; - char_u *expr = NULL; - regmatch_T regmatch; - char_u *save_cpo; - long start = 0; - long nth = 1; - colnr_T startcol = 0; - bool match = false; - list_T *l = NULL; - listitem_T *li = NULL; - long idx = 0; - char_u *tofree = NULL; - - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ - save_cpo = p_cpo; - p_cpo = (char_u *)""; - - rettv->vval.v_number = -1; - switch (type) { - // matchlist(): return empty list when there are no matches. - case kSomeMatchList: { - tv_list_alloc_ret(rettv, kListLenMayKnow); - break; - } - // matchstrpos(): return ["", -1, -1, -1] - case kSomeMatchStrPos: { - tv_list_alloc_ret(rettv, 4); - tv_list_append_string(rettv->vval.v_list, "", 0); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); - break; - } - case kSomeMatchStr: { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - break; - } - case kSomeMatch: - case kSomeMatchEnd: { - // Do nothing: zero is default. - break; - } - } - - if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) == NULL) { - goto theend; - } - li = tv_list_first(l); - } else { - expr = str = (char_u *)tv_get_string(&argvars[0]); - len = (long)STRLEN(str); - } - - char patbuf[NUMBUFLEN]; - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - if (pat == NULL) { - goto theend; - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - start = tv_get_number_chk(&argvars[2], &error); - if (error) { - goto theend; - } - if (l != NULL) { - idx = tv_list_uidx(l, start); - if (idx == -1) { - goto theend; - } - li = tv_list_find(l, idx); - } else { - if (start < 0) - start = 0; - if (start > len) - goto theend; - /* When "count" argument is there ignore matches before "start", - * otherwise skip part of the string. Differs when pattern is "^" - * or "\<". */ - if (argvars[3].v_type != VAR_UNKNOWN) - startcol = start; - else { - str += start; - len -= start; - } - } - - if (argvars[3].v_type != VAR_UNKNOWN) { - nth = tv_get_number_chk(&argvars[3], &error); - } - if (error) { - goto theend; - } - } - - regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); - if (regmatch.regprog != NULL) { - regmatch.rm_ic = p_ic; - - for (;; ) { - if (l != NULL) { - if (li == NULL) { - match = false; - break; - } - xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), - NULL); - if (str == NULL) { - break; - } - } - - match = vim_regexec_nl(®match, str, (colnr_T)startcol); - - if (match && --nth <= 0) - break; - if (l == NULL && !match) - break; - - /* Advance to just after the match. */ - if (l != NULL) { - li = TV_LIST_ITEM_NEXT(l, li); - idx++; - } else { - startcol = (colnr_T)(regmatch.startp[0] - + (*mb_ptr2len)(regmatch.startp[0]) - str); - if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) { - match = false; - break; - } - } - } - - if (match) { - switch (type) { - case kSomeMatchStrPos: { - list_T *const ret_l = rettv->vval.v_list; - listitem_T *li1 = tv_list_first(ret_l); - listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); - listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); - listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); - xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); - - const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); - TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( - (const char *)regmatch.startp[0], rd); - TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( - regmatch.startp[0] - expr); - TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( - regmatch.endp[0] - expr); - if (l != NULL) { - TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; - } - break; - } - case kSomeMatchList: { - // Return list with matched string and submatches. - for (int i = 0; i < NSUBEXP; i++) { - if (regmatch.endp[i] == NULL) { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], - (regmatch.endp[i] - regmatch.startp[i])); - } - } - break; - } - case kSomeMatchStr: { - // Return matched string. - if (l != NULL) { - tv_copy(TV_LIST_ITEM_TV(li), rettv); - } else { - rettv->vval.v_string = (char_u *)xmemdupz( - (const char *)regmatch.startp[0], - (size_t)(regmatch.endp[0] - regmatch.startp[0])); - } - break; - } - case kSomeMatch: - case kSomeMatchEnd: { - if (l != NULL) { - rettv->vval.v_number = idx; - } else { - if (type == kSomeMatch) { - rettv->vval.v_number = - (varnumber_T)(regmatch.startp[0] - str); - } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - str); - } - rettv->vval.v_number += (varnumber_T)(str - expr); - } - break; - } - } - } - vim_regfree(regmatch.regprog); - } - -theend: - if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) { - // matchstrpos() without a list: drop the second item - list_T *const ret_l = rettv->vval.v_list; - tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); - } - - xfree(tofree); - p_cpo = save_cpo; -} - -/* - * "match()" function - */ -static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatch); -} - -static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, - win_T **win) +int matchadd_dict_arg(typval_T *tv, const char **conceal_char, + win_T **win) { dictitem_T *di; @@ -13454,766 +7780,7 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, return OK; } -/* - * "matchadd()" function - */ -static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char grpbuf[NUMBUFLEN]; - char patbuf[NUMBUFLEN]; - const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - int prio = 10; - int id = -1; - bool error = false; - const char *conceal_char = NULL; - win_T *win = curwin; - - rettv->vval.v_number = -1; - - if (grp == NULL || pat == NULL) { - return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error) { - return; - } - if (id >= 1 && id <= 3) { - EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id); - return; - } - - rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); -} - -static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } - - if (argvars[1].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "matchaddpos()"); - return; - } - - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } - - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; - win_T *win = curwin; - - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error == true) { - return; - } - - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); - return; - } - - rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); -} - -/* - * "matcharg()" function - */ -static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int id = tv_get_number(&argvars[0]); - - tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 - ? 2 - : 0)); - - if (id >= 1 && id <= 3) { - matchitem_T *const m = (matchitem_T *)get_match(curwin, id); - - if (m != NULL) { - tv_list_append_string(rettv->vval.v_list, - (const char *)syn_id2name(m->hlg_id), -1); - tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); - } else { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } - } -} - -/* - * "matchdelete()" function - */ -static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = match_delete(curwin, - (int)tv_get_number(&argvars[0]), true); -} - -/* - * "matchend()" function - */ -static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchEnd); -} - -/* - * "matchlist()" function - */ -static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchList); -} - -/* - * "matchstr()" function - */ -static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchStr); -} - -/// "matchstrpos()" function -static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - find_some_match(argvars, rettv, kSomeMatchStrPos); -} - -/// Get maximal/minimal number value in a list or dictionary -/// -/// @param[in] tv List or dictionary to work with. If it contains something -/// that is not an integer number (or cannot be coerced to -/// it) error is given. -/// @param[out] rettv Location where result will be saved. Only assigns -/// vval.v_number, type is not touched. Returns zero for -/// empty lists/dictionaries. -/// @param[in] domax Determines whether maximal or minimal value is desired. -static void max_min(const typval_T *const tv, typval_T *const rettv, - const bool domax) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - - rettv->vval.v_number = 0; - varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); - if (tv->v_type == VAR_LIST) { - if (tv_list_len(tv->vval.v_list) == 0) { - return; - } - TV_LIST_ITER_CONST(tv->vval.v_list, li, { - const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); - if (error) { - return; - } - if (domax ? i > n : i < n) { - n = i; - } - }); - } else if (tv->v_type == VAR_DICT) { - if (tv_dict_len(tv->vval.v_dict) == 0) { - return; - } - TV_DICT_ITER(tv->vval.v_dict, di, { - const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); - if (error) { - return; - } - if (domax ? i > n : i < n) { - n = i; - } - }); - } else { - EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); - return; - } - rettv->vval.v_number = n; -} - -/* - * "max()" function - */ -static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - max_min(argvars, rettv, TRUE); -} - -/* - * "min()" function - */ -static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - max_min(argvars, rettv, FALSE); -} - -/* - * "mkdir()" function - */ -static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int prot = 0755; // -V536 - - rettv->vval.v_number = FAIL; - if (check_restricted() || check_secure()) - return; - - char buf[NUMBUFLEN]; - const char *const dir = tv_get_string_buf(&argvars[0], buf); - if (*dir == NUL) { - return; - } - - if (*path_tail((char_u *)dir) == NUL) { - // Remove trailing slashes. - *path_tail_with_sep((char_u *)dir) = NUL; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_UNKNOWN) { - prot = tv_get_number_chk(&argvars[2], NULL); - if (prot == -1) { - return; - } - } - if (strcmp(tv_get_string(&argvars[1]), "p") == 0) { - char *failed_dir; - int ret = os_mkdir_recurse(dir, prot, &failed_dir); - if (ret != 0) { - EMSG3(_(e_mkdir), failed_dir, os_strerror(ret)); - xfree(failed_dir); - rettv->vval.v_number = FAIL; - return; - } else { - rettv->vval.v_number = OK; - return; - } - } - } - rettv->vval.v_number = vim_mkdir_emsg(dir, prot); -} - -/// "mode()" function -static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char *mode = get_mode(); - - // Clear out the minor mode when the argument is not a non-zero number or - // non-empty string. - if (!non_zero_arg(&argvars[0])) { - mode[1] = NUL; - } - - rettv->vval.v_string = (char_u *)mode; - rettv->v_type = VAR_STRING; -} - -/// "msgpackdump()" function -static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "msgpackdump()"); - return; - } - list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); - list_T *const list = argvars[0].vval.v_list; - msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); - const char *const msg = _("msgpackdump() argument, index %i"); - // Assume that translation will not take more then 4 times more space - char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; - int idx = 0; - TV_LIST_ITER(list, li, { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); - idx++; - if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { - break; - } - }); - msgpack_packer_free(lpacker); -} - -/// "msgpackparse" function -static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "msgpackparse()"); - return; - } - list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); - const list_T *const list = argvars[0].vval.v_list; - if (tv_list_len(list) == 0) { - return; - } - if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "List item is not a string"); - return; - } - ListReaderState lrstate = encode_init_lrstate(list); - msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE); - if (unpacker == NULL) { - EMSG(_(e_outofmem)); - return; - } - msgpack_unpacked unpacked; - msgpack_unpacked_init(&unpacked); - do { - if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { - EMSG(_(e_outofmem)); - goto f_msgpackparse_exit; - } - size_t read_bytes; - const int rlret = encode_read_from_list( - &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes); - if (rlret == FAIL) { - EMSG2(_(e_invarg2), "List item is not a string"); - goto f_msgpackparse_exit; - } - msgpack_unpacker_buffer_consumed(unpacker, read_bytes); - if (read_bytes == 0) { - break; - } - while (unpacker->off < unpacker->used) { - const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, - &unpacked); - if (result == MSGPACK_UNPACK_PARSE_ERROR) { - EMSG2(_(e_invarg2), "Failed to parse msgpack string"); - goto f_msgpackparse_exit; - } - if (result == MSGPACK_UNPACK_NOMEM_ERROR) { - EMSG(_(e_outofmem)); - goto f_msgpackparse_exit; - } - if (result == MSGPACK_UNPACK_SUCCESS) { - typval_T tv = { .v_type = VAR_UNKNOWN }; - if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { - EMSG2(_(e_invarg2), "Failed to convert msgpack string"); - goto f_msgpackparse_exit; - } - tv_list_append_owned_tv(ret_list, tv); - } - if (result == MSGPACK_UNPACK_CONTINUE) { - if (rlret == OK) { - EMSG2(_(e_invarg2), "Incomplete msgpack string"); - } - break; - } - } - if (rlret == OK) { - break; - } - } while (true); - -f_msgpackparse_exit: - msgpack_unpacked_destroy(&unpacked); - msgpack_unpacker_free(unpacker); - return; -} - -/* - * "nextnonblank()" function - */ -static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum; - - for (lnum = tv_get_lnum(argvars);; lnum++) { - if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) { - lnum = 0; - break; - } - if (*skipwhite(ml_get(lnum)) != NUL) { - break; - } - } - rettv->vval.v_number = lnum; -} - -/* - * "nr2char()" function - */ -static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[1].v_type != VAR_UNKNOWN) { - if (!tv_check_num(&argvars[1])) { - return; - } - } - - bool error = false; - const varnumber_T num = tv_get_number_chk(&argvars[0], &error); - if (error) { - return; - } - if (num < 0) { - EMSG(_("E5070: Character number must not be less than zero")); - return; - } - if (num > INT_MAX) { - emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"), - INT_MAX); - return; - } - - char buf[MB_MAXBYTES]; - const int len = utf_char2bytes((int)num, (char_u *)buf); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmemdupz(buf, (size_t)len); -} - -/* - * "or(expr, expr)" function - */ -static void f_or(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); -} - -/* - * "pathshorten()" function - */ -static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *const s = tv_get_string_chk(&argvars[0]); - if (!s) { - return; - } - rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s)); -} - -/* - * "pow()" function - */ -static void f_pow(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 = pow(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "prevnonblank()" function - */ -static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(argvars); - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { - lnum = 0; - } else { - while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) { - lnum--; - } - } - rettv->vval.v_number = lnum; -} - -/* - * "printf()" function - */ -static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - { - int len; - int saved_did_emsg = did_emsg; - - // Get the required length, allocate the buffer and do it for real. - did_emsg = false; - char buf[NUMBUFLEN]; - const char *fmt = tv_get_string_buf(&argvars[0], buf); - len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); - if (!did_emsg) { - char *s = xmalloc(len + 1); - rettv->vval.v_string = (char_u *)s; - (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1); - } - did_emsg |= saved_did_emsg; - } -} - -// "pum_getpos()" function -static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - pum_set_event_info(rettv->vval.v_dict); -} - -/* - * "pumvisible()" function - */ -static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (pum_visible()) - rettv->vval.v_number = 1; -} - -/* - * "pyeval()" function - */ -static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - script_host_eval("python", argvars, rettv); -} - -/* - * "py3eval()" function - */ -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - script_host_eval("python3", argvars, rettv); -} - -// "pyxeval()" function -static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - init_pyxversion(); - if (p_pyx == 2) { - f_pyeval(argvars, rettv, NULL); - } else { - f_py3eval(argvars, rettv, NULL); - } -} - -/* - * "range()" function - */ -static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - varnumber_T start; - varnumber_T end; - varnumber_T stride = 1; - varnumber_T i; - bool error = false; - - start = tv_get_number_chk(&argvars[0], &error); - if (argvars[1].v_type == VAR_UNKNOWN) { - end = start - 1; - start = 0; - } else { - end = tv_get_number_chk(&argvars[1], &error); - if (argvars[2].v_type != VAR_UNKNOWN) { - stride = tv_get_number_chk(&argvars[2], &error); - } - } - - if (error) { - return; // Type error; errmsg already given. - } - if (stride == 0) { - EMSG(_("E726: Stride is zero")); - } else if (stride > 0 ? end + 1 < start : end - 1 > start) { - EMSG(_("E727: Start past end")); - } else { - tv_list_alloc_ret(rettv, (end - start) / stride); - for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { - tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); - } - } -} - -/* - * "readfile()" function - */ -static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool binary = false; - FILE *fd; - char_u buf[(IOSIZE/256)*256]; /* rounded to avoid odd + 1 */ - int io_size = sizeof(buf); - int readlen; /* size of last fread() */ - char_u *prev = NULL; /* previously read bytes, if any */ - long prevlen = 0; /* length of data in prev */ - long prevsize = 0; /* size of prev buffer */ - long maxline = MAXLNUM; - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { - binary = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - maxline = tv_get_number(&argvars[2]); - } - } - - list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); - - // Always open the file in binary mode, library functions have a mind of - // their own about CR-LF conversion. - const char *const fname = tv_get_string(&argvars[0]); - if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { - EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname); - return; - } - - while (maxline < 0 || tv_list_len(l) < maxline) { - readlen = (int)fread(buf, 1, io_size, fd); - - // This for loop processes what was read, but is also entered at end - // of file so that either: - // - an incomplete line gets written - // - a "binary" file gets an empty line at the end if it ends in a - // newline. - char_u *p; // Position in buf. - char_u *start; // Start of current line. - for (p = buf, start = buf; - p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - p++) { - if (*p == '\n' || readlen <= 0) { - char_u *s = NULL; - size_t len = p - start; - - /* Finished a line. Remove CRs before NL. */ - if (readlen > 0 && !binary) { - while (len > 0 && start[len - 1] == '\r') - --len; - /* removal may cross back to the "prev" string */ - if (len == 0) - while (prevlen > 0 && prev[prevlen - 1] == '\r') - --prevlen; - } - if (prevlen == 0) { - assert(len < INT_MAX); - s = vim_strnsave(start, (int)len); - } else { - /* Change "prev" buffer to be the right size. This way - * the bytes are only copied once, and very long lines are - * allocated only once. */ - s = xrealloc(prev, prevlen + len + 1); - memcpy(s + prevlen, start, len); - s[prevlen + len] = NUL; - prev = NULL; /* the list will own the string */ - prevlen = prevsize = 0; - } - - tv_list_append_owned_tv(l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = s, - }); - - start = p + 1; // Step over newline. - if (maxline < 0) { - if (tv_list_len(l) > -maxline) { - assert(tv_list_len(l) == 1 + (-maxline)); - tv_list_item_remove(l, tv_list_first(l)); - } - } else if (tv_list_len(l) >= maxline) { - assert(tv_list_len(l) == maxline); - break; - } - if (readlen <= 0) { - break; - } - } else if (*p == NUL) { - *p = '\n'; - // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - // when finding the BF and check the previous two bytes. - } else if (*p == 0xbf && !binary) { - // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, - // these may be in the "prev" string. - char_u back1 = p >= buf + 1 ? p[-1] - : prevlen >= 1 ? prev[prevlen - 1] : NUL; - char_u back2 = p >= buf + 2 ? p[-2] - : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1] - : prevlen >= 2 ? prev[prevlen - 2] : NUL; - - if (back2 == 0xef && back1 == 0xbb) { - char_u *dest = p - 2; - - /* Usually a BOM is at the beginning of a file, and so at - * the beginning of a line; then we can just step over it. - */ - if (start == dest) - start = p + 1; - else { - /* have to shuffle buf to close gap */ - int adjust_prevlen = 0; - - if (dest < buf) { // -V782 - adjust_prevlen = (int)(buf - dest); // -V782 - // adjust_prevlen must be 1 or 2. - dest = buf; - } - if (readlen > p - buf + 1) - memmove(dest, p + 1, readlen - (p - buf) - 1); - readlen -= 3 - adjust_prevlen; - prevlen -= adjust_prevlen; - p = dest - 1; - } - } - } - } /* for */ - - if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { - break; - } - if (start < p) { - /* There's part of a line in buf, store it in "prev". */ - if (p - start + prevlen >= prevsize) { - - /* A common use case is ordinary text files and "prev" gets a - * fragment of a line, so the first allocation is made - * small, to avoid repeatedly 'allocing' large and - * 'reallocing' small. */ - if (prevsize == 0) - prevsize = (long)(p - start); - else { - long grow50pc = (prevsize * 3) / 2; - long growmin = (long)((p - start) * 2 + prevlen); - prevsize = grow50pc > growmin ? grow50pc : growmin; - } - prev = xrealloc(prev, prevsize); - } - /* Add the line part to end of "prev". */ - memmove(prev + prevlen, start, p - start); - prevlen += (long)(p - start); - } - } /* while */ - - xfree(prev); - fclose(fd); -} - -static void return_register(int regname, typval_T *rettv) +void return_register(int regname, typval_T *rettv) { char_u buf[2] = { regname, 0 }; @@ -14221,810 +7788,7 @@ static void return_register(int regname, typval_T *rettv) rettv->vval.v_string = vim_strsave(buf); } -// "reg_executing()" function -static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - return_register(reg_executing, rettv); -} - -// "reg_recording()" function -static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - return_register(reg_recording, rettv); -} - -/// list2proftime - convert a List to proftime_T -/// -/// @param arg The input list, must be of type VAR_LIST and have -/// exactly 2 items -/// @param[out] tm The proftime_T representation of `arg` -/// @return OK In case of success, FAIL in case of error -static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL -{ - if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { - return FAIL; - } - - bool error = false; - varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error); - varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error); - if (error) { - return FAIL; - } - - // in f_reltime() we split up the 64-bit proftime_T into two 32-bit - // values, now we combine them again. - union { - struct { int32_t low, high; } split; - proftime_T prof; - } u = { .split.high = n1, .split.low = n2 }; - - *tm = u.prof; - - return OK; -} - -/// f_reltime - return an item that represents a time value -/// -/// @param[out] rettv Without an argument it returns the current time. With -/// one argument it returns the time passed since the argument. -/// With two arguments it returns the time passed between -/// the two arguments. -static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - proftime_T res; - proftime_T start; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // no arguments: get current time. - res = profile_start(); - } else if (argvars[1].v_type == VAR_UNKNOWN) { - if (list2proftime(&argvars[0], &res) == FAIL) { - return; - } - res = profile_end(res); - } else { - // two arguments: compute the difference. - if (list2proftime(&argvars[0], &start) == FAIL - || list2proftime(&argvars[1], &res) == FAIL) { - return; - } - res = profile_sub(res, start); - } - - // we have to store the 64-bit proftime_T inside of a list of int's - // (varnumber_T is defined as int). For all our supported platforms, int's - // are at least 32-bits wide. So we'll use two 32-bit values to store it. - union { - struct { int32_t low, high; } split; - proftime_T prof; - } u = { .prof = res }; - - // statically assert that the union type conv will provide the correct - // results, if varnumber_T or proftime_T change, the union cast will need - // to be revised. - STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), - "type punning will produce incorrect results on this platform"); - - tv_list_alloc_ret(rettv, 2); - tv_list_append_number(rettv->vval.v_list, u.split.high); - tv_list_append_number(rettv->vval.v_list, u.split.low); -} - -/// "reltimestr()" function -static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - proftime_T tm; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (list2proftime(&argvars[0], &tm) == OK) { - rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm)); - } -} - -/* - * "remove()" function - */ -static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - list_T *l; - listitem_T *item, *item2; - listitem_T *li; - long idx; - long end; - dict_T *d; - dictitem_T *di; - const char *const arg_errmsg = N_("remove() argument"); - - if (argvars[0].v_type == VAR_DICT) { - if (argvars[2].v_type != VAR_UNKNOWN) { - EMSG2(_(e_toomanyarg), "remove()"); - } else if ((d = argvars[0].vval.v_dict) != NULL - && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *key = tv_get_string_chk(&argvars[1]); - if (key != NULL) { - di = tv_dict_find(d, key, -1); - if (di == NULL) { - EMSG2(_(e_dictkey), key); - } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) - && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { - *rettv = di->di_tv; - di->di_tv = TV_INITIAL_VALUE; - tv_dict_item_remove(d, di); - if (tv_dict_is_watched(d)) { - tv_dict_watcher_notify(d, key, NULL, rettv); - } - } - } - } - } else if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listdictarg), "remove()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - arg_errmsg, TV_TRANSLATE)) { - bool error = false; - - idx = tv_get_number_chk(&argvars[1], &error); - if (error) { - // Type error: do nothing, errmsg already given. - } else if ((item = tv_list_find(l, idx)) == NULL) { - EMSGN(_(e_listidx), idx); - } else { - if (argvars[2].v_type == VAR_UNKNOWN) { - // Remove one item, return its value. - tv_list_drop_items(l, item, item); - *rettv = *TV_LIST_ITEM_TV(item); - xfree(item); - } else { - // Remove range of items, return list with values. - end = tv_get_number_chk(&argvars[2], &error); - if (error) { - // Type error: do nothing. - } else if ((item2 = tv_list_find(l, end)) == NULL) { - EMSGN(_(e_listidx), end); - } else { - int cnt = 0; - - for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - cnt++; - if (li == item2) { - break; - } - } - if (li == NULL) { // Didn't find "item2" after "item". - EMSG(_(e_invrange)); - } else { - tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), - cnt); - } - } - } - } - } -} - -/* - * "rename({from}, {to})" function - */ -static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - rettv->vval.v_number = -1; - } else { - char buf[NUMBUFLEN]; - rettv->vval.v_number = vim_rename( - (const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], buf)); - } -} - -/* - * "repeat()" function - */ -static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - varnumber_T n = tv_get_number(&argvars[1]); - if (argvars[0].v_type == VAR_LIST) { - tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); - while (n-- > 0) { - tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); - } - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (n <= 0) { - return; - } - - const char *const p = tv_get_string(&argvars[0]); - - const size_t slen = strlen(p); - if (slen == 0) { - return; - } - const size_t len = slen * n; - // Detect overflow. - if (len / n != slen) { - return; - } - - char *const r = xmallocz(len); - for (varnumber_T i = 0; i < n; i++) { - memmove(r + i * slen, p, slen); - } - - rettv->vval.v_string = (char_u *)r; - } -} - -/* - * "resolve()" function - */ -static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *fname = tv_get_string(&argvars[0]); -#ifdef WIN32 - char *const v = os_resolve_shortcut(fname); - rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); -#else -# ifdef HAVE_READLINK - { - bool is_relative_to_current = false; - bool has_trailing_pathsep = false; - int limit = 100; - - char *p = xstrdup(fname); - - if (p[0] == '.' && (vim_ispathsep(p[1]) - || (p[1] == '.' && (vim_ispathsep(p[2]))))) { - is_relative_to_current = true; - } - - ptrdiff_t len = (ptrdiff_t)strlen(p); - if (len > 0 && after_pathsep(p, p + len)) { - has_trailing_pathsep = true; - p[len - 1] = NUL; // The trailing slash breaks readlink(). - } - - char *q = (char *)path_next_component(p); - char *remain = NULL; - if (*q != NUL) { - // Separate the first path component in "p", and keep the - // remainder (beginning with the path separator). - remain = xstrdup(q - 1); - q[-1] = NUL; - } - - char *const buf = xmallocz(MAXPATHL); - - char *cpy; - for (;; ) { - for (;; ) { - len = readlink(p, buf, MAXPATHL); - if (len <= 0) { - break; - } - buf[len] = NUL; - - if (limit-- == 0) { - xfree(p); - xfree(remain); - EMSG(_("E655: Too many symbolic links (cycle?)")); - rettv->vval.v_string = NULL; - xfree(buf); - return; - } - - // Ensure that the result will have a trailing path separator - // if the argument has one. */ - if (remain == NULL && has_trailing_pathsep) { - add_pathsep(buf); - } - - // Separate the first path component in the link value and - // concatenate the remainders. */ - q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); - if (*q != NUL) { - cpy = remain; - remain = (remain - ? (char *)concat_str((char_u *)q - 1, (char_u *)remain) - : xstrdup(q - 1)); - xfree(cpy); - q[-1] = NUL; - } - - q = (char *)path_tail((char_u *)p); - if (q > p && *q == NUL) { - // Ignore trailing path separator. - q[-1] = NUL; - q = (char *)path_tail((char_u *)p); - } - if (q > p && !path_is_absolute((const char_u *)buf)) { - // Symlink is relative to directory of argument. Replace the - // symlink with the resolved name in the same directory. - const size_t p_len = strlen(p); - const size_t buf_len = strlen(buf); - p = xrealloc(p, p_len + buf_len + 1); - memcpy(path_tail((char_u *)p), buf, buf_len + 1); - } else { - xfree(p); - p = xstrdup(buf); - } - } - - if (remain == NULL) { - break; - } - - // Append the first path component of "remain" to "p". - q = (char *)path_next_component(remain + 1); - len = q - remain - (*q != NUL); - const size_t p_len = strlen(p); - cpy = xmallocz(p_len + len); - memcpy(cpy, p, p_len + 1); - xstrlcat(cpy + p_len, remain, len + 1); - xfree(p); - p = cpy; - - // Shorten "remain". - if (*q != NUL) { - STRMOVE(remain, q - 1); - } else { - XFREE_CLEAR(remain); - } - } - - // If the result is a relative path name, make it explicitly relative to - // the current directory if and only if the argument had this form. - if (!vim_ispathsep(*p)) { - if (is_relative_to_current - && *p != NUL - && !(p[0] == '.' - && (p[1] == NUL - || vim_ispathsep(p[1]) - || (p[1] == '.' - && (p[2] == NUL - || vim_ispathsep(p[2])))))) { - // Prepend "./". - cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p); - xfree(p); - p = cpy; - } else if (!is_relative_to_current) { - // Strip leading "./". - q = p; - while (q[0] == '.' && vim_ispathsep(q[1])) { - q += 2; - } - if (q > p) { - STRMOVE(p, p + 2); - } - } - } - - // Ensure that the result will have no trailing path separator - // if the argument had none. But keep "/" or "//". - if (!has_trailing_pathsep) { - q = p + strlen(p); - if (after_pathsep(p, q)) { - *path_tail_with_sep((char_u *)p) = NUL; - } - } - - rettv->vval.v_string = (char_u *)p; - xfree(buf); - } -# else - rettv->vval.v_string = (char_u *)xstrdup(p); -# endif -#endif - - simplify_filename(rettv->vval.v_string); -} - -/* - * "reverse({list})" function - */ -static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - list_T *l; - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "reverse()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("reverse() argument"), TV_TRANSLATE)) { - tv_list_reverse(l); - tv_list_set_ret(rettv, l); - } -} - -#define SP_NOMOVE 0x01 ///< don't move cursor -#define SP_REPEAT 0x02 ///< repeat to find outer pair -#define SP_RETCOUNT 0x04 ///< return matchcount -#define SP_SETPCMARK 0x08 ///< set previous context mark -#define SP_START 0x10 ///< accept match at start position -#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern -#define SP_END 0x40 ///< leave cursor at end of match -#define SP_COLUMN 0x80 ///< start at cursor column - -/* - * Get flags for a search function. - * Possibly sets "p_ws". - * Returns BACKWARD, FORWARD or zero (for an error). - */ -static int get_search_arg(typval_T *varp, int *flagsp) -{ - int dir = FORWARD; - int mask; - - if (varp->v_type != VAR_UNKNOWN) { - char nbuf[NUMBUFLEN]; - const char *flags = tv_get_string_buf_chk(varp, nbuf); - if (flags == NULL) { - return 0; // Type error; errmsg already given. - } - while (*flags != NUL) { - switch (*flags) { - case 'b': dir = BACKWARD; break; - case 'w': p_ws = true; break; - case 'W': p_ws = false; break; - default: { - mask = 0; - if (flagsp != NULL) { - switch (*flags) { - case 'c': mask = SP_START; break; - case 'e': mask = SP_END; break; - case 'm': mask = SP_RETCOUNT; break; - case 'n': mask = SP_NOMOVE; break; - case 'p': mask = SP_SUBPAT; break; - case 'r': mask = SP_REPEAT; break; - case 's': mask = SP_SETPCMARK; break; - case 'z': mask = SP_COLUMN; break; - } - } - if (mask == 0) { - emsgf(_(e_invarg2), flags); - dir = 0; - } else { - *flagsp |= mask; - } - } - } - if (dir == 0) { - break; - } - flags++; - } - } - return dir; -} - -// Shared by search() and searchpos() functions. -static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) -{ - int flags; - pos_T pos; - pos_T save_cursor; - bool save_p_ws = p_ws; - int dir; - int retval = 0; /* default: FAIL */ - long lnum_stop = 0; - proftime_T tm; - long time_limit = 0; - int options = SEARCH_KEEP; - int subpatnum; - searchit_arg_T sia; - - const char *const pat = tv_get_string(&argvars[0]); - dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. - if (dir == 0) { - goto theend; - } - flags = *flagsp; - if (flags & SP_START) { - options |= SEARCH_START; - } - if (flags & SP_END) { - options |= SEARCH_END; - } - if (flags & SP_COLUMN) { - options |= SEARCH_COL; - } - - /* Optional arguments: line number to stop searching and timeout. */ - if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[2], NULL); - if (lnum_stop < 0) { - goto theend; - } - if (argvars[3].v_type != VAR_UNKNOWN) { - time_limit = tv_get_number_chk(&argvars[3], NULL); - if (time_limit < 0) { - goto theend; - } - } - } - - /* Set the time limit, if there is one. */ - tm = profile_setlimit(time_limit); - - /* - * This function does not accept SP_REPEAT and SP_RETCOUNT flags. - * Check to make sure only those flags are set. - * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both - * flags cannot be set. Check for that condition also. - */ - if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0) - || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { - EMSG2(_(e_invarg2), tv_get_string(&argvars[1])); - goto theend; - } - - pos = save_cursor = curwin->w_cursor; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = (linenr_T)lnum_stop; - sia.sa_tm = &tm; - subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, - options, RE_SEARCH, &sia); - if (subpatnum != FAIL) { - if (flags & SP_SUBPAT) - retval = subpatnum; - else - retval = pos.lnum; - if (flags & SP_SETPCMARK) - setpcmark(); - curwin->w_cursor = pos; - if (match_pos != NULL) { - /* Store the match cursor position */ - match_pos->lnum = pos.lnum; - match_pos->col = pos.col + 1; - } - /* "/$" will put the cursor after the end of the line, may need to - * correct that here */ - check_cursor(); - } - - /* If 'n' flag is used: restore cursor position. */ - if (flags & SP_NOMOVE) - curwin->w_cursor = save_cursor; - else - curwin->w_set_curswant = TRUE; -theend: - p_ws = save_p_ws; - - return retval; -} - -// "rpcnotify()" function -static void f_rpcnotify(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[0].vval.v_number < 0) { - EMSG2(_(e_invarg2), "Channel id must be a positive integer"); - return; - } - - if (argvars[1].v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "Event type must be a string"); - return; - } - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, - tv_get_string(&argvars[1]), args)) { - EMSG2(_(e_invarg2), "Channel doesn't exist"); - return; - } - - rettv->vval.v_number = 1; -} - -// "rpcrequest()" function -static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - const int l_provider_call_nesting = provider_call_nesting; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) { - EMSG2(_(e_invarg2), "Channel id must be a positive integer"); - return; - } - - if (argvars[1].v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "Method name must be a string"); - return; - } - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - sctx_T save_current_sctx; - uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; - linenr_T save_sourcing_lnum; - int save_autocmd_bufnr; - void *save_funccalp; - - if (l_provider_call_nesting) { - // If this is called from a provider function, restore the scope - // information of the caller. - save_current_sctx = current_sctx; - save_sourcing_name = sourcing_name; - save_sourcing_lnum = sourcing_lnum; - save_autocmd_fname = autocmd_fname; - save_autocmd_match = autocmd_match; - save_autocmd_bufnr = autocmd_bufnr; - save_funccalp = save_funccal(); - - current_sctx = provider_caller_scope.script_ctx; - sourcing_name = provider_caller_scope.sourcing_name; - sourcing_lnum = provider_caller_scope.sourcing_lnum; - autocmd_fname = provider_caller_scope.autocmd_fname; - autocmd_match = provider_caller_scope.autocmd_match; - autocmd_bufnr = provider_caller_scope.autocmd_bufnr; - restore_funccal(provider_caller_scope.funccalp); - } - - - Error err = ERROR_INIT; - - uint64_t chan_id = (uint64_t)argvars[0].vval.v_number; - const char *method = tv_get_string(&argvars[1]); - - Object result = rpc_send_call(chan_id, method, args, &err); - - if (l_provider_call_nesting) { - current_sctx = save_current_sctx; - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - autocmd_fname = save_autocmd_fname; - autocmd_match = save_autocmd_match; - autocmd_bufnr = save_autocmd_bufnr; - restore_funccal(save_funccalp); - } - - if (ERROR_SET(&err)) { - const char *name = NULL; - Channel *chan = find_channel(chan_id); - if (chan) { - name = rpc_client_name(chan); - } - msg_ext_set_kind("rpc_error"); - if (name) { - emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s", - method, chan_id, name, err.msg); - } else { - emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s", - method, chan_id, err.msg); - } - - goto end; - } - - if (!object_to_vim(result, rettv, &err)) { - EMSG2(_("Error converting the call result: %s"), err.msg); - } - -end: - api_free_object(result); - api_clear_error(&err); -} - -// "rpcstart()" function (DEPRECATED) -static void f_rpcstart(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_STRING - || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) { - // Wrong argument types - EMSG(_(e_invarg)); - return; - } - - list_T *args = NULL; - int argsl = 0; - if (argvars[1].v_type == VAR_LIST) { - args = argvars[1].vval.v_list; - argsl = tv_list_len(args); - // Assert that all list items are strings - int i = 0; - TV_LIST_ITER_CONST(args, arg, { - if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { - emsgf(_("E5010: List item %d of the second argument is not a string"), - i); - return; - } - i++; - }); - } - - if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { - EMSG(_(e_api_spawn_failed)); - return; - } - - // Allocate extra memory for the argument vector and the NULL pointer - int argvl = argsl + 2; - char **argv = xmalloc(sizeof(char_u *) * argvl); - - // Copy program name - argv[0] = xstrdup((char *)argvars[0].vval.v_string); - - int i = 1; - // Copy arguments to the vector - if (argsl > 0) { - TV_LIST_ITER_CONST(args, arg, { - argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); - }); - } - - // The last item of argv must be NULL - argv[i] = NULL; - - Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, - CALLBACK_READER_INIT, CALLBACK_NONE, - false, true, false, NULL, 0, 0, NULL, NULL, - &rettv->vval.v_number); - if (chan) { - channel_create_event(chan, NULL); - } -} - -// "rpcstop()" function -static void f_rpcstop(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) { - // Wrong argument types - EMSG(_(e_invarg)); - return; - } - - // if called with a job, stop it, else closes the channel - uint64_t id = argvars[0].vval.v_number; - if (find_job(id, false)) { - f_jobstop(argvars, rettv, NULL); - } else { - const char *error; - rettv->vval.v_number = channel_close(argvars[0].vval.v_number, - kChannelPartRpc, &error); - if (!rettv->vval.v_number) { - EMSG(error); - } - } -} - -static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col) +void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col) { // TODO(bfredl): this is a hack for legacy tests which use screenchar() // to check printed messages on the screen (but not floats etc @@ -15039,501 +7803,9 @@ static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col) } } -/* - * "screenattr()" function - */ -static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int c; - - int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; - int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; - if (row < 0 || row >= default_grid.Rows - || col < 0 || col >= default_grid.Columns) { - c = -1; - } else { - ScreenGrid *grid = &default_grid; - screenchar_adjust_grid(&grid, &row, &col); - c = grid->attrs[grid->line_offset[row] + col]; - } - rettv->vval.v_number = c; -} - -/* - * "screenchar()" function - */ -static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int c; - - int row = tv_get_number_chk(&argvars[0], NULL) - 1; - int col = tv_get_number_chk(&argvars[1], NULL) - 1; - if (row < 0 || row >= default_grid.Rows - || col < 0 || col >= default_grid.Columns) { - c = -1; - } else { - ScreenGrid *grid = &default_grid; - screenchar_adjust_grid(&grid, &row, &col); - c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]); - } - rettv->vval.v_number = c; -} - -/* - * "screencol()" function - * - * First column is 1 to be consistent with virtcol(). - */ -static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ui_current_col() + 1; -} - -/// "screenpos({winid}, {lnum}, {col})" function -static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - int row = 0; - int scol = 0, ccol = 0, ecol = 0; - - tv_dict_alloc_ret(rettv); - dict_T *dict = rettv->vval.v_dict; - - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - - pos.lnum = tv_get_number(&argvars[1]); - pos.col = tv_get_number(&argvars[2]) - 1; - pos.coladd = 0; - textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); - - tv_dict_add_nr(dict, S_LEN("row"), row); - tv_dict_add_nr(dict, S_LEN("col"), scol); - tv_dict_add_nr(dict, S_LEN("curscol"), ccol); - tv_dict_add_nr(dict, S_LEN("endcol"), ecol); -} - -/* - * "screenrow()" function - */ -static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ui_current_row() + 1; -} - -/* - * "search()" function - */ -static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int flags = 0; - - rettv->vval.v_number = search_cmn(argvars, NULL, &flags); -} - -/* - * "searchdecl()" function - */ -static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int locally = 1; - int thisblock = 0; - bool error = false; - - rettv->vval.v_number = 1; /* default: FAIL */ - - const char *const name = tv_get_string_chk(&argvars[0]); - if (argvars[1].v_type != VAR_UNKNOWN) { - locally = tv_get_number_chk(&argvars[1], &error) == 0; - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - thisblock = tv_get_number_chk(&argvars[2], &error) != 0; - } - } - if (!error && name != NULL) { - rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally, - thisblock, SEARCH_KEEP) == FAIL; - } -} - -/* - * Used by searchpair() and searchpairpos() - */ -static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) -{ - bool save_p_ws = p_ws; - int dir; - int flags = 0; - int retval = 0; // default: FAIL - long lnum_stop = 0; - long time_limit = 0; - - // Get the three pattern arguments: start, middle, end. Will result in an - // error if not a valid argument. - char nbuf1[NUMBUFLEN]; - char nbuf2[NUMBUFLEN]; - const char *spat = tv_get_string_chk(&argvars[0]); - const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1); - const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); - if (spat == NULL || mpat == NULL || epat == NULL) { - goto theend; // Type error. - } - - // Handle the optional fourth argument: flags. - dir = get_search_arg(&argvars[3], &flags); // may set p_ws. - if (dir == 0) { - goto theend; - } - - // Don't accept SP_END or SP_SUBPAT. - // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set. - if ((flags & (SP_END | SP_SUBPAT)) != 0 - || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { - EMSG2(_(e_invarg2), tv_get_string(&argvars[3])); - goto theend; - } - - // Using 'r' implies 'W', otherwise it doesn't work. - if (flags & SP_REPEAT) { - p_ws = false; - } - - // Optional fifth argument: skip expression. - const typval_T *skip; - if (argvars[3].v_type == VAR_UNKNOWN - || argvars[4].v_type == VAR_UNKNOWN) { - skip = NULL; - } else { - skip = &argvars[4]; - if (skip->v_type != VAR_FUNC - && skip->v_type != VAR_PARTIAL - && skip->v_type != VAR_STRING) { - emsgf(_(e_invarg2), tv_get_string(&argvars[4])); - goto theend; // Type error. - } - if (argvars[5].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[5], NULL); - if (lnum_stop < 0) { - emsgf(_(e_invarg2), tv_get_string(&argvars[5])); - goto theend; - } - if (argvars[6].v_type != VAR_UNKNOWN) { - time_limit = tv_get_number_chk(&argvars[6], NULL); - if (time_limit < 0) { - emsgf(_(e_invarg2), tv_get_string(&argvars[6])); - goto theend; - } - } - } - } - - retval = do_searchpair( - (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, - flags, match_pos, lnum_stop, time_limit); - -theend: - p_ws = save_p_ws; - - return retval; -} - -/* - * "searchpair()" function - */ -static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = searchpair_cmn(argvars, NULL); -} - -/* - * "searchpairpos()" function - */ -static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T match_pos; - int lnum = 0; - int col = 0; - - tv_list_alloc_ret(rettv, 2); - - if (searchpair_cmn(argvars, &match_pos) > 0) { - lnum = match_pos.lnum; - col = match_pos.col; - } - - tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); - tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); -} - -/* - * Search for a start/middle/end thing. - * Used by searchpair(), see its documentation for the details. - * Returns 0 or -1 for no match, - */ -long -do_searchpair( - char_u *spat, // start pattern - char_u *mpat, // middle pattern - char_u *epat, // end pattern - int dir, // BACKWARD or FORWARD - const typval_T *skip, // skip expression - int flags, // SP_SETPCMARK and other SP_ values - pos_T *match_pos, - linenr_T lnum_stop, // stop at this line if not zero - long time_limit // stop after this many msec -) -{ - char_u *save_cpo; - char_u *pat, *pat2 = NULL, *pat3 = NULL; - long retval = 0; - pos_T pos; - pos_T firstpos; - pos_T foundpos; - pos_T save_cursor; - pos_T save_pos; - int n; - int nest = 1; - bool use_skip = false; - int options = SEARCH_KEEP; - proftime_T tm; - size_t pat2_len; - size_t pat3_len; - - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ - save_cpo = p_cpo; - p_cpo = empty_option; - - /* Set the time limit, if there is one. */ - tm = profile_setlimit(time_limit); - - // Make two search patterns: start/end (pat2, for in nested pairs) and - // start/middle/end (pat3, for the top pair). - pat2_len = STRLEN(spat) + STRLEN(epat) + 17; - pat2 = xmalloc(pat2_len); - pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25; - pat3 = xmalloc(pat3_len); - snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); - if (*mpat == NUL) { - STRCPY(pat3, pat2); - } else { - snprintf((char *)pat3, pat3_len, - "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); - } - if (flags & SP_START) { - options |= SEARCH_START; - } - - if (skip != NULL) { - // Empty string means to not use the skip expression. - if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { - use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; - } - } - - save_cursor = curwin->w_cursor; - pos = curwin->w_cursor; - clearpos(&firstpos); - clearpos(&foundpos); - pat = pat3; - for (;; ) { - searchit_arg_T sia; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = lnum_stop; - sia.sa_tm = &tm; - - n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, - options, RE_SEARCH, &sia); - if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { - // didn't find it or found the first match again: FAIL - break; - } - - if (firstpos.lnum == 0) - firstpos = pos; - if (equalpos(pos, foundpos)) { - /* Found the same position again. Can happen with a pattern that - * has "\zs" at the end and searching backwards. Advance one - * character and try again. */ - if (dir == BACKWARD) - decl(&pos); - else - incl(&pos); - } - foundpos = pos; - - /* clear the start flag to avoid getting stuck here */ - options &= ~SEARCH_START; - - // If the skip pattern matches, ignore this match. - if (use_skip) { - save_pos = curwin->w_cursor; - curwin->w_cursor = pos; - bool err = false; - const bool r = eval_expr_to_bool(skip, &err); - curwin->w_cursor = save_pos; - if (err) { - /* Evaluating {skip} caused an error, break here. */ - curwin->w_cursor = save_cursor; - retval = -1; - break; - } - if (r) - continue; - } - - if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) { - /* Found end when searching backwards or start when searching - * forward: nested pair. */ - ++nest; - pat = pat2; /* nested, don't search for middle */ - } else { - /* Found end when searching forward or start when searching - * backward: end of (nested) pair; or found middle in outer pair. */ - if (--nest == 1) - pat = pat3; /* outer level, search for middle */ - } - - if (nest == 0) { - /* Found the match: return matchcount or line number. */ - if (flags & SP_RETCOUNT) - ++retval; - else - retval = pos.lnum; - if (flags & SP_SETPCMARK) - setpcmark(); - curwin->w_cursor = pos; - if (!(flags & SP_REPEAT)) - break; - nest = 1; /* search for next unmatched */ - } - } - - if (match_pos != NULL) { - /* Store the match cursor position */ - match_pos->lnum = curwin->w_cursor.lnum; - match_pos->col = curwin->w_cursor.col + 1; - } - - /* If 'n' flag is used or search failed: restore cursor position. */ - if ((flags & SP_NOMOVE) || retval == 0) - curwin->w_cursor = save_cursor; - - xfree(pat2); - xfree(pat3); - if (p_cpo == empty_option) - p_cpo = save_cpo; - else - /* Darn, evaluating the {skip} expression changed the value. */ - free_string_option(save_cpo); - - return retval; -} - -/* - * "searchpos()" function - */ -static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T match_pos; - int flags = 0; - - const int n = search_cmn(argvars, &match_pos, &flags); - - tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); - - const int lnum = (n > 0 ? match_pos.lnum : 0); - const int col = (n > 0 ? match_pos.col : 0); - - tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); - tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); - if (flags & SP_SUBPAT) { - tv_list_append_number(rettv->vval.v_list, (varnumber_T)n); - } -} - -/// "serverlist()" function -static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - size_t n; - char **addrs = server_address_list(&n); - - // Copy addrs into a linked list. - list_T *const l = tv_list_alloc_ret(rettv, n); - for (size_t i = 0; i < n; i++) { - tv_list_append_allocated_string(l, addrs[i]); - } - xfree(addrs); -} - -/// "serverstart()" function -static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; // Address of the new server - - if (check_restricted() || check_secure()) { - return; - } - - char *address; - // If the user supplied an address, use it, otherwise use a temp. - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } else { - address = xstrdup(tv_get_string(argvars)); - } - } else { - address = server_address_new(); - } - - int result = server_start(address); - xfree(address); - - if (result != 0) { - EMSG2("Failed to start server: %s", - result > 0 ? "Unknown system error" : uv_strerror(result)); - return; - } - - // Since it's possible server_start adjusted the given {address} (e.g., - // "localhost:" will now have a port), return the final value to the user. - size_t n; - char **addrs = server_address_list(&n); - rettv->vval.v_string = (char_u *)addrs[n - 1]; - - n--; - for (size_t i = 0; i < n; i++) { - xfree(addrs[i]); - } - xfree(addrs); -} - -/// "serverstop()" function -static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - if (argvars[0].vval.v_string) { - bool rv = server_stop((char *)argvars[0].vval.v_string); - rettv->vval.v_number = (rv ? 1 : 0); - } -} - /// Set line or list of lines in buffer "buf". -static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, - const typval_T *lines, typval_T *rettv) +void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, + const typval_T *lines, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(4, 5) { linenr_T lnum = lnum_arg + (append ? 1 : 0); @@ -15647,633 +7919,11 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, } } -/// "setbufline()" function -static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum; - buf_T *buf; - - buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, false, &argvars[2], rettv); - } -} - -/* - * "setbufvar()" function - */ -static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() - || check_secure() - || !tv_check_str_or_nr(&argvars[0])) { - return; - } - const char *varname = tv_get_string_chk(&argvars[1]); - buf_T *const buf = tv_get_buf(&argvars[0], false); - typval_T *varp = &argvars[2]; - - if (buf != NULL && varname != NULL) { - if (*varname == '&') { - long numval; - bool error = false; - aco_save_T aco; - - // set curbuf to be our buf, temporarily - aucmd_prepbuf(&aco, buf); - - varname++; - numval = tv_get_number_chk(varp, &error); - char nbuf[NUMBUFLEN]; - const char *const strval = tv_get_string_buf_chk(varp, nbuf); - if (!error && strval != NULL) { - set_option_value(varname, numval, strval, OPT_LOCAL); - } - - // reset notion of buffer - aucmd_restbuf(&aco); - } else { - buf_T *save_curbuf = curbuf; - - const size_t varname_len = STRLEN(varname); - char *const bufvarname = xmalloc(varname_len + 3); - curbuf = buf; - memcpy(bufvarname, "b:", 2); - memcpy(bufvarname + 2, varname, varname_len + 1); - set_var(bufvarname, varname_len + 2, varp, true); - xfree(bufvarname); - curbuf = save_curbuf; - } - } -} - -static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *d; - dictitem_T *di; - - if (argvars[0].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - - if ((d = argvars[0].vval.v_dict) != NULL) { - char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false); - if (csearch != NULL) { - if (enc_utf8) { - int pcc[MAX_MCO]; - int c = utfc_ptr2char(csearch, pcc); - set_last_csearch(c, csearch, utfc_ptr2len(csearch)); - } - else - set_last_csearch(PTR2CHAR(csearch), - csearch, utfc_ptr2len(csearch)); - } - - di = tv_dict_find(d, S_LEN("forward")); - if (di != NULL) { - set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); - } - - di = tv_dict_find(d, S_LEN("until")); - if (di != NULL) { - set_csearch_until(!!tv_get_number(&di->di_tv)); - } - } -} - -/* - * "setcmdpos()" function - */ -static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int pos = (int)tv_get_number(&argvars[0]) - 1; - - if (pos >= 0) { - rettv->vval.v_number = set_cmdline_pos(pos); - } -} - -/// "setenv()" function -static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char namebuf[NUMBUFLEN]; - char valbuf[NUMBUFLEN]; - const char *name = tv_get_string_buf(&argvars[0], namebuf); - - if (argvars[1].v_type == VAR_SPECIAL - && argvars[1].vval.v_special == kSpecialVarNull) { - os_unsetenv(name); - } else { - os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); - } -} - -/// "setfperm({fname}, {mode})" function -static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 0; - - const char *const fname = tv_get_string_chk(&argvars[0]); - if (fname == NULL) { - return; - } - - char modebuf[NUMBUFLEN]; - const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf); - if (mode_str == NULL) { - return; - } - if (strlen(mode_str) != 9) { - EMSG2(_(e_invarg2), mode_str); - return; - } - - int mask = 1; - int mode = 0; - for (int i = 8; i >= 0; i--) { - if (mode_str[i] != '-') { - mode |= mask; - } - mask = mask << 1; - } - rettv->vval.v_number = os_setperm(fname, mode) == OK; -} - -/* - * "setline()" function - */ -static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(&argvars[0]); - set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); -} - -/// Create quickfix/location list from VimL values -/// -/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid -/// list_arg, action_arg and what_arg arguments in which case errors out, -/// including VAR_UNKNOWN parameters. -/// -/// @param[in,out] wp Window to create location list for. May be NULL in -/// which case quickfix list will be created. -/// @param[in] list_arg Quickfix list contents. -/// @param[in] action_arg Action to perform: append to an existing list, -/// replace its content or create a new one. -/// @param[in] title_arg New list title. Defaults to caller function name. -/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. -static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) - FUNC_ATTR_NONNULL_ARG(2, 3) -{ - static char *e_invact = N_("E927: Invalid action: '%s'"); - const char *title = NULL; - int action = ' '; - static int recursive = 0; - rettv->vval.v_number = -1; - dict_T *d = NULL; - - typval_T *list_arg = &args[0]; - if (list_arg->v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } else if (recursive != 0) { - EMSG(_(e_au_recursive)); - return; - } - - typval_T *action_arg = &args[1]; - if (action_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (action_arg->v_type != VAR_STRING) { - EMSG(_(e_stringreq)); - return; - } - const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') - && act[1] == NUL) { - action = *act; - } else { - EMSG2(_(e_invact), act); - return; - } - - typval_T *title_arg = &args[2]; - if (title_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (title_arg->v_type == VAR_STRING) { - title = tv_get_string_chk(title_arg); - if (!title) { - // Type error. Error already printed by tv_get_string_chk(). - return; - } - } else if (title_arg->v_type == VAR_DICT) { - d = title_arg->vval.v_dict; - } else { - EMSG(_(e_dictreq)); - return; - } - -skip_args: - if (!title) { - title = (wp ? ":setloclist()" : ":setqflist()"); - } - - recursive++; - list_T *const l = list_arg->vval.v_list; - if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { - rettv->vval.v_number = 0; - } - recursive--; -} - -/* - * "setloclist()" function - */ -static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win; - - rettv->vval.v_number = -1; - - win = find_win_by_nr_or_id(&argvars[0]); - if (win != NULL) { - set_qf_ll_list(win, &argvars[1], rettv); - } -} - -/* - * "setmatches()" function - */ -static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *d; - list_T *s = NULL; - - 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; - // To some extent make sure that we are dealing with a list from - // "getmatches()". - int li_idx = 0; - TV_LIST_ITER_CONST(l, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT - || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { - emsgf(_("E474: List item %d is either not a dictionary " - "or an empty one"), li_idx); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - emsgf(_("E474: List item %d is missing one of the required keys"), - li_idx); - return; - } - li_idx++; - }); - - clear_matches(curwin); - bool match_add_failed = false; - TV_LIST_ITER_CONST(l, li, { - int i = 0; - - d = TV_LIST_ITEM_TV(li)->vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(9); - } - - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[30]; // use 30 to avoid compiler warning - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } - - tv_list_append_tv(s, &pos_di->di_tv); - tv_list_ref(s); - } else { - break; - } - } - } - - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(curwin, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; - } - tv_list_unref(s); - s = NULL; - } - }); - if (!match_add_failed) { - rettv->vval.v_number = 0; - } -} - -/* - * "setpos()" function - */ -static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - int fnum; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - const char *const name = tv_get_string_chk(argvars); - if (name != NULL) { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { - if (pos.col != MAXCOL && --pos.col < 0) { - pos.col = 0; - } - if (name[0] == '.' && name[1] == NUL) { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = false; - } - check_cursor(); - rettv->vval.v_number = 0; - } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { - // set mark - if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { - rettv->vval.v_number = 0; - } - } else { - EMSG(_(e_invarg)); - } - } - } -} - -/* - * "setqflist()" function - */ -static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - set_qf_ll_list(NULL, argvars, rettv); -} - -/* - * "setreg()" function - */ -static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int regname; - bool append = false; - MotionType yank_type; - long block_len; - - block_len = -1; - yank_type = kMTUnknown; - - rettv->vval.v_number = 1; // FAIL is default. - - const char *const strregname = tv_get_string_chk(argvars); - if (strregname == NULL) { - return; // Type error; errmsg already given. - } - regname = (uint8_t)(*strregname); - if (regname == 0 || regname == '@') { - regname = '"'; - } - - bool set_unnamed = false; - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *stropt = tv_get_string_chk(&argvars[2]); - if (stropt == NULL) { - return; // Type error. - } - for (; *stropt != NUL; stropt++) { - switch (*stropt) { - case 'a': case 'A': { // append - append = true; - break; - } - case 'v': case 'c': { // character-wise selection - yank_type = kMTCharWise; - break; - } - case 'V': case 'l': { // line-wise selection - yank_type = kMTLineWise; - break; - } - case 'b': case Ctrl_V: { // block-wise selection - yank_type = kMTBlockWise; - if (ascii_isdigit(stropt[1])) { - stropt++; - block_len = getdigits_long((char_u **)&stropt, true, 0) - 1; - stropt--; - } - break; - } - case 'u': case '"': { // unnamed register - set_unnamed = true; - break; - } - } - } - } - - if (argvars[1].v_type == VAR_LIST) { - list_T *ll = argvars[1].vval.v_list; - // If the list is NULL handle like an empty list. - const int len = tv_list_len(ll); - - // First half: use for pointers to result lines; second half: use for - // pointers to allocated copies. - char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2)); - const char **curval = (const char **)lstval; - char **allocval = lstval + len + 2; - char **curallocval = allocval; - - TV_LIST_ITER_CONST(ll, li, { - char buf[NUMBUFLEN]; - *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); - if (*curval == NULL) { - goto free_lstval; - } - if (*curval == buf) { - // Need to make a copy, - // next tv_get_string_buf_chk() will overwrite the string. - *curallocval = xstrdup(*curval); - *curval = *curallocval; - curallocval++; - } - curval++; - }); - *curval++ = NULL; - - write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, - block_len); - -free_lstval: - while (curallocval > allocval) { - xfree(*--curallocval); - } - xfree(lstval); - } else { - const char *strval = tv_get_string_chk(&argvars[1]); - if (strval == NULL) { - return; - } - write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval), - append, yank_type, block_len); - } - rettv->vval.v_number = 0; - - if (set_unnamed) { - // Discard the result. We already handle the error case. - if (op_reg_set_previous(regname)) { } - } -} - -/* - * "settabvar()" function - */ -static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - const char *const varname = tv_get_string_chk(&argvars[1]); - typval_T *const varp = &argvars[2]; - - if (varname != NULL && tp != NULL) { - tabpage_T *const save_curtab = curtab; - goto_tabpage_tp(tp, false, false); - - const size_t varname_len = strlen(varname); - char *const tabvarname = xmalloc(varname_len + 3); - memcpy(tabvarname, "t:", 2); - memcpy(tabvarname + 2, varname, varname_len + 1); - set_var(tabvarname, varname_len + 2, varp, true); - xfree(tabvarname); - - // Restore current tabpage. - if (valid_tabpage(save_curtab)) { - goto_tabpage_tp(save_curtab, false, false); - } - } -} - -/* - * "settabwinvar()" function - */ -static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - setwinvar(argvars, rettv, 1); -} - -// "settagstack()" function -static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - static char *e_invact2 = N_("E962: Invalid action: '%s'"); - win_T *wp; - dict_T *d; - int action = 'r'; - - rettv->vval.v_number = -1; - - // first argument: window number or id - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - - // second argument: dict with items to set in the tag stack - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - d = argvars[1].vval.v_dict; - if (d == NULL) { - return; - } - - // third argument: action - 'a' for append and 'r' for replace. - // default is to replace the stack. - if (argvars[2].v_type == VAR_UNKNOWN) { - action = 'r'; - } else if (argvars[2].v_type == VAR_STRING) { - const char *actstr; - actstr = tv_get_string_chk(&argvars[2]); - if (actstr == NULL) { - return; - } - if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't') - && actstr[1] == NUL) { - action = *actstr; - } else { - EMSG2(_(e_invact2), actstr); - return; - } - } else { - EMSG(_(e_stringreq)); - return; - } - - if (set_tagstack(wp, d, action) == OK) { - rettv->vval.v_number = 0; - } else { - EMSG(_(e_listreq)); - } -} - -/* - * "setwinvar()" function - */ -static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - setwinvar(argvars, rettv, 0); -} - /* * "setwinvar()" and "settabwinvar()" functions */ -static void setwinvar(typval_T *argvars, typval_T *rettv, int off) +void setwinvar(typval_T *argvars, typval_T *rettv, int off) { if (check_secure()) { return; @@ -16321,990 +7971,8 @@ static void setwinvar(typval_T *argvars, typval_T *rettv, int off) } } -/// f_sha256 - sha256({string}) function -static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *p = tv_get_string(&argvars[0]); - const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0); - - // make a copy of the hash (sha256_bytes returns a static buffer) - rettv->vval.v_string = (char_u *)xstrdup(hash); - rettv->v_type = VAR_STRING; -} - -/* - * "shellescape({string})" function - */ -static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const bool do_special = non_zero_arg(&argvars[1]); - - rettv->vval.v_string = vim_strsave_shellescape( - (const char_u *)tv_get_string(&argvars[0]), do_special, do_special); - rettv->v_type = VAR_STRING; -} - -/* - * shiftwidth() function - */ -static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_sw_value(curbuf); -} - -/// "sign_define()" function -static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - dict_T *dict; - char *icon = NULL; - char *linehl = NULL; - char *text = NULL; - char *texthl = NULL; - char *numhl = NULL; - - rettv->vval.v_number = -1; - - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - - // sign attributes - dict = argvars[1].vval.v_dict; - if (tv_dict_find(dict, "icon", -1) != NULL) { - icon = tv_dict_get_string(dict, "icon", true); - } - if (tv_dict_find(dict, "linehl", -1) != NULL) { - linehl = tv_dict_get_string(dict, "linehl", true); - } - if (tv_dict_find(dict, "text", -1) != NULL) { - text = tv_dict_get_string(dict, "text", true); - } - if (tv_dict_find(dict, "texthl", -1) != NULL) { - texthl = tv_dict_get_string(dict, "texthl", true); - } - if (tv_dict_find(dict, "numhl", -1) != NULL) { - numhl = tv_dict_get_string(dict, "numhl", true); - } - } - - if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, (char_u *)numhl) - == OK) { - rettv->vval.v_number = 0; - } - - xfree(icon); - xfree(linehl); - xfree(text); - xfree(texthl); -} - -/// "sign_getdefined()" function -static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name = NULL; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - name = tv_get_string(&argvars[0]); - } - - sign_getlist((const char_u *)name, rettv->vval.v_list); -} - -/// "sign_getplaced()" function -static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf = NULL; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int sign_id = 0; - const char *group = NULL; - bool notanum = false; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // get signs placed in the specified buffer - buf = get_buf_arg(&argvars[0]); - if (buf == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT - || ((dict = argvars[1].vval.v_dict) == NULL)) { - EMSG(_(e_dictreq)); - return; - } - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - // get signs placed at this line - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); - } - if ((di = tv_dict_find(dict, "id", -1)) != NULL) { - // get sign placed with this identifier - sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - } - if ((di = tv_dict_find(dict, "group", -1)) != NULL) { - group = tv_get_string_chk(&di->di_tv); - if (group == NULL) { - return; - } - if (*group == '\0') { // empty string means global group - group = NULL; - } - } - } - } - - sign_get_placed(buf, lnum, sign_id, (const char_u *)group, - rettv->vval.v_list); -} - -/// "sign_jump()" function -static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - char *sign_group = NULL; - buf_T *buf; - bool notanum = false; - - rettv->vval.v_number = -1; - - // Sign identifer - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id <= 0) { - EMSG(_(e_invarg)); - return; - } - - // Sign group - const char * sign_group_chk = tv_get_string_chk(&argvars[1]); - if (sign_group_chk == NULL) { - return; - } - if (sign_group_chk[0] == '\0') { - sign_group = NULL; // global sign group - } else { - sign_group = xstrdup(sign_group_chk); - } - - // Buffer to place the sign - buf = get_buf_arg(&argvars[2]); - if (buf == NULL) { - goto cleanup; - } - - rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); - -cleanup: - xfree(sign_group); -} - -/// "sign_place()" function -static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - char_u *group = NULL; - const char *sign_name; - buf_T *buf; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int prio = SIGN_DEF_PRIO; - bool notanum = false; - - rettv->vval.v_number = -1; - - // Sign identifer - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id < 0) { - EMSG(_(e_invarg)); - return; - } - - // Sign group - const char *group_chk = tv_get_string_chk(&argvars[1]); - if (group_chk == NULL) { - return; - } - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } - - // Sign name - sign_name = tv_get_string_chk(&argvars[2]); - if (sign_name == NULL) { - goto cleanup; - } - - // Buffer to place the sign - buf = get_buf_arg(&argvars[3]); - if (buf == NULL) { - goto cleanup; - } - - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT - || ((dict = argvars[4].vval.v_dict) == NULL)) { - EMSG(_(e_dictreq)); - goto cleanup; - } - - // Line number where the sign is to be placed - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); - } - if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { - // Sign priority - prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } - } - } - - if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio) - == OK) { - rettv->vval.v_number = sign_id; - } - -cleanup: - xfree(group); -} - -/// "sign_undefine()" function -static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // Free all the signs - free_signs(); - rettv->vval.v_number = 0; - } else { - // Free only the specified sign - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (sign_undefine_by_name((const char_u *)name) == OK) { - rettv->vval.v_number = 0; - } - } -} - -/// "sign_unplace()" function -static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict; - dictitem_T *di; - int sign_id = 0; - buf_T *buf = NULL; - char_u *group = NULL; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - - const char *group_chk = tv_get_string(&argvars[0]); - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - goto cleanup; - } - dict = argvars[1].vval.v_dict; - - if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { - buf = get_buf_arg(&di->di_tv); - if (buf == NULL) { - goto cleanup; - } - } - if (tv_dict_find(dict, "id", -1) != NULL) { - sign_id = tv_dict_get_number(dict, "id"); - } - } - - if (buf == NULL) { - // Delete the sign in all the buffers - FOR_ALL_BUFFERS(cbuf) { - if (sign_unplace(sign_id, group, cbuf, 0) == OK) { - rettv->vval.v_number = 0; - } - } - } else { - if (sign_unplace(sign_id, group, buf, 0) == OK) { - rettv->vval.v_number = 0; - } - } - -cleanup: - xfree(group); -} - -/* - * "simplify()" function - */ -static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_string = (char_u *)xstrdup(p); - simplify_filename(rettv->vval.v_string); // Simplify in place. - rettv->v_type = VAR_STRING; -} - -/// "sockconnect()" function -static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) { - // Wrong argument types - EMSG2(_(e_invarg2), "expected dictionary"); - return; - } - - const char *mode = tv_get_string(&argvars[0]); - const char *address = tv_get_string(&argvars[1]); - - bool tcp; - if (strcmp(mode, "tcp") == 0) { - tcp = true; - } else if (strcmp(mode, "pipe") == 0) { - tcp = false; - } else { - EMSG2(_(e_invarg2), "invalid mode"); - return; - } - - bool rpc = false; - CallbackReader on_data = CALLBACK_READER_INIT; - if (argvars[2].v_type == VAR_DICT) { - dict_T *opts = argvars[2].vval.v_dict; - rpc = tv_dict_get_number(opts, "rpc") != 0; - - if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) { - return; - } - on_data.buffered = tv_dict_get_number(opts, "data_buffered"); - if (on_data.buffered && on_data.cb.type == kCallbackNone) { - on_data.self = opts; - } - } - - const char *error = NULL; - uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error); - - if (error) { - EMSG2(_("connection failed: %s"), error); - } - - rettv->vval.v_number = (varnumber_T)id; - rettv->v_type = VAR_NUMBER; -} - -/// struct storing information about current sort -typedef struct { - int item_compare_ic; - bool item_compare_numeric; - bool item_compare_numbers; - bool item_compare_float; - const char *item_compare_func; - partial_T *item_compare_partial; - dict_T *item_compare_selfdict; - bool item_compare_func_err; -} sortinfo_T; -static sortinfo_T *sortinfo = NULL; - -#define ITEM_COMPARE_FAIL 999 - -/* - * Compare functions for f_sort() and f_uniq() below. - */ -static int item_compare(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *const si1 = (ListSortItem *)s1; - ListSortItem *const si2 = (ListSortItem *)s2; - - typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); - typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); - - int res; - - if (sortinfo->item_compare_numbers) { - const varnumber_T v1 = tv_get_number(tv1); - const varnumber_T v2 = tv_get_number(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - if (sortinfo->item_compare_float) { - const float_T v1 = tv_get_float(tv1); - const float_T v2 = tv_get_float(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - char *tofree1 = NULL; - char *tofree2 = NULL; - char *p1; - char *p2; - - // encode_tv2string() puts quotes around a string and allocates memory. Don't - // do that for string variables. Use a single quote when comparing with - // a non-string to do what the docs promise. - if (tv1->v_type == VAR_STRING) { - if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p1 = "'"; - } else { - p1 = (char *)tv1->vval.v_string; - } - } else { - tofree1 = p1 = encode_tv2string(tv1, NULL); - } - if (tv2->v_type == VAR_STRING) { - if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p2 = "'"; - } else { - p2 = (char *)tv2->vval.v_string; - } - } else { - tofree2 = p2 = encode_tv2string(tv2, NULL); - } - if (p1 == NULL) { - p1 = ""; - } - if (p2 == NULL) { - p2 = ""; - } - if (!sortinfo->item_compare_numeric) { - if (sortinfo->item_compare_ic) { - res = STRICMP(p1, p2); - } else { - res = STRCMP(p1, p2); - } - } else { - double n1, n2; - n1 = strtod(p1, &p1); - n2 = strtod(p2, &p2); - res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; - } - - xfree(tofree1); - xfree(tofree2); - -item_compare_end: - // When the result would be zero, compare the item indexes. Makes the - // sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - return res; -} - -static int item_compare_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, true); -} - -static int item_compare_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, false); -} - -static int item_compare2(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *si1, *si2; - int res; - typval_T rettv; - typval_T argv[3]; - int dummy; - const char *func_name; - partial_T *partial = sortinfo->item_compare_partial; - - // shortcut after failure in previous call; compare all items equal - if (sortinfo->item_compare_func_err) { - return 0; - } - - si1 = (ListSortItem *)s1; - si2 = (ListSortItem *)s2; - - if (partial == NULL) { - func_name = sortinfo->item_compare_func; - } else { - func_name = (const char *)partial_name(partial); - } - - // Copy the values. This is needed to be able to set v_lock to VAR_FIXED - // in the copy without changing the original list items. - tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); - tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); - - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this - res = call_func((const char_u *)func_name, - (int)STRLEN(func_name), - &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, - partial, sortinfo->item_compare_selfdict); - tv_clear(&argv[0]); - tv_clear(&argv[1]); - - if (res == FAIL) { - res = ITEM_COMPARE_FAIL; - } else { - res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); - } - if (sortinfo->item_compare_func_err) { - res = ITEM_COMPARE_FAIL; // return value has wrong type - } - tv_clear(&rettv); - - // When the result would be zero, compare the pointers themselves. Makes - // the sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - - return res; -} - -static int item_compare2_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, true); -} - -static int item_compare2_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, false); -} - -/* - * "sort({list})" function - */ -static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) -{ - ListSortItem *ptrs; - long len; - long i; - - // Pointer to current info struct used in compare function. Save and restore - // the current one for nested calls. - sortinfo_T info; - sortinfo_T *old_sortinfo = sortinfo; - sortinfo = &info; - - const char *const arg_errmsg = (sort - ? N_("sort() argument") - : N_("uniq() argument")); - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); - } else { - list_T *const l = argvars[0].vval.v_list; - if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { - goto theend; - } - tv_list_set_ret(rettv, l); - - len = tv_list_len(l); - if (len <= 1) { - goto theend; // short list sorts pretty quickly - } - - info.item_compare_ic = false; - info.item_compare_numeric = false; - info.item_compare_numbers = false; - info.item_compare_float = false; - info.item_compare_func = NULL; - info.item_compare_partial = NULL; - info.item_compare_selfdict = NULL; - - if (argvars[1].v_type != VAR_UNKNOWN) { - /* optional second argument: {func} */ - if (argvars[1].v_type == VAR_FUNC) { - info.item_compare_func = (const char *)argvars[1].vval.v_string; - } else if (argvars[1].v_type == VAR_PARTIAL) { - info.item_compare_partial = argvars[1].vval.v_partial; - } else { - bool error = false; - - i = tv_get_number_chk(&argvars[1], &error); - if (error) { - goto theend; // type error; errmsg already given - } - if (i == 1) { - info.item_compare_ic = true; - } else if (argvars[1].v_type != VAR_NUMBER) { - info.item_compare_func = tv_get_string(&argvars[1]); - } else if (i != 0) { - EMSG(_(e_invarg)); - goto theend; - } - if (info.item_compare_func != NULL) { - if (*info.item_compare_func == NUL) { - // empty string means default sort - info.item_compare_func = NULL; - } else if (strcmp(info.item_compare_func, "n") == 0) { - info.item_compare_func = NULL; - info.item_compare_numeric = true; - } else if (strcmp(info.item_compare_func, "N") == 0) { - info.item_compare_func = NULL; - info.item_compare_numbers = true; - } else if (strcmp(info.item_compare_func, "f") == 0) { - info.item_compare_func = NULL; - info.item_compare_float = true; - } else if (strcmp(info.item_compare_func, "i") == 0) { - info.item_compare_func = NULL; - info.item_compare_ic = true; - } - } - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - // optional third argument: {dict} - if (argvars[2].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - goto theend; - } - info.item_compare_selfdict = argvars[2].vval.v_dict; - } - } - - // Make an array with each entry pointing to an item in the List. - ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - - if (sort) { - info.item_compare_func_err = false; - tv_list_item_sort(l, ptrs, - ((info.item_compare_func == NULL - && info.item_compare_partial == NULL) - ? item_compare_not_keeping_zero - : item_compare2_not_keeping_zero), - &info.item_compare_func_err); - if (info.item_compare_func_err) { - EMSG(_("E702: Sort compare function failed")); - } - } else { - ListSorter item_compare_func_ptr; - - // f_uniq(): ptrs will be a stack of items to remove. - info.item_compare_func_err = false; - if (info.item_compare_func != NULL - || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; - } else { - item_compare_func_ptr = item_compare_keeping_zero; - } - - int idx = 0; - for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) - ; li != NULL;) { - listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); - if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { // -V547 - EMSG(_("E882: Uniq compare function failed")); - break; - } - li = tv_list_item_remove(l, li); - } else { - idx++; - li = TV_LIST_ITEM_NEXT(l, li); - } - } - } - - xfree(ptrs); - } - -theend: - sortinfo = old_sortinfo; -} - -/// "sort"({list})" function -static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, true); -} - -/// "stdioopen()" function -static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - EMSG(_(e_invarg)); - return; - } - - - bool rpc = false; - CallbackReader on_stdin = CALLBACK_READER_INIT; - dict_T *opts = argvars[0].vval.v_dict; - rpc = tv_dict_get_number(opts, "rpc") != 0; - - if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { - return; - } - on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); - if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { - on_stdin.self = opts; - } - - const char *error; - uint64_t id = channel_from_stdio(rpc, on_stdin, &error); - if (!id) { - EMSG2(e_stdiochan2, error); - } - - - rettv->vval.v_number = (varnumber_T)id; - rettv->v_type = VAR_NUMBER; -} - -/// "uniq({list})" function -static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, false); -} - -// "reltimefloat()" function -static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - proftime_T tm; - - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = 0; - if (list2proftime(&argvars[0], &tm) == OK) { - rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0; - } -} - -/* - * "soundfold({word})" function - */ -static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *const s = tv_get_string(&argvars[0]); - rettv->vval.v_string = (char_u *)eval_soundfold(s); -} - -/* - * "spellbadword()" function - */ -static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *word = ""; - hlf_T attr = HLF_COUNT; - size_t len = 0; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // Find the start and length of the badly spelled word. - len = spell_move_to(curwin, FORWARD, true, true, &attr); - if (len != 0) { - word = (char *)get_cursor_pos_ptr(); - curwin->w_set_curswant = true; - } - } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { - const char *str = tv_get_string_chk(&argvars[0]); - int capcol = -1; - - if (str != NULL) { - // Check the argument for spelling. - while (*str != NUL) { - len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); - if (attr != HLF_COUNT) { - word = str; - break; - } - str += len; - capcol -= len; - len = 0; - } - } - } - - assert(len <= INT_MAX); - tv_list_alloc_ret(rettv, 2); - tv_list_append_string(rettv->vval.v_list, word, len); - tv_list_append_string(rettv->vval.v_list, - (attr == HLF_SPB ? "bad" - : attr == HLF_SPR ? "rare" - : attr == HLF_SPL ? "local" - : attr == HLF_SPC ? "caps" - : NULL), -1); -} - -/* - * "spellsuggest()" function - */ -static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool typeerr = false; - int maxcount; - garray_T ga = GA_EMPTY_INIT_VALUE; - bool need_capital = false; - - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { - const char *const str = tv_get_string(&argvars[0]); - if (argvars[1].v_type != VAR_UNKNOWN) { - maxcount = tv_get_number_chk(&argvars[1], &typeerr); - if (maxcount <= 0) { - goto f_spellsuggest_return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - need_capital = tv_get_number_chk(&argvars[2], &typeerr); - if (typeerr) { - goto f_spellsuggest_return; - } - } - } else { - maxcount = 25; - } - - spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); - } - -f_spellsuggest_return: - tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); - for (int i = 0; i < ga.ga_len; i++) { - char *const p = ((char **)ga.ga_data)[i]; - tv_list_append_allocated_string(rettv->vval.v_list, p); - } - ga_clear(&ga); -} - -static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *save_cpo; - int match; - colnr_T col = 0; - bool keepempty = false; - bool typeerr = false; - - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ - save_cpo = p_cpo; - p_cpo = (char_u *)""; - - const char *str = tv_get_string(&argvars[0]); - const char *pat = NULL; - char patbuf[NUMBUFLEN]; - if (argvars[1].v_type != VAR_UNKNOWN) { - pat = tv_get_string_buf_chk(&argvars[1], patbuf); - if (pat == NULL) { - typeerr = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr); - } - } - if (pat == NULL || *pat == NUL) { - pat = "[\\x01- ]\\+"; - } - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (typeerr) { - return; - } - - regmatch_T regmatch = { - .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING), - .startp = { NULL }, - .endp = { NULL }, - .rm_ic = false, - }; - if (regmatch.regprog != NULL) { - while (*str != NUL || keepempty) { - if (*str == NUL) { - match = false; // Empty item at the end. - } else { - match = vim_regexec_nl(®match, (char_u *)str, col); - } - const char *end; - if (match) { - end = (const char *)regmatch.startp[0]; - } else { - end = str + strlen(str); - } - if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 - && *str != NUL - && match - && end < (const char *)regmatch.endp[0])) { - tv_list_append_string(rettv->vval.v_list, str, end - str); - } - if (!match) { - break; - } - // Advance to just after the match. - if (regmatch.endp[0] > (char_u *)str) { - col = 0; - } else { - // Don't get stuck at the same match. - col = (*mb_ptr2len)(regmatch.endp[0]); - } - str = (const char *)regmatch.endp[0]; - } - - vim_regfree(regmatch.regprog); - } - - p_cpo = save_cpo; -} - /// "stdpath()" helper for list results -static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) +void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { const void *iter = NULL; @@ -17330,691 +7998,6 @@ static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) xfree(dirs); } -/// "stdpath(type)" function -static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const p = tv_get_string_chk(&argvars[0]); - if (p == NULL) { - return; // Type error; errmsg already given. - } - - if (strequal(p, "config")) { - rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); - } else if (strequal(p, "data")) { - rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); - } else if (strequal(p, "cache")) { - rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); - } else if (strequal(p, "config_dirs")) { - get_xdg_var_list(kXDGConfigDirs, rettv); - } else if (strequal(p, "data_dirs")) { - get_xdg_var_list(kXDGDataDirs, rettv); - } else { - EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); - } -} - -/* - * "str2float()" function - */ -static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); - bool isneg = (*p == '-'); - - if (*p == '+' || *p == '-') { - p = skipwhite(p + 1); - } - (void)string2float((char *)p, &rettv->vval.v_float); - if (isneg) { - rettv->vval.v_float *= -1; - } - rettv->v_type = VAR_FLOAT; -} - -// "str2nr()" function -static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int base = 10; - varnumber_T n; - int what; - - if (argvars[1].v_type != VAR_UNKNOWN) { - base = tv_get_number(&argvars[1]); - if (base != 2 && base != 8 && base != 10 && base != 16) { - EMSG(_(e_invarg)); - return; - } - } - - char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); - bool isneg = (*p == '-'); - if (*p == '+' || *p == '-') { - p = skipwhite(p + 1); - } - switch (base) { - case 2: { - what = STR2NR_BIN | STR2NR_FORCE; - break; - } - case 8: { - what = STR2NR_OCT | STR2NR_FORCE; - break; - } - case 16: { - what = STR2NR_HEX | STR2NR_FORCE; - break; - } - default: { - what = 0; - } - } - vim_str2nr(p, NULL, NULL, what, &n, NULL, 0); - if (isneg) { - rettv->vval.v_number = -n; - } else { - rettv->vval.v_number = n; - } -} - -/* - * "strftime({format}[, {time}])" function - */ -static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - time_t seconds; - - rettv->v_type = VAR_STRING; - - char *p = (char *)tv_get_string(&argvars[0]); - if (argvars[1].v_type == VAR_UNKNOWN) { - seconds = time(NULL); - } else { - seconds = (time_t)tv_get_number(&argvars[1]); - } - - struct tm curtime; - struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime); - /* MSVC returns NULL for an invalid value of seconds. */ - if (curtime_ptr == NULL) - rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); - else { - vimconv_T conv; - char_u *enc; - - conv.vc_type = CONV_NONE; - enc = enc_locale(); - convert_setup(&conv, p_enc, enc); - if (conv.vc_type != CONV_NONE) { - p = (char *)string_convert(&conv, (char_u *)p, NULL); - } - char result_buf[256]; - if (p != NULL) { - (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr); - } else { - result_buf[0] = NUL; - } - - if (conv.vc_type != CONV_NONE) { - xfree(p); - } - convert_setup(&conv, enc, p_enc); - if (conv.vc_type != CONV_NONE) { - rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL); - } else { - rettv->vval.v_string = (char_u *)xstrdup(result_buf); - } - - // Release conversion descriptors. - convert_setup(&conv, NULL, NULL); - xfree(enc); - } -} - -// "strgetchar()" function -static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - const char *const str = tv_get_string_chk(&argvars[0]); - if (str == NULL) { - return; - } - bool error = false; - varnumber_T charidx = tv_get_number_chk(&argvars[1], &error); - if (error) { - return; - } - - const size_t len = STRLEN(str); - size_t byteidx = 0; - - while (charidx >= 0 && byteidx < len) { - if (charidx == 0) { - rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx); - break; - } - charidx--; - byteidx += MB_CPTR2LEN((const char_u *)str + byteidx); - } -} - -/* - * "stridx()" function - */ -static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *haystack = tv_get_string_buf_chk(&argvars[0], buf); - const char *const haystack_start = haystack; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], - &error); - if (error || start_idx >= (ptrdiff_t)strlen(haystack)) { - return; - } - if (start_idx >= 0) { - haystack += start_idx; - } - } - - const char *pos = strstr(haystack, needle); - if (pos != NULL) { - rettv->vval.v_number = (varnumber_T)(pos - haystack_start); - } -} - -/* - * "string()" function - */ -static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *) encode_tv2string(&argvars[0], NULL); -} - -/* - * "strlen()" function - */ -static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); -} - -/* - * "strchars()" function - */ -static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *s = tv_get_string(&argvars[0]); - int skipcc = 0; - varnumber_T len = 0; - int (*func_mb_ptr2char_adv)(const char_u **pp); - - if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = tv_get_number_chk(&argvars[1], NULL); - } - if (skipcc < 0 || skipcc > 1) { - EMSG(_(e_invarg)); - } else { - func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; - while (*s != NUL) { - func_mb_ptr2char_adv((const char_u **)&s); - len++; - } - rettv->vval.v_number = len; - } -} - -/* - * "strdisplaywidth()" function - */ -static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - int col = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - col = tv_get_number(&argvars[1]); - } - - rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); -} - -/* - * "strwidth()" function - */ -static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - - rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s); -} - -// "strcharpart()" function -static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = STRLEN(p); - - int nbyte = 0; - bool error = false; - varnumber_T nchar = tv_get_number_chk(&argvars[1], &error); - if (!error) { - if (nchar > 0) { - while (nchar > 0 && (size_t)nbyte < slen) { - nbyte += MB_CPTR2LEN((const char_u *)p + nbyte); - nchar--; - } - } else { - nbyte = nchar; - } - } - int len = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - int charlen = tv_get_number(&argvars[2]); - while (charlen > 0 && nbyte + len < (int)slen) { - int off = nbyte + len; - - if (off < 0) { - len += 1; - } else { - len += (size_t)MB_CPTR2LEN((const char_u *)p + off); - } - charlen--; - } - } else { - len = slen - nbyte; // default: all bytes that are available. - } - - // Only return the overlap between the specified part and the actual - // string. - if (nbyte < 0) { - len += nbyte; - nbyte = 0; - } else if ((size_t)nbyte > slen) { - nbyte = slen; - } - if (len < 0) { - len = 0; - } else if (nbyte + len > (int)slen) { - len = slen - nbyte; - } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len); -} - -/* - * "strpart()" function - */ -static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool error = false; - - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = strlen(p); - - varnumber_T n = tv_get_number_chk(&argvars[1], &error); - varnumber_T len; - if (error) { - len = 0; - } else if (argvars[2].v_type != VAR_UNKNOWN) { - len = tv_get_number(&argvars[2]); - } else { - len = slen - n; // Default len: all bytes that are available. - } - - // Only return the overlap between the specified part and the actual - // string. - if (n < 0) { - len += n; - n = 0; - } else if (n > (varnumber_T)slen) { - n = slen; - } - if (len < 0) { - len = 0; - } else if (n + len > (varnumber_T)slen) { - len = slen - n; - } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); -} - -/* - * "strridx()" function - */ -static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf); - - rettv->vval.v_number = -1; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - const size_t haystack_len = STRLEN(haystack); - ptrdiff_t end_idx; - if (argvars[2].v_type != VAR_UNKNOWN) { - // Third argument: upper limit for index. - end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL); - if (end_idx < 0) { - return; // Can never find a match. - } - } else { - end_idx = (ptrdiff_t)haystack_len; - } - - const char *lastmatch = NULL; - if (*needle == NUL) { - // Empty string matches past the end. - lastmatch = haystack + end_idx; - } else { - for (const char *rest = haystack; *rest != NUL; rest++) { - rest = strstr(rest, needle); - if (rest == NULL || rest > haystack + end_idx) { - break; - } - lastmatch = rest; - } - } - - if (lastmatch != NULL) { - rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); - } -} - -/* - * "strtrans()" function - */ -static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0])); -} - -/* - * "submatch()" function - */ -static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool error = false; - int no = (int)tv_get_number_chk(&argvars[0], &error); - if (error) { - return; - } - - if (no < 0 || no >= NSUBEXP) { - emsgf(_("E935: invalid submatch number: %d"), no); - return; - } - int retList = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - retList = tv_get_number_chk(&argvars[1], &error); - if (error) { - return; - } - } - - if (retList == 0) { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = reg_submatch(no); - } else { - rettv->v_type = VAR_LIST; - rettv->vval.v_list = reg_submatch_list(no); - } -} - -/* - * "substitute()" function - */ -static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char patbuf[NUMBUFLEN]; - char subbuf[NUMBUFLEN]; - char flagsbuf[NUMBUFLEN]; - - const char *const str = tv_get_string_chk(&argvars[0]); - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - const char *sub = NULL; - const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf); - - typval_T *expr = NULL; - if (tv_is_func(argvars[2])) { - expr = &argvars[2]; - } else { - sub = tv_get_string_buf_chk(&argvars[2], subbuf); - } - - rettv->v_type = VAR_STRING; - if (str == NULL || pat == NULL || (sub == NULL && expr == NULL) - || flg == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat, - (char_u *)sub, expr, (char_u *)flg); - } -} - -/// "swapinfo(swap_filename)" function -static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); -} - -/// "swapname(expr)" function -static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - buf_T *buf = tv_get_buf(&argvars[0], false); - if (buf == NULL - || buf->b_ml.ml_mfp == NULL - || buf->b_ml.ml_mfp->mf_fname == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname); - } -} - -/// "synID(lnum, col, trans)" function -static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // -1 on type error (both) - const linenr_T lnum = tv_get_lnum(argvars); - const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - - bool transerr = false; - const int trans = tv_get_number_chk(&argvars[2], &transerr); - - int id = 0; - if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count - && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) { - id = syn_get_id(curwin, lnum, col, trans, NULL, false); - } - - rettv->vval.v_number = id; -} - -/* - * "synIDattr(id, what [, mode])" function - */ -static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int id = (int)tv_get_number(&argvars[0]); - const char *const what = tv_get_string(&argvars[1]); - int modec; - if (argvars[2].v_type != VAR_UNKNOWN) { - char modebuf[NUMBUFLEN]; - const char *const mode = tv_get_string_buf(&argvars[2], modebuf); - modec = TOLOWER_ASC(mode[0]); - if (modec != 'c' && modec != 'g') { - modec = 0; // Replace invalid with current. - } - } else if (ui_rgb_attached()) { - modec = 'g'; - } else { - modec = 'c'; - } - - - const char *p = NULL; - switch (TOLOWER_ASC(what[0])) { - case 'b': { - if (TOLOWER_ASC(what[1]) == 'g') { // bg[#] - p = highlight_color(id, what, modec); - } else { // bold - p = highlight_has_attr(id, HL_BOLD, modec); - } - break; - } - case 'f': { // fg[#] or font - p = highlight_color(id, what, modec); - break; - } - case 'i': { - if (TOLOWER_ASC(what[1]) == 'n') { // inverse - p = highlight_has_attr(id, HL_INVERSE, modec); - } else { // italic - p = highlight_has_attr(id, HL_ITALIC, modec); - } - break; - } - case 'n': { // name - p = get_highlight_name_ext(NULL, id - 1, false); - break; - } - case 'r': { // reverse - p = highlight_has_attr(id, HL_INVERSE, modec); - break; - } - case 's': { - if (TOLOWER_ASC(what[1]) == 'p') { // sp[#] - p = highlight_color(id, what, modec); - } else if (TOLOWER_ASC(what[1]) == 't' - && TOLOWER_ASC(what[2]) == 'r') { // strikethrough - p = highlight_has_attr(id, HL_STRIKETHROUGH, modec); - } else { // standout - p = highlight_has_attr(id, HL_STANDOUT, modec); - } - break; - } - case 'u': { - if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline - p = highlight_has_attr(id, HL_UNDERLINE, modec); - } else { // undercurl - p = highlight_has_attr(id, HL_UNDERCURL, modec); - } - break; - } - } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p)); -} - -/* - * "synIDtrans(id)" function - */ -static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int id = tv_get_number(&argvars[0]); - - if (id > 0) { - id = syn_get_final_id(id); - } else { - id = 0; - } - - rettv->vval.v_number = id; -} - -/* - * "synconcealed(lnum, col)" function - */ -static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int syntax_flags = 0; - int cchar; - int matchid = 0; - char_u str[NUMBUFLEN]; - - tv_list_set_ret(rettv, NULL); - - // -1 on type error (both) - const linenr_T lnum = tv_get_lnum(argvars); - const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - - memset(str, NUL, sizeof(str)); - - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 - && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { - (void)syn_get_id(curwin, lnum, col, false, NULL, false); - syntax_flags = get_syntax_info(&matchid); - - // get the conceal character - if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { - cchar = syn_get_sub_char(); - if (cchar == NUL && curwin->w_p_cole == 1) { - cchar = (curwin->w_p_lcs_chars.conceal == NUL) - ? ' ' - : curwin->w_p_lcs_chars.conceal; - } - if (cchar != NUL) { - utf_char2bytes(cchar, str); - } - } - } - - tv_list_alloc_ret(rettv, 3); - tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); - // -1 to auto-determine strlen - tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); - tv_list_append_number(rettv->vval.v_list, matchid); -} - -/* - * "synstack(lnum, col)" function - */ -static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_set_ret(rettv, NULL); - - // -1 on type error (both) - const linenr_T lnum = tv_get_lnum(argvars); - const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - - if (lnum >= 1 - && lnum <= curbuf->b_ml.ml_line_count - && col >= 0 - && (size_t)col <= STRLEN(ml_get(lnum))) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - (void)syn_get_id(curwin, lnum, col, false, NULL, true); - - int id; - int i = 0; - while ((id = syn_get_stack_item(i++)) >= 0) { - tv_list_append_number(rettv->vval.v_list, id); - } - } -} - static list_T *string_to_list(const char *str, size_t len, const bool keepempty) { if (!keepempty && str[len - 1] == NL) { @@ -18026,8 +8009,8 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) } // os_system wrapper. Handles 'verbose', :profile, and v:shell_error. -static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, - bool retlist) +void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, + bool retlist) { proftime_T wait_time; bool profiling = do_profiling == PROF_YES; @@ -18125,317 +8108,6 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, } } -/// f_system - the VimL system() function -static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_system_output_as_rettv(argvars, rettv, false); -} - -static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_system_output_as_rettv(argvars, rettv, true); -} - - -/* - * "tabpagebuflist()" function - */ -static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = NULL; - - if (argvars[0].v_type == VAR_UNKNOWN) { - wp = firstwin; - } else { - tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp != NULL) { - wp = (tp == curtab) ? firstwin : tp->tp_firstwin; - } - } - if (wp != NULL) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - while (wp != NULL) { - tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); - wp = wp->w_next; - } - } -} - -/* - * "tabpagenr()" function - */ -static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - const char *const arg = tv_get_string_chk(&argvars[0]); - nr = 0; - if (arg != NULL) { - if (strcmp(arg, "$") == 0) { - nr = tabpage_index(NULL) - 1; - } else if (strcmp(arg, "#") == 0) { - nr = valid_tabpage(lastused_tabpage) - ? tabpage_index(lastused_tabpage) - : nr; - } else { - EMSG2(_(e_invexpr2), arg); - } - } - } else { - nr = tabpage_index(curtab); - } - rettv->vval.v_number = nr; -} - - - -/* - * Common code for tabpagewinnr() and winnr(). - */ -static int get_winnr(tabpage_T *tp, typval_T *argvar) -{ - win_T *twin; - int nr = 1; - win_T *wp; - - twin = (tp == curtab) ? curwin : tp->tp_curwin; - if (argvar->v_type != VAR_UNKNOWN) { - bool invalid_arg = false; - const char *const arg = tv_get_string_chk(argvar); - if (arg == NULL) { - nr = 0; // Type error; errmsg already given. - } else if (strcmp(arg, "$") == 0) { - twin = (tp == curtab) ? lastwin : tp->tp_lastwin; - } else if (strcmp(arg, "#") == 0) { - twin = (tp == curtab) ? prevwin : tp->tp_prevwin; - if (twin == NULL) { - nr = 0; - } - } else { - // Extract the window count (if specified). e.g. winnr('3j') - char_u *endp; - long count = strtol((char *)arg, (char **)&endp, 10); - if (count <= 0) { - // if count is not specified, default to 1 - count = 1; - } - if (endp != NULL && *endp != '\0') { - if (strequal((char *)endp, "j")) { - twin = win_vert_neighbor(tp, twin, false, count); - } else if (strequal((char *)endp, "k")) { - twin = win_vert_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "h")) { - twin = win_horz_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "l")) { - twin = win_horz_neighbor(tp, twin, false, count); - } else { - invalid_arg = true; - } - } else { - invalid_arg = true; - } - } - - if (invalid_arg) { - EMSG2(_(e_invexpr2), arg); - nr = 0; - } - } - - if (nr > 0) - for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; - wp != twin; wp = wp->w_next) { - if (wp == NULL) { - /* didn't find it in this tabpage */ - nr = 0; - break; - } - ++nr; - } - return nr; -} - -/* - * "tabpagewinnr()" function - */ -static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - nr = 0; - } else { - nr = get_winnr(tp, &argvars[1]); - } - rettv->vval.v_number = nr; -} - -/* - * "tagfiles()" function - */ -static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char *fname; - tagname_T tn; - - tv_list_alloc_ret(rettv, kListLenUnknown); - fname = xmalloc(MAXPATHL); - - bool first = true; - while (get_tagfname(&tn, first, (char_u *)fname) == OK) { - tv_list_append_string(rettv->vval.v_list, fname, -1); - first = false; - } - - tagname_free(&tn); - xfree(fname); -} - -/* - * "taglist()" function - */ -static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const tag_pattern = tv_get_string(&argvars[0]); - - rettv->vval.v_number = false; - if (*tag_pattern == NUL) { - return; - } - - const char *fname = NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - fname = tv_get_string(&argvars[1]); - } - (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), - (char_u *)tag_pattern, (char_u *)fname); -} - -/* - * "tempname()" function - */ -static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_tempname(); -} - -// "termopen(cmd[, cwd])" function -static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (curbuf->b_changed) { - EMSG(_("Can only call this function in an unmodified buffer")); - return; - } - - const char *cmd; - bool executable = true; - char **argv = tv_to_argv(&argvars[0], &cmd, &executable); - 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 type - EMSG2(_(e_invarg2), "expected dictionary"); - shell_free_argv(argv); - return; - } - - CallbackReader on_stdout = CALLBACK_READER_INIT, - on_stderr = CALLBACK_READER_INIT; - Callback on_exit = CALLBACK_NONE; - dict_T *job_opts = NULL; - const char *cwd = "."; - if (argvars[1].v_type == VAR_DICT) { - job_opts = argvars[1].vval.v_dict; - - const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && *new_cwd != NUL) { - 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; - } - } - - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { - shell_free_argv(argv); - return; - } - } - - uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, - true, false, false, cwd, - term_width, curwin->w_height_inner, - xstrdup("xterm-256color"), NULL, - &rettv->vval.v_number); - if (rettv->vval.v_number <= 0) { - return; - } - - int pid = chan->stream.pty.process.pid; - - // "./…" => "/home/foo/…" - vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); - // "/home/foo/…" => "~/…" - size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); - // Trim slash. - if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') { - IObuff[len - 1] = '\0'; - } - - // Terminal URI: "term://$CWD//$PID:$CMD" - snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", - (char *)IObuff, pid, cmd); - // at this point the buffer has no terminal instance associated yet, so unset - // the 'swapfile' option to ensure no swap file will be created - curbuf->b_p_swf = false; - (void)setfname(curbuf, NameBuff, NULL, true); - // Save the job id and pid in b:terminal_job_{id,pid} - Error err = ERROR_INIT; - // deprecated: use 'channel' buffer option - dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), - INTEGER_OBJ(chan->id), false, false, &err); - api_clear_error(&err); - dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), - INTEGER_OBJ(pid), false, false, &err); - api_clear_error(&err); - - channel_terminal_open(chan); - channel_create_event(chan, NULL); -} - -// "test_garbagecollect_now()" function -static void f_test_garbagecollect_now(typval_T *argvars, - typval_T *rettv, FunPtr fptr) -{ - // This is dangerous, any Lists and Dicts used internally may be freed - // while still in use. - garbage_collect(true); -} - -// "test_write_list_log()" function -static void f_test_write_list_log(typval_T *const argvars, - typval_T *const rettv, - FunPtr fptr) -{ - const char *const fname = tv_get_string_chk(&argvars[0]); - if (fname == NULL) { - return; - } - list_write_log(fname); -} - bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -18528,7 +8200,12 @@ static bool set_ref_in_callback_reader(CallbackReader *reader, int copyID, return false; } -static void add_timer_info(typval_T *rettv, timer_T *timer) +timer_T *find_timer_by_nr(varnumber_T xx) +{ + return pmap_get(uint64_t)(timers, xx); +} + +void add_timer_info(typval_T *rettv, timer_T *timer) { list_T *list = rettv->vval.v_list; dict_T *dict = tv_dict_alloc(); @@ -18557,8 +8234,9 @@ static void add_timer_info(typval_T *rettv, timer_T *timer) } } -static void add_timer_info_all(typval_T *rettv) +void add_timer_info_all(typval_T *rettv) { + tv_list_alloc_ret(rettv, timers->table->n_occupied); timer_T *timer; map_foreach_value(timers, timer, { if (!timer->stopped) { @@ -18567,121 +8245,8 @@ static void add_timer_info_all(typval_T *rettv) }) } -/// "timer_info([timer])" function -static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN - ? 1 - : timers->table->n_occupied)); - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_number_exp)); - return; - } - timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); - if (timer != NULL && !timer->stopped) { - add_timer_info(rettv, timer); - } - } else { - add_timer_info_all(rettv); - } -} - -/// "timer_pause(timer, paused)" function -static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_number_exp)); - return; - } - int paused = (bool)tv_get_number(&argvars[1]); - timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); - if (timer != NULL) { - if (!timer->paused && paused) { - time_watcher_stop(&timer->tw); - } else if (timer->paused && !paused) { - time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, - timer->timeout); - } - timer->paused = paused; - } -} - -/// "timer_start(timeout, callback, opts)" function -static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const long timeout = tv_get_number(&argvars[0]); - timer_T *timer; - int repeat = 1; - dict_T *dict; - - rettv->vval.v_number = -1; - - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT - || (dict = argvars[2].vval.v_dict) == NULL) { - EMSG2(_(e_invarg2), tv_get_string(&argvars[2])); - return; - } - dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat")); - if (di != NULL) { - repeat = tv_get_number(&di->di_tv); - if (repeat == 0) { - repeat = 1; - } - } - } - - Callback callback; - if (!callback_from_typval(&callback, &argvars[1])) { - return; - } - - timer = xmalloc(sizeof *timer); - timer->refcount = 1; - timer->stopped = false; - timer->paused = false; - timer->emsg_count = 0; - timer->repeat_count = repeat; - timer->timeout = timeout; - timer->timer_id = last_timer_id++; - timer->callback = callback; - - time_watcher_init(&main_loop, &timer->tw, timer); - timer->tw.events = multiqueue_new_child(main_loop.events); - // if main loop is blocked, don't queue up multiple events - timer->tw.blockable = true; - time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout); - - pmap_put(uint64_t)(timers, timer->timer_id, timer); - rettv->vval.v_number = timer->timer_id; -} - - -// "timer_stop(timerid)" function -static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_NUMBER) { - EMSG(_(e_number_exp)); - return; - } - - timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); - - if (timer == NULL) { - return; - } - - timer_stop(timer); -} - -static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - timer_stop_all(); -} - // invoked on the main loop -static void timer_due_cb(TimeWatcher *tw, void *data) +void timer_due_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; int save_did_emsg = did_emsg; @@ -18733,7 +8298,31 @@ static void timer_due_cb(TimeWatcher *tw, void *data) timer_decref(timer); } -static void timer_stop(timer_T *timer) +uint64_t timer_start(const long timeout, + const int repeat_count, + const Callback *const callback) +{ + timer_T *timer = xmalloc(sizeof *timer); + timer->refcount = 1; + timer->stopped = false; + timer->paused = false; + timer->emsg_count = 0; + timer->repeat_count = repeat_count; + timer->timeout = timeout; + timer->timer_id = last_timer_id++; + timer->callback = *callback; + + time_watcher_init(&main_loop, &timer->tw, timer); + timer->tw.events = multiqueue_new_child(main_loop.events); + // if main loop is blocked, don't queue up multiple events + timer->tw.blockable = true; + time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout); + + pmap_put(uint64_t)(timers, timer->timer_id, timer); + return timer->timer_id; +} + +void timer_stop(timer_T *timer) { if (timer->stopped) { // avoid double free @@ -18762,7 +8351,7 @@ static void timer_decref(timer_T *timer) } } -static void timer_stop_all(void) +void timer_stop_all(void) { timer_T *timer; map_foreach_value(timers, timer, { @@ -18775,527 +8364,6 @@ void timer_teardown(void) timer_stop_all(); } -/* - * "tolower(string)" function - */ -static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), - false); -} - -/* - * "toupper(string)" function - */ -static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), - true); -} - -/* - * "tr(string, fromstr, tostr)" function - */ -static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - - const char *in_str = tv_get_string(&argvars[0]); - const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf); - const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2); - - // Default return value: empty string. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (fromstr == NULL || tostr == NULL) { - return; // Type error; errmsg already given. - } - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - - if (!has_mbyte) { - // Not multi-byte: fromstr and tostr must be the same length. - if (strlen(fromstr) != strlen(tostr)) { - goto error; - } - } - - // fromstr and tostr have to contain the same number of chars. - bool first = true; - while (*in_str != NUL) { - if (has_mbyte) { - const char *cpstr = in_str; - const int inlen = (*mb_ptr2len)((const char_u *)in_str); - int cplen = inlen; - int idx = 0; - int fromlen; - for (const char *p = fromstr; *p != NUL; p += fromlen) { - fromlen = (*mb_ptr2len)((const char_u *)p); - if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) { - int tolen; - for (p = tostr; *p != NUL; p += tolen) { - tolen = (*mb_ptr2len)((const char_u *)p); - if (idx-- == 0) { - cplen = tolen; - cpstr = (char *)p; - break; - } - } - if (*p == NUL) { // tostr is shorter than fromstr. - goto error; - } - break; - } - idx++; - } - - if (first && cpstr == in_str) { - // Check that fromstr and tostr have the same number of - // (multi-byte) characters. Done only once when a character - // of in_str doesn't appear in fromstr. - first = false; - int tolen; - for (const char *p = tostr; *p != NUL; p += tolen) { - tolen = (*mb_ptr2len)((const char_u *)p); - idx--; - } - if (idx != 0) { - goto error; - } - } - - ga_grow(&ga, cplen); - memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen); - ga.ga_len += cplen; - - in_str += inlen; - } else { - // When not using multi-byte chars we can do it faster. - const char *const p = strchr(fromstr, *in_str); - if (p != NULL) { - ga_append(&ga, tostr[p - fromstr]); - } else { - ga_append(&ga, *in_str); - } - in_str++; - } - } - - // add a terminating NUL - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - return; -error: - EMSG2(_(e_invarg2), fromstr); - ga_clear(&ga); - return; -} - -// "trim({expr})" function -static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1); - const char_u *mask = NULL; - const char_u *tail; - const char_u *prev; - const char_u *p; - int c1; - - rettv->v_type = VAR_STRING; - if (head == NULL) { - rettv->vval.v_string = NULL; - return; - } - - if (argvars[1].v_type == VAR_STRING) { - mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); - } - - while (*head != NUL) { - c1 = PTR2CHAR(head); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - MB_PTR_ADV(head); - } - - for (tail = head + STRLEN(head); tail > head; tail = prev) { - prev = tail; - MB_PTR_BACK(head, prev); - c1 = PTR2CHAR(prev); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - } - rettv->vval.v_string = vim_strnsave(head, (int)(tail - head)); -} - -/* - * "type(expr)" function - */ -static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = -1; - - switch (argvars[0].v_type) { - case VAR_NUMBER: n = VAR_TYPE_NUMBER; break; - case VAR_STRING: n = VAR_TYPE_STRING; break; - case VAR_PARTIAL: - case VAR_FUNC: n = VAR_TYPE_FUNC; break; - case VAR_LIST: n = VAR_TYPE_LIST; break; - case VAR_DICT: n = VAR_TYPE_DICT; break; - case VAR_FLOAT: n = VAR_TYPE_FLOAT; break; - case VAR_SPECIAL: { - switch (argvars[0].vval.v_special) { - case kSpecialVarTrue: - case kSpecialVarFalse: { - n = VAR_TYPE_BOOL; - break; - } - case kSpecialVarNull: { - n = 7; - break; - } - } - break; - } - case VAR_UNKNOWN: { - internal_error("f_type(UNKNOWN)"); - break; - } - } - rettv->vval.v_number = n; -} - -/* - * "undofile(name)" function - */ -static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - const char *const fname = tv_get_string(&argvars[0]); - - if (*fname == NUL) { - // If there is no file name there will be no undo file. - rettv->vval.v_string = NULL; - } else { - char *ffname = FullName_save(fname, true); - - if (ffname != NULL) { - rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false); - } - xfree(ffname); - } -} - -/* - * "undotree()" function - */ -static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); - tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); - tv_dict_add_nr(dict, S_LEN("save_last"), - (varnumber_T)curbuf->b_u_save_nr_last); - tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur); - tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); - tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); - - tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); -} - -/* - * "values(dict)" function - */ -static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 1); -} - -/* - * "virtcol(string)" function - */ -static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - colnr_T vcol = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count - && fnum == curbuf->b_fnum) { - // Limit the column to a valid value, getvvcol() doesn't check. - if (fp->col < 0) { - fp->col = 0; - } else { - const size_t len = STRLEN(ml_get(fp->lnum)); - if (fp->col > (colnr_T)len) { - fp->col = (colnr_T)len; - } - } - getvvcol(curwin, fp, NULL, NULL, &vcol); - ++vcol; - } - - rettv->vval.v_number = vcol; -} - -/* - * "visualmode()" function - */ -static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u str[2]; - - rettv->v_type = VAR_STRING; - str[0] = curbuf->b_visual_mode_eval; - str[1] = NUL; - rettv->vval.v_string = vim_strsave(str); - - /* A non-zero number or non-empty string argument: reset mode. */ - if (non_zero_arg(&argvars[0])) - curbuf->b_visual_mode_eval = NUL; -} - -/* - * "wildmenumode()" function - */ -static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { - rettv->vval.v_number = 1; - } -} - -/// "win_findbuf()" function -static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - win_findbuf(argvars, rettv->vval.v_list); -} - -/// "win_getid()" function -static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_getid(argvars); -} - -/// "win_gotoid()" function -static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_gotoid(argvars); -} - -/// "win_id2tabwin()" function -static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_id2tabwin(argvars, rettv); -} - -/// "win_id2win()" function -static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_id2win(argvars); -} - -/// "winbufnr(nr)" function -static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_buffer->b_fnum; - } -} - -/* - * "wincol()" function - */ -static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wcol + 1; -} - -/// "winheight(nr)" function -static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_height; - } -} - -// "winlayout()" function -static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tabpage_T *tp; - - tv_list_alloc_ret(rettv, 2); - - if (argvars[0].v_type == VAR_UNKNOWN) { - tp = curtab; - } else { - tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - return; - } - } - - get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); -} - -/* - * "winline()" function - */ -static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wrow + 1; -} - -/* - * "winnr()" function - */ -static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - - nr = get_winnr(curtab, &argvars[0]); - rettv->vval.v_number = nr; -} - -/* - * "winrestcmd()" function - */ -static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int winnr = 1; - garray_T ga; - char_u buf[50]; - - ga_init(&ga, (int)sizeof(char), 70); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); - ga_concat(&ga, buf); - sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); - ga_concat(&ga, buf); - ++winnr; - } - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - rettv->v_type = VAR_STRING; -} - -/* - * "winrestview()" function - */ -static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict; - - if (argvars[0].v_type != VAR_DICT - || (dict = argvars[0].vval.v_dict) == NULL) { - EMSG(_(e_invarg)); - } else { - dictitem_T *di; - if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { - curwin->w_cursor.lnum = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { - curwin->w_cursor.col = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { - curwin->w_cursor.coladd = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { - curwin->w_curswant = tv_get_number(&di->di_tv); - curwin->w_set_curswant = false; - } - if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { - set_topline(curwin, tv_get_number(&di->di_tv)); - } - if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { - curwin->w_topfill = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { - curwin->w_leftcol = tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { - curwin->w_skipcol = tv_get_number(&di->di_tv); - } - - check_cursor(); - win_new_height(curwin, curwin->w_height); - win_new_width(curwin, curwin->w_width); - changed_window_setting(); - - if (curwin->w_topline <= 0) - curwin->w_topline = 1; - if (curwin->w_topline > curbuf->b_ml.ml_line_count) - curwin->w_topline = curbuf->b_ml.ml_line_count; - check_topfill(curwin, true); - } -} - -/* - * "winsaveview()" function - */ -static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict; - - tv_dict_alloc_ret(rettv); - dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); - tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); - tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); - update_curswant(); - tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); - - tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); - tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); - tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); - tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); -} - /// Write "list" of strings to file "fd". /// /// @param fp File to write to. @@ -19303,8 +8371,8 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @param[in] binary Whether to write in binary mode. /// /// @return true in case of success, false otherwise. -static bool write_list(FileDescriptor *const fp, const list_T *const list, - const bool binary) +bool write_list(FileDescriptor *const fp, const list_T *const list, + const bool binary) FUNC_ATTR_NONNULL_ARG(1) { int error = 0; @@ -19362,7 +8430,7 @@ write_list_error: /// @param[in] endnl If true, the output will end in a newline (if a list). /// @returns an allocated string if `tv` represents a VimL string, list, or /// number; NULL otherwise. -static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) +char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { *len = 0; @@ -19441,101 +8509,6 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) return ret; } -/// "winwidth(nr)" function -static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_width; - } -} - -/// "wordcount()" function -static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - cursor_pos_info(rettv->vval.v_dict); -} - -/// "writefile()" function -static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "writefile()"); - return; - } - const list_T *const list = argvars[0].vval.v_list; - TV_LIST_ITER_CONST(list, li, { - if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { - return; - } - }); - - bool binary = false; - bool append = false; - bool do_fsync = !!p_fs; - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const flags = tv_get_string_chk(&argvars[2]); - if (flags == NULL) { - return; - } - for (const char *p = flags; *p; p++) { - switch (*p) { - case 'b': { binary = true; break; } - case 'a': { append = true; break; } - case 's': { do_fsync = true; break; } - case 'S': { do_fsync = false; break; } - default: { - // Using %s, p and not %c, *p to preserve multibyte characters - emsgf(_("E5060: Unknown flag: %s"), p); - return; - } - } - } - } - - char buf[NUMBUFLEN]; - const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); - if (fname == NULL) { - return; - } - FileDescriptor fp; - int error; - if (*fname == NUL) { - EMSG(_("E482: Can't open file with an empty name")); - } else if ((error = file_open(&fp, fname, - ((append ? kFileAppend : kFileTruncate) - | kFileCreate), 0666)) != 0) { - emsgf(_("E482: Can't open file %s for writing: %s"), - fname, os_strerror(error)); - } else { - if (write_list(&fp, list, binary)) { - rettv->vval.v_number = 0; - } - if ((error = file_close(&fp, do_fsync)) != 0) { - emsgf(_("E80: Error when closing file %s: %s"), - fname, os_strerror(error)); - } - } -} -/* - * "xor(expr, expr)" function - */ -static void f_xor(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); -} - - /// Translate a VimL object into a position /// /// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid @@ -19773,10 +8746,10 @@ static int get_id_len(const char **const arg) * If the name contains 'magic' {}'s, expand them and return the * expanded name in an allocated string via 'alias' - caller must free. */ -static int get_name_len(const char **const arg, - char **alias, - int evaluate, - int verbose) +int get_name_len(const char **const arg, + char **alias, + int evaluate, + int verbose) { int len; @@ -20234,7 +9207,7 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) * Get the value of internal variable "name". * Return OK or FAIL. */ -static int get_var_tv( +int get_var_tv( const char *name, int len, // length of "name" typval_T *rettv, // NULL when only checking existence @@ -20308,7 +9281,7 @@ static int check_luafunc_name(const char *str, bool paren) /// Handle expr[expr], expr[expr:expr] subscript and .name lookup. /// Also handle function call with Funcref variable: func(expr) /// Can all be combined: dict.func(expr)[idx]['func'](expr) -static int +int handle_subscript( const char **const arg, typval_T *rettv, @@ -20482,8 +9455,8 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict) // Careful: "a:0" variables don't have a name. // When "htp" is not NULL we are writing to the variable, set "htp" to the // hashtab_T used. -static dictitem_T *find_var(const char *const name, const size_t name_len, - hashtab_T **htp, int no_autoload) +dictitem_T *find_var(const char *const name, const size_t name_len, + hashtab_T **htp, int no_autoload) { const char *varname; hashtab_T *const ht = find_var_ht(name, name_len, &varname); @@ -20516,11 +9489,11 @@ static dictitem_T *find_var(const char *const name, const size_t name_len, /// /// @return pointer to the dictionary item with the found variable or NULL if it /// was not found. -static dictitem_T *find_var_in_ht(hashtab_T *const ht, - int htname, - const char *const varname, - const size_t varname_len, - int no_autoload) +dictitem_T *find_var_in_ht(hashtab_T *const ht, + int htname, + const char *const varname, + const size_t varname_len, + int no_autoload) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { hashitem_T *hi; @@ -20880,8 +9853,8 @@ static void list_one_var_a(const char *prefix, const char *name, /// @param[in] name_len Length of the variable name. /// @param tv Variable value. /// @param[in] copy True if value in tv is to be copied. -static void set_var(const char *name, const size_t name_len, typval_T *const tv, - const bool copy) +void set_var(const char *name, const size_t name_len, typval_T *const tv, + const bool copy) FUNC_ATTR_NONNULL_ALL { set_var_const(name, name_len, tv, copy, false); @@ -21050,7 +10023,7 @@ bool var_check_ro(const int flags, const char *name, { const char *error_message = NULL; if (flags & DI_FLAGS_RO) { - error_message = N_(e_readonlyvar); + error_message = _(e_readonlyvar); } else if ((flags & DI_FLAGS_RO_SBX) && sandbox) { error_message = N_("E794: Cannot set variable in the sandbox: \"%.*s\""); } @@ -21088,8 +10061,8 @@ bool var_check_ro(const int flags, const char *name, /// gettext. /// /// @return True if variable is fixed, false otherwise. -static bool var_check_fixed(const int flags, const char *name, - size_t name_len) +bool var_check_fixed(const int flags, const char *name, + size_t name_len) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (flags & DI_FLAGS_FIX) { @@ -22521,7 +11494,7 @@ bool translated_function_exists(const char *name) /// @param[in] no_deref Whether to dereference a Funcref. /// /// @return True if it exists, false otherwise. -static bool function_exists(const char *const name, bool no_deref) +bool function_exists(const char *const name, bool no_deref) { const char_u *nm = (const char_u *)name; bool n = false; @@ -24492,10 +13465,10 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, /// common code for getting job callbacks for jobstart, termopen and rpcstart /// /// @return true/false on success/failure. -static inline bool common_job_callbacks(dict_T *vopts, - CallbackReader *on_stdout, - CallbackReader *on_stderr, - Callback *on_exit) +bool common_job_callbacks(dict_T *vopts, + CallbackReader *on_stdout, + CallbackReader *on_stderr, + Callback *on_exit) { if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), &on_stdout->cb) &&tv_dict_get_callback(vopts, S_LEN("on_stderr"), &on_stderr->cb) @@ -24519,7 +13492,7 @@ static inline bool common_job_callbacks(dict_T *vopts, } -static Channel *find_job(uint64_t id, bool show_error) +Channel *find_job(uint64_t id, bool show_error) { Channel *data = find_channel(id); if (!data || data->streamtype != kChannelStreamProc @@ -24537,7 +13510,7 @@ static Channel *find_job(uint64_t id, bool show_error) } -static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) +void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) { if (check_restricted() || check_secure()) { return; diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 2aa08e2074..918823c311 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -1,16 +1,13 @@ #ifndef NVIM_EVAL_H #define NVIM_EVAL_H -#include "nvim/hashtab.h" // For hashtab_T #include "nvim/buffer_defs.h" -#include "nvim/ex_cmds_defs.h" // For exarg_T -#include "nvim/eval/typval.h" -#include "nvim/profile.h" -#include "nvim/garray.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" #include "nvim/channel.h" -#include "nvim/os/stdpaths_defs.h" +#include "nvim/eval/funcs.h" // For FunPtr +#include "nvim/event/time.h" // For TimeWatcher +#include "nvim/ex_cmds_defs.h" // For exarg_T +#include "nvim/os/fileio.h" // For FileDescriptor +#include "nvim/os/stdpaths_defs.h" // For XDGVarType #define COPYID_INC 2 #define COPYID_MASK (~0x1) @@ -18,12 +15,63 @@ // All user-defined functions are found in this hashtable. extern hashtab_T func_hashtab; +///< 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; + // From user function to hashitem and back. EXTERN ufunc_T dumuf; #define UF2HIKEY(fp) ((fp)->uf_name) #define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) #define HI2UF(hi) HIKEY2UF((hi)->hi_key) +/* + * 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; + /// enum used by var_flavour() typedef enum { VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase @@ -142,6 +190,58 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1]; typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int called_func_argcount); +/// 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 for find_name_end() +#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 */ + +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; + +/// 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.h.generated.h" #endif diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c new file mode 100644 index 0000000000..8232136783 --- /dev/null +++ b/src/nvim/eval/funcs.c @@ -0,0 +1,10920 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include <float.h> +#include <math.h> + +#include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" +#include "nvim/ascii.h" +#include "nvim/assert.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/eval.h" +#include "nvim/eval/decode.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/executor.h" +#include "nvim/eval/funcs.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/file_search.h" +#include "nvim/fileio.h" +#include "nvim/fold.h" +#include "nvim/if_cscope.h" +#include "nvim/indent.h" +#include "nvim/indent_c.h" +#include "nvim/lua/executor.h" +#include "nvim/mark.h" +#include "nvim/math.h" +#include "nvim/memline.h" +#include "nvim/misc1.h" +#include "nvim/mouse.h" +#include "nvim/move.h" +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/server.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/os/dl.h" +#include "nvim/os/input.h" +#include "nvim/os/shell.h" +#include "nvim/path.h" +#include "nvim/popupmnu.h" +#include "nvim/quickfix.h" +#include "nvim/regexp.h" +#include "nvim/screen.h" +#include "nvim/search.h" +#include "nvim/sha256.h" +#include "nvim/sign.h" +#include "nvim/spell.h" +#include "nvim/state.h" +#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/ui.h" +#include "nvim/undo.h" +#include "nvim/version.h" +#include "nvim/vim.h" + + +/// 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; + +KHASH_MAP_INIT_STR(functions, VimLFuncDef) + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/funcs.c.generated.h" + +#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 + + +static char *e_listarg = N_("E686: Argument of %s must be a List"); +static char *e_stringreq = N_("E928: String required"); + +/// 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; + + +/// 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. +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 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)); +} + +// 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; + } +} + +/* + * "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); + } +} + +static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + garray_T ga; + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (!called_vim_beep) { + prepare_assert_error(&ga); + ga_concat(&ga, (const char_u *)"command did not beep: "); + ga_concat(&ga, (const char_u *)cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 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); +} + +// "assert_equalfile(fname-one, fname-two)" function +static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equalfile(argvars); +} + +// "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); +} + +/// "assert_report(msg) +static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + prepare_assert_error(&ga); + ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); + 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); +} + +/// "assert_fails(cmd [, error [, msg]])" function +static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_fails(argvars); +} + +// "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); +} + +/// "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); +} + +/* + * "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); +} + +/* + * Get buffer by number or pattern. + */ +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. +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; +} + +/* + * "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); +} + +/// "call(func, arglist [, dict])" function +static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + 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 + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + selfdict = argvars[2].vval.v_dict; + } + + func_call(func, &argvars[1], partial, selfdict, rettv); +} + +/* + * "changenr()" function + */ +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) +{ + 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; + } + + 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); + 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])) { + return; + } + } + + rettv->vval.v_number = utf_ptr2char( + (const char_u *)tv_get_string(&argvars[0])); +} + +/* + * "cindent(lnum)" function + */ +static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T pos; + linenr_T lnum; + + 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; +} + +/* + * "clearmatches()" function + */ +static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + clear_matches(curwin); +} + +/* + * "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; + } 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; + } + } + } + } + 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; + } + + const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); + if (startcol <= 0) { + return; + } + + 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) +{ + tv_dict_alloc_ret(rettv); + + list_T *what_list = NULL; + + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + what_list = argvars[0].vval.v_list; + } + get_complete_info(what_list, rettv->vval.v_dict); +} + +/* + * "confirm(message, buttons[, default [, type]])" function + */ +static void f_confirm(typval_T *argvars, typval_T *rettv, 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; + } + } + } + } + } + + if (buttons == NULL || *buttons == NUL) { + buttons = _("&Ok"); + } + + if (!error) { + rettv->vval.v_number = do_dialog( + type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false); + } +} + +/* + * "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); + + 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); + } + } + } + } 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[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); + } + } + } + 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++; + } + } + } + } else if (argvars[0].v_type == VAR_DICT) { + int todo; + dict_T *d; + hashitem_T *hi; + + 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)); + } + } + + 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++; + } + } + } + } + } else { + EMSG2(_(e_listdictarg), "count()"); + } + rettv->vval.v_number = n; +} + +/* + * "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) +{ + int num = 0; + const char *dbpath = NULL; + const char *prepend = NULL; + char buf[NUMBUFLEN]; + + 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); + } + } + + rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, + (char_u *)prepend); +} + +/// "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; + } + + Context *ctx = ctx_get(index); + if (ctx == NULL) { + EMSG3(_(e_invargNval), "index", "out of bounds"); + return; + } + + 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); +} + +/// "ctxpop()" function +static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (!ctx_restore(NULL, kCtxAll)) { + EMSG(_("Context stack is empty")); + } +} + +/// "ctxpush([{types}])" function +static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + 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; + } + } + }); + } 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) +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "expected dictionary as first argument"); + return; + } + + 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; + } + + Context *ctx = ctx_get(index); + if (ctx == NULL) { + EMSG3(_(e_invargNval), "index", "out of bounds"); + return; + } + + 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; + } + + api_free_dictionary(dict); + did_emsg = save_did_emsg; +} + +/// "ctxsize()" function +static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = ctx_size(); +} + +/// "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; + + rettv->vval.v_number = -1; + if (argvars[1].v_type == VAR_UNKNOWN) { + pos_T pos; + colnr_T curswant = -1; + + 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(); + } + + curwin->w_set_curswant = set_curswant; + rettv->vval.v_number = 0; +} + +/* + * "deepcopy()" function + */ +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) +{ + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_DICT) { + emsgf(_(e_invarg2), "dict"); + return; + } + + if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { + emsgf(_(e_invarg2), "funcref"); + return; + } + + const char *const key_pattern = tv_get_string_chk(argvars + 1); + if (key_pattern == NULL) { + return; + } + + Callback callback; + if (!callback_from_typval(&callback, &argvars[2])) { + return; + } + + 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"); + } + + callback_free(&callback); +} + +/// "deletebufline()" function +static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + 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); + } + + 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; + } + } + } + check_cursor_col(); + deleted_lines_mark(first, count); + + if (!is_curbuf) { + curbuf = curbuf_save; + curwin = curwin_save; + } +} + +/* + * "did_filetype()" function + */ +static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = did_filetype; +} + +/* + * "diff_filler()" function + */ +static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); +} + +/* + * "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 (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); +} + +/* + * "empty({expr})" function + */ +static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool n = true; + + 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; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + n = true; + break; + } + } + break; + } + case VAR_UNKNOWN: { + internal_error("f_empty(UNKNOWN)"); + break; + } + } + + 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); + } + os_free_fullenv(env); +} + +/* + * "escape({string}, {chars})" function + */ +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) +{ + 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; +} + +/* + * "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); + } + 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) { + return; + } + if (*s == NUL) { + echo_output = true; + } + if (strncmp(s, "silent", 6) == 0) { + msg_silent++; + } + if (strcmp(s, "silent!") == 0) { + emsg_silent = true; + emsg_noredir = true; + } + } else { + msg_silent++; + } + + 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 + } + + 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; + } + + ga_append(capture_ga, NUL); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = capture_ga->ga_data; + + capture_ga = save_capture_ga; +} + +/// "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; + + (void)os_can_exe(arg, &path, true); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)path; +} + +/* + * "exists()" function + */ +static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int n = false; + int len = 0; + + 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; + } + 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; + } + 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); + } + } + } + if (*p != NUL) + n = FALSE; + + xfree(tofree); + } + + rettv->vval.v_number = n; +} + +/* + * "expand()" function + */ +static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + 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; + } + } +} + + +/// "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); + } + 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"); + + 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; +} + + +/* + * "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 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); + } +} + +/// "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); + } +} + +/* + * "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--; + + set_vim_var_nr(VV_MOUSE_WIN, 0); + set_vim_var_nr(VV_MOUSE_WINID, 0); + set_vim_var_nr(VV_MOUSE_LNUM, 0); + set_vim_var_nr(VV_MOUSE_COL, 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; + set_vim_var_nr(VV_MOUSE_WIN, winnr); + set_vim_var_nr(VV_MOUSE_WINID, wp->handle); + set_vim_var_nr(VV_MOUSE_LNUM, lnum); + set_vim_var_nr(VV_MOUSE_COL, 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); +} + +/// "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 = _(get_vim_var_str(VV_REG)); + } + + 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 = _(get_vim_var_str(VV_REG)); + } + + 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); +} + +/// "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); +} + +/// "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; +} + +/// "getwinvar()" function +static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getwinvar(argvars, rettv, 0); +} + +/* + * "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 bool inputsecret_flag = false; + +/* + * "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, inputsecret_flag); +} + +/* + * "inputdialog()" function + */ +static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_user_input(argvars, rettv, TRUE, inputsecret_flag); +} + +/* + * "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 = true; + f_input(argvars, rettv, NULL); + cmdline_star--; + inputsecret_flag = false; +} + +/* + * "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); +} + +/// "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; +} + +// "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 *)"<Nop>"); + } 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); +} + +/* + * "map()" function + */ +static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + filter_map(argvars, rettv, TRUE); +} + +/* + * "maparg()" function + */ +static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_maparg(argvars, rettv, TRUE); +} + +/* + * "mapcheck()" function + */ +static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_maparg(argvars, rettv, FALSE); +} + + +static void find_some_match(typval_T *const argvars, typval_T *const rettv, + const SomeMatchType type) +{ + char_u *str = NULL; + long len = 0; + char_u *expr = NULL; + regmatch_T regmatch; + char_u *save_cpo; + long start = 0; + long nth = 1; + colnr_T startcol = 0; + bool match = false; + list_T *l = NULL; + listitem_T *li = NULL; + long idx = 0; + char_u *tofree = NULL; + + /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + rettv->vval.v_number = -1; + switch (type) { + // matchlist(): return empty list when there are no matches. + case kSomeMatchList: { + tv_list_alloc_ret(rettv, kListLenMayKnow); + break; + } + // matchstrpos(): return ["", -1, -1, -1] + case kSomeMatchStrPos: { + tv_list_alloc_ret(rettv, 4); + tv_list_append_string(rettv->vval.v_list, "", 0); + tv_list_append_number(rettv->vval.v_list, -1); + tv_list_append_number(rettv->vval.v_list, -1); + tv_list_append_number(rettv->vval.v_list, -1); + break; + } + case kSomeMatchStr: { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + // Do nothing: zero is default. + break; + } + } + + if (argvars[0].v_type == VAR_LIST) { + if ((l = argvars[0].vval.v_list) == NULL) { + goto theend; + } + li = tv_list_first(l); + } else { + expr = str = (char_u *)tv_get_string(&argvars[0]); + len = (long)STRLEN(str); + } + + char patbuf[NUMBUFLEN]; + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) { + goto theend; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + + start = tv_get_number_chk(&argvars[2], &error); + if (error) { + goto theend; + } + if (l != NULL) { + idx = tv_list_uidx(l, start); + if (idx == -1) { + goto theend; + } + li = tv_list_find(l, idx); + } else { + if (start < 0) + start = 0; + if (start > len) + goto theend; + /* When "count" argument is there ignore matches before "start", + * otherwise skip part of the string. Differs when pattern is "^" + * or "\<". */ + if (argvars[3].v_type != VAR_UNKNOWN) + startcol = start; + else { + str += start; + len -= start; + } + } + + if (argvars[3].v_type != VAR_UNKNOWN) { + nth = tv_get_number_chk(&argvars[3], &error); + } + if (error) { + goto theend; + } + } + + regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + + for (;; ) { + if (l != NULL) { + if (li == NULL) { + match = false; + break; + } + xfree(tofree); + tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), + NULL); + if (str == NULL) { + break; + } + } + + match = vim_regexec_nl(®match, str, (colnr_T)startcol); + + if (match && --nth <= 0) + break; + if (l == NULL && !match) + break; + + /* Advance to just after the match. */ + if (l != NULL) { + li = TV_LIST_ITEM_NEXT(l, li); + idx++; + } else { + startcol = (colnr_T)(regmatch.startp[0] + + (*mb_ptr2len)(regmatch.startp[0]) - str); + if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) { + match = false; + break; + } + } + } + + if (match) { + switch (type) { + case kSomeMatchStrPos: { + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); + if (l != NULL) { + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + } + break; + } + case kSomeMatchList: { + // Return list with matched string and submatches. + for (int i = 0; i < NSUBEXP; i++) { + if (regmatch.endp[i] == NULL) { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } else { + tv_list_append_string(rettv->vval.v_list, + (const char *)regmatch.startp[i], + (regmatch.endp[i] - regmatch.startp[i])); + } + } + break; + } + case kSomeMatchStr: { + // Return matched string. + if (l != NULL) { + tv_copy(TV_LIST_ITEM_TV(li), rettv); + } else { + rettv->vval.v_string = (char_u *)xmemdupz( + (const char *)regmatch.startp[0], + (size_t)(regmatch.endp[0] - regmatch.startp[0])); + } + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + if (l != NULL) { + rettv->vval.v_number = idx; + } else { + if (type == kSomeMatch) { + rettv->vval.v_number = + (varnumber_T)(regmatch.startp[0] - str); + } else { + rettv->vval.v_number = + (varnumber_T)(regmatch.endp[0] - str); + } + rettv->vval.v_number += (varnumber_T)(str - expr); + } + break; + } + } + } + vim_regfree(regmatch.regprog); + } + +theend: + if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) { + // matchstrpos() without a list: drop the second item + list_T *const ret_l = rettv->vval.v_list; + tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); + } + + xfree(tofree); + p_cpo = save_cpo; +} + +/* + * "match()" function + */ +static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatch); +} + +/* + * "matchadd()" function + */ +static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char grpbuf[NUMBUFLEN]; + char patbuf[NUMBUFLEN]; + const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + int prio = 10; + int id = -1; + bool error = false; + const char *conceal_char = NULL; + win_T *win = curwin; + + rettv->vval.v_number = -1; + + if (grp == NULL || pat == NULL) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error) { + return; + } + if (id >= 1 && id <= 3) { + EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id); + return; + } + + rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); +} + +static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } + + if (argvars[1].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "matchaddpos()"); + return; + } + + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } + + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; + win_T *win = curwin; + + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error == true) { + return; + } + + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); + return; + } + + rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); +} + +/* + * "matcharg()" function + */ +static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int id = tv_get_number(&argvars[0]); + + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); + + if (id >= 1 && id <= 3) { + matchitem_T *const m = (matchitem_T *)get_match(curwin, id); + + if (m != NULL) { + tv_list_append_string(rettv->vval.v_list, + (const char *)syn_id2name(m->hlg_id), -1); + tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); + } else { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } + } +} + +/* + * "matchdelete()" function + */ +static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = match_delete(curwin, + (int)tv_get_number(&argvars[0]), true); +} + +/* + * "matchend()" function + */ +static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchEnd); +} + +/* + * "matchlist()" function + */ +static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchList); +} + +/* + * "matchstr()" function + */ +static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchStr); +} + +/// "matchstrpos()" function +static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + find_some_match(argvars, rettv, kSomeMatchStrPos); +} + +/// Get maximal/minimal number value in a list or dictionary +/// +/// @param[in] tv List or dictionary to work with. If it contains something +/// that is not an integer number (or cannot be coerced to +/// it) error is given. +/// @param[out] rettv Location where result will be saved. Only assigns +/// vval.v_number, type is not touched. Returns zero for +/// empty lists/dictionaries. +/// @param[in] domax Determines whether maximal or minimal value is desired. +static void max_min(const typval_T *const tv, typval_T *const rettv, + const bool domax) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + + rettv->vval.v_number = 0; + varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); + if (tv->v_type == VAR_LIST) { + if (tv_list_len(tv->vval.v_list) == 0) { + return; + } + TV_LIST_ITER_CONST(tv->vval.v_list, li, { + const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); + } else if (tv->v_type == VAR_DICT) { + if (tv_dict_len(tv->vval.v_dict) == 0) { + return; + } + TV_DICT_ITER(tv->vval.v_dict, di, { + const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); + } else { + EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); + return; + } + rettv->vval.v_number = n; +} + +/* + * "max()" function + */ +static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + max_min(argvars, rettv, TRUE); +} + +/* + * "min()" function + */ +static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + max_min(argvars, rettv, FALSE); +} + +/* + * "mkdir()" function + */ +static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int prot = 0755; // -V536 + + rettv->vval.v_number = FAIL; + if (check_restricted() || check_secure()) + return; + + char buf[NUMBUFLEN]; + const char *const dir = tv_get_string_buf(&argvars[0], buf); + if (*dir == NUL) { + return; + } + + if (*path_tail((char_u *)dir) == NUL) { + // Remove trailing slashes. + *path_tail_with_sep((char_u *)dir) = NUL; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_UNKNOWN) { + prot = tv_get_number_chk(&argvars[2], NULL); + if (prot == -1) { + return; + } + } + if (strcmp(tv_get_string(&argvars[1]), "p") == 0) { + char *failed_dir; + int ret = os_mkdir_recurse(dir, prot, &failed_dir); + if (ret != 0) { + EMSG3(_(e_mkdir), failed_dir, os_strerror(ret)); + xfree(failed_dir); + rettv->vval.v_number = FAIL; + return; + } else { + rettv->vval.v_number = OK; + return; + } + } + } + rettv->vval.v_number = vim_mkdir_emsg(dir, prot); +} + +/// "mode()" function +static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char *mode = get_mode(); + + // Clear out the minor mode when the argument is not a non-zero number or + // non-empty string. + if (!non_zero_arg(&argvars[0])) { + mode[1] = NUL; + } + + rettv->vval.v_string = (char_u *)mode; + rettv->v_type = VAR_STRING; +} + +/// "msgpackdump()" function +static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "msgpackdump()"); + return; + } + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + list_T *const list = argvars[0].vval.v_list; + msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); + const char *const msg = _("msgpackdump() argument, index %i"); + // Assume that translation will not take more then 4 times more space + char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; + int idx = 0; + TV_LIST_ITER(list, li, { + vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); + idx++; + if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { + break; + } + }); + msgpack_packer_free(lpacker); +} + +/// "msgpackparse" function +static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "msgpackparse()"); + return; + } + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + const list_T *const list = argvars[0].vval.v_list; + if (tv_list_len(list) == 0) { + return; + } + if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "List item is not a string"); + return; + } + ListReaderState lrstate = encode_init_lrstate(list); + msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE); + if (unpacker == NULL) { + EMSG(_(e_outofmem)); + return; + } + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + do { + if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { + EMSG(_(e_outofmem)); + goto f_msgpackparse_exit; + } + size_t read_bytes; + const int rlret = encode_read_from_list( + &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes); + if (rlret == FAIL) { + EMSG2(_(e_invarg2), "List item is not a string"); + goto f_msgpackparse_exit; + } + msgpack_unpacker_buffer_consumed(unpacker, read_bytes); + if (read_bytes == 0) { + break; + } + while (unpacker->off < unpacker->used) { + const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, + &unpacked); + if (result == MSGPACK_UNPACK_PARSE_ERROR) { + EMSG2(_(e_invarg2), "Failed to parse msgpack string"); + goto f_msgpackparse_exit; + } + if (result == MSGPACK_UNPACK_NOMEM_ERROR) { + EMSG(_(e_outofmem)); + goto f_msgpackparse_exit; + } + if (result == MSGPACK_UNPACK_SUCCESS) { + typval_T tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { + EMSG2(_(e_invarg2), "Failed to convert msgpack string"); + goto f_msgpackparse_exit; + } + tv_list_append_owned_tv(ret_list, tv); + } + if (result == MSGPACK_UNPACK_CONTINUE) { + if (rlret == OK) { + EMSG2(_(e_invarg2), "Incomplete msgpack string"); + } + break; + } + } + if (rlret == OK) { + break; + } + } while (true); + +f_msgpackparse_exit: + msgpack_unpacked_destroy(&unpacked); + msgpack_unpacker_free(unpacker); + return; +} + +/* + * "nextnonblank()" function + */ +static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum; + + for (lnum = tv_get_lnum(argvars);; lnum++) { + if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) { + lnum = 0; + break; + } + if (*skipwhite(ml_get(lnum)) != NUL) { + break; + } + } + rettv->vval.v_number = lnum; +} + +/* + * "nr2char()" function + */ +static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[1].v_type != VAR_UNKNOWN) { + if (!tv_check_num(&argvars[1])) { + return; + } + } + + bool error = false; + const varnumber_T num = tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + if (num < 0) { + EMSG(_("E5070: Character number must not be less than zero")); + return; + } + if (num > INT_MAX) { + emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"), + INT_MAX); + return; + } + + char buf[MB_MAXBYTES]; + const int len = utf_char2bytes((int)num, (char_u *)buf); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xmemdupz(buf, (size_t)len); +} + +/* + * "or(expr, expr)" function + */ +static void f_or(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); +} + +/* + * "pathshorten()" function + */ +static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *const s = tv_get_string_chk(&argvars[0]); + if (!s) { + return; + } + rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s)); +} + +/* + * "pow()" function + */ +static void f_pow(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 = pow(fx, fy); + } else { + rettv->vval.v_float = 0.0; + } +} + +/* + * "prevnonblank()" function + */ +static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum = tv_get_lnum(argvars); + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { + lnum = 0; + } else { + while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) { + lnum--; + } + } + rettv->vval.v_number = lnum; +} + +/* + * "printf()" function + */ +static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + { + int len; + int saved_did_emsg = did_emsg; + + // Get the required length, allocate the buffer and do it for real. + did_emsg = false; + char buf[NUMBUFLEN]; + const char *fmt = tv_get_string_buf(&argvars[0], buf); + len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); + if (!did_emsg) { + char *s = xmalloc(len + 1); + rettv->vval.v_string = (char_u *)s; + (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1); + } + did_emsg |= saved_did_emsg; + } +} + +// "pum_getpos()" function +static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + pum_set_event_info(rettv->vval.v_dict); +} + +/* + * "pumvisible()" function + */ +static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (pum_visible()) + rettv->vval.v_number = 1; +} + +/* + * "pyeval()" function + */ +static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + script_host_eval("python", argvars, rettv); +} + +/* + * "py3eval()" function + */ +static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + script_host_eval("python3", argvars, rettv); +} + +// "pyxeval()" function +static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + init_pyxversion(); + if (p_pyx == 2) { + f_pyeval(argvars, rettv, NULL); + } else { + f_py3eval(argvars, rettv, NULL); + } +} + +/* + * "range()" function + */ +static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + varnumber_T start; + varnumber_T end; + varnumber_T stride = 1; + varnumber_T i; + bool error = false; + + start = tv_get_number_chk(&argvars[0], &error); + if (argvars[1].v_type == VAR_UNKNOWN) { + end = start - 1; + start = 0; + } else { + end = tv_get_number_chk(&argvars[1], &error); + if (argvars[2].v_type != VAR_UNKNOWN) { + stride = tv_get_number_chk(&argvars[2], &error); + } + } + + if (error) { + return; // Type error; errmsg already given. + } + if (stride == 0) { + EMSG(_("E726: Stride is zero")); + } else if (stride > 0 ? end + 1 < start : end - 1 > start) { + EMSG(_("E727: Start past end")); + } else { + tv_list_alloc_ret(rettv, (end - start) / stride); + for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { + tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); + } + } +} + +/* + * "readfile()" function + */ +static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool binary = false; + FILE *fd; + char_u buf[(IOSIZE/256)*256]; /* rounded to avoid odd + 1 */ + int io_size = sizeof(buf); + int readlen; /* size of last fread() */ + char_u *prev = NULL; /* previously read bytes, if any */ + long prevlen = 0; /* length of data in prev */ + long prevsize = 0; /* size of prev buffer */ + long maxline = MAXLNUM; + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { + binary = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + maxline = tv_get_number(&argvars[2]); + } + } + + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); + + // Always open the file in binary mode, library functions have a mind of + // their own about CR-LF conversion. + const char *const fname = tv_get_string(&argvars[0]); + if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { + EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname); + return; + } + + while (maxline < 0 || tv_list_len(l) < maxline) { + readlen = (int)fread(buf, 1, io_size, fd); + + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char_u *p; // Position in buf. + char_u *start; // Start of current line. + for (p = buf, start = buf; + p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); + p++) { + if (*p == '\n' || readlen <= 0) { + char_u *s = NULL; + size_t len = p - start; + + /* Finished a line. Remove CRs before NL. */ + if (readlen > 0 && !binary) { + while (len > 0 && start[len - 1] == '\r') + --len; + /* removal may cross back to the "prev" string */ + if (len == 0) + while (prevlen > 0 && prev[prevlen - 1] == '\r') + --prevlen; + } + if (prevlen == 0) { + assert(len < INT_MAX); + s = vim_strnsave(start, (int)len); + } else { + /* Change "prev" buffer to be the right size. This way + * the bytes are only copied once, and very long lines are + * allocated only once. */ + s = xrealloc(prev, prevlen + len + 1); + memcpy(s + prevlen, start, len); + s[prevlen + len] = NUL; + prev = NULL; /* the list will own the string */ + prevlen = prevsize = 0; + } + + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); + + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); + break; + } + if (readlen <= 0) { + break; + } + } else if (*p == NUL) { + *p = '\n'; + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if (*p == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. + char_u back1 = p >= buf + 1 ? p[-1] + : prevlen >= 1 ? prev[prevlen - 1] : NUL; + char_u back2 = p >= buf + 2 ? p[-2] + : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1] + : prevlen >= 2 ? prev[prevlen - 2] : NUL; + + if (back2 == 0xef && back1 == 0xbb) { + char_u *dest = p - 2; + + /* Usually a BOM is at the beginning of a file, and so at + * the beginning of a line; then we can just step over it. + */ + if (start == dest) + start = p + 1; + else { + /* have to shuffle buf to close gap */ + int adjust_prevlen = 0; + + if (dest < buf) { // -V782 + adjust_prevlen = (int)(buf - dest); // -V782 + // adjust_prevlen must be 1 or 2. + dest = buf; + } + if (readlen > p - buf + 1) + memmove(dest, p + 1, readlen - (p - buf) - 1); + readlen -= 3 - adjust_prevlen; + prevlen -= adjust_prevlen; + p = dest - 1; + } + } + } + } /* for */ + + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { + break; + } + if (start < p) { + /* There's part of a line in buf, store it in "prev". */ + if (p - start + prevlen >= prevsize) { + /* A common use case is ordinary text files and "prev" gets a + * fragment of a line, so the first allocation is made + * small, to avoid repeatedly 'allocing' large and + * 'reallocing' small. */ + if (prevsize == 0) + prevsize = (long)(p - start); + else { + long grow50pc = (prevsize * 3) / 2; + long growmin = (long)((p - start) * 2 + prevlen); + prevsize = grow50pc > growmin ? grow50pc : growmin; + } + prev = xrealloc(prev, prevsize); + } + /* Add the line part to end of "prev". */ + memmove(prev + prevlen, start, p - start); + prevlen += (long)(p - start); + } + } /* while */ + + xfree(prev); + fclose(fd); +} + +// "reg_executing()" function +static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + return_register(reg_executing, rettv); +} + +// "reg_recording()" function +static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + return_register(reg_recording, rettv); +} + +/// list2proftime - convert a List to proftime_T +/// +/// @param arg The input list, must be of type VAR_LIST and have +/// exactly 2 items +/// @param[out] tm The proftime_T representation of `arg` +/// @return OK In case of success, FAIL in case of error +static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL +{ + if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { + return FAIL; + } + + bool error = false; + varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error); + varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error); + if (error) { + return FAIL; + } + + // in f_reltime() we split up the 64-bit proftime_T into two 32-bit + // values, now we combine them again. + union { + struct { int32_t low, high; } split; + proftime_T prof; + } u = { .split.high = n1, .split.low = n2 }; + + *tm = u.prof; + + return OK; +} + +/// f_reltime - return an item that represents a time value +/// +/// @param[out] rettv Without an argument it returns the current time. With +/// one argument it returns the time passed since the argument. +/// With two arguments it returns the time passed between +/// the two arguments. +static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + proftime_T res; + proftime_T start; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // no arguments: get current time. + res = profile_start(); + } else if (argvars[1].v_type == VAR_UNKNOWN) { + if (list2proftime(&argvars[0], &res) == FAIL) { + return; + } + res = profile_end(res); + } else { + // two arguments: compute the difference. + if (list2proftime(&argvars[0], &start) == FAIL + || list2proftime(&argvars[1], &res) == FAIL) { + return; + } + res = profile_sub(res, start); + } + + // we have to store the 64-bit proftime_T inside of a list of int's + // (varnumber_T is defined as int). For all our supported platforms, int's + // are at least 32-bits wide. So we'll use two 32-bit values to store it. + union { + struct { int32_t low, high; } split; + proftime_T prof; + } u = { .prof = res }; + + // statically assert that the union type conv will provide the correct + // results, if varnumber_T or proftime_T change, the union cast will need + // to be revised. + STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), + "type punning will produce incorrect results on this platform"); + + tv_list_alloc_ret(rettv, 2); + tv_list_append_number(rettv->vval.v_list, u.split.high); + tv_list_append_number(rettv->vval.v_list, u.split.low); +} + +/// "reltimestr()" function +static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + proftime_T tm; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (list2proftime(&argvars[0], &tm) == OK) { + rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm)); + } +} + +/* + * "remove()" function + */ +static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + list_T *l; + listitem_T *item, *item2; + listitem_T *li; + long idx; + long end; + dict_T *d; + dictitem_T *di; + const char *const arg_errmsg = N_("remove() argument"); + + if (argvars[0].v_type == VAR_DICT) { + if (argvars[2].v_type != VAR_UNKNOWN) { + EMSG2(_(e_toomanyarg), "remove()"); + } else if ((d = argvars[0].vval.v_dict) != NULL + && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { + const char *key = tv_get_string_chk(&argvars[1]); + if (key != NULL) { + di = tv_dict_find(d, key, -1); + if (di == NULL) { + EMSG2(_(e_dictkey), key); + } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) + && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { + *rettv = di->di_tv; + di->di_tv = TV_INITIAL_VALUE; + tv_dict_item_remove(d, di); + if (tv_dict_is_watched(d)) { + tv_dict_watcher_notify(d, key, NULL, rettv); + } + } + } + } + } else if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listdictarg), "remove()"); + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { + bool error = false; + + idx = tv_get_number_chk(&argvars[1], &error); + if (error) { + // Type error: do nothing, errmsg already given. + } else if ((item = tv_list_find(l, idx)) == NULL) { + EMSGN(_(e_listidx), idx); + } else { + if (argvars[2].v_type == VAR_UNKNOWN) { + // Remove one item, return its value. + tv_list_drop_items(l, item, item); + *rettv = *TV_LIST_ITEM_TV(item); + xfree(item); + } else { + // Remove range of items, return list with values. + end = tv_get_number_chk(&argvars[2], &error); + if (error) { + // Type error: do nothing. + } else if ((item2 = tv_list_find(l, end)) == NULL) { + EMSGN(_(e_listidx), end); + } else { + int cnt = 0; + + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { + break; + } + } + if (li == NULL) { // Didn't find "item2" after "item". + EMSG(_(e_invrange)); + } else { + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); + } + } + } + } + } +} + +/* + * "rename({from}, {to})" function + */ +static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + rettv->vval.v_number = -1; + } else { + char buf[NUMBUFLEN]; + rettv->vval.v_number = vim_rename( + (const char_u *)tv_get_string(&argvars[0]), + (const char_u *)tv_get_string_buf(&argvars[1], buf)); + } +} + +/* + * "repeat()" function + */ +static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + varnumber_T n = tv_get_number(&argvars[1]); + if (argvars[0].v_type == VAR_LIST) { + tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); + while (n-- > 0) { + tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); + } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (n <= 0) { + return; + } + + const char *const p = tv_get_string(&argvars[0]); + + const size_t slen = strlen(p); + if (slen == 0) { + return; + } + const size_t len = slen * n; + // Detect overflow. + if (len / n != slen) { + return; + } + + char *const r = xmallocz(len); + for (varnumber_T i = 0; i < n; i++) { + memmove(r + i * slen, p, slen); + } + + rettv->vval.v_string = (char_u *)r; + } +} + +/* + * "resolve()" function + */ +static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *fname = tv_get_string(&argvars[0]); +#ifdef WIN32 + char *const v = os_resolve_shortcut(fname); + rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); +#else +# ifdef HAVE_READLINK + { + bool is_relative_to_current = false; + bool has_trailing_pathsep = false; + int limit = 100; + + char *p = xstrdup(fname); + + if (p[0] == '.' && (vim_ispathsep(p[1]) + || (p[1] == '.' && (vim_ispathsep(p[2]))))) { + is_relative_to_current = true; + } + + ptrdiff_t len = (ptrdiff_t)strlen(p); + if (len > 0 && after_pathsep(p, p + len)) { + has_trailing_pathsep = true; + p[len - 1] = NUL; // The trailing slash breaks readlink(). + } + + char *q = (char *)path_next_component(p); + char *remain = NULL; + if (*q != NUL) { + // Separate the first path component in "p", and keep the + // remainder (beginning with the path separator). + remain = xstrdup(q - 1); + q[-1] = NUL; + } + + char *const buf = xmallocz(MAXPATHL); + + char *cpy; + for (;; ) { + for (;; ) { + len = readlink(p, buf, MAXPATHL); + if (len <= 0) { + break; + } + buf[len] = NUL; + + if (limit-- == 0) { + xfree(p); + xfree(remain); + EMSG(_("E655: Too many symbolic links (cycle?)")); + rettv->vval.v_string = NULL; + xfree(buf); + return; + } + + // Ensure that the result will have a trailing path separator + // if the argument has one. */ + if (remain == NULL && has_trailing_pathsep) { + add_pathsep(buf); + } + + // Separate the first path component in the link value and + // concatenate the remainders. */ + q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); + if (*q != NUL) { + cpy = remain; + remain = (remain + ? (char *)concat_str((char_u *)q - 1, (char_u *)remain) + : xstrdup(q - 1)); + xfree(cpy); + q[-1] = NUL; + } + + q = (char *)path_tail((char_u *)p); + if (q > p && *q == NUL) { + // Ignore trailing path separator. + q[-1] = NUL; + q = (char *)path_tail((char_u *)p); + } + if (q > p && !path_is_absolute((const char_u *)buf)) { + // Symlink is relative to directory of argument. Replace the + // symlink with the resolved name in the same directory. + const size_t p_len = strlen(p); + const size_t buf_len = strlen(buf); + p = xrealloc(p, p_len + buf_len + 1); + memcpy(path_tail((char_u *)p), buf, buf_len + 1); + } else { + xfree(p); + p = xstrdup(buf); + } + } + + if (remain == NULL) { + break; + } + + // Append the first path component of "remain" to "p". + q = (char *)path_next_component(remain + 1); + len = q - remain - (*q != NUL); + const size_t p_len = strlen(p); + cpy = xmallocz(p_len + len); + memcpy(cpy, p, p_len + 1); + xstrlcat(cpy + p_len, remain, len + 1); + xfree(p); + p = cpy; + + // Shorten "remain". + if (*q != NUL) { + STRMOVE(remain, q - 1); + } else { + XFREE_CLEAR(remain); + } + } + + // If the result is a relative path name, make it explicitly relative to + // the current directory if and only if the argument had this form. + if (!vim_ispathsep(*p)) { + if (is_relative_to_current + && *p != NUL + && !(p[0] == '.' + && (p[1] == NUL + || vim_ispathsep(p[1]) + || (p[1] == '.' + && (p[2] == NUL + || vim_ispathsep(p[2])))))) { + // Prepend "./". + cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p); + xfree(p); + p = cpy; + } else if (!is_relative_to_current) { + // Strip leading "./". + q = p; + while (q[0] == '.' && vim_ispathsep(q[1])) { + q += 2; + } + if (q > p) { + STRMOVE(p, p + 2); + } + } + } + + // Ensure that the result will have no trailing path separator + // if the argument had none. But keep "/" or "//". + if (!has_trailing_pathsep) { + q = p + strlen(p); + if (after_pathsep(p, q)) { + *path_tail_with_sep((char_u *)p) = NUL; + } + } + + rettv->vval.v_string = (char_u *)p; + xfree(buf); + } +# else + rettv->vval.v_string = (char_u *)xstrdup(p); +# endif +#endif + + simplify_filename(rettv->vval.v_string); +} + +/* + * "reverse({list})" function + */ +static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + list_T *l; + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "reverse()"); + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("reverse() argument"), TV_TRANSLATE)) { + tv_list_reverse(l); + tv_list_set_ret(rettv, l); + } +} + +#define SP_NOMOVE 0x01 ///< don't move cursor +#define SP_REPEAT 0x02 ///< repeat to find outer pair +#define SP_RETCOUNT 0x04 ///< return matchcount +#define SP_SETPCMARK 0x08 ///< set previous context mark +#define SP_START 0x10 ///< accept match at start position +#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern +#define SP_END 0x40 ///< leave cursor at end of match +#define SP_COLUMN 0x80 ///< start at cursor column + +/* + * Get flags for a search function. + * Possibly sets "p_ws". + * Returns BACKWARD, FORWARD or zero (for an error). + */ +static int get_search_arg(typval_T *varp, int *flagsp) +{ + int dir = FORWARD; + int mask; + + if (varp->v_type != VAR_UNKNOWN) { + char nbuf[NUMBUFLEN]; + const char *flags = tv_get_string_buf_chk(varp, nbuf); + if (flags == NULL) { + return 0; // Type error; errmsg already given. + } + while (*flags != NUL) { + switch (*flags) { + case 'b': dir = BACKWARD; break; + case 'w': p_ws = true; break; + case 'W': p_ws = false; break; + default: { + mask = 0; + if (flagsp != NULL) { + switch (*flags) { + case 'c': mask = SP_START; break; + case 'e': mask = SP_END; break; + case 'm': mask = SP_RETCOUNT; break; + case 'n': mask = SP_NOMOVE; break; + case 'p': mask = SP_SUBPAT; break; + case 'r': mask = SP_REPEAT; break; + case 's': mask = SP_SETPCMARK; break; + case 'z': mask = SP_COLUMN; break; + } + } + if (mask == 0) { + emsgf(_(e_invarg2), flags); + dir = 0; + } else { + *flagsp |= mask; + } + } + } + if (dir == 0) { + break; + } + flags++; + } + } + return dir; +} + +// Shared by search() and searchpos() functions. +static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) +{ + int flags; + pos_T pos; + pos_T save_cursor; + bool save_p_ws = p_ws; + int dir; + int retval = 0; /* default: FAIL */ + long lnum_stop = 0; + proftime_T tm; + long time_limit = 0; + int options = SEARCH_KEEP; + int subpatnum; + searchit_arg_T sia; + + const char *const pat = tv_get_string(&argvars[0]); + dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. + if (dir == 0) { + goto theend; + } + flags = *flagsp; + if (flags & SP_START) { + options |= SEARCH_START; + } + if (flags & SP_END) { + options |= SEARCH_END; + } + if (flags & SP_COLUMN) { + options |= SEARCH_COL; + } + + /* Optional arguments: line number to stop searching and timeout. */ + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + lnum_stop = tv_get_number_chk(&argvars[2], NULL); + if (lnum_stop < 0) { + goto theend; + } + if (argvars[3].v_type != VAR_UNKNOWN) { + time_limit = tv_get_number_chk(&argvars[3], NULL); + if (time_limit < 0) { + goto theend; + } + } + } + + /* Set the time limit, if there is one. */ + tm = profile_setlimit(time_limit); + + /* + * This function does not accept SP_REPEAT and SP_RETCOUNT flags. + * Check to make sure only those flags are set. + * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both + * flags cannot be set. Check for that condition also. + */ + if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0) + || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[1])); + goto theend; + } + + pos = save_cursor = curwin->w_cursor; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = (linenr_T)lnum_stop; + sia.sa_tm = &tm; + subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, + options, RE_SEARCH, &sia); + if (subpatnum != FAIL) { + if (flags & SP_SUBPAT) + retval = subpatnum; + else + retval = pos.lnum; + if (flags & SP_SETPCMARK) + setpcmark(); + curwin->w_cursor = pos; + if (match_pos != NULL) { + /* Store the match cursor position */ + match_pos->lnum = pos.lnum; + match_pos->col = pos.col + 1; + } + /* "/$" will put the cursor after the end of the line, may need to + * correct that here */ + check_cursor(); + } + + /* If 'n' flag is used: restore cursor position. */ + if (flags & SP_NOMOVE) + curwin->w_cursor = save_cursor; + else + curwin->w_set_curswant = TRUE; +theend: + p_ws = save_p_ws; + + return retval; +} + +// "rpcnotify()" function +static void f_rpcnotify(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[0].vval.v_number < 0) { + EMSG2(_(e_invarg2), "Channel id must be a positive integer"); + return; + } + + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "Event type must be a string"); + return; + } + + Array args = ARRAY_DICT_INIT; + + for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { + ADD(args, vim_to_object(tv)); + } + + if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, + tv_get_string(&argvars[1]), args)) { + EMSG2(_(e_invarg2), "Channel doesn't exist"); + return; + } + + rettv->vval.v_number = 1; +} + +// "rpcrequest()" function +static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + const int l_provider_call_nesting = provider_call_nesting; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) { + EMSG2(_(e_invarg2), "Channel id must be a positive integer"); + return; + } + + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "Method name must be a string"); + return; + } + + Array args = ARRAY_DICT_INIT; + + for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { + ADD(args, vim_to_object(tv)); + } + + sctx_T save_current_sctx; + uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; + linenr_T save_sourcing_lnum; + int save_autocmd_bufnr; + void *save_funccalp; + + if (l_provider_call_nesting) { + // If this is called from a provider function, restore the scope + // information of the caller. + save_current_sctx = current_sctx; + save_sourcing_name = sourcing_name; + save_sourcing_lnum = sourcing_lnum; + save_autocmd_fname = autocmd_fname; + save_autocmd_match = autocmd_match; + save_autocmd_bufnr = autocmd_bufnr; + save_funccalp = save_funccal(); + + current_sctx = provider_caller_scope.script_ctx; + sourcing_name = provider_caller_scope.sourcing_name; + sourcing_lnum = provider_caller_scope.sourcing_lnum; + autocmd_fname = provider_caller_scope.autocmd_fname; + autocmd_match = provider_caller_scope.autocmd_match; + autocmd_bufnr = provider_caller_scope.autocmd_bufnr; + restore_funccal(provider_caller_scope.funccalp); + } + + + Error err = ERROR_INIT; + + uint64_t chan_id = (uint64_t)argvars[0].vval.v_number; + const char *method = tv_get_string(&argvars[1]); + + Object result = rpc_send_call(chan_id, method, args, &err); + + if (l_provider_call_nesting) { + current_sctx = save_current_sctx; + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + autocmd_fname = save_autocmd_fname; + autocmd_match = save_autocmd_match; + autocmd_bufnr = save_autocmd_bufnr; + restore_funccal(save_funccalp); + } + + if (ERROR_SET(&err)) { + const char *name = NULL; + Channel *chan = find_channel(chan_id); + if (chan) { + name = rpc_client_name(chan); + } + msg_ext_set_kind("rpc_error"); + if (name) { + emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s", + method, chan_id, name, err.msg); + } else { + emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s", + method, chan_id, err.msg); + } + + goto end; + } + + if (!object_to_vim(result, rettv, &err)) { + EMSG2(_("Error converting the call result: %s"), err.msg); + } + +end: + api_free_object(result); + api_clear_error(&err); +} + +// "rpcstart()" function (DEPRECATED) +static void f_rpcstart(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_STRING + || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) { + // Wrong argument types + EMSG(_(e_invarg)); + return; + } + + list_T *args = NULL; + int argsl = 0; + if (argvars[1].v_type == VAR_LIST) { + args = argvars[1].vval.v_list; + argsl = tv_list_len(args); + // Assert that all list items are strings + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { + emsgf(_("E5010: List item %d of the second argument is not a string"), + i); + return; + } + i++; + }); + } + + if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { + EMSG(_(e_api_spawn_failed)); + return; + } + + // Allocate extra memory for the argument vector and the NULL pointer + int argvl = argsl + 2; + char **argv = xmalloc(sizeof(char_u *) * argvl); + + // Copy program name + argv[0] = xstrdup((char *)argvars[0].vval.v_string); + + int i = 1; + // Copy arguments to the vector + if (argsl > 0) { + TV_LIST_ITER_CONST(args, arg, { + argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); + }); + } + + // The last item of argv must be NULL + argv[i] = NULL; + + Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, + CALLBACK_READER_INIT, CALLBACK_NONE, + false, true, false, NULL, 0, 0, NULL, NULL, + &rettv->vval.v_number); + if (chan) { + channel_create_event(chan, NULL); + } +} + +// "rpcstop()" function +static void f_rpcstop(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) { + // Wrong argument types + EMSG(_(e_invarg)); + return; + } + + // if called with a job, stop it, else closes the channel + uint64_t id = argvars[0].vval.v_number; + if (find_job(id, false)) { + f_jobstop(argvars, rettv, NULL); + } else { + const char *error; + rettv->vval.v_number = channel_close(argvars[0].vval.v_number, + kChannelPartRpc, &error); + if (!rettv->vval.v_number) { + EMSG(error); + } + } +} + +/* + * "screenattr()" function + */ +static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int c; + + int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; + int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + if (row < 0 || row >= default_grid.Rows + || col < 0 || col >= default_grid.Columns) { + c = -1; + } else { + ScreenGrid *grid = &default_grid; + screenchar_adjust_grid(&grid, &row, &col); + c = grid->attrs[grid->line_offset[row] + col]; + } + rettv->vval.v_number = c; +} + +/* + * "screenchar()" function + */ +static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int c; + + int row = tv_get_number_chk(&argvars[0], NULL) - 1; + int col = tv_get_number_chk(&argvars[1], NULL) - 1; + if (row < 0 || row >= default_grid.Rows + || col < 0 || col >= default_grid.Columns) { + c = -1; + } else { + ScreenGrid *grid = &default_grid; + screenchar_adjust_grid(&grid, &row, &col); + c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]); + } + rettv->vval.v_number = c; +} + +/* + * "screencol()" function + * + * First column is 1 to be consistent with virtcol(). + */ +static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = ui_current_col() + 1; +} + +/// "screenpos({winid}, {lnum}, {col})" function +static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T pos; + int row = 0; + int scol = 0, ccol = 0, ecol = 0; + + tv_dict_alloc_ret(rettv); + dict_T *dict = rettv->vval.v_dict; + + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + return; + } + + pos.lnum = tv_get_number(&argvars[1]); + pos.col = tv_get_number(&argvars[2]) - 1; + pos.coladd = 0; + textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); + + tv_dict_add_nr(dict, S_LEN("row"), row); + tv_dict_add_nr(dict, S_LEN("col"), scol); + tv_dict_add_nr(dict, S_LEN("curscol"), ccol); + tv_dict_add_nr(dict, S_LEN("endcol"), ecol); +} + +/* + * "screenrow()" function + */ +static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = ui_current_row() + 1; +} + +/* + * "search()" function + */ +static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int flags = 0; + + rettv->vval.v_number = search_cmn(argvars, NULL, &flags); +} + +/* + * "searchdecl()" function + */ +static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int locally = 1; + int thisblock = 0; + bool error = false; + + rettv->vval.v_number = 1; /* default: FAIL */ + + const char *const name = tv_get_string_chk(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN) { + locally = tv_get_number_chk(&argvars[1], &error) == 0; + if (!error && argvars[2].v_type != VAR_UNKNOWN) { + thisblock = tv_get_number_chk(&argvars[2], &error) != 0; + } + } + if (!error && name != NULL) { + rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally, + thisblock, SEARCH_KEEP) == FAIL; + } +} + +/* + * Used by searchpair() and searchpairpos() + */ +static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) +{ + bool save_p_ws = p_ws; + int dir; + int flags = 0; + int retval = 0; // default: FAIL + long lnum_stop = 0; + long time_limit = 0; + + // Get the three pattern arguments: start, middle, end. Will result in an + // error if not a valid argument. + char nbuf1[NUMBUFLEN]; + char nbuf2[NUMBUFLEN]; + const char *spat = tv_get_string_chk(&argvars[0]); + const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1); + const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); + if (spat == NULL || mpat == NULL || epat == NULL) { + goto theend; // Type error. + } + + // Handle the optional fourth argument: flags. + dir = get_search_arg(&argvars[3], &flags); // may set p_ws. + if (dir == 0) { + goto theend; + } + + // Don't accept SP_END or SP_SUBPAT. + // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set. + if ((flags & (SP_END | SP_SUBPAT)) != 0 + || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[3])); + goto theend; + } + + // Using 'r' implies 'W', otherwise it doesn't work. + if (flags & SP_REPEAT) { + p_ws = false; + } + + // Optional fifth argument: skip expression. + const typval_T *skip; + if (argvars[3].v_type == VAR_UNKNOWN + || argvars[4].v_type == VAR_UNKNOWN) { + skip = NULL; + } else { + skip = &argvars[4]; + if (skip->v_type != VAR_FUNC + && skip->v_type != VAR_PARTIAL + && skip->v_type != VAR_STRING) { + emsgf(_(e_invarg2), tv_get_string(&argvars[4])); + goto theend; // Type error. + } + if (argvars[5].v_type != VAR_UNKNOWN) { + lnum_stop = tv_get_number_chk(&argvars[5], NULL); + if (lnum_stop < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[5])); + goto theend; + } + if (argvars[6].v_type != VAR_UNKNOWN) { + time_limit = tv_get_number_chk(&argvars[6], NULL); + if (time_limit < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[6])); + goto theend; + } + } + } + } + + retval = do_searchpair( + (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, + flags, match_pos, lnum_stop, time_limit); + +theend: + p_ws = save_p_ws; + + return retval; +} + +/* + * "searchpair()" function + */ +static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = searchpair_cmn(argvars, NULL); +} + +/* + * "searchpairpos()" function + */ +static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T match_pos; + int lnum = 0; + int col = 0; + + tv_list_alloc_ret(rettv, 2); + + if (searchpair_cmn(argvars, &match_pos) > 0) { + lnum = match_pos.lnum; + col = match_pos.col; + } + + tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); +} + +/* + * Search for a start/middle/end thing. + * Used by searchpair(), see its documentation for the details. + * Returns 0 or -1 for no match, + */ +long +do_searchpair( + char_u *spat, // start pattern + char_u *mpat, // middle pattern + char_u *epat, // end pattern + int dir, // BACKWARD or FORWARD + const typval_T *skip, // skip expression + int flags, // SP_SETPCMARK and other SP_ values + pos_T *match_pos, + linenr_T lnum_stop, // stop at this line if not zero + long time_limit // stop after this many msec +) +{ + char_u *save_cpo; + char_u *pat, *pat2 = NULL, *pat3 = NULL; + long retval = 0; + pos_T pos; + pos_T firstpos; + pos_T foundpos; + pos_T save_cursor; + pos_T save_pos; + int n; + int nest = 1; + bool use_skip = false; + int options = SEARCH_KEEP; + proftime_T tm; + size_t pat2_len; + size_t pat3_len; + + /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + save_cpo = p_cpo; + p_cpo = empty_option; + + /* Set the time limit, if there is one. */ + tm = profile_setlimit(time_limit); + + // Make two search patterns: start/end (pat2, for in nested pairs) and + // start/middle/end (pat3, for the top pair). + pat2_len = STRLEN(spat) + STRLEN(epat) + 17; + pat2 = xmalloc(pat2_len); + pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25; + pat3 = xmalloc(pat3_len); + snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); + if (*mpat == NUL) { + STRCPY(pat3, pat2); + } else { + snprintf((char *)pat3, pat3_len, + "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); + } + if (flags & SP_START) { + options |= SEARCH_START; + } + + if (skip != NULL) { + // Empty string means to not use the skip expression. + if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { + use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; + } + } + + save_cursor = curwin->w_cursor; + pos = curwin->w_cursor; + clearpos(&firstpos); + clearpos(&foundpos); + pat = pat3; + for (;; ) { + searchit_arg_T sia; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = lnum_stop; + sia.sa_tm = &tm; + + n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + options, RE_SEARCH, &sia); + if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { + // didn't find it or found the first match again: FAIL + break; + } + + if (firstpos.lnum == 0) + firstpos = pos; + if (equalpos(pos, foundpos)) { + /* Found the same position again. Can happen with a pattern that + * has "\zs" at the end and searching backwards. Advance one + * character and try again. */ + if (dir == BACKWARD) + decl(&pos); + else + incl(&pos); + } + foundpos = pos; + + /* clear the start flag to avoid getting stuck here */ + options &= ~SEARCH_START; + + // If the skip pattern matches, ignore this match. + if (use_skip) { + save_pos = curwin->w_cursor; + curwin->w_cursor = pos; + bool err = false; + const bool r = eval_expr_to_bool(skip, &err); + curwin->w_cursor = save_pos; + if (err) { + /* Evaluating {skip} caused an error, break here. */ + curwin->w_cursor = save_cursor; + retval = -1; + break; + } + if (r) + continue; + } + + if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) { + /* Found end when searching backwards or start when searching + * forward: nested pair. */ + ++nest; + pat = pat2; /* nested, don't search for middle */ + } else { + /* Found end when searching forward or start when searching + * backward: end of (nested) pair; or found middle in outer pair. */ + if (--nest == 1) + pat = pat3; /* outer level, search for middle */ + } + + if (nest == 0) { + /* Found the match: return matchcount or line number. */ + if (flags & SP_RETCOUNT) + ++retval; + else + retval = pos.lnum; + if (flags & SP_SETPCMARK) + setpcmark(); + curwin->w_cursor = pos; + if (!(flags & SP_REPEAT)) + break; + nest = 1; /* search for next unmatched */ + } + } + + if (match_pos != NULL) { + /* Store the match cursor position */ + match_pos->lnum = curwin->w_cursor.lnum; + match_pos->col = curwin->w_cursor.col + 1; + } + + /* If 'n' flag is used or search failed: restore cursor position. */ + if ((flags & SP_NOMOVE) || retval == 0) + curwin->w_cursor = save_cursor; + + xfree(pat2); + xfree(pat3); + if (p_cpo == empty_option) + p_cpo = save_cpo; + else + /* Darn, evaluating the {skip} expression changed the value. */ + free_string_option(save_cpo); + + return retval; +} + +/* + * "searchpos()" function + */ +static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T match_pos; + int flags = 0; + + const int n = search_cmn(argvars, &match_pos, &flags); + + tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + + const int lnum = (n > 0 ? match_pos.lnum : 0); + const int col = (n > 0 ? match_pos.col : 0); + + tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); + if (flags & SP_SUBPAT) { + tv_list_append_number(rettv->vval.v_list, (varnumber_T)n); + } +} + +/// "serverlist()" function +static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + size_t n; + char **addrs = server_address_list(&n); + + // Copy addrs into a linked list. + list_T *const l = tv_list_alloc_ret(rettv, n); + for (size_t i = 0; i < n; i++) { + tv_list_append_allocated_string(l, addrs[i]); + } + xfree(addrs); +} + +/// "serverstart()" function +static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; // Address of the new server + + if (check_restricted() || check_secure()) { + return; + } + + char *address; + // If the user supplied an address, use it, otherwise use a temp. + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[0].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } else { + address = xstrdup(tv_get_string(argvars)); + } + } else { + address = server_address_new(); + } + + int result = server_start(address); + xfree(address); + + if (result != 0) { + EMSG2("Failed to start server: %s", + result > 0 ? "Unknown system error" : uv_strerror(result)); + return; + } + + // Since it's possible server_start adjusted the given {address} (e.g., + // "localhost:" will now have a port), return the final value to the user. + size_t n; + char **addrs = server_address_list(&n); + rettv->vval.v_string = (char_u *)addrs[n - 1]; + + n--; + for (size_t i = 0; i < n; i++) { + xfree(addrs[i]); + } + xfree(addrs); +} + +/// "serverstop()" function +static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + if (argvars[0].vval.v_string) { + bool rv = server_stop((char *)argvars[0].vval.v_string); + rettv->vval.v_number = (rv ? 1 : 0); + } +} + +/// "setbufline()" function +static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum; + buf_T *buf; + + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + } else { + lnum = tv_get_lnum_buf(&argvars[1], buf); + set_buffer_lines(buf, lnum, false, &argvars[2], rettv); + } +} + +/* + * "setbufvar()" function + */ +static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() + || check_secure() + || !tv_check_str_or_nr(&argvars[0])) { + return; + } + const char *varname = tv_get_string_chk(&argvars[1]); + buf_T *const buf = tv_get_buf(&argvars[0], false); + typval_T *varp = &argvars[2]; + + if (buf != NULL && varname != NULL) { + if (*varname == '&') { + long numval; + bool error = false; + aco_save_T aco; + + // set curbuf to be our buf, temporarily + aucmd_prepbuf(&aco, buf); + + varname++; + numval = tv_get_number_chk(varp, &error); + char nbuf[NUMBUFLEN]; + const char *const strval = tv_get_string_buf_chk(varp, nbuf); + if (!error && strval != NULL) { + set_option_value(varname, numval, strval, OPT_LOCAL); + } + + // reset notion of buffer + aucmd_restbuf(&aco); + } else { + buf_T *save_curbuf = curbuf; + + const size_t varname_len = STRLEN(varname); + char *const bufvarname = xmalloc(varname_len + 3); + curbuf = buf; + memcpy(bufvarname, "b:", 2); + memcpy(bufvarname + 2, varname, varname_len + 1); + set_var(bufvarname, varname_len + 2, varp, true); + xfree(bufvarname); + curbuf = save_curbuf; + } + } +} + +static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *d; + dictitem_T *di; + + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + + if ((d = argvars[0].vval.v_dict) != NULL) { + char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false); + if (csearch != NULL) { + if (enc_utf8) { + int pcc[MAX_MCO]; + int c = utfc_ptr2char(csearch, pcc); + set_last_csearch(c, csearch, utfc_ptr2len(csearch)); + } + else + set_last_csearch(PTR2CHAR(csearch), + csearch, utfc_ptr2len(csearch)); + } + + di = tv_dict_find(d, S_LEN("forward")); + if (di != NULL) { + set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); + } + + di = tv_dict_find(d, S_LEN("until")); + if (di != NULL) { + set_csearch_until(!!tv_get_number(&di->di_tv)); + } + } +} + +/* + * "setcmdpos()" function + */ +static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int pos = (int)tv_get_number(&argvars[0]) - 1; + + if (pos >= 0) { + rettv->vval.v_number = set_cmdline_pos(pos); + } +} + +/// "setenv()" function +static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char namebuf[NUMBUFLEN]; + char valbuf[NUMBUFLEN]; + const char *name = tv_get_string_buf(&argvars[0], namebuf); + + if (argvars[1].v_type == VAR_SPECIAL + && argvars[1].vval.v_special == kSpecialVarNull) { + os_unsetenv(name); + } else { + os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); + } +} + +/// "setfperm({fname}, {mode})" function +static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = 0; + + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + + char modebuf[NUMBUFLEN]; + const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf); + if (mode_str == NULL) { + return; + } + if (strlen(mode_str) != 9) { + EMSG2(_(e_invarg2), mode_str); + return; + } + + int mask = 1; + int mode = 0; + for (int i = 8; i >= 0; i--) { + if (mode_str[i] != '-') { + mode |= mask; + } + mask = mask << 1; + } + rettv->vval.v_number = os_setperm(fname, mode) == OK; +} + +/* + * "setline()" function + */ +static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum = tv_get_lnum(&argvars[0]); + set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); +} + +/// Create quickfix/location list from VimL values +/// +/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid +/// list_arg, action_arg and what_arg arguments in which case errors out, +/// including VAR_UNKNOWN parameters. +/// +/// @param[in,out] wp Window to create location list for. May be NULL in +/// which case quickfix list will be created. +/// @param[in] list_arg Quickfix list contents. +/// @param[in] action_arg Action to perform: append to an existing list, +/// replace its content or create a new one. +/// @param[in] title_arg New list title. Defaults to caller function name. +/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. +static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(2, 3) +{ + static char *e_invact = N_("E927: Invalid action: '%s'"); + const char *title = NULL; + int action = ' '; + static int recursive = 0; + rettv->vval.v_number = -1; + dict_T *d = NULL; + + typval_T *list_arg = &args[0]; + if (list_arg->v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } else if (recursive != 0) { + EMSG(_(e_au_recursive)); + return; + } + + typval_T *action_arg = &args[1]; + if (action_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (action_arg->v_type != VAR_STRING) { + EMSG(_(e_stringreq)); + return; + } + const char *const act = tv_get_string_chk(action_arg); + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { + action = *act; + } else { + EMSG2(_(e_invact), act); + return; + } + + typval_T *title_arg = &args[2]; + if (title_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (title_arg->v_type == VAR_STRING) { + title = tv_get_string_chk(title_arg); + if (!title) { + // Type error. Error already printed by tv_get_string_chk(). + return; + } + } else if (title_arg->v_type == VAR_DICT) { + d = title_arg->vval.v_dict; + } else { + EMSG(_(e_dictreq)); + return; + } + +skip_args: + if (!title) { + title = (wp ? ":setloclist()" : ":setqflist()"); + } + + recursive++; + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { + rettv->vval.v_number = 0; + } + recursive--; +} + +/* + * "setloclist()" function + */ +static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *win; + + rettv->vval.v_number = -1; + + win = find_win_by_nr_or_id(&argvars[0]); + if (win != NULL) { + set_qf_ll_list(win, &argvars[1], rettv); + } +} + +/* + * "setmatches()" function + */ +static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *d; + list_T *s = NULL; + + 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; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int li_idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), li_idx); + return; + } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + emsgf(_("E474: List item %d is missing one of the required keys"), + li_idx); + return; + } + li_idx++; + }); + + clear_matches(curwin); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { + int i = 0; + + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(9); + } + + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[30]; // use 30 to avoid compiler warning + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; + } + + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; + } + } + } + + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(curwin, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; + } + } else { + if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; + } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; + } +} + +/* + * "setpos()" function + */ +static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T pos; + int fnum; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + const char *const name = tv_get_string_chk(argvars); + if (name != NULL) { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { + if (pos.col != MAXCOL && --pos.col < 0) { + pos.col = 0; + } + if (name[0] == '.' && name[1] == NUL) { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = false; + } + check_cursor(); + rettv->vval.v_number = 0; + } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { + // set mark + if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { + rettv->vval.v_number = 0; + } + } else { + EMSG(_(e_invarg)); + } + } + } +} + +/* + * "setqflist()" function + */ +static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_qf_ll_list(NULL, argvars, rettv); +} + +/* + * "setreg()" function + */ +static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int regname; + bool append = false; + MotionType yank_type; + long block_len; + + block_len = -1; + yank_type = kMTUnknown; + + rettv->vval.v_number = 1; // FAIL is default. + + const char *const strregname = tv_get_string_chk(argvars); + if (strregname == NULL) { + return; // Type error; errmsg already given. + } + regname = (uint8_t)(*strregname); + if (regname == 0 || regname == '@') { + regname = '"'; + } + + bool set_unnamed = false; + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *stropt = tv_get_string_chk(&argvars[2]); + if (stropt == NULL) { + return; // Type error. + } + for (; *stropt != NUL; stropt++) { + switch (*stropt) { + case 'a': case 'A': { // append + append = true; + break; + } + case 'v': case 'c': { // character-wise selection + yank_type = kMTCharWise; + break; + } + case 'V': case 'l': { // line-wise selection + yank_type = kMTLineWise; + break; + } + case 'b': case Ctrl_V: { // block-wise selection + yank_type = kMTBlockWise; + if (ascii_isdigit(stropt[1])) { + stropt++; + block_len = getdigits_long((char_u **)&stropt, true, 0) - 1; + stropt--; + } + break; + } + case 'u': case '"': { // unnamed register + set_unnamed = true; + break; + } + } + } + } + + if (argvars[1].v_type == VAR_LIST) { + list_T *ll = argvars[1].vval.v_list; + // If the list is NULL handle like an empty list. + const int len = tv_list_len(ll); + + // First half: use for pointers to result lines; second half: use for + // pointers to allocated copies. + char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2)); + const char **curval = (const char **)lstval; + char **allocval = lstval + len + 2; + char **curallocval = allocval; + + TV_LIST_ITER_CONST(ll, li, { + char buf[NUMBUFLEN]; + *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); + if (*curval == NULL) { + goto free_lstval; + } + if (*curval == buf) { + // Need to make a copy, + // next tv_get_string_buf_chk() will overwrite the string. + *curallocval = xstrdup(*curval); + *curval = *curallocval; + curallocval++; + } + curval++; + }); + *curval++ = NULL; + + write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, + block_len); + +free_lstval: + while (curallocval > allocval) { + xfree(*--curallocval); + } + xfree(lstval); + } else { + const char *strval = tv_get_string_chk(&argvars[1]); + if (strval == NULL) { + return; + } + write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval), + append, yank_type, block_len); + } + rettv->vval.v_number = 0; + + if (set_unnamed) { + // Discard the result. We already handle the error case. + if (op_reg_set_previous(regname)) { } + } +} + +/* + * "settabvar()" function + */ +static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + const char *const varname = tv_get_string_chk(&argvars[1]); + typval_T *const varp = &argvars[2]; + + if (varname != NULL && tp != NULL) { + tabpage_T *const save_curtab = curtab; + goto_tabpage_tp(tp, false, false); + + const size_t varname_len = strlen(varname); + char *const tabvarname = xmalloc(varname_len + 3); + memcpy(tabvarname, "t:", 2); + memcpy(tabvarname + 2, varname, varname_len + 1); + set_var(tabvarname, varname_len + 2, varp, true); + xfree(tabvarname); + + // Restore current tabpage. + if (valid_tabpage(save_curtab)) { + goto_tabpage_tp(save_curtab, false, false); + } + } +} + +/* + * "settabwinvar()" function + */ +static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + setwinvar(argvars, rettv, 1); +} + +// "settagstack()" function +static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + static char *e_invact2 = N_("E962: Invalid action: '%s'"); + win_T *wp; + dict_T *d; + int action = 'r'; + + rettv->vval.v_number = -1; + + // first argument: window number or id + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + return; + } + + // second argument: dict with items to set in the tag stack + if (argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + d = argvars[1].vval.v_dict; + if (d == NULL) { + return; + } + + // third argument: action - 'a' for append and 'r' for replace. + // default is to replace the stack. + if (argvars[2].v_type == VAR_UNKNOWN) { + action = 'r'; + } else if (argvars[2].v_type == VAR_STRING) { + const char *actstr; + actstr = tv_get_string_chk(&argvars[2]); + if (actstr == NULL) { + return; + } + if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't') + && actstr[1] == NUL) { + action = *actstr; + } else { + EMSG2(_(e_invact2), actstr); + return; + } + } else { + EMSG(_(e_stringreq)); + return; + } + + if (set_tagstack(wp, d, action) == OK) { + rettv->vval.v_number = 0; + } else { + EMSG(_(e_listreq)); + } +} + +/* + * "setwinvar()" function + */ +static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + setwinvar(argvars, rettv, 0); +} + +/// f_sha256 - sha256({string}) function +static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *p = tv_get_string(&argvars[0]); + const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0); + + // make a copy of the hash (sha256_bytes returns a static buffer) + rettv->vval.v_string = (char_u *)xstrdup(hash); + rettv->v_type = VAR_STRING; +} + +/* + * "shellescape({string})" function + */ +static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const bool do_special = non_zero_arg(&argvars[1]); + + rettv->vval.v_string = vim_strsave_shellescape( + (const char_u *)tv_get_string(&argvars[0]), do_special, do_special); + rettv->v_type = VAR_STRING; +} + +/* + * shiftwidth() function + */ +static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = get_sw_value(curbuf); +} + +/// "sign_define()" function +static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + dict_T *dict; + char *icon = NULL; + char *linehl = NULL; + char *text = NULL; + char *texthl = NULL; + char *numhl = NULL; + + rettv->vval.v_number = -1; + + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + + // sign attributes + dict = argvars[1].vval.v_dict; + if (tv_dict_find(dict, "icon", -1) != NULL) { + icon = tv_dict_get_string(dict, "icon", true); + } + if (tv_dict_find(dict, "linehl", -1) != NULL) { + linehl = tv_dict_get_string(dict, "linehl", true); + } + if (tv_dict_find(dict, "text", -1) != NULL) { + text = tv_dict_get_string(dict, "text", true); + } + if (tv_dict_find(dict, "texthl", -1) != NULL) { + texthl = tv_dict_get_string(dict, "texthl", true); + } + if (tv_dict_find(dict, "numhl", -1) != NULL) { + numhl = tv_dict_get_string(dict, "numhl", true); + } + } + + if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, + (char_u *)text, (char_u *)texthl, (char_u *)numhl) + == OK) { + rettv->vval.v_number = 0; + } + + xfree(icon); + xfree(linehl); + xfree(text); + xfree(texthl); +} + +/// "sign_getdefined()" function +static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name = NULL; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + name = tv_get_string(&argvars[0]); + } + + sign_getlist((const char_u *)name, rettv->vval.v_list); +} + +/// "sign_getplaced()" function +static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf = NULL; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int sign_id = 0; + const char *group = NULL; + bool notanum = false; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // get signs placed in the specified buffer + buf = get_buf_arg(&argvars[0]); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT + || ((dict = argvars[1].vval.v_dict) == NULL)) { + EMSG(_(e_dictreq)); + return; + } + if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { + // get signs placed at this line + lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + (void)lnum; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, "id", -1)) != NULL) { + // get sign placed with this identifier + sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + } + if ((di = tv_dict_find(dict, "group", -1)) != NULL) { + group = tv_get_string_chk(&di->di_tv); + if (group == NULL) { + return; + } + if (*group == '\0') { // empty string means global group + group = NULL; + } + } + } + } + + sign_get_placed(buf, lnum, sign_id, (const char_u *)group, + rettv->vval.v_list); +} + +/// "sign_jump()" function +static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char *sign_group = NULL; + buf_T *buf; + bool notanum = false; + + rettv->vval.v_number = -1; + + // Sign identifer + sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + if (notanum) { + return; + } + if (sign_id <= 0) { + EMSG(_(e_invarg)); + return; + } + + // Sign group + const char * sign_group_chk = tv_get_string_chk(&argvars[1]); + if (sign_group_chk == NULL) { + return; + } + if (sign_group_chk[0] == '\0') { + sign_group = NULL; // global sign group + } else { + sign_group = xstrdup(sign_group_chk); + } + + // Buffer to place the sign + buf = get_buf_arg(&argvars[2]); + if (buf == NULL) { + goto cleanup; + } + + rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); + +cleanup: + xfree(sign_group); +} + +/// "sign_place()" function +static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char_u *group = NULL; + const char *sign_name; + buf_T *buf; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int prio = SIGN_DEF_PRIO; + bool notanum = false; + + rettv->vval.v_number = -1; + + // Sign identifer + sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + if (notanum) { + return; + } + if (sign_id < 0) { + EMSG(_(e_invarg)); + return; + } + + // Sign group + const char *group_chk = tv_get_string_chk(&argvars[1]); + if (group_chk == NULL) { + return; + } + if (group_chk[0] == '\0') { + group = NULL; // global sign group + } else { + group = vim_strsave((const char_u *)group_chk); + } + + // Sign name + sign_name = tv_get_string_chk(&argvars[2]); + if (sign_name == NULL) { + goto cleanup; + } + + // Buffer to place the sign + buf = get_buf_arg(&argvars[3]); + if (buf == NULL) { + goto cleanup; + } + + if (argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_DICT + || ((dict = argvars[4].vval.v_dict) == NULL)) { + EMSG(_(e_dictreq)); + goto cleanup; + } + + // Line number where the sign is to be placed + if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { + lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + goto cleanup; + } + (void)lnum; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { + // Sign priority + prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + goto cleanup; + } + } + } + + if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio) + == OK) { + rettv->vval.v_number = sign_id; + } + +cleanup: + xfree(group); +} + +/// "sign_undefine()" function +static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // Free all the signs + free_signs(); + rettv->vval.v_number = 0; + } else { + // Free only the specified sign + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (sign_undefine_by_name((const char_u *)name) == OK) { + rettv->vval.v_number = 0; + } + } +} + +/// "sign_unplace()" function +static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict; + dictitem_T *di; + int sign_id = 0; + buf_T *buf = NULL; + char_u *group = NULL; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + + const char *group_chk = tv_get_string(&argvars[0]); + if (group_chk[0] == '\0') { + group = NULL; // global sign group + } else { + group = vim_strsave((const char_u *)group_chk); + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + goto cleanup; + } + dict = argvars[1].vval.v_dict; + + if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { + buf = get_buf_arg(&di->di_tv); + if (buf == NULL) { + goto cleanup; + } + } + if (tv_dict_find(dict, "id", -1) != NULL) { + sign_id = tv_dict_get_number(dict, "id"); + } + } + + if (buf == NULL) { + // Delete the sign in all the buffers + FOR_ALL_BUFFERS(cbuf) { + if (sign_unplace(sign_id, group, cbuf, 0) == OK) { + rettv->vval.v_number = 0; + } + } + } else { + if (sign_unplace(sign_id, group, buf, 0) == OK) { + rettv->vval.v_number = 0; + } + } + +cleanup: + xfree(group); +} + +/* + * "simplify()" function + */ +static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + rettv->vval.v_string = (char_u *)xstrdup(p); + simplify_filename(rettv->vval.v_string); // Simplify in place. + rettv->v_type = VAR_STRING; +} + +/// "sockconnect()" function +static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) { + // Wrong argument types + EMSG2(_(e_invarg2), "expected dictionary"); + return; + } + + const char *mode = tv_get_string(&argvars[0]); + const char *address = tv_get_string(&argvars[1]); + + bool tcp; + if (strcmp(mode, "tcp") == 0) { + tcp = true; + } else if (strcmp(mode, "pipe") == 0) { + tcp = false; + } else { + EMSG2(_(e_invarg2), "invalid mode"); + return; + } + + bool rpc = false; + CallbackReader on_data = CALLBACK_READER_INIT; + if (argvars[2].v_type == VAR_DICT) { + dict_T *opts = argvars[2].vval.v_dict; + rpc = tv_dict_get_number(opts, "rpc") != 0; + + if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) { + return; + } + on_data.buffered = tv_dict_get_number(opts, "data_buffered"); + if (on_data.buffered && on_data.cb.type == kCallbackNone) { + on_data.self = opts; + } + } + + const char *error = NULL; + uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error); + + if (error) { + EMSG2(_("connection failed: %s"), error); + } + + rettv->vval.v_number = (varnumber_T)id; + rettv->v_type = VAR_NUMBER; +} + +/// struct storing information about current sort +typedef struct { + int item_compare_ic; + bool item_compare_numeric; + bool item_compare_numbers; + bool item_compare_float; + const char *item_compare_func; + partial_T *item_compare_partial; + dict_T *item_compare_selfdict; + bool item_compare_func_err; +} sortinfo_T; +static sortinfo_T *sortinfo = NULL; + +#define ITEM_COMPARE_FAIL 999 + +/* + * Compare functions for f_sort() and f_uniq() below. + */ +static int item_compare(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; + + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); + + int res; + + if (sortinfo->item_compare_numbers) { + const varnumber_T v1 = tv_get_number(tv1); + const varnumber_T v2 = tv_get_number(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + if (sortinfo->item_compare_float) { + const float_T v1 = tv_get_float(tv1); + const float_T v2 = tv_get_float(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + char *tofree1 = NULL; + char *tofree2 = NULL; + char *p1; + char *p2; + + // encode_tv2string() puts quotes around a string and allocates memory. Don't + // do that for string variables. Use a single quote when comparing with + // a non-string to do what the docs promise. + if (tv1->v_type == VAR_STRING) { + if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p1 = "'"; + } else { + p1 = (char *)tv1->vval.v_string; + } + } else { + tofree1 = p1 = encode_tv2string(tv1, NULL); + } + if (tv2->v_type == VAR_STRING) { + if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p2 = "'"; + } else { + p2 = (char *)tv2->vval.v_string; + } + } else { + tofree2 = p2 = encode_tv2string(tv2, NULL); + } + if (p1 == NULL) { + p1 = ""; + } + if (p2 == NULL) { + p2 = ""; + } + if (!sortinfo->item_compare_numeric) { + if (sortinfo->item_compare_ic) { + res = STRICMP(p1, p2); + } else { + res = STRCMP(p1, p2); + } + } else { + double n1, n2; + n1 = strtod(p1, &p1); + n2 = strtod(p2, &p2); + res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; + } + + xfree(tofree1); + xfree(tofree2); + +item_compare_end: + // When the result would be zero, compare the item indexes. Makes the + // sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + return res; +} + +static int item_compare_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, true); +} + +static int item_compare_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, false); +} + +static int item_compare2(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *si1, *si2; + int res; + typval_T rettv; + typval_T argv[3]; + int dummy; + const char *func_name; + partial_T *partial = sortinfo->item_compare_partial; + + // shortcut after failure in previous call; compare all items equal + if (sortinfo->item_compare_func_err) { + return 0; + } + + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; + + if (partial == NULL) { + func_name = sortinfo->item_compare_func; + } else { + func_name = (const char *)partial_name(partial); + } + + // Copy the values. This is needed to be able to set v_lock to VAR_FIXED + // in the copy without changing the original list items. + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); + + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + res = call_func((const char_u *)func_name, + (int)STRLEN(func_name), + &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, + partial, sortinfo->item_compare_selfdict); + tv_clear(&argv[0]); + tv_clear(&argv[1]); + + if (res == FAIL) { + res = ITEM_COMPARE_FAIL; + } else { + res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); + } + if (sortinfo->item_compare_func_err) { + res = ITEM_COMPARE_FAIL; // return value has wrong type + } + tv_clear(&rettv); + + // When the result would be zero, compare the pointers themselves. Makes + // the sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + + return res; +} + +static int item_compare2_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, true); +} + +static int item_compare2_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, false); +} + +/* + * "sort({list})" function + */ +static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) +{ + ListSortItem *ptrs; + long len; + long i; + + // Pointer to current info struct used in compare function. Save and restore + // the current one for nested calls. + sortinfo_T info; + sortinfo_T *old_sortinfo = sortinfo; + sortinfo = &info; + + const char *const arg_errmsg = (sort + ? N_("sort() argument") + : N_("uniq() argument")); + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); + } else { + list_T *const l = argvars[0].vval.v_list; + if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + goto theend; + } + tv_list_set_ret(rettv, l); + + len = tv_list_len(l); + if (len <= 1) { + goto theend; // short list sorts pretty quickly + } + + info.item_compare_ic = false; + info.item_compare_numeric = false; + info.item_compare_numbers = false; + info.item_compare_float = false; + info.item_compare_func = NULL; + info.item_compare_partial = NULL; + info.item_compare_selfdict = NULL; + + if (argvars[1].v_type != VAR_UNKNOWN) { + /* optional second argument: {func} */ + if (argvars[1].v_type == VAR_FUNC) { + info.item_compare_func = (const char *)argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + info.item_compare_partial = argvars[1].vval.v_partial; + } else { + bool error = false; + + i = tv_get_number_chk(&argvars[1], &error); + if (error) { + goto theend; // type error; errmsg already given + } + if (i == 1) { + info.item_compare_ic = true; + } else if (argvars[1].v_type != VAR_NUMBER) { + info.item_compare_func = tv_get_string(&argvars[1]); + } else if (i != 0) { + EMSG(_(e_invarg)); + goto theend; + } + if (info.item_compare_func != NULL) { + if (*info.item_compare_func == NUL) { + // empty string means default sort + info.item_compare_func = NULL; + } else if (strcmp(info.item_compare_func, "n") == 0) { + info.item_compare_func = NULL; + info.item_compare_numeric = true; + } else if (strcmp(info.item_compare_func, "N") == 0) { + info.item_compare_func = NULL; + info.item_compare_numbers = true; + } else if (strcmp(info.item_compare_func, "f") == 0) { + info.item_compare_func = NULL; + info.item_compare_float = true; + } else if (strcmp(info.item_compare_func, "i") == 0) { + info.item_compare_func = NULL; + info.item_compare_ic = true; + } + } + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + // optional third argument: {dict} + if (argvars[2].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + goto theend; + } + info.item_compare_selfdict = argvars[2].vval.v_dict; + } + } + + // Make an array with each entry pointing to an item in the List. + ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); + + if (sort) { + info.item_compare_func_err = false; + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { + EMSG(_("E702: Sort compare function failed")); + } + } else { + ListSorter item_compare_func_ptr; + + // f_uniq(): ptrs will be a stack of items to remove. + info.item_compare_func_err = false; + if (info.item_compare_func != NULL + || info.item_compare_partial != NULL) { + item_compare_func_ptr = item_compare2_keeping_zero; + } else { + item_compare_func_ptr = item_compare_keeping_zero; + } + + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL;) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { + if (info.item_compare_func_err) { // -V547 + EMSG(_("E882: Uniq compare function failed")); + break; + } + li = tv_list_item_remove(l, li); + } else { + idx++; + li = TV_LIST_ITEM_NEXT(l, li); + } + } + } + + xfree(ptrs); + } + +theend: + sortinfo = old_sortinfo; +} + +/// "sort"({list})" function +static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, true); +} + +/// "stdioopen()" function +static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_invarg)); + return; + } + + + bool rpc = false; + CallbackReader on_stdin = CALLBACK_READER_INIT; + dict_T *opts = argvars[0].vval.v_dict; + rpc = tv_dict_get_number(opts, "rpc") != 0; + + if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { + return; + } + on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); + if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { + on_stdin.self = opts; + } + + const char *error; + uint64_t id = channel_from_stdio(rpc, on_stdin, &error); + if (!id) { + EMSG2(e_stdiochan2, error); + } + + + rettv->vval.v_number = (varnumber_T)id; + rettv->v_type = VAR_NUMBER; +} + +/// "uniq({list})" function +static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, false); +} + +// "reltimefloat()" function +static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + proftime_T tm; + + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = 0; + if (list2proftime(&argvars[0], &tm) == OK) { + rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0; + } +} + +/* + * "soundfold({word})" function + */ +static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *const s = tv_get_string(&argvars[0]); + rettv->vval.v_string = (char_u *)eval_soundfold(s); +} + +/* + * "spellbadword()" function + */ +static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *word = ""; + hlf_T attr = HLF_COUNT; + size_t len = 0; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // Find the start and length of the badly spelled word. + len = spell_move_to(curwin, FORWARD, true, true, &attr); + if (len != 0) { + word = (char *)get_cursor_pos_ptr(); + curwin->w_set_curswant = true; + } + } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { + const char *str = tv_get_string_chk(&argvars[0]); + int capcol = -1; + + if (str != NULL) { + // Check the argument for spelling. + while (*str != NUL) { + len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + if (attr != HLF_COUNT) { + word = str; + break; + } + str += len; + capcol -= len; + len = 0; + } + } + } + + assert(len <= INT_MAX); + tv_list_alloc_ret(rettv, 2); + tv_list_append_string(rettv->vval.v_list, word, len); + tv_list_append_string(rettv->vval.v_list, + (attr == HLF_SPB ? "bad" + : attr == HLF_SPR ? "rare" + : attr == HLF_SPL ? "local" + : attr == HLF_SPC ? "caps" + : NULL), -1); +} + +/* + * "spellsuggest()" function + */ +static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool typeerr = false; + int maxcount; + garray_T ga = GA_EMPTY_INIT_VALUE; + bool need_capital = false; + + if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { + const char *const str = tv_get_string(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN) { + maxcount = tv_get_number_chk(&argvars[1], &typeerr); + if (maxcount <= 0) { + goto f_spellsuggest_return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + need_capital = tv_get_number_chk(&argvars[2], &typeerr); + if (typeerr) { + goto f_spellsuggest_return; + } + } + } else { + maxcount = 25; + } + + spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + } + +f_spellsuggest_return: + tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + char *const p = ((char **)ga.ga_data)[i]; + tv_list_append_allocated_string(rettv->vval.v_list, p); + } + ga_clear(&ga); +} + +static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *save_cpo; + int match; + colnr_T col = 0; + bool keepempty = false; + bool typeerr = false; + + /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + const char *str = tv_get_string(&argvars[0]); + const char *pat = NULL; + char patbuf[NUMBUFLEN]; + if (argvars[1].v_type != VAR_UNKNOWN) { + pat = tv_get_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) { + typeerr = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr); + } + } + if (pat == NULL || *pat == NUL) { + pat = "[\\x01- ]\\+"; + } + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (typeerr) { + return; + } + + regmatch_T regmatch = { + .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING), + .startp = { NULL }, + .endp = { NULL }, + .rm_ic = false, + }; + if (regmatch.regprog != NULL) { + while (*str != NUL || keepempty) { + if (*str == NUL) { + match = false; // Empty item at the end. + } else { + match = vim_regexec_nl(®match, (char_u *)str, col); + } + const char *end; + if (match) { + end = (const char *)regmatch.startp[0]; + } else { + end = str + strlen(str); + } + if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 + && *str != NUL + && match + && end < (const char *)regmatch.endp[0])) { + tv_list_append_string(rettv->vval.v_list, str, end - str); + } + if (!match) { + break; + } + // Advance to just after the match. + if (regmatch.endp[0] > (char_u *)str) { + col = 0; + } else { + // Don't get stuck at the same match. + col = (*mb_ptr2len)(regmatch.endp[0]); + } + str = (const char *)regmatch.endp[0]; + } + + vim_regfree(regmatch.regprog); + } + + p_cpo = save_cpo; +} + +/// "stdpath(type)" function +static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + const char *const p = tv_get_string_chk(&argvars[0]); + if (p == NULL) { + return; // Type error; errmsg already given. + } + + if (strequal(p, "config")) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); + } else if (strequal(p, "data")) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); + } else if (strequal(p, "cache")) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); + } else if (strequal(p, "config_dirs")) { + get_xdg_var_list(kXDGConfigDirs, rettv); + } else if (strequal(p, "data_dirs")) { + get_xdg_var_list(kXDGDataDirs, rettv); + } else { + EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); + } +} + +/* + * "str2float()" function + */ +static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); + bool isneg = (*p == '-'); + + if (*p == '+' || *p == '-') { + p = skipwhite(p + 1); + } + (void)string2float((char *)p, &rettv->vval.v_float); + if (isneg) { + rettv->vval.v_float *= -1; + } + rettv->v_type = VAR_FLOAT; +} + +// "str2nr()" function +static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int base = 10; + varnumber_T n; + int what; + + if (argvars[1].v_type != VAR_UNKNOWN) { + base = tv_get_number(&argvars[1]); + if (base != 2 && base != 8 && base != 10 && base != 16) { + EMSG(_(e_invarg)); + return; + } + } + + char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); + bool isneg = (*p == '-'); + if (*p == '+' || *p == '-') { + p = skipwhite(p + 1); + } + switch (base) { + case 2: { + what = STR2NR_BIN | STR2NR_FORCE; + break; + } + case 8: { + what = STR2NR_OCT | STR2NR_FORCE; + break; + } + case 16: { + what = STR2NR_HEX | STR2NR_FORCE; + break; + } + default: { + what = 0; + } + } + vim_str2nr(p, NULL, NULL, what, &n, NULL, 0); + if (isneg) { + rettv->vval.v_number = -n; + } else { + rettv->vval.v_number = n; + } +} + +/* + * "strftime({format}[, {time}])" function + */ +static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + time_t seconds; + + rettv->v_type = VAR_STRING; + + char *p = (char *)tv_get_string(&argvars[0]); + if (argvars[1].v_type == VAR_UNKNOWN) { + seconds = time(NULL); + } else { + seconds = (time_t)tv_get_number(&argvars[1]); + } + + struct tm curtime; + struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime); + /* MSVC returns NULL for an invalid value of seconds. */ + if (curtime_ptr == NULL) + rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); + else { + vimconv_T conv; + char_u *enc; + + conv.vc_type = CONV_NONE; + enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) { + p = (char *)string_convert(&conv, (char_u *)p, NULL); + } + char result_buf[256]; + if (p != NULL) { + (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr); + } else { + result_buf[0] = NUL; + } + + if (conv.vc_type != CONV_NONE) { + xfree(p); + } + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) { + rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL); + } else { + rettv->vval.v_string = (char_u *)xstrdup(result_buf); + } + + // Release conversion descriptors. + convert_setup(&conv, NULL, NULL); + xfree(enc); + } +} + +// "strgetchar()" function +static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + const char *const str = tv_get_string_chk(&argvars[0]); + if (str == NULL) { + return; + } + bool error = false; + varnumber_T charidx = tv_get_number_chk(&argvars[1], &error); + if (error) { + return; + } + + const size_t len = STRLEN(str); + size_t byteidx = 0; + + while (charidx >= 0 && byteidx < len) { + if (charidx == 0) { + rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx); + break; + } + charidx--; + byteidx += MB_CPTR2LEN((const char_u *)str + byteidx); + } +} + +/* + * "stridx()" function + */ +static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + char buf[NUMBUFLEN]; + const char *const needle = tv_get_string_chk(&argvars[1]); + const char *haystack = tv_get_string_buf_chk(&argvars[0], buf); + const char *const haystack_start = haystack; + if (needle == NULL || haystack == NULL) { + return; // Type error; errmsg already given. + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + + const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], + &error); + if (error || start_idx >= (ptrdiff_t)strlen(haystack)) { + return; + } + if (start_idx >= 0) { + haystack += start_idx; + } + } + + const char *pos = strstr(haystack, needle); + if (pos != NULL) { + rettv->vval.v_number = (varnumber_T)(pos - haystack_start); + } +} + +/* + * "string()" function + */ +static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL); +} + +/* + * "strlen()" function + */ +static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); +} + +/* + * "strchars()" function + */ +static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *s = tv_get_string(&argvars[0]); + int skipcc = 0; + varnumber_T len = 0; + int (*func_mb_ptr2char_adv)(const char_u **pp); + + if (argvars[1].v_type != VAR_UNKNOWN) { + skipcc = tv_get_number_chk(&argvars[1], NULL); + } + if (skipcc < 0 || skipcc > 1) { + EMSG(_(e_invarg)); + } else { + func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; + while (*s != NUL) { + func_mb_ptr2char_adv((const char_u **)&s); + len++; + } + rettv->vval.v_number = len; + } +} + +/* + * "strdisplaywidth()" function + */ +static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const s = tv_get_string(&argvars[0]); + int col = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) { + col = tv_get_number(&argvars[1]); + } + + rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); +} + +/* + * "strwidth()" function + */ +static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const s = tv_get_string(&argvars[0]); + + rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s); +} + +// "strcharpart()" function +static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + const size_t slen = STRLEN(p); + + int nbyte = 0; + bool error = false; + varnumber_T nchar = tv_get_number_chk(&argvars[1], &error); + if (!error) { + if (nchar > 0) { + while (nchar > 0 && (size_t)nbyte < slen) { + nbyte += MB_CPTR2LEN((const char_u *)p + nbyte); + nchar--; + } + } else { + nbyte = nchar; + } + } + int len = 0; + if (argvars[2].v_type != VAR_UNKNOWN) { + int charlen = tv_get_number(&argvars[2]); + while (charlen > 0 && nbyte + len < (int)slen) { + int off = nbyte + len; + + if (off < 0) { + len += 1; + } else { + len += (size_t)MB_CPTR2LEN((const char_u *)p + off); + } + charlen--; + } + } else { + len = slen - nbyte; // default: all bytes that are available. + } + + // Only return the overlap between the specified part and the actual + // string. + if (nbyte < 0) { + len += nbyte; + nbyte = 0; + } else if ((size_t)nbyte > slen) { + nbyte = slen; + } + if (len < 0) { + len = 0; + } else if (nbyte + len > (int)slen) { + len = slen - nbyte; + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len); +} + +/* + * "strpart()" function + */ +static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool error = false; + + const char *const p = tv_get_string(&argvars[0]); + const size_t slen = strlen(p); + + varnumber_T n = tv_get_number_chk(&argvars[1], &error); + varnumber_T len; + if (error) { + len = 0; + } else if (argvars[2].v_type != VAR_UNKNOWN) { + len = tv_get_number(&argvars[2]); + } else { + len = slen - n; // Default len: all bytes that are available. + } + + // Only return the overlap between the specified part and the actual + // string. + if (n < 0) { + len += n; + n = 0; + } else if (n > (varnumber_T)slen) { + n = slen; + } + if (len < 0) { + len = 0; + } else if (n + len > (varnumber_T)slen) { + len = slen - n; + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); +} + +/* + * "strridx()" function + */ +static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf[NUMBUFLEN]; + const char *const needle = tv_get_string_chk(&argvars[1]); + const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf); + + rettv->vval.v_number = -1; + if (needle == NULL || haystack == NULL) { + return; // Type error; errmsg already given. + } + + const size_t haystack_len = STRLEN(haystack); + ptrdiff_t end_idx; + if (argvars[2].v_type != VAR_UNKNOWN) { + // Third argument: upper limit for index. + end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL); + if (end_idx < 0) { + return; // Can never find a match. + } + } else { + end_idx = (ptrdiff_t)haystack_len; + } + + const char *lastmatch = NULL; + if (*needle == NUL) { + // Empty string matches past the end. + lastmatch = haystack + end_idx; + } else { + for (const char *rest = haystack; *rest != NUL; rest++) { + rest = strstr(rest, needle); + if (rest == NULL || rest > haystack + end_idx) { + break; + } + lastmatch = rest; + } + } + + if (lastmatch != NULL) { + rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); + } +} + +/* + * "strtrans()" function + */ +static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0])); +} + +/* + * "submatch()" function + */ +static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool error = false; + int no = (int)tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + + if (no < 0 || no >= NSUBEXP) { + emsgf(_("E935: invalid submatch number: %d"), no); + return; + } + int retList = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) { + retList = tv_get_number_chk(&argvars[1], &error); + if (error) { + return; + } + } + + if (retList == 0) { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = reg_submatch(no); + } else { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = reg_submatch_list(no); + } +} + +/* + * "substitute()" function + */ +static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char patbuf[NUMBUFLEN]; + char subbuf[NUMBUFLEN]; + char flagsbuf[NUMBUFLEN]; + + const char *const str = tv_get_string_chk(&argvars[0]); + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + const char *sub = NULL; + const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf); + + typval_T *expr = NULL; + if (tv_is_func(argvars[2])) { + expr = &argvars[2]; + } else { + sub = tv_get_string_buf_chk(&argvars[2], subbuf); + } + + rettv->v_type = VAR_STRING; + if (str == NULL || pat == NULL || (sub == NULL && expr == NULL) + || flg == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat, + (char_u *)sub, expr, (char_u *)flg); + } +} + +/// "swapinfo(swap_filename)" function +static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); +} + +/// "swapname(expr)" function +static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + buf_T *buf = tv_get_buf(&argvars[0], false); + if (buf == NULL + || buf->b_ml.ml_mfp == NULL + || buf->b_ml.ml_mfp->mf_fname == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname); + } +} + +/// "synID(lnum, col, trans)" function +static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // -1 on type error (both) + const linenr_T lnum = tv_get_lnum(argvars); + const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; + + bool transerr = false; + const int trans = tv_get_number_chk(&argvars[2], &transerr); + + int id = 0; + if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) { + id = syn_get_id(curwin, lnum, col, trans, NULL, false); + } + + rettv->vval.v_number = id; +} + +/* + * "synIDattr(id, what [, mode])" function + */ +static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int id = (int)tv_get_number(&argvars[0]); + const char *const what = tv_get_string(&argvars[1]); + int modec; + if (argvars[2].v_type != VAR_UNKNOWN) { + char modebuf[NUMBUFLEN]; + const char *const mode = tv_get_string_buf(&argvars[2], modebuf); + modec = TOLOWER_ASC(mode[0]); + if (modec != 'c' && modec != 'g') { + modec = 0; // Replace invalid with current. + } + } else if (ui_rgb_attached()) { + modec = 'g'; + } else { + modec = 'c'; + } + + + const char *p = NULL; + switch (TOLOWER_ASC(what[0])) { + case 'b': { + if (TOLOWER_ASC(what[1]) == 'g') { // bg[#] + p = highlight_color(id, what, modec); + } else { // bold + p = highlight_has_attr(id, HL_BOLD, modec); + } + break; + } + case 'f': { // fg[#] or font + p = highlight_color(id, what, modec); + break; + } + case 'i': { + if (TOLOWER_ASC(what[1]) == 'n') { // inverse + p = highlight_has_attr(id, HL_INVERSE, modec); + } else { // italic + p = highlight_has_attr(id, HL_ITALIC, modec); + } + break; + } + case 'n': { // name + p = get_highlight_name_ext(NULL, id - 1, false); + break; + } + case 'r': { // reverse + p = highlight_has_attr(id, HL_INVERSE, modec); + break; + } + case 's': { + if (TOLOWER_ASC(what[1]) == 'p') { // sp[#] + p = highlight_color(id, what, modec); + } else if (TOLOWER_ASC(what[1]) == 't' + && TOLOWER_ASC(what[2]) == 'r') { // strikethrough + p = highlight_has_attr(id, HL_STRIKETHROUGH, modec); + } else { // standout + p = highlight_has_attr(id, HL_STANDOUT, modec); + } + break; + } + case 'u': { + if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline + p = highlight_has_attr(id, HL_UNDERLINE, modec); + } else { // undercurl + p = highlight_has_attr(id, HL_UNDERCURL, modec); + } + break; + } + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p)); +} + +/* + * "synIDtrans(id)" function + */ +static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int id = tv_get_number(&argvars[0]); + + if (id > 0) { + id = syn_get_final_id(id); + } else { + id = 0; + } + + rettv->vval.v_number = id; +} + +/* + * "synconcealed(lnum, col)" function + */ +static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int syntax_flags = 0; + int cchar; + int matchid = 0; + char_u str[NUMBUFLEN]; + + tv_list_set_ret(rettv, NULL); + + // -1 on type error (both) + const linenr_T lnum = tv_get_lnum(argvars); + const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; + + memset(str, NUL, sizeof(str)); + + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 + && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { + (void)syn_get_id(curwin, lnum, col, false, NULL, false); + syntax_flags = get_syntax_info(&matchid); + + // get the conceal character + if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { + cchar = syn_get_sub_char(); + if (cchar == NUL && curwin->w_p_cole == 1) { + cchar = (curwin->w_p_lcs_chars.conceal == NUL) + ? ' ' + : curwin->w_p_lcs_chars.conceal; + } + if (cchar != NUL) { + utf_char2bytes(cchar, str); + } + } + } + + tv_list_alloc_ret(rettv, 3); + tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); + // -1 to auto-determine strlen + tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); + tv_list_append_number(rettv->vval.v_list, matchid); +} + +/* + * "synstack(lnum, col)" function + */ +static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_set_ret(rettv, NULL); + + // -1 on type error (both) + const linenr_T lnum = tv_get_lnum(argvars); + const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; + + if (lnum >= 1 + && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 + && (size_t)col <= STRLEN(ml_get(lnum))) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + (void)syn_get_id(curwin, lnum, col, false, NULL, true); + + int id; + int i = 0; + while ((id = syn_get_stack_item(i++)) >= 0) { + tv_list_append_number(rettv->vval.v_list, id); + } + } +} + +/// f_system - the VimL system() function +static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_system_output_as_rettv(argvars, rettv, false); +} + +static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_system_output_as_rettv(argvars, rettv, true); +} + + +/* + * "tabpagebuflist()" function + */ +static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = NULL; + + if (argvars[0].v_type == VAR_UNKNOWN) { + wp = firstwin; + } else { + tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp != NULL) { + wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + } + } + if (wp != NULL) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + while (wp != NULL) { + tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); + wp = wp->w_next; + } + } +} + +/* + * "tabpagenr()" function + */ +static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int nr = 1; + + if (argvars[0].v_type != VAR_UNKNOWN) { + const char *const arg = tv_get_string_chk(&argvars[0]); + nr = 0; + if (arg != NULL) { + if (strcmp(arg, "$") == 0) { + nr = tabpage_index(NULL) - 1; + } else if (strcmp(arg, "#") == 0) { + nr = valid_tabpage(lastused_tabpage) + ? tabpage_index(lastused_tabpage) + : nr; + } else { + EMSG2(_(e_invexpr2), arg); + } + } + } else { + nr = tabpage_index(curtab); + } + rettv->vval.v_number = nr; +} + + + +/* + * Common code for tabpagewinnr() and winnr(). + */ +static int get_winnr(tabpage_T *tp, typval_T *argvar) +{ + win_T *twin; + int nr = 1; + win_T *wp; + + twin = (tp == curtab) ? curwin : tp->tp_curwin; + if (argvar->v_type != VAR_UNKNOWN) { + bool invalid_arg = false; + const char *const arg = tv_get_string_chk(argvar); + if (arg == NULL) { + nr = 0; // Type error; errmsg already given. + } else if (strcmp(arg, "$") == 0) { + twin = (tp == curtab) ? lastwin : tp->tp_lastwin; + } else if (strcmp(arg, "#") == 0) { + twin = (tp == curtab) ? prevwin : tp->tp_prevwin; + if (twin == NULL) { + nr = 0; + } + } else { + // Extract the window count (if specified). e.g. winnr('3j') + char_u *endp; + long count = strtol((char *)arg, (char **)&endp, 10); + if (count <= 0) { + // if count is not specified, default to 1 + count = 1; + } + if (endp != NULL && *endp != '\0') { + if (strequal((char *)endp, "j")) { + twin = win_vert_neighbor(tp, twin, false, count); + } else if (strequal((char *)endp, "k")) { + twin = win_vert_neighbor(tp, twin, true, count); + } else if (strequal((char *)endp, "h")) { + twin = win_horz_neighbor(tp, twin, true, count); + } else if (strequal((char *)endp, "l")) { + twin = win_horz_neighbor(tp, twin, false, count); + } else { + invalid_arg = true; + } + } else { + invalid_arg = true; + } + } + + if (invalid_arg) { + EMSG2(_(e_invexpr2), arg); + nr = 0; + } + } + + if (nr > 0) + for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + wp != twin; wp = wp->w_next) { + if (wp == NULL) { + /* didn't find it in this tabpage */ + nr = 0; + break; + } + ++nr; + } + return nr; +} + +/* + * "tabpagewinnr()" function + */ +static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int nr = 1; + tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + nr = 0; + } else { + nr = get_winnr(tp, &argvars[1]); + } + rettv->vval.v_number = nr; +} + +/* + * "tagfiles()" function + */ +static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char *fname; + tagname_T tn; + + tv_list_alloc_ret(rettv, kListLenUnknown); + fname = xmalloc(MAXPATHL); + + bool first = true; + while (get_tagfname(&tn, first, (char_u *)fname) == OK) { + tv_list_append_string(rettv->vval.v_list, fname, -1); + first = false; + } + + tagname_free(&tn); + xfree(fname); +} + +/* + * "taglist()" function + */ +static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const tag_pattern = tv_get_string(&argvars[0]); + + rettv->vval.v_number = false; + if (*tag_pattern == NUL) { + return; + } + + const char *fname = NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + fname = tv_get_string(&argvars[1]); + } + (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), + (char_u *)tag_pattern, (char_u *)fname); +} + +/* + * "tempname()" function + */ +static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_tempname(); +} + +// "termopen(cmd[, cwd])" function +static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_restricted() || check_secure()) { + return; + } + + if (curbuf->b_changed) { + EMSG(_("Can only call this function in an unmodified buffer")); + return; + } + + const char *cmd; + bool executable = true; + char **argv = tv_to_argv(&argvars[0], &cmd, &executable); + 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 type + EMSG2(_(e_invarg2), "expected dictionary"); + shell_free_argv(argv); + return; + } + + CallbackReader on_stdout = CALLBACK_READER_INIT, + on_stderr = CALLBACK_READER_INIT; + Callback on_exit = CALLBACK_NONE; + dict_T *job_opts = NULL; + const char *cwd = "."; + if (argvars[1].v_type == VAR_DICT) { + job_opts = argvars[1].vval.v_dict; + + const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false); + if (new_cwd && *new_cwd != NUL) { + 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; + } + } + + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { + shell_free_argv(argv); + return; + } + } + + uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); + Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, + true, false, false, cwd, + term_width, curwin->w_height_inner, + xstrdup("xterm-256color"), NULL, + &rettv->vval.v_number); + if (rettv->vval.v_number <= 0) { + return; + } + + int pid = chan->stream.pty.process.pid; + + // "./…" => "/home/foo/…" + vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); + // "/home/foo/…" => "~/…" + size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); + // Trim slash. + if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') { + IObuff[len - 1] = '\0'; + } + + // Terminal URI: "term://$CWD//$PID:$CMD" + snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", + (char *)IObuff, pid, cmd); + // at this point the buffer has no terminal instance associated yet, so unset + // the 'swapfile' option to ensure no swap file will be created + curbuf->b_p_swf = false; + (void)setfname(curbuf, NameBuff, NULL, true); + // Save the job id and pid in b:terminal_job_{id,pid} + Error err = ERROR_INIT; + // deprecated: use 'channel' buffer option + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), + INTEGER_OBJ(chan->id), false, false, &err); + api_clear_error(&err); + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), + INTEGER_OBJ(pid), false, false, &err); + api_clear_error(&err); + + channel_terminal_open(chan); + channel_create_event(chan, NULL); +} + +// "test_garbagecollect_now()" function +static void f_test_garbagecollect_now(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + // This is dangerous, any Lists and Dicts used internally may be freed + // while still in use. + garbage_collect(true); +} + +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, + typval_T *const rettv, + FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + +/// "timer_info([timer])" function +static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_number_exp)); + return; + } + tv_list_alloc_ret(rettv, 1); + timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); + if (timer != NULL && !timer->stopped) { + add_timer_info(rettv, timer); + } + } else { + add_timer_info_all(rettv); + } +} + +/// "timer_pause(timer, paused)" function +static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_number_exp)); + return; + } + int paused = (bool)tv_get_number(&argvars[1]); + timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); + if (timer != NULL) { + if (!timer->paused && paused) { + time_watcher_stop(&timer->tw); + } else if (timer->paused && !paused) { + time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, + timer->timeout); + } + timer->paused = paused; + } +} + +/// "timer_start(timeout, callback, opts)" function +static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int repeat = 1; + dict_T *dict; + + rettv->vval.v_number = -1; + + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT + || (dict = argvars[2].vval.v_dict) == NULL) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[2])); + return; + } + dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat")); + if (di != NULL) { + repeat = tv_get_number(&di->di_tv); + if (repeat == 0) { + repeat = 1; + } + } + } + + Callback callback; + if (!callback_from_typval(&callback, &argvars[1])) { + return; + } + rettv->vval.v_number = + timer_start(tv_get_number(&argvars[0]), repeat, &callback); +} + + +// "timer_stop(timerid)" function +static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_number_exp)); + return; + } + + timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); + if (timer == NULL) { + return; + } + + timer_stop(timer); +} + +static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ + timer_stop_all(); +} + +/* + * "tolower(string)" function + */ +static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), + false); +} + +/* + * "toupper(string)" function + */ +static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), + true); +} + +/* + * "tr(string, fromstr, tostr)" function + */ +static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + + const char *in_str = tv_get_string(&argvars[0]); + const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf); + const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2); + + // Default return value: empty string. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (fromstr == NULL || tostr == NULL) { + return; // Type error; errmsg already given. + } + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + + if (!has_mbyte) { + // Not multi-byte: fromstr and tostr must be the same length. + if (strlen(fromstr) != strlen(tostr)) { + goto error; + } + } + + // fromstr and tostr have to contain the same number of chars. + bool first = true; + while (*in_str != NUL) { + if (has_mbyte) { + const char *cpstr = in_str; + const int inlen = (*mb_ptr2len)((const char_u *)in_str); + int cplen = inlen; + int idx = 0; + int fromlen; + for (const char *p = fromstr; *p != NUL; p += fromlen) { + fromlen = (*mb_ptr2len)((const char_u *)p); + if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) { + int tolen; + for (p = tostr; *p != NUL; p += tolen) { + tolen = (*mb_ptr2len)((const char_u *)p); + if (idx-- == 0) { + cplen = tolen; + cpstr = (char *)p; + break; + } + } + if (*p == NUL) { // tostr is shorter than fromstr. + goto error; + } + break; + } + idx++; + } + + if (first && cpstr == in_str) { + // Check that fromstr and tostr have the same number of + // (multi-byte) characters. Done only once when a character + // of in_str doesn't appear in fromstr. + first = false; + int tolen; + for (const char *p = tostr; *p != NUL; p += tolen) { + tolen = (*mb_ptr2len)((const char_u *)p); + idx--; + } + if (idx != 0) { + goto error; + } + } + + ga_grow(&ga, cplen); + memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen); + ga.ga_len += cplen; + + in_str += inlen; + } else { + // When not using multi-byte chars we can do it faster. + const char *const p = strchr(fromstr, *in_str); + if (p != NULL) { + ga_append(&ga, tostr[p - fromstr]); + } else { + ga_append(&ga, *in_str); + } + in_str++; + } + } + + // add a terminating NUL + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; + return; +error: + EMSG2(_(e_invarg2), fromstr); + ga_clear(&ga); + return; +} + +// "trim({expr})" function +static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1); + const char_u *mask = NULL; + const char_u *tail; + const char_u *prev; + const char_u *p; + int c1; + + rettv->v_type = VAR_STRING; + if (head == NULL) { + rettv->vval.v_string = NULL; + return; + } + + if (argvars[1].v_type == VAR_STRING) { + mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); + } + + while (*head != NUL) { + c1 = PTR2CHAR(head); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { + break; + } + } + MB_PTR_ADV(head); + } + + for (tail = head + STRLEN(head); tail > head; tail = prev) { + prev = tail; + MB_PTR_BACK(head, prev); + c1 = PTR2CHAR(prev); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { + break; + } + } + } + rettv->vval.v_string = vim_strnsave(head, (int)(tail - head)); +} + +/* + * "type(expr)" function + */ +static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int n = -1; + + switch (argvars[0].v_type) { + case VAR_NUMBER: n = VAR_TYPE_NUMBER; break; + case VAR_STRING: n = VAR_TYPE_STRING; break; + case VAR_PARTIAL: + case VAR_FUNC: n = VAR_TYPE_FUNC; break; + case VAR_LIST: n = VAR_TYPE_LIST; break; + case VAR_DICT: n = VAR_TYPE_DICT; break; + case VAR_FLOAT: n = VAR_TYPE_FLOAT; break; + case VAR_SPECIAL: { + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: + case kSpecialVarFalse: { + n = VAR_TYPE_BOOL; + break; + } + case kSpecialVarNull: { + n = 7; + break; + } + } + break; + } + case VAR_UNKNOWN: { + internal_error("f_type(UNKNOWN)"); + break; + } + } + rettv->vval.v_number = n; +} + +/* + * "undofile(name)" function + */ +static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + const char *const fname = tv_get_string(&argvars[0]); + + if (*fname == NUL) { + // If there is no file name there will be no undo file. + rettv->vval.v_string = NULL; + } else { + char *ffname = FullName_save(fname, true); + + if (ffname != NULL) { + rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false); + } + xfree(ffname); + } +} + +/* + * "undotree()" function + */ +static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + dict_T *dict = rettv->vval.v_dict; + + tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); + tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); + tv_dict_add_nr(dict, S_LEN("save_last"), + (varnumber_T)curbuf->b_u_save_nr_last); + tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur); + tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); + tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); + + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); +} + +/* + * "values(dict)" function + */ +static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_list(argvars, rettv, 1); +} + +/* + * "virtcol(string)" function + */ +static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + colnr_T vcol = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum); + if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count + && fnum == curbuf->b_fnum) { + // Limit the column to a valid value, getvvcol() doesn't check. + if (fp->col < 0) { + fp->col = 0; + } else { + const size_t len = STRLEN(ml_get(fp->lnum)); + if (fp->col > (colnr_T)len) { + fp->col = (colnr_T)len; + } + } + getvvcol(curwin, fp, NULL, NULL, &vcol); + ++vcol; + } + + rettv->vval.v_number = vcol; +} + +/* + * "visualmode()" function + */ +static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u str[2]; + + rettv->v_type = VAR_STRING; + str[0] = curbuf->b_visual_mode_eval; + str[1] = NUL; + rettv->vval.v_string = vim_strsave(str); + + /* A non-zero number or non-empty string argument: reset mode. */ + if (non_zero_arg(&argvars[0])) + curbuf->b_visual_mode_eval = NUL; +} + +/* + * "wildmenumode()" function + */ +static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { + rettv->vval.v_number = 1; + } +} + +/// "win_findbuf()" function +static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + win_findbuf(argvars, rettv->vval.v_list); +} + +/// "win_getid()" function +static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = win_getid(argvars); +} + +/// "win_gotoid()" function +static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = win_gotoid(argvars); +} + +/// "win_id2tabwin()" function +static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_id2tabwin(argvars, rettv); +} + +/// "win_id2win()" function +static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = win_id2win(argvars); +} + +/// "winbufnr(nr)" function +static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_buffer->b_fnum; + } +} + +/* + * "wincol()" function + */ +static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wcol + 1; +} + +/// "winheight(nr)" function +static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_height; + } +} + +// "winlayout()" function +static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tabpage_T *tp; + + tv_list_alloc_ret(rettv, 2); + + if (argvars[0].v_type == VAR_UNKNOWN) { + tp = curtab; + } else { + tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + return; + } + } + + get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); +} + +/* + * "winline()" function + */ +static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wrow + 1; +} + +/* + * "winnr()" function + */ +static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int nr = 1; + + nr = get_winnr(curtab, &argvars[0]); + rettv->vval.v_number = nr; +} + +/* + * "winrestcmd()" function + */ +static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int winnr = 1; + garray_T ga; + char_u buf[50]; + + ga_init(&ga, (int)sizeof(char), 70); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); + ga_concat(&ga, buf); + sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); + ga_concat(&ga, buf); + ++winnr; + } + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; + rettv->v_type = VAR_STRING; +} + +/* + * "winrestview()" function + */ +static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict; + + if (argvars[0].v_type != VAR_DICT + || (dict = argvars[0].vval.v_dict) == NULL) { + EMSG(_(e_invarg)); + } else { + dictitem_T *di; + if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { + curwin->w_cursor.lnum = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { + curwin->w_cursor.col = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { + curwin->w_cursor.coladd = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { + curwin->w_curswant = tv_get_number(&di->di_tv); + curwin->w_set_curswant = false; + } + if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { + set_topline(curwin, tv_get_number(&di->di_tv)); + } + if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { + curwin->w_topfill = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { + curwin->w_leftcol = tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { + curwin->w_skipcol = tv_get_number(&di->di_tv); + } + + check_cursor(); + win_new_height(curwin, curwin->w_height); + win_new_width(curwin, curwin->w_width); + changed_window_setting(); + + if (curwin->w_topline <= 0) + curwin->w_topline = 1; + if (curwin->w_topline > curbuf->b_ml.ml_line_count) + curwin->w_topline = curbuf->b_ml.ml_line_count; + check_topfill(curwin, true); + } +} + +/* + * "winsaveview()" function + */ +static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict; + + tv_dict_alloc_ret(rettv); + dict = rettv->vval.v_dict; + + tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); + tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); + tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); + update_curswant(); + tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); + + tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); + tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); + tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); + tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); +} + +/// "winwidth(nr)" function +static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_width; + } +} + +/// "wordcount()" function +static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + cursor_pos_info(rettv->vval.v_dict); +} + +/// "writefile()" function +static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "writefile()"); + return; + } + const list_T *const list = argvars[0].vval.v_list; + TV_LIST_ITER_CONST(list, li, { + if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { + return; + } + }); + + bool binary = false; + bool append = false; + bool do_fsync = !!p_fs; + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const flags = tv_get_string_chk(&argvars[2]); + if (flags == NULL) { + return; + } + for (const char *p = flags; *p; p++) { + switch (*p) { + case 'b': { binary = true; break; } + case 'a': { append = true; break; } + case 's': { do_fsync = true; break; } + case 'S': { do_fsync = false; break; } + default: { + // Using %s, p and not %c, *p to preserve multibyte characters + emsgf(_("E5060: Unknown flag: %s"), p); + return; + } + } + } + } + + char buf[NUMBUFLEN]; + const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); + if (fname == NULL) { + return; + } + FileDescriptor fp; + int error; + if (*fname == NUL) { + EMSG(_("E482: Can't open file with an empty name")); + } else if ((error = file_open(&fp, fname, + ((append ? kFileAppend : kFileTruncate) + | kFileCreate), 0666)) != 0) { + emsgf(_("E482: Can't open file %s for writing: %s"), + fname, os_strerror(error)); + } else { + if (write_list(&fp, list, binary)) { + rettv->vval.v_number = 0; + } + if ((error = file_close(&fp, do_fsync)) != 0) { + emsgf(_("E80: Error when closing file %s: %s"), + fname, os_strerror(error)); + } + } +} +/* + * "xor(expr, expr)" function + */ +static void f_xor(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); +} diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h new file mode 100644 index 0000000000..a343290734 --- /dev/null +++ b/src/nvim/eval/funcs.h @@ -0,0 +1,24 @@ +#ifndef NVIM_EVAL_FUNCS_H +#define NVIM_EVAL_FUNCS_H + +#include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" + +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; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/funcs.h.generated.h" +#endif +#endif // NVIM_EVAL_FUNCS_H diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 0d419d202c..a53879188c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -993,6 +993,14 @@ EXTERN char_u e_re_damg[] INIT(= N_("E43: Damaged match string")); EXTERN char_u e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); EXTERN char_u e_readonly[] INIT(= N_( "E45: 'readonly' option is set (add ! to override)")); +EXTERN char_u e_readonlyvar[] INIT(= N_( + "E46: Cannot change read-only variable \"%.*s\"")); +EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required")); +EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); +EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); +EXTERN char_u e_listreq[] INIT(= N_("E714: List required")); +EXTERN char_u e_listdictarg[] INIT(= N_( + "E712: Argument of %s must be a List or Dictionary")); EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox")); EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here")); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9a8347cf19..9e0063ebaa 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -528,7 +528,7 @@ int nlua_debug(lua_State *lstate) for (;;) { lua_settop(lstate, 0); typval_T input; - get_user_input(input_args, &input, false); + get_user_input(input_args, &input, false, false); msg_putchar('\n'); // Avoid outputting on input line. if (input.v_type != VAR_STRING || input.vval.v_string == NULL |