From ab860cb5f6c749b937e0986167702a36e45000ca Mon Sep 17 00:00:00 2001 From: erw7 Date: Mon, 2 Dec 2019 19:09:30 +0900 Subject: dictwatcher: fix use-after-free #11495 fixes #11494 --- src/nvim/eval/typval.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 106b8f6eed..72ee45a03a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1200,6 +1200,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, typval_T rettv; + dict->dv_refcount++; QUEUE *w; QUEUE_FOREACH(w, &dict->watchers) { DictWatcher *watcher = tv_dict_watcher_node_data(w); @@ -1211,6 +1212,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, tv_clear(&rettv); } } + tv_dict_unref(dict); for (size_t i = 1; i < ARRAY_SIZE(argv); i++) { tv_clear(argv + i); -- cgit From 138480939a6d696d77b866474bcdda054d8fbfa1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 21 Dec 2019 07:55:15 -0500 Subject: vim-patch:8.2.0025: repeated word in comment (#11586) Problem: Repeated word in comment. Solution: Remove one. (Rene Nyffenegger, closes vim/vim#5384) https://github.com/vim/vim/commit/fe72d08400d9064b3f959f1f62f279527e64835a --- src/nvim/eval/typval.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 0b04170cac..02d241f6f5 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -246,7 +246,7 @@ typedef int scid_T; /// Format argument for scid_T #define PRIdSCID "d" -// SCript ConteXt (SCTX): identifies a script script line. +// SCript ConteXt (SCTX): identifies a script line. // When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current // line number. When executing a user function "sc_lnum" is the line where the // function was defined, "sourcing_lnum" is the line number inside the -- cgit From 80ebfc304e295cdb1bed3d0f2781d7884933719a Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 18:45:17 -0500 Subject: vim-patch:8.2.0030: "gF" does not work on output of "verbose command" Problem: "gF" does not work on output of "verbose command". Solution: Recognize " line " and translations. (closes vim/vim#5391) https://github.com/vim/vim/commit/64e74c9cc7d5aab215cf72d9bdd3aac32e128191 --- src/nvim/eval/typval.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 72ee45a03a..728e3a7fa3 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2732,16 +2732,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) return n; } case VAR_SPECIAL: { - switch (tv->vval.v_special) { - case kSpecialVarTrue: { - return 1; - } - case kSpecialVarFalse: - case kSpecialVarNull: { - return 0; - } - } - break; + return tv->vval.v_special == kSpecialVarTrue ? 1 : 0; } case VAR_UNKNOWN: { emsgf(_(e_intern2), "tv_get_number(UNKNOWN)"); -- cgit From dec165b268803d08425e0f173b2487c76a2c440c Mon Sep 17 00:00:00 2001 From: Husain Alshehhi Date: Wed, 1 Jan 2020 07:59:37 -0600 Subject: PVS/V618: fix emsgf format specifier #11643 --- src/nvim/eval/typval.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 02d241f6f5..008453b87f 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -798,7 +798,7 @@ static inline bool tv_get_float_chk(const typval_T *const tv, *ret_f = (float_T)tv->vval.v_number; return true; } - emsgf(_("E808: Number or Float required")); + emsgf("%s", _("E808: Number or Float required")); return false; } -- cgit From 1aacab49ea0e5cff94bd89595737b6af677f4490 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 5 Jan 2020 10:40:02 -0500 Subject: vim-patch:8.1.1579: dict and list could be GC'ed while displaying error Problem: Dict and list could be GC'ed while displaying error in a timer. (Yasuhiro Matsumoto) Solution: Block garbage collection when executing a timer. Add test_garbagecollect_soon(). Add "no_wait_return" to test_override(). (closes vim/vim#4571) https://github.com/vim/vim/commit/adc6714aac20f5462a0ecec50ab4806b2f3ab0db --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 728e3a7fa3..da97eccc65 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1306,7 +1306,7 @@ void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item) dict_T *tv_dict_alloc(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { - dict_T *const d = xmalloc(sizeof(dict_T)); + dict_T *const d = xcalloc(1, sizeof(dict_T)); // Add the dict to the list of dicts for garbage collection. if (gc_first_dict != NULL) { -- cgit From 14a8b3b98c245087ef431070195f3a2fa3db16c0 Mon Sep 17 00:00:00 2001 From: Hye Sung Jung Date: Fri, 31 Jan 2020 00:56:34 -0600 Subject: doc: fix typos [ci skip] #11787 --- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval_encode.c.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 6074e4ee69..138f638eb2 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -248,7 +248,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, /// @param[out] read_bytes Is set to amount of bytes read. /// /// @return OK when reading was finished, FAIL in case of error (i.e. list item -/// was not a string), NOTDONE if reading was successfull, but there are +/// was not a string), NOTDONE if reading was successful, but there are /// more bytes to read. int encode_read_from_list(ListReaderState *const state, char *const buf, const size_t nbuf, size_t *const read_bytes) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 289c3ee99c..af21a6fbe3 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -173,7 +173,7 @@ /// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK /// @brief Macros used to check special dictionary key /// -/// @param label Label for goto in case check was not successfull. +/// @param label Label for goto in case check was not successful. /// @param key typval_T key to check. /// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY -- cgit From 6c5bbf07d988ef55e5e8ba8d70b62c1f0885261b Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 00:33:26 -0800 Subject: eval.c: factor out eval/funcs.c #11828 close #11828 ref #5081 cf. vim patch 7.4.2063 --- src/nvim/eval/funcs.c | 10920 ++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/eval/funcs.h | 24 + 2 files changed, 10944 insertions(+) create mode 100644 src/nvim/eval/funcs.c create mode 100644 src/nvim/eval/funcs.h (limited to 'src/nvim/eval') 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 +#include + +#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 *)""); + } 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 ? _("") : 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 -- cgit From 4813ad48cd12a03ca50c01ac1b20518bf4df57f2 Mon Sep 17 00:00:00 2001 From: erw7 Date: Mon, 20 May 2019 11:57:45 +0900 Subject: vim-patch:8.1.0027: difficult to make a plugin that feeds a line to a job Problem: Difficult to make a plugin that feeds a line to a job. Solution: Add the nitial code for the "prompt" buftype. https://github.com/vim/vim/commit/f273245f6433d5d43a5671306b520a3230c35787 --- src/nvim/eval/funcs.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8232136783..a89d62fdfd 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6076,6 +6076,51 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "prompt_setcallback({buffer}, {callback})" function +static void f_prompt_setcallback(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + Callback prompt_callback = { .type = kCallbackNone }; + + if (check_secure()) { + return; + } + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { + if (!callback_from_typval(&prompt_callback, &argvars[1])) { + return; + } + } + + callback_free(&buf->b_prompt_callback); + buf->b_prompt_callback = prompt_callback; +} + +// "prompt_setprompt({buffer}, {text})" function +static void f_prompt_setprompt(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + const char_u *text; + + if (check_secure()) { + return; + } + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + text = (const char_u *)tv_get_string(&argvars[1]); + xfree(buf->b_prompt_text); + buf->b_prompt_text = vim_strsave(text); +} + // "pum_getpos()" function static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { -- cgit From fe395ae210914eeec0f8592268b1960cb1b819b8 Mon Sep 17 00:00:00 2001 From: erw7 Date: Thu, 23 May 2019 06:38:02 +0900 Subject: vim-patch:8.1.0069: cannot handle pressing CTRL-C in a prompt buffer Problem: Cannot handle pressing CTRL-C in a prompt buffer. Solution: Add prompt_setinterrupt(). https://github.com/vim/vim/commit/0e5979a6d491f68c4a8c86fab489016919329a6b --- src/nvim/eval/funcs.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index a89d62fdfd..48e2ef8c51 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6101,6 +6101,31 @@ static void f_prompt_setcallback(typval_T *argvars, buf->b_prompt_callback = prompt_callback; } +// "prompt_setinterrupt({buffer}, {callback})" function +static void f_prompt_setinterrupt(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + buf_T *buf; + Callback interrupt_callback = { .type = kCallbackNone }; + + if (check_secure()) { + return; + } + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) { + if (!callback_from_typval(&interrupt_callback, &argvars[1])) { + return; + } + } + + callback_free(&buf->b_prompt_interrupt); + buf->b_prompt_interrupt= interrupt_callback; +} + // "prompt_setprompt({buffer}, {text})" function static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) -- cgit From aec3d7915c55fc0b7dc73f6186cf0ae45d416d33 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 25 May 2019 11:14:35 +0900 Subject: vim-patch:8.1.0091: MS-Windows: Cannot interrupt gdb when program is running Problem: MS-Windows: Cannot interrupt gdb when program is running. Solution: Add debugbreak() and use it in the terminal debugger. Respect 'modified' in a prompt buffer. https://github.com/vim/vim/commit/4551c0a9fcdbdef52836d4852686d54b5e47fdaf --- src/nvim/eval/funcs.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 48e2ef8c51..36782e4223 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1408,9 +1408,31 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; } -/* - * "deepcopy()" function - */ +// "debugbreak()" function +static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int pid; + + rettv->vval.v_number = FAIL; + pid = (int)tv_get_number(&argvars[0]); + if (pid == 0) { + EMSG(_(e_invarg)); + } else { +#ifdef WIN32 + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + + if (hProcess != NULL) { + DebugBreakProcess(hProcess); + CloseHandle(hProcess); + rettv->vval.v_number = OK; + } +#else + uv_kill(pid, SIGINT); +#endif + } +} + +// "deepcopy()" function static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int noref = 0; -- cgit From 9b140fafe3c002a1c67fe35af231bbfe23302ea1 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Sun, 9 Feb 2020 15:34:39 +0100 Subject: moved functions to user_funcs.c (no code changes) --- src/nvim/eval/user_funcs.c | 3188 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3188 insertions(+) create mode 100644 src/nvim/eval/user_funcs.c (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c new file mode 100644 index 0000000000..24a7251e94 --- /dev/null +++ b/src/nvim/eval/user_funcs.c @@ -0,0 +1,3188 @@ +// flags used in uf_flags +#define FC_ABORT 0x01 // abort function on error +#define FC_RANGE 0x02 // function accepts range +#define FC_DICT 0x04 // Dict function, uses "self" +#define FC_CLOSURE 0x08 // closure, uses outer scope variables +#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 +#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 +#define FC_SANDBOX 0x40 // function defined in the sandbox + +#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] +#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] + +/// Short variable name length +#define VAR_SHORT_LEN 20 +/// Number of fixed variables used for arguments +#define FIXVAR_CNT 12 + +struct funccall_S { + ufunc_T *func; ///< Function being called. + int linenr; ///< Next line to be executed. + int returned; ///< ":return" used. + /// Fixed variables for arguments. + TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; + dict_T l_vars; ///< l: local function variables. + ScopeDictDictItem l_vars_var; ///< Variable for l: scope. + dict_T l_avars; ///< a: argument variables. + ScopeDictDictItem l_avars_var; ///< Variable for a: scope. + list_T l_varlist; ///< List for a:000. + listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. + typval_T *rettv; ///< Return value. + linenr_T breakpoint; ///< Next line with breakpoint or zero. + int dbg_tick; ///< Debug_tick when breakpoint was set. + int level; ///< Top nesting level of executed function. + proftime_T prof_child; ///< Time spent in a child. + funccall_T *caller; ///< Calling function or NULL. + int fc_refcount; ///< Number of user functions that reference this funccall. + int fc_copyID; ///< CopyID used for garbage collection. + garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". +}; + +hashtab_T func_hashtab; + +// Used by get_func_tv() +static garray_T funcargs = GA_EMPTY_INIT_VALUE; + +/* pointer to funccal for currently active function */ +funccall_T *current_funccal = NULL; + +// Pointer to list of previously used funccal, still around because some +// item in it is still being used. +funccall_T *previous_funccal = NULL; + +static char *e_funcexts = N_( + "E122: Function %s already exists, add ! to replace it"); +static char *e_funcdict = N_("E717: Dictionary entry already exists"); +static char *e_funcref = N_("E718: Funcref required"); +static char *e_nofunc = N_("E130: Unknown function: %s"); + +/// Get function arguments. +static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, + int *varargs, bool skip) +{ + bool mustend = false; + char_u *arg = *argp; + char_u *p = arg; + int c; + int i; + + if (newargs != NULL) { + ga_init(newargs, (int)sizeof(char_u *), 3); + } + + if (varargs != NULL) { + *varargs = false; + } + + // Isolate the arguments: "arg1, arg2, ...)" + while (*p != endchar) { + if (p[0] == '.' && p[1] == '.' && p[2] == '.') { + if (varargs != NULL) { + *varargs = true; + } + p += 3; + mustend = true; + } else { + arg = p; + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit(*arg) + || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) + || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (!skip) { + EMSG2(_("E125: Illegal argument: %s"), arg); + } + break; + } + if (newargs != NULL) { + ga_grow(newargs, 1); + c = *p; + *p = NUL; + arg = vim_strsave(arg); + + // Check for duplicate argument name. + for (i = 0; i < newargs->ga_len; i++) { + if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { + EMSG2(_("E853: Duplicate argument name: %s"), arg); + xfree(arg); + goto err_ret; + } + } + ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; + newargs->ga_len++; + + *p = c; + } + if (*p == ',') { + p++; + } else { + mustend = true; + } + } + p = skipwhite(p); + if (mustend && *p != endchar) { + if (!skip) { + EMSG2(_(e_invarg2), *argp); + } + break; + } + } + if (*p != endchar) { + goto err_ret; + } + p++; // skip "endchar" + + *argp = p; + return OK; + +err_ret: + if (newargs != NULL) { + ga_clear_strings(newargs); + } + return FAIL; +} + +/// Register function "fp" as using "current_funccal" as its scope. +static void register_closure(ufunc_T *fp) +{ + if (fp->uf_scoped == current_funccal) { + // no change + return; + } + funccal_unref(fp->uf_scoped, fp, false); + fp->uf_scoped = current_funccal; + current_funccal->fc_refcount++; + ga_grow(¤t_funccal->fc_funcs, 1); + ((ufunc_T **)current_funccal->fc_funcs.ga_data) + [current_funccal->fc_funcs.ga_len++] = fp; +} + +/// Parse a lambda expression and get a Funcref from "*arg". +/// +/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. +static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) +{ + garray_T newargs = GA_EMPTY_INIT_VALUE; + garray_T *pnewargs; + ufunc_T *fp = NULL; + int varargs; + int ret; + char_u *start = skipwhite(*arg + 1); + char_u *s, *e; + static int lambda_no = 0; + int *old_eval_lavars = eval_lavars_used; + int eval_lavars = false; + + // First, check if this is a lambda expression. "->" must exists. + ret = get_function_args(&start, '-', NULL, NULL, true); + if (ret == FAIL || *start != '>') { + return NOTDONE; + } + + // Parse the arguments again. + if (evaluate) { + pnewargs = &newargs; + } else { + pnewargs = NULL; + } + *arg = skipwhite(*arg + 1); + ret = get_function_args(arg, '-', pnewargs, &varargs, false); + if (ret == FAIL || **arg != '>') { + goto errret; + } + + // Set up a flag for checking local variables and arguments. + if (evaluate) { + eval_lavars_used = &eval_lavars; + } + + // Get the start and the end of the expression. + *arg = skipwhite(*arg + 1); + s = *arg; + ret = skip_expr(arg); + if (ret == FAIL) { + goto errret; + } + e = *arg; + *arg = skipwhite(*arg); + if (**arg != '}') { + goto errret; + } + (*arg)++; + + if (evaluate) { + int len, flags = 0; + char_u *p; + char_u name[20]; + partial_T *pt; + garray_T newlines; + + lambda_no++; + snprintf((char *)name, sizeof(name), "%d", lambda_no); + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + pt = xcalloc(1, sizeof(partial_T)); + + ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_grow(&newlines, 1); + + // Add "return " before the expression. + len = 7 + e - s + 1; + p = (char_u *)xmalloc(len); + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + STRCPY(p, "return "); + STRLCPY(p + 7, s, e - s + 1); + + fp->uf_refcount = 1; + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + fp->uf_args = newargs; + fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + + if (prof_def_func()) { + func_do_profile(fp); + } + if (sandbox) { + flags |= FC_SANDBOX; + } + fp->uf_varargs = true; + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; + + pt->pt_func = fp; + pt->pt_refcount = 1; + rettv->vval.v_partial = pt; + rettv->v_type = VAR_PARTIAL; + } + + eval_lavars_used = old_eval_lavars; + return OK; + +errret: + ga_clear_strings(&newargs); + xfree(fp); + eval_lavars_used = old_eval_lavars; + return FAIL; +} + +/// Return name of the function corresponding to `name` +/// +/// If `name` points to variable that is either a function or partial then +/// corresponding function name is returned. Otherwise it returns `name` itself. +/// +/// @param[in] name Function name to check. +/// @param[in,out] lenp Location where length of the returned name is stored. +/// Must be set to the length of the `name` argument. +/// @param[out] partialp Location where partial will be stored if found +/// function appears to be a partial. May be NULL if this +/// is not needed. +/// @param[in] no_autoload If true, do not source autoload scripts if function +/// was not found. +/// +/// @return name of the function. +static char_u *deref_func_name(const char *name, int *lenp, + partial_T **const partialp, bool no_autoload) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + if (partialp != NULL) { + *partialp = NULL; + } + + dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + if (v->di_tv.vval.v_string == NULL) { // just in case + *lenp = 0; + return (char_u *)""; + } + *lenp = (int)STRLEN(v->di_tv.vval.v_string); + return v->di_tv.vval.v_string; + } + + if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { + partial_T *const pt = v->di_tv.vval.v_partial; + + if (pt == NULL) { // just in case + *lenp = 0; + return (char_u *)""; + } + if (partialp != NULL) { + *partialp = pt; + } + char_u *s = partial_name(pt); + *lenp = (int)STRLEN(s); + return s; + } + + return (char_u *)name; +} + +/// Give an error message with a function name. Handle things. +/// +/// @param ermsg must be passed without translation (use N_() instead of _()). +/// @param name function name +static void emsg_funcname(char *ermsg, const char_u *name) +{ + char_u *p; + + if (*name == K_SPECIAL) { + p = concat_str((char_u *)"", name + 3); + } else { + p = (char_u *)name; + } + + EMSG2(_(ermsg), p); + + if (p != name) { + xfree(p); + } +} + +/* + * Allocate a variable for the result of a function. + * Return OK or FAIL. + */ +static int +get_func_tv( + const char_u *name, // name of the function + int len, // length of "name" + typval_T *rettv, + char_u **arg, // argument, pointing to the '(' + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // return: function handled range + int evaluate, + partial_T *partial, // for extra arguments + dict_T *selfdict // Dictionary for "self" +) +{ + char_u *argp; + int ret = OK; + typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */ + int argcount = 0; /* number of arguments found */ + + /* + * Get the arguments. + */ + argp = *arg; + while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + argp = skipwhite(argp + 1); // skip the '(' or ',' + if (*argp == ')' || *argp == ',' || *argp == NUL) { + break; + } + if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { + ret = FAIL; + break; + } + ++argcount; + if (*argp != ',') + break; + } + if (*argp == ')') + ++argp; + else + ret = FAIL; + + if (ret == OK) { + int i = 0; + + if (get_vim_var_nr(VV_TESTING)) { + // Prepare for calling garbagecollect_for_testing(), need to know + // what variables are used on the call stack. + if (funcargs.ga_itemsize == 0) { + ga_init(&funcargs, (int)sizeof(typval_T *), 50); + } + for (i = 0; i < argcount; i++) { + ga_grow(&funcargs, 1); + ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; + } + } + ret = call_func(name, len, rettv, argcount, argvars, NULL, + firstline, lastline, doesrange, evaluate, + partial, selfdict); + + funcargs.ga_len -= i; + } else if (!aborting()) { + if (argcount == MAX_FUNC_ARGS) { + emsg_funcname(N_("E740: Too many arguments for function %s"), name); + } else { + emsg_funcname(N_("E116: Invalid arguments for function %s"), name); + } + } + + while (--argcount >= 0) { + tv_clear(&argvars[argcount]); + } + + *arg = skipwhite(argp); + return ret; +} + +#define FLEN_FIXED 40 + +/// Check whether function name starts with or s: +/// +/// @warning Only works for names previously checked by eval_fname_script(), if +/// it returned non-zero. +/// +/// @param[in] name Name to check. +/// +/// @return true if it starts with or s:, false otherwise. +static inline bool eval_fname_sid(const char *const name) + FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; +} + +/// In a script transform script-local names into actually used names +/// +/// Transforms "" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and +/// "" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have +/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. +/// +/// @param[in] name Name to transform. +/// @param fname_buf Buffer to save resulting function name to, if it fits. +/// Must have at least #FLEN_FIXED + 1 length. +/// @param[out] tofree Location where pointer to an allocated memory is saved +/// in case result does not fit into fname_buf. +/// @param[out] error Location where error type is saved, @see +/// FnameTransError. +/// +/// @return transformed name: either `fname_buf` or a pointer to an allocated +/// memory. +static char_u *fname_trans_sid(const char_u *const name, + char_u *const fname_buf, + char_u **const tofree, int *const error) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + char_u *fname; + const int llen = eval_fname_script((const char *)name); + if (llen > 0) { + fname_buf[0] = K_SPECIAL; + fname_buf[1] = KS_EXTRA; + fname_buf[2] = (int)KE_SNR; + int i = 3; + if (eval_fname_sid((const char *)name)) { // "" or "s:" + if (current_sctx.sc_sid <= 0) { + *error = ERROR_SCRIPT; + } else { + snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + i = (int)STRLEN(fname_buf); + } + } + if (i + STRLEN(name + llen) < FLEN_FIXED) { + STRCPY(fname_buf + i, name + llen); + fname = fname_buf; + } else { + fname = xmalloc(i + STRLEN(name + llen) + 1); + *tofree = fname; + memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); + } + } else { + fname = (char_u *)name; + } + + return fname; +} + +/// Find a function by name, return pointer to it in ufuncs. +/// @return NULL for unknown function. +static ufunc_T *find_func(const char_u *name) +{ + hashitem_T *hi; + + hi = hash_find(&func_hashtab, name); + if (!HASHITEM_EMPTY(hi)) + return HI2UF(hi); + return NULL; +} + +/* + * Copy the function name of "fp" to buffer "buf". + * "buf" must be able to hold the function name plus three bytes. + * Takes care of script-local function names. + */ +static void cat_func_name(char_u *buf, ufunc_T *fp) +{ + if (fp->uf_name[0] == K_SPECIAL) { + STRCPY(buf, ""); + STRCAT(buf, fp->uf_name + 3); + } else + STRCPY(buf, fp->uf_name); +} + +/* + * Add a number variable "name" to dict "dp" with value "nr". + */ +static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) +{ +#ifndef __clang_analyzer__ + STRCPY(v->di_key, name); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(dp, v); + v->di_tv.v_type = VAR_NUMBER; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_number = nr; +} + +/* + * Free "fc" and what it contains. + */ +static void +free_funccal( + funccall_T *fc, + int free_val /* a: vars were allocated */ +) +{ + for (int i = 0; i < fc->fc_funcs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + + // When garbage collecting a funccall_T may be freed before the + // function that references it, clear its uf_scoped field. + // The function may have been redefined and point to another + // funccal_T, don't clear it then. + if (fp != NULL && fp->uf_scoped == fc) { + fp->uf_scoped = NULL; + } + } + ga_clear(&fc->fc_funcs); + + // The a: variables typevals may not have been allocated, only free the + // allocated variables. + vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); + + // Free all l: variables. + vars_clear(&fc->l_vars.dv_hashtab); + + // Free the a:000 variables if they were allocated. + if (free_val) { + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); + } + + func_ptr_unref(fc->func); + xfree(fc); +} + +/// Handle the last part of returning from a function: free the local hashtable. +/// Unless it is still in use by a closure. +static void cleanup_function_call(funccall_T *fc) +{ + current_funccal = fc->caller; + + // If the a:000 list and the l: and a: dicts are not referenced and there + // is no closure using it, we can free the funccall_T and what's in it. + if (!fc_referenced(fc)) { + free_funccal(fc, false); + } else { + // "fc" is still in use. This can happen when returning "a:000", + // assigning "l:" to a global variable or defining a closure. + // Link "fc" in the list for garbage collection later. + fc->caller = previous_funccal; + previous_funccal = fc; + + // Make a copy of the a: variables, since we didn't do that above. + TV_DICT_ITER(&fc->l_avars, di, { + tv_copy(&di->di_tv, &di->di_tv); + }); + + // Make a copy of the a:000 items, since we didn't do that above. + TV_LIST_ITER(&fc->l_varlist, li, { + tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); + }); + } +} + +/// Unreference "fc": decrement the reference count and free it when it +/// becomes zero. "fp" is detached from "fc". +/// +/// @param[in] force When true, we are exiting. +static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) +{ + funccall_T **pfc; + int i; + + if (fc == NULL) { + return; + } + + fc->fc_refcount--; + if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { + for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + if (fc == *pfc) { + *pfc = fc->caller; + free_funccal(fc, true); + return; + } + } + } + for (i = 0; i < fc->fc_funcs.ga_len; i++) { + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + } + } +} + +/// Remove the function from the function hashtable. If the function was +/// deleted while it still has references this was already done. +/// +/// @return true if the entry was deleted, false if it wasn't found. +static bool func_remove(ufunc_T *fp) +{ + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + + if (!HASHITEM_EMPTY(hi)) { + hash_remove(&func_hashtab, hi); + return true; + } + + return false; +} + +static void func_clear_items(ufunc_T *fp) +{ + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + + XFREE_CLEAR(fp->uf_tml_count); + XFREE_CLEAR(fp->uf_tml_total); + XFREE_CLEAR(fp->uf_tml_self); +} + +/// Free all things that a function contains. Does not free the function +/// itself, use func_free() for that. +/// +/// param[in] force When true, we are exiting. +static void func_clear(ufunc_T *fp, bool force) +{ + if (fp->uf_cleared) { + return; + } + fp->uf_cleared = true; + + // clear this function + func_clear_items(fp); + funccal_unref(fp->uf_scoped, fp, force); +} + +/// Free a function and remove it from the list of functions. Does not free +/// what a function contains, call func_clear() first. +/// +/// param[in] fp The function to free. +static void func_free(ufunc_T *fp) +{ + // only remove it when not done already, otherwise we would remove a newer + // version of the function + if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { + func_remove(fp); + } + xfree(fp); +} + +/// Free all things that a function contains and free the function itself. +/// +/// param[in] force When true, we are exiting. +static void func_clear_free(ufunc_T *fp, bool force) +{ + func_clear(fp, force); + func_free(fp); +} + +/// Call a user function +/// +/// @param fp Function to call. +/// @param[in] argcount Number of arguments. +/// @param argvars Arguments. +/// @param[out] rettv Return value. +/// @param[in] firstline First line of range. +/// @param[in] lastline Last line of range. +/// @param selfdict Dictionary for "self" for dictionary functions. +void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, + typval_T *rettv, linenr_T firstline, linenr_T lastline, + dict_T *selfdict) + FUNC_ATTR_NONNULL_ARG(1, 3, 4) +{ + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + bool using_sandbox = false; + funccall_T *fc; + int save_did_emsg; + static int depth = 0; + dictitem_T *v; + int fixvar_idx = 0; /* index in fixvar[] */ + int ai; + bool islambda = false; + char_u numbuf[NUMBUFLEN]; + char_u *name; + proftime_T wait_start; + proftime_T call_start; + int started_profiling = false; + bool did_save_redo = false; + save_redo_T save_redo; + + /* If depth of calling is getting too high, don't execute the function */ + if (depth >= p_mfd) { + EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'")); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + return; + } + ++depth; + // Save search patterns and redo buffer. + save_search_patterns(); + if (!ins_compl_active()) { + saveRedobuff(&save_redo); + did_save_redo = true; + } + ++fp->uf_calls; + // check for CTRL-C hit + line_breakcheck(); + // prepare the funccall_T structure + fc = xmalloc(sizeof(funccall_T)); + fc->caller = current_funccal; + current_funccal = fc; + fc->func = fp; + fc->rettv = rettv; + rettv->vval.v_number = 0; + fc->linenr = 0; + fc->returned = FALSE; + fc->level = ex_nesting_level; + /* Check if this function has a breakpoint. */ + fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); + fc->dbg_tick = debug_tick; + + // Set up fields for closure. + fc->fc_refcount = 0; + fc->fc_copyID = 0; + ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); + func_ptr_ref(fp); + + if (STRNCMP(fp->uf_name, "", 8) == 0) { + islambda = true; + } + + // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables + // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free + // each argument variable and saves a lot of time. + // + // Init l: variables. + init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); + if (selfdict != NULL) { + // Set l:self to "selfdict". Use "name" to avoid a warning from + // some compiler that checks the destination size. + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; +#ifndef __clang_analyzer__ + name = v->di_key; + STRCPY(name, "self"); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(&fc->l_vars, v); + v->di_tv.v_type = VAR_DICT; + v->di_tv.v_lock = 0; + v->di_tv.vval.v_dict = selfdict; + ++selfdict->dv_refcount; + } + + /* + * Init a: variables. + * Set a:0 to "argcount". + * Set a:000 to a list with room for the "..." arguments. + */ + init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", + (varnumber_T)(argcount - fp->uf_args.ga_len)); + fc->l_avars.dv_lock = VAR_FIXED; + // Use "name" to avoid a warning from some compiler that checks the + // destination size. + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; +#ifndef __clang_analyzer__ + name = v->di_key; + STRCPY(name, "000"); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(&fc->l_avars, v); + v->di_tv.v_type = VAR_LIST; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_list = &fc->l_varlist; + tv_list_init_static(&fc->l_varlist); + tv_list_set_lock(&fc->l_varlist, VAR_FIXED); + + // Set a:firstline to "firstline" and a:lastline to "lastline". + // Set a:name to named arguments. + // Set a:N to the "..." arguments. + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "firstline", (varnumber_T)firstline); + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "lastline", (varnumber_T)lastline); + for (int i = 0; i < argcount; i++) { + bool addlocal = false; + + ai = i - fp->uf_args.ga_len; + if (ai < 0) { + // named argument a:name + name = FUNCARG(fp, i); + if (islambda) { + addlocal = true; + } + } else { + // "..." argument a:1, a:2, etc. + snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); + name = numbuf; + } + if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + } else { + v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + } + STRCPY(v->di_key, name); + + // Note: the values are copied directly to avoid alloc/free. + // "argvars" must have VAR_FIXED for v_lock. + v->di_tv = argvars[i]; + v->di_tv.v_lock = VAR_FIXED; + + if (addlocal) { + // Named arguments can be accessed without the "a:" prefix in lambda + // expressions. Add to the l: dict. + tv_copy(&v->di_tv, &v->di_tv); + tv_dict_add(&fc->l_vars, v); + } else { + tv_dict_add(&fc->l_avars, v); + } + + if (ai >= 0 && ai < MAX_FUNC_ARGS) { + tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); + *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; + TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; + } + } + + /* Don't redraw while executing the function. */ + ++RedrawingDisabled; + save_sourcing_name = sourcing_name; + save_sourcing_lnum = sourcing_lnum; + sourcing_lnum = 1; + + if (fp->uf_flags & FC_SANDBOX) { + using_sandbox = true; + sandbox++; + } + + // need space for new sourcing_name: + // * save_sourcing_name + // * "["number"].." or "function " + // * "" + fp->uf_name - 3 + // * terminating NUL + size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) + + STRLEN(fp->uf_name) + 27; + sourcing_name = xmalloc(len); + { + if (save_sourcing_name != NULL + && STRNCMP(save_sourcing_name, "function ", 9) == 0) { + vim_snprintf((char *)sourcing_name, + len, + "%s[%" PRId64 "]..", + save_sourcing_name, + (int64_t)save_sourcing_lnum); + } else { + STRCPY(sourcing_name, "function "); + } + cat_func_name(sourcing_name + STRLEN(sourcing_name), fp); + + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg(_("calling %s"), sourcing_name); + if (p_verbose >= 14) { + msg_puts("("); + for (int i = 0; i < argcount; i++) { + if (i > 0) { + msg_puts(", "); + } + if (argvars[i].v_type == VAR_NUMBER) { + msg_outnum((long)argvars[i].vval.v_number); + } else { + // Do not want errors such as E724 here. + emsg_off++; + char *tofree = encode_tv2string(&argvars[i], NULL); + emsg_off--; + if (tofree != NULL) { + char *s = tofree; + char buf[MSG_BUF_LEN]; + if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { + trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, + sizeof(buf)); + s = buf; + } + msg_puts(s); + xfree(tofree); + } + } + } + msg_puts(")"); + } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + } + + const bool do_profiling_yes = do_profiling == PROF_YES; + + bool func_not_yet_profiling_but_should = + do_profiling_yes + && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); + + if (func_not_yet_profiling_but_should) { + started_profiling = true; + func_do_profile(fp); + } + + bool func_or_func_caller_profiling = + do_profiling_yes + && (fp->uf_profiling + || (fc->caller != NULL && fc->caller->func->uf_profiling)); + + if (func_or_func_caller_profiling) { + ++fp->uf_tm_count; + call_start = profile_start(); + fp->uf_tm_children = profile_zero(); + } + + if (do_profiling_yes) { + script_prof_save(&wait_start); + } + + const sctx_T save_current_sctx = current_sctx; + current_sctx = fp->uf_script_ctx; + save_did_emsg = did_emsg; + did_emsg = FALSE; + + /* call do_cmdline() to execute the lines */ + do_cmdline(NULL, get_func_line, (void *)fc, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + + --RedrawingDisabled; + + // when the function was aborted because of an error, return -1 + if ((did_emsg + && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) { + tv_clear(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + } + + if (func_or_func_caller_profiling) { + call_start = profile_end(call_start); + call_start = profile_sub_wait(wait_start, call_start); // -V614 + fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); + fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, + fp->uf_tm_children); + if (fc->caller != NULL && fc->caller->func->uf_profiling) { + fc->caller->func->uf_tm_children = + profile_add(fc->caller->func->uf_tm_children, call_start); + fc->caller->func->uf_tml_children = + profile_add(fc->caller->func->uf_tml_children, call_start); + } + if (started_profiling) { + // make a ":profdel func" stop profiling the function + fp->uf_profiling = false; + } + } + + /* when being verbose, mention the return value */ + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + if (aborting()) + smsg(_("%s aborted"), sourcing_name); + else if (fc->rettv->v_type == VAR_NUMBER) + smsg(_("%s returning #%" PRId64 ""), + sourcing_name, (int64_t)fc->rettv->vval.v_number); + else { + char_u buf[MSG_BUF_LEN]; + + // The value may be very long. Skip the middle part, so that we + // have some idea how it starts and ends. smsg() would always + // truncate it at the end. Don't want errors such as E724 here. + emsg_off++; + char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL); + char_u *tofree = s; + emsg_off--; + if (s != NULL) { + if (vim_strsize(s) > MSG_BUF_CLEN) { + trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); + s = buf; + } + smsg(_("%s returning %s"), sourcing_name, s); + xfree(tofree); + } + } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + + xfree(sourcing_name); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + current_sctx = save_current_sctx; + if (do_profiling_yes) { + script_prof_restore(&wait_start); + } + if (using_sandbox) { + sandbox--; + } + + if (p_verbose >= 12 && sourcing_name != NULL) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg(_("continuing in %s"), sourcing_name); + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + + did_emsg |= save_did_emsg; + depth--; + + cleanup_function_call(fc); + + if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { + // Function was unreferenced while being used, free it now. + func_clear_free(fp, false); + } + // restore search patterns and redo buffer + if (did_save_redo) { + restoreRedobuff(&save_redo); + } + restore_search_patterns(); +} + +/// There are two kinds of function names: +/// 1. ordinary names, function defined with :function +/// 2. numbered functions and lambdas +/// For the first we only count the name stored in func_hashtab as a reference, +/// using function() does not count as a reference, because the function is +/// looked up by name. +static bool func_name_refcount(char_u *name) +{ + return isdigit(*name) || *name == '<'; +} + +/* + * Save the current function call pointer, and set it to NULL. + * Used when executing autocommands and for ":source". + */ +void *save_funccal(void) +{ + funccall_T *fc = current_funccal; + + current_funccal = NULL; + return (void *)fc; +} + +void restore_funccal(void *vfc) +{ + funccall_T *fc = (funccall_T *)vfc; + + current_funccal = fc; +} + +#if defined(EXITFREE) +void free_all_functions(void) +{ + hashitem_T *hi; + ufunc_T *fp; + uint64_t skipped = 0; + uint64_t todo = 1; + uint64_t used; + + // Clean up the call stack. + while (current_funccal != NULL) { + tv_clear(current_funccal->rettv); + cleanup_function_call(current_funccal); + } + + // First clear what the functions contain. Since this may lower the + // reference count of a function, it may also free a function and change + // the hash table. Restart if that happens. + while (todo > 0) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + used = func_hashtab.ht_used; + func_clear(fp, true); + if (used != func_hashtab.ht_used) { + skipped = 0; + break; + } + } + todo--; + } + } + } + + // Now actually free the functions. Need to start all over every time, + // because func_free() may change the hash table. + skipped = 0; + while (func_hashtab.ht_used > skipped) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + func_free(fp); + skipped = 0; + break; + } + } + } + } + if (skipped == 0) { + hash_clear(&func_hashtab); + } +} + +#endif + +/// Checks if a builtin function with the given name exists. +/// +/// @param[in] name name of the builtin function to check. +/// @param[in] len length of "name", or -1 for NUL terminated. +/// +/// @return true if "name" looks like a builtin function name: starts with a +/// lower case letter and doesn't contain AUTOLOAD_CHAR. +static bool builtin_function(const char *name, int len) +{ + if (!ASCII_ISLOWER(name[0])) { + return false; + } + + const char *p = (len == -1 + ? strchr(name, AUTOLOAD_CHAR) + : memchr(name, AUTOLOAD_CHAR, (size_t)len)); + + return p == NULL; +} + +int func_call(char_u *name, typval_T *args, partial_T *partial, + dict_T *selfdict, typval_T *rettv) +{ + typval_T argv[MAX_FUNC_ARGS + 1]; + int argc = 0; + int dummy; + int r = 0; + + TV_LIST_ITER(args->vval.v_list, item, { + if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + EMSG(_("E699: Too many arguments")); + goto func_call_skip_call; + } + // Make a copy of each argument. This is needed to be able to set + // v_lock to VAR_FIXED in the copy without changing the original list. + tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); + }); + + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); + +func_call_skip_call: + // Free the arguments. + while (argc > 0) { + tv_clear(&argv[--argc]); + } + + return r; +} + +/// Call a function with its resolved parameters +/// +/// "argv_func", when not NULL, can be used to fill in arguments only when the +/// invoked function uses them. It is called like this: +/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) +/// +/// @return FAIL if function cannot be called, else OK (even if an error +/// occurred while executing the function! Set `msg_list` to capture +/// the error, see do_cmdline()). +int +call_func( + const char_u *funcname, // name of the function + int len, // length of "name" + typval_T *rettv, // [out] value goes here + int argcount_in, // number of "argvars" + typval_T *argvars_in, // vars for arguments, must have "argcount" + // PLUS ONE elements! + ArgvFunc argv_func, // function to fill in argvars + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // [out] function handled range + bool evaluate, + partial_T *partial, // optional, can be NULL + dict_T *selfdict_in // Dictionary for "self" +) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) +{ + int ret = FAIL; + int error = ERROR_NONE; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + char_u *name; + int argcount = argcount_in; + typval_T *argvars = argvars_in; + dict_T *selfdict = selfdict_in; + typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL + int argv_clear = 0; + + // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) + // even when call_func() returns FAIL. + rettv->v_type = VAR_UNKNOWN; + + // Make a copy of the name, if it comes from a funcref variable it could + // be changed or deleted in the called function. + name = vim_strnsave(funcname, len); + + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + + *doesrange = false; + + if (partial != NULL) { + // When the function has a partial with a dict and there is a dict + // argument, use the dict argument. That is backwards compatible. + // When the dict was bound explicitly use the one from the partial. + if (partial->pt_dict != NULL + && (selfdict_in == NULL || !partial->pt_auto)) { + selfdict = partial->pt_dict; + } + if (error == ERROR_NONE && partial->pt_argc > 0) { + for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { + tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]); + } + for (int i = 0; i < argcount_in; i++) { + argv[i + argv_clear] = argvars_in[i]; + } + argvars = argv; + argcount = partial->pt_argc + argcount_in; + } + } + + if (error == ERROR_NONE && evaluate) { + char_u *rfname = fname; + + /* Ignore "g:" before a function name. */ + if (fname[0] == 'g' && fname[1] == ':') { + rfname = fname + 2; + } + + rettv->v_type = VAR_NUMBER; /* default rettv is number zero */ + rettv->vval.v_number = 0; + error = ERROR_UNKNOWN; + + if (partial == vvlua_partial) { + if (len > 0) { + error = ERROR_NONE; + executor_call_lua((const char *)funcname, len, + argvars, argcount, rettv); + } + } else if (!builtin_function((const char *)rfname, -1)) { + // User defined function. + if (partial != NULL && partial->pt_func != NULL) { + fp = partial->pt_func; + } else { + fp = find_func(rfname); + } + + // Trigger FuncUndefined event, may load the function. + if (fp == NULL + && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) + && !aborting()) { + /* executed an autocommand, search for the function again */ + fp = find_func(rfname); + } + // Try loading a package. + if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), + true) && !aborting()) { + // Loaded a package, search for the function again. + fp = find_func(rfname); + } + + if (fp != NULL && (fp->uf_flags & FC_DELETED)) { + error = ERROR_DELETED; + } else if (fp != NULL) { + if (argv_func != NULL) { + argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); + } + if (fp->uf_flags & FC_RANGE) { + *doesrange = true; + } + if (argcount < fp->uf_args.ga_len) { + error = ERROR_TOOFEW; + } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { + error = ERROR_TOOMANY; + } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { + error = ERROR_DICT; + } else { + // Call the user function. + call_user_func(fp, argcount, argvars, rettv, firstline, lastline, + (fp->uf_flags & FC_DICT) ? selfdict : NULL); + error = ERROR_NONE; + } + } + } else { + // Find the function name in the table, call its implementation. + const VimLFuncDef *const fdef = find_internal_func((const char *)fname); + if (fdef != NULL) { + if (argcount < fdef->min_argc) { + error = ERROR_TOOFEW; + } else if (argcount > fdef->max_argc) { + error = ERROR_TOOMANY; + } else { + argvars[argcount].v_type = VAR_UNKNOWN; + fdef->func(argvars, rettv, fdef->data); + error = ERROR_NONE; + } + } + } + /* + * The function call (or "FuncUndefined" autocommand sequence) might + * have been aborted by an error, an interrupt, or an explicitly thrown + * exception that has not been caught so far. This situation can be + * tested for by calling aborting(). For an error in an internal + * function or for the "E132" error in call_user_func(), however, the + * throw point at which the "force_abort" flag (temporarily reset by + * emsg()) is normally updated has not been reached yet. We need to + * update that flag first to make aborting() reliable. + */ + update_force_abort(); + } + if (error == ERROR_NONE) + ret = OK; + + /* + * Report an error unless the argument evaluation or function call has been + * cancelled due to an aborting error, an interrupt, or an exception. + */ + if (!aborting()) { + switch (error) { + case ERROR_UNKNOWN: + emsg_funcname(N_("E117: Unknown function: %s"), name); + break; + case ERROR_DELETED: + emsg_funcname(N_("E933: Function was deleted: %s"), name); + break; + case ERROR_TOOMANY: + emsg_funcname(_(e_toomanyarg), name); + break; + case ERROR_TOOFEW: + emsg_funcname(N_("E119: Not enough arguments for function: %s"), + name); + break; + case ERROR_SCRIPT: + emsg_funcname(N_("E120: Using not in a script context: %s"), + name); + break; + case ERROR_DICT: + emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), + name); + break; + } + } + + while (argv_clear > 0) { + tv_clear(&argv[--argv_clear]); + } + xfree(tofree); + xfree(name); + + return ret; +} + +/// List the head of the function: "name(arg1, arg2)". +/// +/// @param[in] fp Function pointer. +/// @param[in] indent Indent line. +/// @param[in] force Include bang "!" (i.e.: "function!"). +static void list_func_head(ufunc_T *fp, int indent, bool force) +{ + msg_start(); + if (indent) + MSG_PUTS(" "); + MSG_PUTS(force ? "function! " : "function "); + if (fp->uf_name[0] == K_SPECIAL) { + MSG_PUTS_ATTR("", HL_ATTR(HLF_8)); + msg_puts((const char *)fp->uf_name + 3); + } else { + msg_puts((const char *)fp->uf_name); + } + msg_putchar('('); + int j; + for (j = 0; j < fp->uf_args.ga_len; j++) { + if (j) { + msg_puts(", "); + } + msg_puts((const char *)FUNCARG(fp, j)); + } + if (fp->uf_varargs) { + if (j) { + msg_puts(", "); + } + msg_puts("..."); + } + msg_putchar(')'); + if (fp->uf_flags & FC_ABORT) { + msg_puts(" abort"); + } + if (fp->uf_flags & FC_RANGE) { + msg_puts(" range"); + } + if (fp->uf_flags & FC_DICT) { + msg_puts(" dict"); + } + if (fp->uf_flags & FC_CLOSURE) { + msg_puts(" closure"); + } + msg_clr_eos(); + if (p_verbose > 0) { + last_set_msg(fp->uf_script_ctx); + } +} + +/// Get a function name, translating "" and "". +/// Also handles a Funcref in a List or Dictionary. +/// flags: +/// TFN_INT: internal function name OK +/// TFN_QUIET: be quiet +/// TFN_NO_AUTOLOAD: do not use script autoloading +/// TFN_NO_DEREF: do not dereference a Funcref +/// Advances "pp" to just after the function name (if no error). +/// +/// @return the function name in allocated memory, or NULL for failure. +static char_u * +trans_function_name( + char_u **pp, + bool skip, // only find the end, don't evaluate + int flags, + funcdict_T *fdp, // return: info about dictionary used + partial_T **partial // return: partial of a FuncRef +) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u *name = NULL; + const char_u *start; + const char_u *end; + int lead; + int len; + lval_T lv; + + if (fdp != NULL) + memset(fdp, 0, sizeof(funcdict_T)); + start = *pp; + + /* Check for hard coded : already translated function ID (from a user + * command). */ + if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA + && (*pp)[2] == (int)KE_SNR) { + *pp += 3; + len = get_id_len((const char **)pp) + 3; + return (char_u *)xmemdupz(start, len); + } + + /* A name starting with "" or "" is local to a script. But + * don't skip over "s:", get_lval() needs it for "s:dict.func". */ + lead = eval_fname_script((const char *)start); + if (lead > 2) { + start += lead; + } + + // Note that TFN_ flags use the same values as GLV_ flags. + end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, + lead > 2 ? 0 : FNE_CHECK_START); + if (end == start) { + if (!skip) + EMSG(_("E129: Function name required")); + goto theend; + } + if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { + /* + * Report an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (end != NULL) { + emsgf(_(e_invarg2), start); + } + } else { + *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR); + } + goto theend; + } + + if (lv.ll_tv != NULL) { + if (fdp != NULL) { + fdp->fd_dict = lv.ll_dict; + fdp->fd_newkey = lv.ll_newkey; + lv.ll_newkey = NULL; + fdp->fd_di = lv.ll_di; + } + if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { + name = vim_strsave(lv.ll_tv->vval.v_string); + *pp = (char_u *)end; + } else if (lv.ll_tv->v_type == VAR_PARTIAL + && lv.ll_tv->vval.v_partial != NULL) { + if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { + len = check_luafunc_name((const char *)end+1, true); + if (len == 0) { + EMSG2(e_invexpr2, "v:lua"); + goto theend; + } + name = xmallocz(len); + memcpy(name, end+1, len); + *pp = (char_u *)end+1+len; + } else { + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + *pp = (char_u *)end; + } + if (partial != NULL) { + *partial = lv.ll_tv->vval.v_partial; + } + } else { + if (!skip && !(flags & TFN_QUIET) && (fdp == NULL + || lv.ll_dict == NULL + || fdp->fd_newkey == NULL)) { + EMSG(_(e_funcref)); + } else { + *pp = (char_u *)end; + } + name = NULL; + } + goto theend; + } + + if (lv.ll_name == NULL) { + // Error found, but continue after the function name. + *pp = (char_u *)end; + goto theend; + } + + /* Check if the name is a Funcref. If so, use the value. */ + if (lv.ll_exp_name != NULL) { + len = (int)strlen(lv.ll_exp_name); + name = deref_func_name(lv.ll_exp_name, &len, partial, + flags & TFN_NO_AUTOLOAD); + if ((const char *)name == lv.ll_exp_name) { + name = NULL; + } + } else if (!(flags & TFN_NO_DEREF)) { + len = (int)(end - *pp); + name = deref_func_name((const char *)(*pp), &len, partial, + flags & TFN_NO_AUTOLOAD); + if (name == *pp) { + name = NULL; + } + } + if (name != NULL) { + name = vim_strsave(name); + *pp = (char_u *)end; + if (strncmp((char *)name, "", 5) == 0) { + // Change "" to the byte sequence. + name[0] = K_SPECIAL; + name[1] = KS_EXTRA; + name[2] = (int)KE_SNR; + memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); + } + goto theend; + } + + if (lv.ll_exp_name != NULL) { + len = (int)strlen(lv.ll_exp_name); + if (lead <= 2 && lv.ll_name == lv.ll_exp_name + && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) { + // When there was "s:" already or the name expanded to get a + // leading "s:" then remove it. + lv.ll_name += 2; + lv.ll_name_len -= 2; + len -= 2; + lead = 2; + } + } else { + // Skip over "s:" and "g:". + if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) { + lv.ll_name += 2; + lv.ll_name_len -= 2; + } + len = (int)((const char *)end - lv.ll_name); + } + + size_t sid_buf_len = 0; + char sid_buf[20]; + + // Copy the function name to allocated memory. + // Accept name() inside a script, translate into 123_name(). + // Accept 123_name() outside a script. + if (skip) { + lead = 0; // do nothing + } else if (lead > 0) { + lead = 3; + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid((const char *)(*pp))) { + // It's "s:" or "". + if (current_sctx.sc_sid <= 0) { + EMSG(_(e_usingsid)); + goto theend; + } + sid_buf_len = snprintf(sid_buf, sizeof(sid_buf), + "%" PRIdSCID "_", current_sctx.sc_sid); + lead += sid_buf_len; + } + } else if (!(flags & TFN_INT) + && builtin_function(lv.ll_name, lv.ll_name_len)) { + EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), + start); + goto theend; + } + + if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { + char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); + + if (cp != NULL && cp < end) { + EMSG2(_("E884: Function name cannot contain a colon: %s"), start); + goto theend; + } + } + + name = xmalloc(len + lead + 1); + if (!skip && lead > 0) { + name[0] = K_SPECIAL; + name[1] = KS_EXTRA; + name[2] = (int)KE_SNR; + if (sid_buf_len > 0) { // If it's "" + memcpy(name + 3, sid_buf, sid_buf_len); + } + } + memmove(name + lead, lv.ll_name, len); + name[lead + len] = NUL; + *pp = (char_u *)end; + +theend: + clear_lval(&lv); + return name; +} + +/* + * ":function" + */ +void ex_function(exarg_T *eap) +{ + char_u *theline; + char_u *line_to_free = NULL; + int c; + int saved_did_emsg; + int saved_wait_return = need_wait_return; + char_u *name = NULL; + char_u *p; + char_u *arg; + char_u *line_arg = NULL; + garray_T newargs; + garray_T newlines; + int varargs = false; + int flags = 0; + ufunc_T *fp; + bool overwrite = false; + int indent; + int nesting; + dictitem_T *v; + funcdict_T fudi; + static int func_nr = 0; /* number for nameless function */ + int paren; + hashtab_T *ht; + int todo; + hashitem_T *hi; + linenr_T sourcing_lnum_off; + linenr_T sourcing_lnum_top; + bool is_heredoc = false; + char_u *skip_until = NULL; + char_u *heredoc_trimmed = NULL; + bool show_block = false; + bool do_concat = true; + + /* + * ":function" without argument: list functions. + */ + if (ends_excmd(*eap->arg)) { + if (!eap->skip) { + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (message_filtered(fp->uf_name)) { + continue; + } + if (!func_name_refcount(fp->uf_name)) { + list_func_head(fp, false, false); + } + } + } + } + eap->nextcmd = check_nextcmd(eap->arg); + return; + } + + /* + * ":function /pat": list functions matching pattern. + */ + if (*eap->arg == '/') { + p = skip_regexp(eap->arg + 1, '/', TRUE, NULL); + if (!eap->skip) { + regmatch_T regmatch; + + c = *p; + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); + *p = c; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (!isdigit(*fp->uf_name) + && vim_regexec(®match, fp->uf_name, 0)) + list_func_head(fp, false, false); + } + } + vim_regfree(regmatch.regprog); + } + } + if (*p == '/') + ++p; + eap->nextcmd = check_nextcmd(p); + return; + } + + // Get the function name. There are these situations: + // func function name + // "name" == func, "fudi.fd_dict" == NULL + // dict.func new dictionary entry + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func + // dict.func existing dict entry with a Funcref + // "name" == func, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // dict.func existing dict entry that's not a Funcref + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // s:func script-local function name + // g:func global function name, same as "func" + p = eap->arg; + name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); + paren = (vim_strchr(p, '(') != NULL); + if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { + /* + * Return on an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (fudi.fd_newkey != NULL) { + EMSG2(_(e_dictkey), fudi.fd_newkey); + } + xfree(fudi.fd_newkey); + return; + } else + eap->skip = TRUE; + } + + /* An error in a function call during evaluation of an expression in magic + * braces should not cause the function not to be defined. */ + saved_did_emsg = did_emsg; + did_emsg = FALSE; + + // + // ":function func" with only function name: list function. + // If bang is given: + // - include "!" in function head + // - exclude line numbers from function body + // + if (!paren) { + if (!ends_excmd(*skipwhite(p))) { + EMSG(_(e_trailing)); + goto ret_free; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + if (!eap->skip && !got_int) { + fp = find_func(name); + if (fp != NULL) { + list_func_head(fp, !eap->forceit, eap->forceit); + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { + continue; + } + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum((long)j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } + } + msg_prt_line(FUNCLINE(fp, j), false); + ui_flush(); // show a line at a time + os_breakcheck(); + } + if (!got_int) { + msg_putchar('\n'); + msg_puts(eap->forceit ? "endfunction" : " endfunction"); + } + } else + emsg_funcname(N_("E123: Undefined function: %s"), name); + } + goto ret_free; + } + + /* + * ":function name(arg1, arg2)" Define function. + */ + p = skipwhite(p); + if (*p != '(') { + if (!eap->skip) { + EMSG2(_("E124: Missing '(': %s"), eap->arg); + goto ret_free; + } + /* attempt to continue by skipping some text */ + if (vim_strchr(p, '(') != NULL) + p = vim_strchr(p, '('); + } + p = skipwhite(p + 1); + + ga_init(&newargs, (int)sizeof(char_u *), 3); + ga_init(&newlines, (int)sizeof(char_u *), 3); + + if (!eap->skip) { + /* Check the name of the function. Unless it's a dictionary function + * (that we are overwriting). */ + if (name != NULL) + arg = name; + else + arg = fudi.fd_newkey; + if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { + int j = (*arg == K_SPECIAL) ? 3 : 0; + while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) + : eval_isnamec(arg[j]))) + ++j; + if (arg[j] != NUL) + emsg_funcname((char *)e_invarg2, arg); + } + /* Disallow using the g: dict. */ + if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) + EMSG(_("E862: Cannot use g: here")); + } + + if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { + goto errret_2; + } + + if (KeyTyped && ui_has(kUICmdline)) { + show_block = true; + ui_ext_cmdline_block_append(0, (const char *)eap->cmd); + } + + // find extra arguments "range", "dict", "abort" and "closure" + for (;; ) { + p = skipwhite(p); + if (STRNCMP(p, "range", 5) == 0) { + flags |= FC_RANGE; + p += 5; + } else if (STRNCMP(p, "dict", 4) == 0) { + flags |= FC_DICT; + p += 4; + } else if (STRNCMP(p, "abort", 5) == 0) { + flags |= FC_ABORT; + p += 5; + } else if (STRNCMP(p, "closure", 7) == 0) { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) { + emsg_funcname(N_ + ("E932: Closure function should not be at top level: %s"), + name == NULL ? (char_u *)"" : name); + goto erret; + } + } else { + break; + } + } + + /* When there is a line break use what follows for the function body. + * Makes 'exe "func Test()\n...\nendfunc"' work. */ + if (*p == '\n') { + line_arg = p + 1; + } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { + EMSG(_(e_trailing)); + } + + /* + * Read the body of the function, until ":endfunction" is found. + */ + if (KeyTyped) { + /* Check if the function already exists, don't let the user type the + * whole function before telling him it doesn't work! For a script we + * need to skip the body to be able to find what follows. */ + if (!eap->skip && !eap->forceit) { + if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) + EMSG(_(e_funcdict)); + else if (name != NULL && find_func(name) != NULL) + emsg_funcname(e_funcexts, name); + } + + if (!eap->skip && did_emsg) + goto erret; + + if (!ui_has(kUICmdline)) { + msg_putchar('\n'); // don't overwrite the function name + } + cmdline_row = msg_row; + } + + // Save the starting line number. + sourcing_lnum_top = sourcing_lnum; + + indent = 2; + nesting = 0; + for (;; ) { + if (KeyTyped) { + msg_scroll = true; + saved_wait_return = false; + } + need_wait_return = false; + + if (line_arg != NULL) { + /* Use eap->arg, split up in parts by line breaks. */ + theline = line_arg; + p = vim_strchr(theline, '\n'); + if (p == NULL) + line_arg += STRLEN(line_arg); + else { + *p = NUL; + line_arg = p + 1; + } + } else { + xfree(line_to_free); + if (eap->getline == NULL) { + theline = getcmdline(':', 0L, indent, do_concat); + } else { + theline = eap->getline(':', eap->cookie, indent, do_concat); + } + line_to_free = theline; + } + if (KeyTyped) { + lines_left = Rows - 1; + } + if (theline == NULL) { + EMSG(_("E126: Missing :endfunction")); + goto erret; + } + if (show_block) { + assert(indent >= 0); + ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); + } + + // Detect line continuation: sourcing_lnum increased more than one. + sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); + if (sourcing_lnum < sourcing_lnum_off) { + sourcing_lnum_off -= sourcing_lnum; + } else { + sourcing_lnum_off = 0; + } + + if (skip_until != NULL) { + // Don't check for ":endfunc" between + // * ":append" and "." + // * ":python < 0) { + give_warning2((char_u *)_("W22: Text found after :endfunction: %s"), + p, true); + } + if (nextcmd != NULL) { + // Another command follows. If the line came from "eap" we + // can simply point into it, otherwise we need to change + // "eap->cmdlinep". + eap->nextcmd = nextcmd; + if (line_to_free != NULL) { + xfree(*eap->cmdlinep); + *eap->cmdlinep = line_to_free; + line_to_free = NULL; + } + } + break; + } + + /* Increase indent inside "if", "while", "for" and "try", decrease + * at "end". */ + if (indent > 2 && STRNCMP(p, "end", 3) == 0) + indent -= 2; + else if (STRNCMP(p, "if", 2) == 0 + || STRNCMP(p, "wh", 2) == 0 + || STRNCMP(p, "for", 3) == 0 + || STRNCMP(p, "try", 3) == 0) + indent += 2; + + /* Check for defining a function inside this function. */ + if (checkforcmd(&p, "function", 2)) { + if (*p == '!') { + p = skipwhite(p + 1); + } + p += eval_fname_script((const char *)p); + xfree(trans_function_name(&p, true, 0, NULL, NULL)); + if (*skipwhite(p) == '(') { + nesting++; + indent += 2; + } + } + + // Check for ":append", ":change", ":insert". + p = skip_range(p, NULL); + if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) + || (p[0] == 'c' + && (!ASCII_ISALPHA(p[1]) + || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) + || (p[2] == 'a' + && (STRNCMP(&p[3], "nge", 3) != 0 + || !ASCII_ISALPHA(p[6]))))))) + || (p[0] == 'i' + && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' + && (!ASCII_ISALPHA(p[2]) + || (p[2] == 's')))))) { + skip_until = vim_strsave((char_u *)"."); + } + + // heredoc: Check for ":python < 0) + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; + + /* Check for end of eap->arg. */ + if (line_arg != NULL && *line_arg == NUL) + line_arg = NULL; + } + + /* Don't define the function when skipping commands or when an error was + * detected. */ + if (eap->skip || did_emsg) + goto erret; + + /* + * If there are no errors, add the function + */ + if (fudi.fd_dict == NULL) { + v = find_var((const char *)name, STRLEN(name), &ht, false); + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + emsg_funcname(N_("E707: Function name conflicts with variable: %s"), + name); + goto erret; + } + + fp = find_func(name); + if (fp != NULL) { + // Function can be replaced with "function!" and when sourcing the + // same script again, but only once. + if (!eap->forceit + && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid + || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { + emsg_funcname(e_funcexts, name); + goto erret; + } + if (fp->uf_calls > 0) { + emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), + name); + goto erret; + } + if (fp->uf_refcount > 1) { + // This function is referenced somewhere, don't redefine it but + // create a new one. + (fp->uf_refcount)--; + fp->uf_flags |= FC_REMOVED; + fp = NULL; + overwrite = true; + } else { + // redefine existing function + XFREE_CLEAR(name); + func_clear_items(fp); + fp->uf_profiling = false; + fp->uf_prof_initialized = false; + } + } + } else { + char numbuf[20]; + + fp = NULL; + if (fudi.fd_newkey == NULL && !eap->forceit) { + EMSG(_(e_funcdict)); + goto erret; + } + if (fudi.fd_di == NULL) { + if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, + TV_CSTRING)) { + // Can't add a function to a locked dictionary + goto erret; + } + } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, + TV_CSTRING)) { + // Can't change an existing function if it is locked + goto erret; + } + + /* Give the function a sequential number. Can only be used with a + * Funcref! */ + xfree(name); + sprintf(numbuf, "%d", ++func_nr); + name = vim_strsave((char_u *)numbuf); + } + + if (fp == NULL) { + if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { + int slen, plen; + char_u *scriptname; + + /* Check that the autoload name matches the script name. */ + int j = FAIL; + if (sourcing_name != NULL) { + scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); + p = vim_strchr(scriptname, '/'); + plen = (int)STRLEN(p); + slen = (int)STRLEN(sourcing_name); + if (slen > plen && fnamecmp(p, + sourcing_name + slen - plen) == 0) + j = OK; + xfree(scriptname); + } + if (j == FAIL) { + EMSG2(_( + "E746: Function name does not match script file name: %s"), + name); + goto erret; + } + } + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + + if (fudi.fd_dict != NULL) { + if (fudi.fd_di == NULL) { + // Add new dict entry + fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); + if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { + xfree(fudi.fd_di); + xfree(fp); + goto erret; + } + } else { + // Overwrite existing dict entry. + tv_clear(&fudi.fd_di->di_tv); + } + fudi.fd_di->di_tv.v_type = VAR_FUNC; + fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); + + /* behave like "dict" was used */ + flags |= FC_DICT; + } + + /* insert the new function in the function list */ + STRCPY(fp->uf_name, name); + if (overwrite) { + hi = hash_find(&func_hashtab, name); + hi->hi_key = UF2HIKEY(fp); + } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { + xfree(fp); + goto erret; + } + fp->uf_refcount = 1; + } + fp->uf_args = newargs; + fp->uf_lines = newlines; + if ((flags & FC_CLOSURE) != 0) { + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + if (prof_def_func()) { + func_do_profile(fp); + } + fp->uf_varargs = varargs; + if (sandbox) { + flags |= FC_SANDBOX; + } + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + + goto ret_free; + +erret: + ga_clear_strings(&newargs); +errret_2: + ga_clear_strings(&newlines); +ret_free: + xfree(skip_until); + xfree(line_to_free); + xfree(fudi.fd_newkey); + xfree(name); + did_emsg |= saved_did_emsg; + need_wait_return |= saved_wait_return; + if (show_block) { + ui_ext_cmdline_block_leave(); + } +} // NOLINT(readability/fn_size) + +/* + * Return 5 if "p" starts with "" or "" (ignoring case). + * Return 2 if "p" starts with "s:". + * Return 0 otherwise. + */ +static int eval_fname_script(const char *const p) +{ + // Use mb_strnicmp() because in Turkish comparing the "I" may not work with + // the standard library function. + if (p[0] == '<' + && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 + || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { + return 5; + } + if (p[0] == 's' && p[1] == ':') { + return 2; + } + return 0; +} + +bool translated_function_exists(const char *name) +{ + if (builtin_function(name, -1)) { + return find_internal_func((char *)name) != NULL; + } + return find_func((const char_u *)name) != NULL; +} + +/// Check whether function with the given name exists +/// +/// @param[in] name Function name. +/// @param[in] no_deref Whether to dereference a Funcref. +/// +/// @return True if it exists, false otherwise. +bool function_exists(const char *const name, bool no_deref) +{ + const char_u *nm = (const char_u *)name; + bool n = false; + int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; + + if (no_deref) { + flag |= TFN_NO_DEREF; + } + char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL, + NULL); + nm = skipwhite(nm); + + /* Only accept "funcname", "funcname ", "funcname (..." and + * "funcname(...", not "funcname!...". */ + if (p != NULL && (*nm == NUL || *nm == '(')) { + n = translated_function_exists(p); + } + xfree(p); + return n; +} + +/* + * Function given to ExpandGeneric() to obtain the list of user defined + * function names. + */ +char_u *get_user_func_name(expand_T *xp, int idx) +{ + static size_t done; + static hashitem_T *hi; + ufunc_T *fp; + + if (idx == 0) { + done = 0; + hi = func_hashtab.ht_array; + } + assert(hi); + if (done < func_hashtab.ht_used) { + if (done++ > 0) + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + fp = HI2UF(hi); + + if ((fp->uf_flags & FC_DICT) + || STRNCMP(fp->uf_name, "", 8) == 0) { + return (char_u *)""; // don't show dict and lambda functions + } + + if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { + return fp->uf_name; // Prevent overflow. + } + + cat_func_name(IObuff, fp); + if (xp->xp_context != EXPAND_USER_FUNC) { + STRCAT(IObuff, "("); + if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) + STRCAT(IObuff, ")"); + } + return IObuff; + } + return NULL; +} + +/// ":delfunction {name}" +void ex_delfunction(exarg_T *eap) +{ + ufunc_T *fp = NULL; + char_u *p; + char_u *name; + funcdict_T fudi; + + p = eap->arg; + name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + xfree(fudi.fd_newkey); + if (name == NULL) { + if (fudi.fd_dict != NULL && !eap->skip) + EMSG(_(e_funcref)); + return; + } + if (!ends_excmd(*skipwhite(p))) { + xfree(name); + EMSG(_(e_trailing)); + return; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + + if (!eap->skip) + fp = find_func(name); + xfree(name); + + if (!eap->skip) { + if (fp == NULL) { + if (!eap->forceit) { + EMSG2(_(e_nofunc), eap->arg); + } + return; + } + if (fp->uf_calls > 0) { + EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg); + return; + } + // check `uf_refcount > 2` because deleting a function should also reduce + // the reference count, and 1 is the initial refcount. + if (fp->uf_refcount > 2) { + EMSG2(_("Cannot delete function %s: It is being used internally"), + eap->arg); + return; + } + + if (fudi.fd_dict != NULL) { + // Delete the dict item that refers to the function, it will + // invoke func_unref() and possibly delete the function. + tv_dict_item_remove(fudi.fd_dict, fudi.fd_di); + } else { + // A normal function (not a numbered function or lambda) has a + // refcount of 1 for the entry in the hashtable. When deleting + // it and the refcount is more than one, it should be kept. + // A numbered function or lambda should be kept if the refcount is + // one or more. + if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { + // Function is still referenced somewhere. Don't free it but + // do remove it from the hashtable. + if (func_remove(fp)) { + fp->uf_refcount--; + } + fp->uf_flags |= FC_DELETED; + } else { + func_clear_free(fp, false); + } + } + } +} + +/* + * Unreference a Function: decrement the reference count and free it when it + * becomes zero. + */ +void func_unref(char_u *name) +{ + ufunc_T *fp = NULL; + + if (name == NULL || !func_name_refcount(name)) { + return; + } + + fp = find_func(name); + if (fp == NULL && isdigit(*name)) { +#ifdef EXITFREE + if (!entered_free_all_mem) { + internal_error("func_unref()"); + abort(); + } +#else + internal_error("func_unref()"); + abort(); +#endif + } + func_ptr_unref(fp); +} + +/// Unreference a Function: decrement the reference count and free it when it +/// becomes zero. +/// Unreference user function, freeing it if needed +/// +/// Decrements the reference count and frees when it becomes zero. +/// +/// @param fp Function to unreference. +void func_ptr_unref(ufunc_T *fp) +{ + if (fp != NULL && --fp->uf_refcount <= 0) { + // Only delete it when it's not being used. Otherwise it's done + // when "uf_calls" becomes zero. + if (fp->uf_calls == 0) { + func_clear_free(fp, false); + } + } +} + +/// Count a reference to a Function. +void func_ref(char_u *name) +{ + ufunc_T *fp; + + if (name == NULL || !func_name_refcount(name)) { + return; + } + fp = find_func(name); + if (fp != NULL) { + (fp->uf_refcount)++; + } else if (isdigit(*name)) { + // Only give an error for a numbered function. + // Fail silently, when named or lambda function isn't found. + internal_error("func_ref()"); + } +} + +/// Count a reference to a Function. +void func_ptr_ref(ufunc_T *fp) +{ + if (fp != NULL) { + (fp->uf_refcount)++; + } +} + +/// @return true if items in "fc" do not have "copyID". That means they are not +/// referenced from anywhere that is in use. +static int can_free_funccal(funccall_T *fc, int copyID) +{ + return fc->l_varlist.lv_copyID != copyID + && fc->l_vars.dv_copyID != copyID + && fc->l_avars.dv_copyID != copyID + && fc->fc_copyID != copyID; +} + +/* + * ":return [expr]" + */ +void ex_return(exarg_T *eap) +{ + char_u *arg = eap->arg; + typval_T rettv; + int returning = FALSE; + + if (current_funccal == NULL) { + EMSG(_("E133: :return not inside a function")); + return; + } + + if (eap->skip) + ++emsg_skip; + + eap->nextcmd = NULL; + if ((*arg != NUL && *arg != '|' && *arg != '\n') + && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + if (!eap->skip) { + returning = do_return(eap, false, true, &rettv); + } else { + tv_clear(&rettv); + } + } else if (!eap->skip) { // It's safer to return also on error. + // In return statement, cause_abort should be force_abort. + update_force_abort(); + + // Return unless the expression evaluation has been cancelled due to an + // aborting error, an interrupt, or an exception. + if (!aborting()) { + returning = do_return(eap, false, true, NULL); + } + } + + /* When skipping or the return gets pending, advance to the next command + * in this line (!returning). Otherwise, ignore the rest of the line. + * Following lines will be ignored by get_func_line(). */ + if (returning) + eap->nextcmd = NULL; + else if (eap->nextcmd == NULL) /* no argument */ + eap->nextcmd = check_nextcmd(arg); + + if (eap->skip) + --emsg_skip; +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/* + * ":1,25call func(arg1, arg2)" function call. + */ +void ex_call(exarg_T *eap) +{ + char_u *arg = eap->arg; + char_u *startarg; + char_u *name; + char_u *tofree; + int len; + typval_T rettv; + linenr_T lnum; + int doesrange; + bool failed = false; + funcdict_T fudi; + partial_T *partial = NULL; + + if (eap->skip) { + // trans_function_name() doesn't work well when skipping, use eval0() + // instead to skip to any following command, e.g. for: + // :if 0 | call dict.foo().bar() | endif. + emsg_skip++; + if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) { + tv_clear(&rettv); + } + emsg_skip--; + return; + } + + tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); + if (fudi.fd_newkey != NULL) { + // Still need to give an error message for missing key. + EMSG2(_(e_dictkey), fudi.fd_newkey); + xfree(fudi.fd_newkey); + } + if (tofree == NULL) { + return; + } + + // Increase refcount on dictionary, it could get deleted when evaluating + // the arguments. + if (fudi.fd_dict != NULL) { + fudi.fd_dict->dv_refcount++; + } + + // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its + // contents. For VAR_PARTIAL get its partial, unless we already have one + // from trans_function_name(). + len = (int)STRLEN(tofree); + name = deref_func_name((const char *)tofree, &len, + partial != NULL ? NULL : &partial, false); + + // Skip white space to allow ":call func ()". Not good, but required for + // backward compatibility. + startarg = skipwhite(arg); + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. + + if (*startarg != '(') { + EMSG2(_("E107: Missing parentheses: %s"), eap->arg); + goto end; + } + + lnum = eap->line1; + for (; lnum <= eap->line2; lnum++) { + if (eap->addr_count > 0) { // -V560 + if (lnum > curbuf->b_ml.ml_line_count) { + // If the function deleted lines or switched to another buffer + // the line number may become invalid. + EMSG(_(e_invrange)); + break; + } + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + } + arg = startarg; + if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, + eap->line1, eap->line2, &doesrange, + true, partial, fudi.fd_dict) == FAIL) { + failed = true; + break; + } + + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript((const char **)&arg, &rettv, true, true) + == FAIL) { + failed = true; + break; + } + + tv_clear(&rettv); + if (doesrange) { + break; + } + + // Stop when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + // get_func_tv() returned OK, so that the check for trailing + // characters below is executed. + if (aborting()) { + break; + } + } + + if (!failed) { + // Check for trailing illegal characters and a following command. + if (!ends_excmd(*arg)) { + emsg_severe = TRUE; + EMSG(_(e_trailing)); + } else { + eap->nextcmd = check_nextcmd(arg); + } + } + +end: + tv_dict_unref(fudi.fd_dict); + xfree(tofree); +} + +/* + * Return from a function. Possibly makes the return pending. Also called + * for a pending return at the ":endtry" or after returning from an extra + * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set + * when called due to a ":return" command. "rettv" may point to a typval_T + * with the return rettv. Returns TRUE when the return can be carried out, + * FALSE when the return gets pending. + */ +int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) +{ + int idx; + cstack_T *const cstack = eap->cstack; + + if (reanimate) + /* Undo the return. */ + current_funccal->returned = FALSE; + + /* + * Cleanup (and inactivate) conditionals, but stop when a try conditional + * not in its finally clause (which then is to be executed next) is found. + * In this case, make the ":return" pending for execution at the ":endtry". + * Otherwise, return normally. + */ + idx = cleanup_conditionals(eap->cstack, 0, TRUE); + if (idx >= 0) { + cstack->cs_pending[idx] = CSTP_RETURN; + + if (!is_cmd && !reanimate) + /* A pending return again gets pending. "rettv" points to an + * allocated variable with the rettv of the original ":return"'s + * argument if present or is NULL else. */ + cstack->cs_rettv[idx] = rettv; + else { + /* When undoing a return in order to make it pending, get the stored + * return rettv. */ + if (reanimate) { + assert(current_funccal->rettv); + rettv = current_funccal->rettv; + } + + if (rettv != NULL) { + /* Store the value of the pending return. */ + cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T)); + *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv; + } else + cstack->cs_rettv[idx] = NULL; + + if (reanimate) { + /* The pending return value could be overwritten by a ":return" + * without argument in a finally clause; reset the default + * return value. */ + current_funccal->rettv->v_type = VAR_NUMBER; + current_funccal->rettv->vval.v_number = 0; + } + } + report_make_pending(CSTP_RETURN, rettv); + } else { + current_funccal->returned = TRUE; + + /* If the return is carried out now, store the return value. For + * a return immediately after reanimation, the value is already + * there. */ + if (!reanimate && rettv != NULL) { + tv_clear(current_funccal->rettv); + *current_funccal->rettv = *(typval_T *)rettv; + if (!is_cmd) + xfree(rettv); + } + } + + return idx < 0; +} + +/* + * Generate a return command for producing the value of "rettv". The result + * is an allocated string. Used by report_pending() for verbose messages. + */ +char_u *get_return_cmd(void *rettv) +{ + char_u *s = NULL; + char_u *tofree = NULL; + + if (rettv != NULL) { + tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL); + } + if (s == NULL) { + s = (char_u *)""; + } + + STRCPY(IObuff, ":return "); + STRLCPY(IObuff + 8, s, IOSIZE - 8); + if (STRLEN(s) + 8 >= IOSIZE) + STRCPY(IObuff + IOSIZE - 4, "..."); + xfree(tofree); + return vim_strsave(IObuff); +} + +/* + * Get next function line. + * Called by do_cmdline() to get the next line. + * Returns allocated string, or NULL for end of function. + */ +char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + char_u *retval; + garray_T *gap; /* growarray with function lines */ + + /* If breakpoints have been added/deleted need to check for it. */ + if (fcp->dbg_tick != debug_tick) { + fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) + func_line_end(cookie); + + gap = &fp->uf_lines; + if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned) + retval = NULL; + else { + /* Skip NULL lines (continuation lines). */ + while (fcp->linenr < gap->ga_len + && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) + ++fcp->linenr; + if (fcp->linenr >= gap->ga_len) + retval = NULL; + else { + retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); + sourcing_lnum = fcp->linenr; + if (do_profiling == PROF_YES) + func_line_start(cookie); + } + } + + /* Did we encounter a breakpoint? */ + if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { + dbg_breakpoint(fp->uf_name, sourcing_lnum); + /* Find next breakpoint. */ + fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + + return retval; +} + +/* + * Return TRUE if the currently active function should be ended, because a + * return was encountered or an error occurred. Used inside a ":while". + */ +int func_has_ended(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + + /* Ignore the "abort" flag if the abortion behavior has been changed due to + * an error inside a try conditional. */ + return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned; +} + +/* + * return TRUE if cookie indicates a function which "abort"s on errors. + */ +int func_has_abort(void *cookie) +{ + return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; +} + +/* + * Return the name of the executed function. + */ +char_u *func_name(void *cookie) +{ + return ((funccall_T *)cookie)->func->uf_name; +} + +/* + * Return the address holding the next breakpoint line for a funccall cookie. + */ +linenr_T *func_breakpoint(void *cookie) +{ + return &((funccall_T *)cookie)->breakpoint; +} + +/* + * Return the address holding the debug tick for a funccall cookie. + */ +int *func_dbg_tick(void *cookie) +{ + return &((funccall_T *)cookie)->dbg_tick; +} + +/* + * Return the nesting level for a funccall cookie. + */ +int func_level(void *cookie) +{ + return ((funccall_T *)cookie)->level; +} + +/* + * Return TRUE when a function was ended by a ":return" command. + */ +int current_func_returned(void) +{ + return current_funccal->returned; +} + +// Get function call environment based on backtrace debug level +static funccall_T *get_funccal(void) +{ + funccall_T *funccal = current_funccal; + if (debug_backtrace_level > 0) { + for (int i = 0; i < debug_backtrace_level; i++) { + funccall_T *temp_funccal = funccal->caller; + if (temp_funccal) { + funccal = temp_funccal; + } else { + // backtrace level overflow. reset to max + debug_backtrace_level = i; + } + } + } + + return funccal; +} + +/// Return the hashtable used for local variables in the current funccal. +/// Return NULL if there is no current funccal. +static hashtab_T *get_funccal_local_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_vars.dv_hashtab; +} + +/// Return the hashtable used for argument in the current funccal. +/// Return NULL if there is no current funccal. +static hashtab_T *get_funccal_args_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_avars.dv_hashtab; +} + +/* + * List function variables, if there is a function. + */ +static void list_func_vars(int *first) +{ + if (current_funccal != NULL) { + list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, + first); + } +} + +/// Search hashitem in parent scope. +hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + funccall_T *old_current_funccal = current_funccal; + hashitem_T *hi = NULL; + const size_t namelen = strlen(name); + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal != NULL) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + hi = hash_find_len(ht, varname, namelen - (varname - name)); + if (!HASHITEM_EMPTY(hi)) { + *pht = ht; + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return hi; +} + +/// Search variable in parent scope. +dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, + int no_autoload) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + dictitem_T *v = NULL; + funccall_T *old_current_funccal = current_funccal; + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + v = find_var_in_ht(ht, *name, varname, + namelen - (size_t)(varname - name), no_autoload); + if (v != NULL) { + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return v; +} + +static bool set_ref_in_funccal(funccall_T *fc, int copyID) +{ + bool abort = false; + + if (fc->fc_copyID != copyID) { + fc->fc_copyID = copyID; + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_func(NULL, fc->func, copyID); + } + return abort; +} + +/// Set "copyID" in all functions available by name. +bool set_ref_in_functions(int copyID) +{ + int todo; + hashitem_T *hi = NULL; + bool abort = false; + ufunc_T *fp; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + fp = HI2UF(hi); + if (!func_name_refcount(fp->uf_name)) { + abort = abort || set_ref_in_func(NULL, fp, copyID); + } + } + } + return abort; +} + +/// Mark all lists and dicts referenced through function "name" with "copyID". +/// "list_stack" is used to add lists to be marked. Can be NULL. +/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. +/// +/// @return true if setting references failed somehow. +bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +{ + ufunc_T *fp = fp_in; + funccall_T *fc; + int error = ERROR_NONE; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + bool abort = false; + if (name == NULL && fp_in == NULL) { + return false; + } + + if (fp_in == NULL) { + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + fp = find_func(fname); + } + if (fp != NULL) { + for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { + abort = abort || set_ref_in_funccal(fc, copyID); + } + } + xfree(tofree); + return abort; +} -- cgit From dc5debadc2ee2bef2674f25b9aaa64d2d08a9c7c Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Fri, 7 Feb 2020 19:13:23 +0100 Subject: created header file --- src/nvim/eval/user_funcs.c | 7 +++++++ src/nvim/eval/user_funcs.h | 9 +++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/nvim/eval/user_funcs.h (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 24a7251e94..2531b943c7 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -1,3 +1,10 @@ +// 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 + +// User defined function support + +#include "nvim/eval/user_funcs.h" + // flags used in uf_flags #define FC_ABORT 0x01 // abort function on error #define FC_RANGE 0x02 // function accepts range diff --git a/src/nvim/eval/user_funcs.h b/src/nvim/eval/user_funcs.h new file mode 100644 index 0000000000..5c82d32c8a --- /dev/null +++ b/src/nvim/eval/user_funcs.h @@ -0,0 +1,9 @@ +#ifndef NVIM_EVAL_USER_FUNCS_H +#define NVIM_EVAL_USER_FUNCS_H + +#include "nvim/eval/typeval.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/user_funcs.h.generated.h" +#endif +#endif // NVIM_EVAL_USER_FUNCS_H -- cgit From 3b0475927cc9335a1e0ff238bd9221c7444914bb Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Fri, 7 Feb 2020 19:46:11 +0100 Subject: moved more stuff --- src/nvim/eval/user_funcs.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.h b/src/nvim/eval/user_funcs.h index 5c82d32c8a..3c714c042d 100644 --- a/src/nvim/eval/user_funcs.h +++ b/src/nvim/eval/user_funcs.h @@ -3,6 +3,26 @@ #include "nvim/eval/typeval.h" +///< 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; + +/// errors for when calling a function +typedef enum { + ERROR_UNKNOWN = 0, + ERROR_TOOMANY, + ERROR_TOOFEW, + ERROR_SCRIPT, + ERROR_DICT, + ERROR_NONE, + ERROR_OTHER, + ERROR_BOTH, + ERROR_DELETED, +} FnameTransError; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/user_funcs.h.generated.h" #endif -- cgit From 700d7a037c60abe2a76c4949bdc81b5421de4a8c Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Sun, 9 Feb 2020 12:23:13 +0100 Subject: unstatic some functions --- src/nvim/eval/user_funcs.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 2531b943c7..a4031b4b5e 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -168,7 +168,7 @@ static void register_closure(ufunc_T *fp) /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) +int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) { garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; @@ -296,7 +296,7 @@ errret: /// was not found. /// /// @return name of the function. -static char_u *deref_func_name(const char *name, int *lenp, +char_u *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) FUNC_ATTR_NONNULL_ARG(1, 2) { @@ -357,7 +357,7 @@ static void emsg_funcname(char *ermsg, const char_u *name) * Allocate a variable for the result of a function. * Return OK or FAIL. */ -static int +int get_func_tv( const char_u *name, // name of the function int len, // length of "name" @@ -505,7 +505,7 @@ static char_u *fname_trans_sid(const char_u *const name, /// Find a function by name, return pointer to it in ufuncs. /// @return NULL for unknown function. -static ufunc_T *find_func(const char_u *name) +ufunc_T *find_func(const char_u *name) { hashitem_T *hi; @@ -1499,7 +1499,7 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) /// Advances "pp" to just after the function name (if no error). /// /// @return the function name in allocated memory, or NULL for failure. -static char_u * +char_u * trans_function_name( char_u **pp, bool skip, // only find the end, don't evaluate @@ -2387,7 +2387,7 @@ ret_free: * Return 2 if "p" starts with "s:". * Return 0 otherwise. */ -static int eval_fname_script(const char *const p) +int eval_fname_script(const char *const p) { // Use mb_strnicmp() because in Turkish comparing the "I" may not work with // the standard library function. @@ -3014,7 +3014,7 @@ int current_func_returned(void) } // Get function call environment based on backtrace debug level -static funccall_T *get_funccal(void) +funccall_T *get_funccal(void) { funccall_T *funccal = current_funccal; if (debug_backtrace_level > 0) { @@ -3034,7 +3034,7 @@ static funccall_T *get_funccal(void) /// Return the hashtable used for local variables in the current funccal. /// Return NULL if there is no current funccal. -static hashtab_T *get_funccal_local_ht(void) +hashtab_T *get_funccal_local_ht(void) { if (current_funccal == NULL) { return NULL; @@ -3044,7 +3044,7 @@ static hashtab_T *get_funccal_local_ht(void) /// Return the hashtable used for argument in the current funccal. /// Return NULL if there is no current funccal. -static hashtab_T *get_funccal_args_ht(void) +hashtab_T *get_funccal_args_ht(void) { if (current_funccal == NULL) { return NULL; @@ -3055,7 +3055,7 @@ static hashtab_T *get_funccal_args_ht(void) /* * List function variables, if there is a function. */ -static void list_func_vars(int *first) +void list_func_vars(int *first) { if (current_funccal != NULL) { list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, -- cgit From c769b15fe49fb56151df065b40a80d69fb15b4fc Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Fri, 7 Feb 2020 20:20:41 +0100 Subject: fix: func_init --- src/nvim/eval/user_funcs.c | 5 +++++ src/nvim/eval/user_funcs.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index a4031b4b5e..7de9922f96 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -63,6 +63,11 @@ static char *e_funcdict = N_("E717: Dictionary entry already exists"); static char *e_funcref = N_("E718: Funcref required"); static char *e_nofunc = N_("E130: Unknown function: %s"); +void func_init(void) +{ + hash_init(&func_hashtab); +} + /// Get function arguments. static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs, bool skip) diff --git a/src/nvim/eval/user_funcs.h b/src/nvim/eval/user_funcs.h index 3c714c042d..81a5ac12b4 100644 --- a/src/nvim/eval/user_funcs.h +++ b/src/nvim/eval/user_funcs.h @@ -1,7 +1,7 @@ #ifndef NVIM_EVAL_USER_FUNCS_H #define NVIM_EVAL_USER_FUNCS_H -#include "nvim/eval/typeval.h" +#include "nvim/eval/typval.h" ///< Structure used by trans_function_name() typedef struct { -- cgit From e50b545676822eefa3ab6b00b4222cc34c5f4633 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Sun, 9 Feb 2020 23:11:36 +0100 Subject: fix: factor out new functions free_unref_funccal get_funccal_local_var get_funccal_args_var get_current_funccal_dict set_ref_in_previous_funccal set_ref_in_call_stack set_ref_in_func_args note: In vim semantic for garbage_collect was changed (the result of free_unref_funccal is ignored, bug or intentional?) For nvim I did leave previous behavior thus did_free = did_free || free_unref_funccal(copyID, testing); instead of just free_unref_funccal(copyID, testing); --- src/nvim/eval/user_funcs.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 7de9922f96..487d290f6b 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -3018,6 +3018,30 @@ int current_func_returned(void) return current_funccal->returned; } +bool free_unref_funccal(int copyID, int testing) +{ + bool did_free = false; + bool did_free_funccal = false; + + for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) { + if (can_free_funccal(*pfc, copyID)) { + funccall_T *fc = *pfc; + *pfc = fc->caller; + free_funccal(fc, true); + did_free = true; + did_free_funccal = true; + } else { + pfc = &(*pfc)->caller; + } + } + if (did_free_funccal) { + // When a funccal was freed some more items might be garbage + // collected, so run again. + (void)garbage_collect(testing); + } + return did_free; +} + // Get function call environment based on backtrace debug level funccall_T *get_funccal(void) { @@ -3047,6 +3071,16 @@ hashtab_T *get_funccal_local_ht(void) return &get_funccal()->l_vars.dv_hashtab; } +/// Return the l: scope variable. +/// Return NULL if there is no current funccal. +dictitem_T *get_funccal_local_var(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return (dictitem_T *)&get_funccal()->l_vars_var; +} + /// Return the hashtable used for argument in the current funccal. /// Return NULL if there is no current funccal. hashtab_T *get_funccal_args_ht(void) @@ -3057,6 +3091,16 @@ hashtab_T *get_funccal_args_ht(void) return &get_funccal()->l_avars.dv_hashtab; } +/// Return the a: scope variable. +/// Return NULL if there is no current funccal. +dictitem_T *get_funccal_args_var(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return (dictitem_T *)¤t_funccal->l_avars_var; +} + /* * List function variables, if there is a function. */ @@ -3068,6 +3112,17 @@ void list_func_vars(int *first) } } +/// If "ht" is the hashtable for local variables in the current funccal, return +/// the dict that contains it. +/// Otherwise return NULL. +dict_T *get_current_funccal_dict(hashtab_T *ht) +{ + if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { + return ¤t_funccal->l_vars; + } + return NULL; +} + /// Search hashitem in parent scope. hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) { @@ -3134,6 +3189,18 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, return v; } +/// Set "copyID + 1" in previous_funccal and callers. +bool set_ref_in_previous_funccal(int copyID) +{ + bool abort = false; + + for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL); + } + return abort; +} + static bool set_ref_in_funccal(funccall_T *fc, int copyID) { bool abort = false; @@ -3147,6 +3214,18 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID) return abort; } +/// Set "copyID" in all local vars and arguments in the call stack. +bool set_ref_in_call_stack(int copyID) +{ + bool abort = false; + + for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + } + return abort; +} + /// Set "copyID" in all functions available by name. bool set_ref_in_functions(int copyID) { @@ -3168,6 +3247,18 @@ bool set_ref_in_functions(int copyID) return abort; } +/// Set "copyID" in all function arguments. +bool set_ref_in_func_args(int copyID) +{ + bool abort = false; + + for (int i = 0; i < funcargs.ga_len; i++) { + abort = abort || set_ref_in_item(((typval_T **)funcargs.ga_data)[i], + copyID, NULL, NULL); + } + return abort; +} + /// Mark all lists and dicts referenced through function "name" with "copyID". /// "list_stack" is used to add lists to be marked. Can be NULL. /// "ht_stack" is used to add hashtabs to be marked. Can be NULL. -- cgit From 04f7e1f56d5f8c92fad49504e0f2215fdd72d326 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 11:23:58 +0100 Subject: fix: var_set_global --- src/nvim/eval/user_funcs.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 487d290f6b..3c64e807ba 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -1117,9 +1117,7 @@ void *save_funccal(void) void restore_funccal(void *vfc) { - funccall_T *fc = (funccall_T *)vfc; - - current_funccal = fc; + current_funccal = (funccall_T *)vfc; } #if defined(EXITFREE) -- cgit From b683957763f4c804d343aec9242ff5e67e0b8653 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 12:03:55 +0100 Subject: fix: prof functions --- src/nvim/eval/user_funcs.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 3c64e807ba..1b8d714566 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -1120,6 +1120,11 @@ void restore_funccal(void *vfc) current_funccal = (funccall_T *)vfc; } +funccall_T *get_current_funccal(void) +{ + return current_funccal; +} + #if defined(EXITFREE) void free_all_functions(void) { -- cgit From 423fcbee4a0edb6b89180e0097b59a8e6c0fc23f Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 13:49:30 +0100 Subject: fix: factor out make_partial --- src/nvim/eval/user_funcs.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 1b8d714566..72313c3bd7 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -2981,6 +2981,67 @@ int func_has_abort(void *cookie) return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; } +/// Turn "dict.Func" into a partial for "Func" bound to "dict". +/// Changes "rettv" in-place. +void make_partial(dict_T *const selfdict, typval_T *const rettv) +{ + char_u *fname; + char_u *tofree = NULL; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + int error; + + if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { + fp = rettv->vval.v_partial->pt_func; + } else { + fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING + ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; + // Translate "s:func" to the stored function name. + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); + fp = find_func(fname); + xfree(tofree); + } + + // Turn "dict.Func" into a partial for "Func" with "dict". + if (fp != NULL && (fp->uf_flags & FC_DICT)) { + partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + pt->pt_refcount = 1; + pt->pt_dict = selfdict; + (selfdict->dv_refcount)++; + pt->pt_auto = true; + if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { + // Just a function: Take over the function name and use selfdict. + pt->pt_name = rettv->vval.v_string; + } else { + partial_T *ret_pt = rettv->vval.v_partial; + int i; + + // Partial: copy the function name, use selfdict and copy + // args. Can't take over name or args, the partial might + // be referenced elsewhere. + if (ret_pt->pt_name != NULL) { + pt->pt_name = vim_strsave(ret_pt->pt_name); + func_ref(pt->pt_name); + } else { + pt->pt_func = ret_pt->pt_func; + func_ptr_ref(pt->pt_func); + } + if (ret_pt->pt_argc > 0) { + size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; + pt->pt_argv = (typval_T *)xmalloc(arg_size); + pt->pt_argc = ret_pt->pt_argc; + for (i = 0; i < pt->pt_argc; i++) { + tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); + } + } + partial_unref(ret_pt); + } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } +} + /* * Return the name of the executed function. */ -- cgit From 3477512f810f5dc9717301e4ff76b5ef368d67e8 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 14:20:12 +0100 Subject: fix: header updates --- src/nvim/eval/typval.h | 33 ++++++++++++++++++++++++++++++--- src/nvim/eval/user_funcs.c | 28 ---------------------------- src/nvim/eval/user_funcs.h | 3 +++ 3 files changed, 33 insertions(+), 31 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 008453b87f..cc12ad08fa 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -258,9 +258,39 @@ typedef struct { linenr_T sc_lnum; // line number } sctx_T; +/// Maximum number of function arguments +#define MAX_FUNC_ARGS 20 +/// Short variable name length +#define VAR_SHORT_LEN 20 +/// Number of fixed variables used for arguments +#define FIXVAR_CNT 12 + // Structure to hold info for a function that is currently being executed. typedef struct funccall_S funccall_T; +struct funccall_S { + ufunc_T *func; ///< Function being called. + int linenr; ///< Next line to be executed. + int returned; ///< ":return" used. + /// Fixed variables for arguments. + TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; + dict_T l_vars; ///< l: local function variables. + ScopeDictDictItem l_vars_var; ///< Variable for l: scope. + dict_T l_avars; ///< a: argument variables. + ScopeDictDictItem l_avars_var; ///< Variable for a: scope. + list_T l_varlist; ///< List for a:000. + listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. + typval_T *rettv; ///< Return value. + linenr_T breakpoint; ///< Next line with breakpoint or zero. + int dbg_tick; ///< Debug_tick when breakpoint was set. + int level; ///< Top nesting level of executed function. + proftime_T prof_child; ///< Time spent in a child. + funccall_T *caller; ///< Calling function or NULL. + int fc_refcount; ///< Number of user functions that reference this funccall. + int fc_copyID; ///< CopyID used for garbage collection. + garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". +}; + /// Structure to hold info for a user function. struct ufunc { int uf_varargs; ///< variable nr of arguments @@ -293,9 +323,6 @@ struct ufunc { ///< ( is K_SPECIAL KS_EXTRA KE_SNR) }; -/// Maximum number of function arguments -#define MAX_FUNC_ARGS 20 - struct partial_S { int pt_refcount; ///< Reference count. char_u *pt_name; ///< Function name; when NULL use pt_func->name. diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 72313c3bd7..76a9438314 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -17,34 +17,6 @@ #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] -/// Short variable name length -#define VAR_SHORT_LEN 20 -/// Number of fixed variables used for arguments -#define FIXVAR_CNT 12 - -struct funccall_S { - ufunc_T *func; ///< Function being called. - int linenr; ///< Next line to be executed. - int returned; ///< ":return" used. - /// Fixed variables for arguments. - TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; - dict_T l_vars; ///< l: local function variables. - ScopeDictDictItem l_vars_var; ///< Variable for l: scope. - dict_T l_avars; ///< a: argument variables. - ScopeDictDictItem l_avars_var; ///< Variable for a: scope. - list_T l_varlist; ///< List for a:000. - listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. - typval_T *rettv; ///< Return value. - linenr_T breakpoint; ///< Next line with breakpoint or zero. - int dbg_tick; ///< Debug_tick when breakpoint was set. - int level; ///< Top nesting level of executed function. - proftime_T prof_child; ///< Time spent in a child. - funccall_T *caller; ///< Calling function or NULL. - int fc_refcount; ///< Number of user functions that reference this funccall. - int fc_copyID; ///< CopyID used for garbage collection. - garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". -}; - hashtab_T func_hashtab; // Used by get_func_tv() diff --git a/src/nvim/eval/user_funcs.h b/src/nvim/eval/user_funcs.h index 81a5ac12b4..a62172973f 100644 --- a/src/nvim/eval/user_funcs.h +++ b/src/nvim/eval/user_funcs.h @@ -23,6 +23,9 @@ typedef enum { ERROR_DELETED, } FnameTransError; +typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, + int called_func_argcount); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/user_funcs.h.generated.h" #endif -- cgit From 9d7ce03ef1f7332aee8ff2d752400ab0846d4191 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 20:42:54 +0100 Subject: fix: include static function declarations --- src/nvim/eval/user_funcs.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 76a9438314..5ce7641a28 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -17,6 +17,10 @@ #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "eval/user_funcs.c.generated.h" +#endif + hashtab_T func_hashtab; // Used by get_func_tv() -- cgit From 1e0a9b26908ab1f48e58435da9efca691179d63d Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Tue, 11 Feb 2020 12:18:22 +0100 Subject: fix: made eval_lavars_used global --- src/nvim/eval/user_funcs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 5ce7641a28..b3bd1e2860 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -159,8 +159,8 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) char_u *start = skipwhite(*arg + 1); char_u *s, *e; static int lambda_no = 0; - int *old_eval_lavars = eval_lavars_used; - int eval_lavars = false; + bool *old_eval_lavars = eval_lavars_used; + bool eval_lavars = false; // First, check if this is a lambda expression. "->" must exists. ret = get_function_args(&start, '-', NULL, NULL, true); -- cgit From 3c413f15238d3c48c0606d661841477cb2c9ed5c Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Tue, 11 Feb 2020 13:08:32 +0100 Subject: fix: vvlua_partial --- src/nvim/eval/user_funcs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index b3bd1e2860..b3706c7754 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -1304,7 +1304,7 @@ call_func( rettv->vval.v_number = 0; error = ERROR_UNKNOWN; - if (partial == vvlua_partial) { + if (is_luafunc(partial)) { if (len > 0) { error = ERROR_NONE; executor_call_lua((const char *)funcname, len, @@ -1556,7 +1556,7 @@ trans_function_name( *pp = (char_u *)end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { - if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { + if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') { len = check_luafunc_name((const char *)end+1, true); if (len == 0) { EMSG2(e_invexpr2, "v:lua"); -- cgit From c7ef0e8d2d6fd033f89441839dde4d1d2588a940 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Tue, 11 Feb 2020 15:55:21 +0100 Subject: fix: moved some static inline function --- src/nvim/eval/user_funcs.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index b3706c7754..7b13b0c539 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -2607,6 +2607,22 @@ void func_ptr_ref(ufunc_T *fp) } } +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + != DO_NOT_FREE_CNT) + || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_refcount > 0); +} + /// @return true if items in "fc" do not have "copyID". That means they are not /// referenced from anywhere that is in use. static int can_free_funccal(funccall_T *fc, int copyID) -- cgit From e367e80e1123d9cd4f6c4c650cf92fc6f15a5bcc Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Tue, 11 Feb 2020 15:57:26 +0100 Subject: fix: moved macros --- src/nvim/eval/user_funcs.c | 3 --- src/nvim/eval/user_funcs.h | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 7b13b0c539..825232f01b 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -14,9 +14,6 @@ #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 #define FC_SANDBOX 0x40 // function defined in the sandbox -#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] -#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] - #ifdef INCLUDE_GENERATED_DECLARATIONS #include "eval/user_funcs.c.generated.h" #endif diff --git a/src/nvim/eval/user_funcs.h b/src/nvim/eval/user_funcs.h index a62172973f..409a1243ac 100644 --- a/src/nvim/eval/user_funcs.h +++ b/src/nvim/eval/user_funcs.h @@ -26,6 +26,9 @@ typedef enum { typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int called_func_argcount); +#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] +#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/user_funcs.h.generated.h" #endif -- cgit From d50c1123d5616c9757bb5707416696cda1a43716 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 18:34:18 +0100 Subject: fix: includes --- src/nvim/eval/funcs.c | 1 + src/nvim/eval/typval.c | 1 + src/nvim/eval/user_funcs.c | 19 +++++++++++++++++++ src/nvim/eval/user_funcs.h | 1 + 4 files changed, 22 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 36782e4223..0b849d8a4f 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -21,6 +21,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/user_funcs.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index da97eccc65..16b38b47dc 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -15,6 +15,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/typval_encode.h" #include "nvim/eval.h" +#include "nvim/eval/user_funcs.h" #include "nvim/types.h" #include "nvim/assert.h" #include "nvim/memory.h" diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c index 825232f01b..a3eb3c813f 100644 --- a/src/nvim/eval/user_funcs.c +++ b/src/nvim/eval/user_funcs.c @@ -3,7 +3,26 @@ // User defined function support +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" #include "nvim/eval/user_funcs.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/ex_getln.h" +#include "nvim/fileio.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" +#include "nvim/lua/executor.h" +#include "nvim/misc1.h" +#include "nvim/os/input.h" +#include "nvim/regexp.h" +#include "nvim/search.h" +#include "nvim/ui.h" +#include "nvim/vim.h" // flags used in uf_flags #define FC_ABORT 0x01 // abort function on error diff --git a/src/nvim/eval/user_funcs.h b/src/nvim/eval/user_funcs.h index 409a1243ac..b325c2e976 100644 --- a/src/nvim/eval/user_funcs.h +++ b/src/nvim/eval/user_funcs.h @@ -2,6 +2,7 @@ #define NVIM_EVAL_USER_FUNCS_H #include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" ///< Structure used by trans_function_name() typedef struct { -- cgit From 5e815edece308a91296720cd6cb8d988af6c90c8 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Tue, 11 Feb 2020 16:19:14 +0100 Subject: rename: user_funcs -> userfunc Lets stick with vim for now --- src/nvim/eval/funcs.c | 2 +- src/nvim/eval/typval.c | 2 +- src/nvim/eval/user_funcs.c | 3363 -------------------------------------------- src/nvim/eval/user_funcs.h | 36 - src/nvim/eval/userfunc.c | 3363 ++++++++++++++++++++++++++++++++++++++++++++ src/nvim/eval/userfunc.h | 36 + 6 files changed, 3401 insertions(+), 3401 deletions(-) delete mode 100644 src/nvim/eval/user_funcs.c delete mode 100644 src/nvim/eval/user_funcs.h create mode 100644 src/nvim/eval/userfunc.c create mode 100644 src/nvim/eval/userfunc.h (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0b849d8a4f..b7e02d2ce7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -21,7 +21,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" -#include "nvim/eval/user_funcs.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 16b38b47dc..c8b5fc294c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -15,7 +15,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/typval_encode.h" #include "nvim/eval.h" -#include "nvim/eval/user_funcs.h" +#include "nvim/eval/userfunc.h" #include "nvim/types.h" #include "nvim/assert.h" #include "nvim/memory.h" diff --git a/src/nvim/eval/user_funcs.c b/src/nvim/eval/user_funcs.c deleted file mode 100644 index a3eb3c813f..0000000000 --- a/src/nvim/eval/user_funcs.c +++ /dev/null @@ -1,3363 +0,0 @@ -// 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 - -// User defined function support - -#include "nvim/ascii.h" -#include "nvim/charset.h" -#include "nvim/edit.h" -#include "nvim/eval.h" -#include "nvim/eval/encode.h" -#include "nvim/eval/user_funcs.h" -#include "nvim/ex_cmds2.h" -#include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/getchar.h" -#include "nvim/globals.h" -#include "nvim/lua/executor.h" -#include "nvim/misc1.h" -#include "nvim/os/input.h" -#include "nvim/regexp.h" -#include "nvim/search.h" -#include "nvim/ui.h" -#include "nvim/vim.h" - -// flags used in uf_flags -#define FC_ABORT 0x01 // abort function on error -#define FC_RANGE 0x02 // function accepts range -#define FC_DICT 0x04 // Dict function, uses "self" -#define FC_CLOSURE 0x08 // closure, uses outer scope variables -#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 -#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 -#define FC_SANDBOX 0x40 // function defined in the sandbox - -#ifdef INCLUDE_GENERATED_DECLARATIONS -#include "eval/user_funcs.c.generated.h" -#endif - -hashtab_T func_hashtab; - -// Used by get_func_tv() -static garray_T funcargs = GA_EMPTY_INIT_VALUE; - -/* pointer to funccal for currently active function */ -funccall_T *current_funccal = NULL; - -// Pointer to list of previously used funccal, still around because some -// item in it is still being used. -funccall_T *previous_funccal = NULL; - -static char *e_funcexts = N_( - "E122: Function %s already exists, add ! to replace it"); -static char *e_funcdict = N_("E717: Dictionary entry already exists"); -static char *e_funcref = N_("E718: Funcref required"); -static char *e_nofunc = N_("E130: Unknown function: %s"); - -void func_init(void) -{ - hash_init(&func_hashtab); -} - -/// Get function arguments. -static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, - int *varargs, bool skip) -{ - bool mustend = false; - char_u *arg = *argp; - char_u *p = arg; - int c; - int i; - - if (newargs != NULL) { - ga_init(newargs, (int)sizeof(char_u *), 3); - } - - if (varargs != NULL) { - *varargs = false; - } - - // Isolate the arguments: "arg1, arg2, ...)" - while (*p != endchar) { - if (p[0] == '.' && p[1] == '.' && p[2] == '.') { - if (varargs != NULL) { - *varargs = true; - } - p += 3; - mustend = true; - } else { - arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') { - p++; - } - if (arg == p || isdigit(*arg) - || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) - || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { - if (!skip) { - EMSG2(_("E125: Illegal argument: %s"), arg); - } - break; - } - if (newargs != NULL) { - ga_grow(newargs, 1); - c = *p; - *p = NUL; - arg = vim_strsave(arg); - - // Check for duplicate argument name. - for (i = 0; i < newargs->ga_len; i++) { - if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { - EMSG2(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto err_ret; - } - } - ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; - newargs->ga_len++; - - *p = c; - } - if (*p == ',') { - p++; - } else { - mustend = true; - } - } - p = skipwhite(p); - if (mustend && *p != endchar) { - if (!skip) { - EMSG2(_(e_invarg2), *argp); - } - break; - } - } - if (*p != endchar) { - goto err_ret; - } - p++; // skip "endchar" - - *argp = p; - return OK; - -err_ret: - if (newargs != NULL) { - ga_clear_strings(newargs); - } - return FAIL; -} - -/// Register function "fp" as using "current_funccal" as its scope. -static void register_closure(ufunc_T *fp) -{ - if (fp->uf_scoped == current_funccal) { - // no change - return; - } - funccal_unref(fp->uf_scoped, fp, false); - fp->uf_scoped = current_funccal; - current_funccal->fc_refcount++; - ga_grow(¤t_funccal->fc_funcs, 1); - ((ufunc_T **)current_funccal->fc_funcs.ga_data) - [current_funccal->fc_funcs.ga_len++] = fp; -} - -/// Parse a lambda expression and get a Funcref from "*arg". -/// -/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) -{ - garray_T newargs = GA_EMPTY_INIT_VALUE; - garray_T *pnewargs; - ufunc_T *fp = NULL; - int varargs; - int ret; - char_u *start = skipwhite(*arg + 1); - char_u *s, *e; - static int lambda_no = 0; - bool *old_eval_lavars = eval_lavars_used; - bool eval_lavars = false; - - // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, true); - if (ret == FAIL || *start != '>') { - return NOTDONE; - } - - // Parse the arguments again. - if (evaluate) { - pnewargs = &newargs; - } else { - pnewargs = NULL; - } - *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', pnewargs, &varargs, false); - if (ret == FAIL || **arg != '>') { - goto errret; - } - - // Set up a flag for checking local variables and arguments. - if (evaluate) { - eval_lavars_used = &eval_lavars; - } - - // Get the start and the end of the expression. - *arg = skipwhite(*arg + 1); - s = *arg; - ret = skip_expr(arg); - if (ret == FAIL) { - goto errret; - } - e = *arg; - *arg = skipwhite(*arg); - if (**arg != '}') { - goto errret; - } - (*arg)++; - - if (evaluate) { - int len, flags = 0; - char_u *p; - char_u name[20]; - partial_T *pt; - garray_T newlines; - - lambda_no++; - snprintf((char *)name, sizeof(name), "%d", lambda_no); - - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); - pt = xcalloc(1, sizeof(partial_T)); - - ga_init(&newlines, (int)sizeof(char_u *), 1); - ga_grow(&newlines, 1); - - // Add "return " before the expression. - len = 7 + e - s + 1; - p = (char_u *)xmalloc(len); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; - STRCPY(p, "return "); - STRLCPY(p + 7, s, e - s + 1); - - fp->uf_refcount = 1; - STRCPY(fp->uf_name, name); - hash_add(&func_hashtab, UF2HIKEY(fp)); - fp->uf_args = newargs; - fp->uf_lines = newlines; - if (current_funccal != NULL && eval_lavars) { - flags |= FC_CLOSURE; - register_closure(fp); - } else { - fp->uf_scoped = NULL; - } - - if (prof_def_func()) { - func_do_profile(fp); - } - if (sandbox) { - flags |= FC_SANDBOX; - } - fp->uf_varargs = true; - fp->uf_flags = flags; - fp->uf_calls = 0; - fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; - - pt->pt_func = fp; - pt->pt_refcount = 1; - rettv->vval.v_partial = pt; - rettv->v_type = VAR_PARTIAL; - } - - eval_lavars_used = old_eval_lavars; - return OK; - -errret: - ga_clear_strings(&newargs); - xfree(fp); - eval_lavars_used = old_eval_lavars; - return FAIL; -} - -/// Return name of the function corresponding to `name` -/// -/// If `name` points to variable that is either a function or partial then -/// corresponding function name is returned. Otherwise it returns `name` itself. -/// -/// @param[in] name Function name to check. -/// @param[in,out] lenp Location where length of the returned name is stored. -/// Must be set to the length of the `name` argument. -/// @param[out] partialp Location where partial will be stored if found -/// function appears to be a partial. May be NULL if this -/// is not needed. -/// @param[in] no_autoload If true, do not source autoload scripts if function -/// was not found. -/// -/// @return name of the function. -char_u *deref_func_name(const char *name, int *lenp, - partial_T **const partialp, bool no_autoload) - FUNC_ATTR_NONNULL_ARG(1, 2) -{ - if (partialp != NULL) { - *partialp = NULL; - } - - dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - if (v->di_tv.vval.v_string == NULL) { // just in case - *lenp = 0; - return (char_u *)""; - } - *lenp = (int)STRLEN(v->di_tv.vval.v_string); - return v->di_tv.vval.v_string; - } - - if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - partial_T *const pt = v->di_tv.vval.v_partial; - - if (pt == NULL) { // just in case - *lenp = 0; - return (char_u *)""; - } - if (partialp != NULL) { - *partialp = pt; - } - char_u *s = partial_name(pt); - *lenp = (int)STRLEN(s); - return s; - } - - return (char_u *)name; -} - -/// Give an error message with a function name. Handle things. -/// -/// @param ermsg must be passed without translation (use N_() instead of _()). -/// @param name function name -static void emsg_funcname(char *ermsg, const char_u *name) -{ - char_u *p; - - if (*name == K_SPECIAL) { - p = concat_str((char_u *)"", name + 3); - } else { - p = (char_u *)name; - } - - EMSG2(_(ermsg), p); - - if (p != name) { - xfree(p); - } -} - -/* - * Allocate a variable for the result of a function. - * Return OK or FAIL. - */ -int -get_func_tv( - const char_u *name, // name of the function - int len, // length of "name" - typval_T *rettv, - char_u **arg, // argument, pointing to the '(' - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // return: function handled range - int evaluate, - partial_T *partial, // for extra arguments - dict_T *selfdict // Dictionary for "self" -) -{ - char_u *argp; - int ret = OK; - typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */ - int argcount = 0; /* number of arguments found */ - - /* - * Get the arguments. - */ - argp = *arg; - while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { - argp = skipwhite(argp + 1); // skip the '(' or ',' - if (*argp == ')' || *argp == ',' || *argp == NUL) { - break; - } - if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { - ret = FAIL; - break; - } - ++argcount; - if (*argp != ',') - break; - } - if (*argp == ')') - ++argp; - else - ret = FAIL; - - if (ret == OK) { - int i = 0; - - if (get_vim_var_nr(VV_TESTING)) { - // Prepare for calling garbagecollect_for_testing(), need to know - // what variables are used on the call stack. - if (funcargs.ga_itemsize == 0) { - ga_init(&funcargs, (int)sizeof(typval_T *), 50); - } - for (i = 0; i < argcount; i++) { - ga_grow(&funcargs, 1); - ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; - } - } - ret = call_func(name, len, rettv, argcount, argvars, NULL, - firstline, lastline, doesrange, evaluate, - partial, selfdict); - - funcargs.ga_len -= i; - } else if (!aborting()) { - if (argcount == MAX_FUNC_ARGS) { - emsg_funcname(N_("E740: Too many arguments for function %s"), name); - } else { - emsg_funcname(N_("E116: Invalid arguments for function %s"), name); - } - } - - while (--argcount >= 0) { - tv_clear(&argvars[argcount]); - } - - *arg = skipwhite(argp); - return ret; -} - -#define FLEN_FIXED 40 - -/// Check whether function name starts with or s: -/// -/// @warning Only works for names previously checked by eval_fname_script(), if -/// it returned non-zero. -/// -/// @param[in] name Name to check. -/// -/// @return true if it starts with or s:, false otherwise. -static inline bool eval_fname_sid(const char *const name) - FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT - FUNC_ATTR_NONNULL_ALL -{ - return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; -} - -/// In a script transform script-local names into actually used names -/// -/// Transforms "" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and -/// "" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have -/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. -/// -/// @param[in] name Name to transform. -/// @param fname_buf Buffer to save resulting function name to, if it fits. -/// Must have at least #FLEN_FIXED + 1 length. -/// @param[out] tofree Location where pointer to an allocated memory is saved -/// in case result does not fit into fname_buf. -/// @param[out] error Location where error type is saved, @see -/// FnameTransError. -/// -/// @return transformed name: either `fname_buf` or a pointer to an allocated -/// memory. -static char_u *fname_trans_sid(const char_u *const name, - char_u *const fname_buf, - char_u **const tofree, int *const error) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - char_u *fname; - const int llen = eval_fname_script((const char *)name); - if (llen > 0) { - fname_buf[0] = K_SPECIAL; - fname_buf[1] = KS_EXTRA; - fname_buf[2] = (int)KE_SNR; - int i = 3; - if (eval_fname_sid((const char *)name)) { // "" or "s:" - if (current_sctx.sc_sid <= 0) { - *error = ERROR_SCRIPT; - } else { - snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)STRLEN(fname_buf); - } - } - if (i + STRLEN(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); - fname = fname_buf; - } else { - fname = xmalloc(i + STRLEN(name + llen) + 1); - *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); - } - } else { - fname = (char_u *)name; - } - - return fname; -} - -/// Find a function by name, return pointer to it in ufuncs. -/// @return NULL for unknown function. -ufunc_T *find_func(const char_u *name) -{ - hashitem_T *hi; - - hi = hash_find(&func_hashtab, name); - if (!HASHITEM_EMPTY(hi)) - return HI2UF(hi); - return NULL; -} - -/* - * Copy the function name of "fp" to buffer "buf". - * "buf" must be able to hold the function name plus three bytes. - * Takes care of script-local function names. - */ -static void cat_func_name(char_u *buf, ufunc_T *fp) -{ - if (fp->uf_name[0] == K_SPECIAL) { - STRCPY(buf, ""); - STRCAT(buf, fp->uf_name + 3); - } else - STRCPY(buf, fp->uf_name); -} - -/* - * Add a number variable "name" to dict "dp" with value "nr". - */ -static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) -{ -#ifndef __clang_analyzer__ - STRCPY(v->di_key, name); -#endif - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(dp, v); - v->di_tv.v_type = VAR_NUMBER; - v->di_tv.v_lock = VAR_FIXED; - v->di_tv.vval.v_number = nr; -} - -/* - * Free "fc" and what it contains. - */ -static void -free_funccal( - funccall_T *fc, - int free_val /* a: vars were allocated */ -) -{ - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { - ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; - - // When garbage collecting a funccall_T may be freed before the - // function that references it, clear its uf_scoped field. - // The function may have been redefined and point to another - // funccal_T, don't clear it then. - if (fp != NULL && fp->uf_scoped == fc) { - fp->uf_scoped = NULL; - } - } - ga_clear(&fc->fc_funcs); - - // The a: variables typevals may not have been allocated, only free the - // allocated variables. - vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); - - // Free all l: variables. - vars_clear(&fc->l_vars.dv_hashtab); - - // Free the a:000 variables if they were allocated. - if (free_val) { - TV_LIST_ITER(&fc->l_varlist, li, { - tv_clear(TV_LIST_ITEM_TV(li)); - }); - } - - func_ptr_unref(fc->func); - xfree(fc); -} - -/// Handle the last part of returning from a function: free the local hashtable. -/// Unless it is still in use by a closure. -static void cleanup_function_call(funccall_T *fc) -{ - current_funccal = fc->caller; - - // If the a:000 list and the l: and a: dicts are not referenced and there - // is no closure using it, we can free the funccall_T and what's in it. - if (!fc_referenced(fc)) { - free_funccal(fc, false); - } else { - // "fc" is still in use. This can happen when returning "a:000", - // assigning "l:" to a global variable or defining a closure. - // Link "fc" in the list for garbage collection later. - fc->caller = previous_funccal; - previous_funccal = fc; - - // Make a copy of the a: variables, since we didn't do that above. - TV_DICT_ITER(&fc->l_avars, di, { - tv_copy(&di->di_tv, &di->di_tv); - }); - - // Make a copy of the a:000 items, since we didn't do that above. - TV_LIST_ITER(&fc->l_varlist, li, { - tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); - }); - } -} - -/// Unreference "fc": decrement the reference count and free it when it -/// becomes zero. "fp" is detached from "fc". -/// -/// @param[in] force When true, we are exiting. -static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) -{ - funccall_T **pfc; - int i; - - if (fc == NULL) { - return; - } - - fc->fc_refcount--; - if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { - for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { - if (fc == *pfc) { - *pfc = fc->caller; - free_funccal(fc, true); - return; - } - } - } - for (i = 0; i < fc->fc_funcs.ga_len; i++) { - if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { - ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; - } - } -} - -/// Remove the function from the function hashtable. If the function was -/// deleted while it still has references this was already done. -/// -/// @return true if the entry was deleted, false if it wasn't found. -static bool func_remove(ufunc_T *fp) -{ - hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); - - if (!HASHITEM_EMPTY(hi)) { - hash_remove(&func_hashtab, hi); - return true; - } - - return false; -} - -static void func_clear_items(ufunc_T *fp) -{ - ga_clear_strings(&(fp->uf_args)); - ga_clear_strings(&(fp->uf_lines)); - - XFREE_CLEAR(fp->uf_tml_count); - XFREE_CLEAR(fp->uf_tml_total); - XFREE_CLEAR(fp->uf_tml_self); -} - -/// Free all things that a function contains. Does not free the function -/// itself, use func_free() for that. -/// -/// param[in] force When true, we are exiting. -static void func_clear(ufunc_T *fp, bool force) -{ - if (fp->uf_cleared) { - return; - } - fp->uf_cleared = true; - - // clear this function - func_clear_items(fp); - funccal_unref(fp->uf_scoped, fp, force); -} - -/// Free a function and remove it from the list of functions. Does not free -/// what a function contains, call func_clear() first. -/// -/// param[in] fp The function to free. -static void func_free(ufunc_T *fp) -{ - // only remove it when not done already, otherwise we would remove a newer - // version of the function - if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { - func_remove(fp); - } - xfree(fp); -} - -/// Free all things that a function contains and free the function itself. -/// -/// param[in] force When true, we are exiting. -static void func_clear_free(ufunc_T *fp, bool force) -{ - func_clear(fp, force); - func_free(fp); -} - -/// Call a user function -/// -/// @param fp Function to call. -/// @param[in] argcount Number of arguments. -/// @param argvars Arguments. -/// @param[out] rettv Return value. -/// @param[in] firstline First line of range. -/// @param[in] lastline Last line of range. -/// @param selfdict Dictionary for "self" for dictionary functions. -void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, - typval_T *rettv, linenr_T firstline, linenr_T lastline, - dict_T *selfdict) - FUNC_ATTR_NONNULL_ARG(1, 3, 4) -{ - char_u *save_sourcing_name; - linenr_T save_sourcing_lnum; - bool using_sandbox = false; - funccall_T *fc; - int save_did_emsg; - static int depth = 0; - dictitem_T *v; - int fixvar_idx = 0; /* index in fixvar[] */ - int ai; - bool islambda = false; - char_u numbuf[NUMBUFLEN]; - char_u *name; - proftime_T wait_start; - proftime_T call_start; - int started_profiling = false; - bool did_save_redo = false; - save_redo_T save_redo; - - /* If depth of calling is getting too high, don't execute the function */ - if (depth >= p_mfd) { - EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'")); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = -1; - return; - } - ++depth; - // Save search patterns and redo buffer. - save_search_patterns(); - if (!ins_compl_active()) { - saveRedobuff(&save_redo); - did_save_redo = true; - } - ++fp->uf_calls; - // check for CTRL-C hit - line_breakcheck(); - // prepare the funccall_T structure - fc = xmalloc(sizeof(funccall_T)); - fc->caller = current_funccal; - current_funccal = fc; - fc->func = fp; - fc->rettv = rettv; - rettv->vval.v_number = 0; - fc->linenr = 0; - fc->returned = FALSE; - fc->level = ex_nesting_level; - /* Check if this function has a breakpoint. */ - fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); - fc->dbg_tick = debug_tick; - - // Set up fields for closure. - fc->fc_refcount = 0; - fc->fc_copyID = 0; - ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); - func_ptr_ref(fp); - - if (STRNCMP(fp->uf_name, "", 8) == 0) { - islambda = true; - } - - // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables - // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free - // each argument variable and saves a lot of time. - // - // Init l: variables. - init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); - if (selfdict != NULL) { - // Set l:self to "selfdict". Use "name" to avoid a warning from - // some compiler that checks the destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ - name = v->di_key; - STRCPY(name, "self"); -#endif - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_vars, v); - v->di_tv.v_type = VAR_DICT; - v->di_tv.v_lock = 0; - v->di_tv.vval.v_dict = selfdict; - ++selfdict->dv_refcount; - } - - /* - * Init a: variables. - * Set a:0 to "argcount". - * Set a:000 to a list with room for the "..." arguments. - */ - init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", - (varnumber_T)(argcount - fp->uf_args.ga_len)); - fc->l_avars.dv_lock = VAR_FIXED; - // Use "name" to avoid a warning from some compiler that checks the - // destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ - name = v->di_key; - STRCPY(name, "000"); -#endif - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_avars, v); - v->di_tv.v_type = VAR_LIST; - v->di_tv.v_lock = VAR_FIXED; - v->di_tv.vval.v_list = &fc->l_varlist; - tv_list_init_static(&fc->l_varlist); - tv_list_set_lock(&fc->l_varlist, VAR_FIXED); - - // Set a:firstline to "firstline" and a:lastline to "lastline". - // Set a:name to named arguments. - // Set a:N to the "..." arguments. - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], - "firstline", (varnumber_T)firstline); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], - "lastline", (varnumber_T)lastline); - for (int i = 0; i < argcount; i++) { - bool addlocal = false; - - ai = i - fp->uf_args.ga_len; - if (ai < 0) { - // named argument a:name - name = FUNCARG(fp, i); - if (islambda) { - addlocal = true; - } - } else { - // "..." argument a:1, a:2, etc. - snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); - name = numbuf; - } - if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - } else { - v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; - } - STRCPY(v->di_key, name); - - // Note: the values are copied directly to avoid alloc/free. - // "argvars" must have VAR_FIXED for v_lock. - v->di_tv = argvars[i]; - v->di_tv.v_lock = VAR_FIXED; - - if (addlocal) { - // Named arguments can be accessed without the "a:" prefix in lambda - // expressions. Add to the l: dict. - tv_copy(&v->di_tv, &v->di_tv); - tv_dict_add(&fc->l_vars, v); - } else { - tv_dict_add(&fc->l_avars, v); - } - - if (ai >= 0 && ai < MAX_FUNC_ARGS) { - tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; - TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; - } - } - - /* Don't redraw while executing the function. */ - ++RedrawingDisabled; - save_sourcing_name = sourcing_name; - save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 1; - - if (fp->uf_flags & FC_SANDBOX) { - using_sandbox = true; - sandbox++; - } - - // need space for new sourcing_name: - // * save_sourcing_name - // * "["number"].." or "function " - // * "" + fp->uf_name - 3 - // * terminating NUL - size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) - + STRLEN(fp->uf_name) + 27; - sourcing_name = xmalloc(len); - { - if (save_sourcing_name != NULL - && STRNCMP(save_sourcing_name, "function ", 9) == 0) { - vim_snprintf((char *)sourcing_name, - len, - "%s[%" PRId64 "]..", - save_sourcing_name, - (int64_t)save_sourcing_lnum); - } else { - STRCPY(sourcing_name, "function "); - } - cat_func_name(sourcing_name + STRLEN(sourcing_name), fp); - - if (p_verbose >= 12) { - ++no_wait_return; - verbose_enter_scroll(); - - smsg(_("calling %s"), sourcing_name); - if (p_verbose >= 14) { - msg_puts("("); - for (int i = 0; i < argcount; i++) { - if (i > 0) { - msg_puts(", "); - } - if (argvars[i].v_type == VAR_NUMBER) { - msg_outnum((long)argvars[i].vval.v_number); - } else { - // Do not want errors such as E724 here. - emsg_off++; - char *tofree = encode_tv2string(&argvars[i], NULL); - emsg_off--; - if (tofree != NULL) { - char *s = tofree; - char buf[MSG_BUF_LEN]; - if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { - trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, - sizeof(buf)); - s = buf; - } - msg_puts(s); - xfree(tofree); - } - } - } - msg_puts(")"); - } - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; - } - } - - const bool do_profiling_yes = do_profiling == PROF_YES; - - bool func_not_yet_profiling_but_should = - do_profiling_yes - && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); - - if (func_not_yet_profiling_but_should) { - started_profiling = true; - func_do_profile(fp); - } - - bool func_or_func_caller_profiling = - do_profiling_yes - && (fp->uf_profiling - || (fc->caller != NULL && fc->caller->func->uf_profiling)); - - if (func_or_func_caller_profiling) { - ++fp->uf_tm_count; - call_start = profile_start(); - fp->uf_tm_children = profile_zero(); - } - - if (do_profiling_yes) { - script_prof_save(&wait_start); - } - - const sctx_T save_current_sctx = current_sctx; - current_sctx = fp->uf_script_ctx; - save_did_emsg = did_emsg; - did_emsg = FALSE; - - /* call do_cmdline() to execute the lines */ - do_cmdline(NULL, get_func_line, (void *)fc, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); - - --RedrawingDisabled; - - // when the function was aborted because of an error, return -1 - if ((did_emsg - && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) { - tv_clear(rettv); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = -1; - } - - if (func_or_func_caller_profiling) { - call_start = profile_end(call_start); - call_start = profile_sub_wait(wait_start, call_start); // -V614 - fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); - fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, - fp->uf_tm_children); - if (fc->caller != NULL && fc->caller->func->uf_profiling) { - fc->caller->func->uf_tm_children = - profile_add(fc->caller->func->uf_tm_children, call_start); - fc->caller->func->uf_tml_children = - profile_add(fc->caller->func->uf_tml_children, call_start); - } - if (started_profiling) { - // make a ":profdel func" stop profiling the function - fp->uf_profiling = false; - } - } - - /* when being verbose, mention the return value */ - if (p_verbose >= 12) { - ++no_wait_return; - verbose_enter_scroll(); - - if (aborting()) - smsg(_("%s aborted"), sourcing_name); - else if (fc->rettv->v_type == VAR_NUMBER) - smsg(_("%s returning #%" PRId64 ""), - sourcing_name, (int64_t)fc->rettv->vval.v_number); - else { - char_u buf[MSG_BUF_LEN]; - - // The value may be very long. Skip the middle part, so that we - // have some idea how it starts and ends. smsg() would always - // truncate it at the end. Don't want errors such as E724 here. - emsg_off++; - char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL); - char_u *tofree = s; - emsg_off--; - if (s != NULL) { - if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); - s = buf; - } - smsg(_("%s returning %s"), sourcing_name, s); - xfree(tofree); - } - } - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; - } - - xfree(sourcing_name); - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - current_sctx = save_current_sctx; - if (do_profiling_yes) { - script_prof_restore(&wait_start); - } - if (using_sandbox) { - sandbox--; - } - - if (p_verbose >= 12 && sourcing_name != NULL) { - ++no_wait_return; - verbose_enter_scroll(); - - smsg(_("continuing in %s"), sourcing_name); - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; - } - - did_emsg |= save_did_emsg; - depth--; - - cleanup_function_call(fc); - - if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { - // Function was unreferenced while being used, free it now. - func_clear_free(fp, false); - } - // restore search patterns and redo buffer - if (did_save_redo) { - restoreRedobuff(&save_redo); - } - restore_search_patterns(); -} - -/// There are two kinds of function names: -/// 1. ordinary names, function defined with :function -/// 2. numbered functions and lambdas -/// For the first we only count the name stored in func_hashtab as a reference, -/// using function() does not count as a reference, because the function is -/// looked up by name. -static bool func_name_refcount(char_u *name) -{ - return isdigit(*name) || *name == '<'; -} - -/* - * Save the current function call pointer, and set it to NULL. - * Used when executing autocommands and for ":source". - */ -void *save_funccal(void) -{ - funccall_T *fc = current_funccal; - - current_funccal = NULL; - return (void *)fc; -} - -void restore_funccal(void *vfc) -{ - current_funccal = (funccall_T *)vfc; -} - -funccall_T *get_current_funccal(void) -{ - return current_funccal; -} - -#if defined(EXITFREE) -void free_all_functions(void) -{ - hashitem_T *hi; - ufunc_T *fp; - uint64_t skipped = 0; - uint64_t todo = 1; - uint64_t used; - - // Clean up the call stack. - while (current_funccal != NULL) { - tv_clear(current_funccal->rettv); - cleanup_function_call(current_funccal); - } - - // First clear what the functions contain. Since this may lower the - // reference count of a function, it may also free a function and change - // the hash table. Restart if that happens. - while (todo > 0) { - todo = func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - // Only free functions that are not refcounted, those are - // supposed to be freed when no longer referenced. - fp = HI2UF(hi); - if (func_name_refcount(fp->uf_name)) { - skipped++; - } else { - used = func_hashtab.ht_used; - func_clear(fp, true); - if (used != func_hashtab.ht_used) { - skipped = 0; - break; - } - } - todo--; - } - } - } - - // Now actually free the functions. Need to start all over every time, - // because func_free() may change the hash table. - skipped = 0; - while (func_hashtab.ht_used > skipped) { - todo = func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - // Only free functions that are not refcounted, those are - // supposed to be freed when no longer referenced. - fp = HI2UF(hi); - if (func_name_refcount(fp->uf_name)) { - skipped++; - } else { - func_free(fp); - skipped = 0; - break; - } - } - } - } - if (skipped == 0) { - hash_clear(&func_hashtab); - } -} - -#endif - -/// Checks if a builtin function with the given name exists. -/// -/// @param[in] name name of the builtin function to check. -/// @param[in] len length of "name", or -1 for NUL terminated. -/// -/// @return true if "name" looks like a builtin function name: starts with a -/// lower case letter and doesn't contain AUTOLOAD_CHAR. -static bool builtin_function(const char *name, int len) -{ - if (!ASCII_ISLOWER(name[0])) { - return false; - } - - const char *p = (len == -1 - ? strchr(name, AUTOLOAD_CHAR) - : memchr(name, AUTOLOAD_CHAR, (size_t)len)); - - return p == NULL; -} - -int func_call(char_u *name, typval_T *args, partial_T *partial, - dict_T *selfdict, typval_T *rettv) -{ - typval_T argv[MAX_FUNC_ARGS + 1]; - int argc = 0; - int dummy; - int r = 0; - - TV_LIST_ITER(args->vval.v_list, item, { - if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { - EMSG(_("E699: Too many arguments")); - goto func_call_skip_call; - } - // Make a copy of each argument. This is needed to be able to set - // v_lock to VAR_FIXED in the copy without changing the original list. - tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); - }); - - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); - -func_call_skip_call: - // Free the arguments. - while (argc > 0) { - tv_clear(&argv[--argc]); - } - - return r; -} - -/// Call a function with its resolved parameters -/// -/// "argv_func", when not NULL, can be used to fill in arguments only when the -/// invoked function uses them. It is called like this: -/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) -/// -/// @return FAIL if function cannot be called, else OK (even if an error -/// occurred while executing the function! Set `msg_list` to capture -/// the error, see do_cmdline()). -int -call_func( - const char_u *funcname, // name of the function - int len, // length of "name" - typval_T *rettv, // [out] value goes here - int argcount_in, // number of "argvars" - typval_T *argvars_in, // vars for arguments, must have "argcount" - // PLUS ONE elements! - ArgvFunc argv_func, // function to fill in argvars - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // [out] function handled range - bool evaluate, - partial_T *partial, // optional, can be NULL - dict_T *selfdict_in // Dictionary for "self" -) - FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) -{ - int ret = FAIL; - int error = ERROR_NONE; - ufunc_T *fp; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; - char_u *name; - int argcount = argcount_in; - typval_T *argvars = argvars_in; - dict_T *selfdict = selfdict_in; - typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL - int argv_clear = 0; - - // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) - // even when call_func() returns FAIL. - rettv->v_type = VAR_UNKNOWN; - - // Make a copy of the name, if it comes from a funcref variable it could - // be changed or deleted in the called function. - name = vim_strnsave(funcname, len); - - fname = fname_trans_sid(name, fname_buf, &tofree, &error); - - *doesrange = false; - - if (partial != NULL) { - // When the function has a partial with a dict and there is a dict - // argument, use the dict argument. That is backwards compatible. - // When the dict was bound explicitly use the one from the partial. - if (partial->pt_dict != NULL - && (selfdict_in == NULL || !partial->pt_auto)) { - selfdict = partial->pt_dict; - } - if (error == ERROR_NONE && partial->pt_argc > 0) { - for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { - tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]); - } - for (int i = 0; i < argcount_in; i++) { - argv[i + argv_clear] = argvars_in[i]; - } - argvars = argv; - argcount = partial->pt_argc + argcount_in; - } - } - - if (error == ERROR_NONE && evaluate) { - char_u *rfname = fname; - - /* Ignore "g:" before a function name. */ - if (fname[0] == 'g' && fname[1] == ':') { - rfname = fname + 2; - } - - rettv->v_type = VAR_NUMBER; /* default rettv is number zero */ - rettv->vval.v_number = 0; - error = ERROR_UNKNOWN; - - if (is_luafunc(partial)) { - if (len > 0) { - error = ERROR_NONE; - executor_call_lua((const char *)funcname, len, - argvars, argcount, rettv); - } - } else if (!builtin_function((const char *)rfname, -1)) { - // User defined function. - if (partial != NULL && partial->pt_func != NULL) { - fp = partial->pt_func; - } else { - fp = find_func(rfname); - } - - // Trigger FuncUndefined event, may load the function. - if (fp == NULL - && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) - && !aborting()) { - /* executed an autocommand, search for the function again */ - fp = find_func(rfname); - } - // Try loading a package. - if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), - true) && !aborting()) { - // Loaded a package, search for the function again. - fp = find_func(rfname); - } - - if (fp != NULL && (fp->uf_flags & FC_DELETED)) { - error = ERROR_DELETED; - } else if (fp != NULL) { - if (argv_func != NULL) { - argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); - } - if (fp->uf_flags & FC_RANGE) { - *doesrange = true; - } - if (argcount < fp->uf_args.ga_len) { - error = ERROR_TOOFEW; - } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { - error = ERROR_TOOMANY; - } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { - error = ERROR_DICT; - } else { - // Call the user function. - call_user_func(fp, argcount, argvars, rettv, firstline, lastline, - (fp->uf_flags & FC_DICT) ? selfdict : NULL); - error = ERROR_NONE; - } - } - } else { - // Find the function name in the table, call its implementation. - const VimLFuncDef *const fdef = find_internal_func((const char *)fname); - if (fdef != NULL) { - if (argcount < fdef->min_argc) { - error = ERROR_TOOFEW; - } else if (argcount > fdef->max_argc) { - error = ERROR_TOOMANY; - } else { - argvars[argcount].v_type = VAR_UNKNOWN; - fdef->func(argvars, rettv, fdef->data); - error = ERROR_NONE; - } - } - } - /* - * The function call (or "FuncUndefined" autocommand sequence) might - * have been aborted by an error, an interrupt, or an explicitly thrown - * exception that has not been caught so far. This situation can be - * tested for by calling aborting(). For an error in an internal - * function or for the "E132" error in call_user_func(), however, the - * throw point at which the "force_abort" flag (temporarily reset by - * emsg()) is normally updated has not been reached yet. We need to - * update that flag first to make aborting() reliable. - */ - update_force_abort(); - } - if (error == ERROR_NONE) - ret = OK; - - /* - * Report an error unless the argument evaluation or function call has been - * cancelled due to an aborting error, an interrupt, or an exception. - */ - if (!aborting()) { - switch (error) { - case ERROR_UNKNOWN: - emsg_funcname(N_("E117: Unknown function: %s"), name); - break; - case ERROR_DELETED: - emsg_funcname(N_("E933: Function was deleted: %s"), name); - break; - case ERROR_TOOMANY: - emsg_funcname(_(e_toomanyarg), name); - break; - case ERROR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), - name); - break; - case ERROR_SCRIPT: - emsg_funcname(N_("E120: Using not in a script context: %s"), - name); - break; - case ERROR_DICT: - emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), - name); - break; - } - } - - while (argv_clear > 0) { - tv_clear(&argv[--argv_clear]); - } - xfree(tofree); - xfree(name); - - return ret; -} - -/// List the head of the function: "name(arg1, arg2)". -/// -/// @param[in] fp Function pointer. -/// @param[in] indent Indent line. -/// @param[in] force Include bang "!" (i.e.: "function!"). -static void list_func_head(ufunc_T *fp, int indent, bool force) -{ - msg_start(); - if (indent) - MSG_PUTS(" "); - MSG_PUTS(force ? "function! " : "function "); - if (fp->uf_name[0] == K_SPECIAL) { - MSG_PUTS_ATTR("", HL_ATTR(HLF_8)); - msg_puts((const char *)fp->uf_name + 3); - } else { - msg_puts((const char *)fp->uf_name); - } - msg_putchar('('); - int j; - for (j = 0; j < fp->uf_args.ga_len; j++) { - if (j) { - msg_puts(", "); - } - msg_puts((const char *)FUNCARG(fp, j)); - } - if (fp->uf_varargs) { - if (j) { - msg_puts(", "); - } - msg_puts("..."); - } - msg_putchar(')'); - if (fp->uf_flags & FC_ABORT) { - msg_puts(" abort"); - } - if (fp->uf_flags & FC_RANGE) { - msg_puts(" range"); - } - if (fp->uf_flags & FC_DICT) { - msg_puts(" dict"); - } - if (fp->uf_flags & FC_CLOSURE) { - msg_puts(" closure"); - } - msg_clr_eos(); - if (p_verbose > 0) { - last_set_msg(fp->uf_script_ctx); - } -} - -/// Get a function name, translating "" and "". -/// Also handles a Funcref in a List or Dictionary. -/// flags: -/// TFN_INT: internal function name OK -/// TFN_QUIET: be quiet -/// TFN_NO_AUTOLOAD: do not use script autoloading -/// TFN_NO_DEREF: do not dereference a Funcref -/// Advances "pp" to just after the function name (if no error). -/// -/// @return the function name in allocated memory, or NULL for failure. -char_u * -trans_function_name( - char_u **pp, - bool skip, // only find the end, don't evaluate - int flags, - funcdict_T *fdp, // return: info about dictionary used - partial_T **partial // return: partial of a FuncRef -) - FUNC_ATTR_NONNULL_ARG(1) -{ - char_u *name = NULL; - const char_u *start; - const char_u *end; - int lead; - int len; - lval_T lv; - - if (fdp != NULL) - memset(fdp, 0, sizeof(funcdict_T)); - start = *pp; - - /* Check for hard coded : already translated function ID (from a user - * command). */ - if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA - && (*pp)[2] == (int)KE_SNR) { - *pp += 3; - len = get_id_len((const char **)pp) + 3; - return (char_u *)xmemdupz(start, len); - } - - /* A name starting with "" or "" is local to a script. But - * don't skip over "s:", get_lval() needs it for "s:dict.func". */ - lead = eval_fname_script((const char *)start); - if (lead > 2) { - start += lead; - } - - // Note that TFN_ flags use the same values as GLV_ flags. - end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, - lead > 2 ? 0 : FNE_CHECK_START); - if (end == start) { - if (!skip) - EMSG(_("E129: Function name required")); - goto theend; - } - if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { - /* - * Report an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ - if (!aborting()) { - if (end != NULL) { - emsgf(_(e_invarg2), start); - } - } else { - *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR); - } - goto theend; - } - - if (lv.ll_tv != NULL) { - if (fdp != NULL) { - fdp->fd_dict = lv.ll_dict; - fdp->fd_newkey = lv.ll_newkey; - lv.ll_newkey = NULL; - fdp->fd_di = lv.ll_di; - } - if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { - name = vim_strsave(lv.ll_tv->vval.v_string); - *pp = (char_u *)end; - } else if (lv.ll_tv->v_type == VAR_PARTIAL - && lv.ll_tv->vval.v_partial != NULL) { - if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') { - len = check_luafunc_name((const char *)end+1, true); - if (len == 0) { - EMSG2(e_invexpr2, "v:lua"); - goto theend; - } - name = xmallocz(len); - memcpy(name, end+1, len); - *pp = (char_u *)end+1+len; - } else { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; - } - if (partial != NULL) { - *partial = lv.ll_tv->vval.v_partial; - } - } else { - if (!skip && !(flags & TFN_QUIET) && (fdp == NULL - || lv.ll_dict == NULL - || fdp->fd_newkey == NULL)) { - EMSG(_(e_funcref)); - } else { - *pp = (char_u *)end; - } - name = NULL; - } - goto theend; - } - - if (lv.ll_name == NULL) { - // Error found, but continue after the function name. - *pp = (char_u *)end; - goto theend; - } - - /* Check if the name is a Funcref. If so, use the value. */ - if (lv.ll_exp_name != NULL) { - len = (int)strlen(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, - flags & TFN_NO_AUTOLOAD); - if ((const char *)name == lv.ll_exp_name) { - name = NULL; - } - } else if (!(flags & TFN_NO_DEREF)) { - len = (int)(end - *pp); - name = deref_func_name((const char *)(*pp), &len, partial, - flags & TFN_NO_AUTOLOAD); - if (name == *pp) { - name = NULL; - } - } - if (name != NULL) { - name = vim_strsave(name); - *pp = (char_u *)end; - if (strncmp((char *)name, "", 5) == 0) { - // Change "" to the byte sequence. - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; - memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); - } - goto theend; - } - - if (lv.ll_exp_name != NULL) { - len = (int)strlen(lv.ll_exp_name); - if (lead <= 2 && lv.ll_name == lv.ll_exp_name - && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) { - // When there was "s:" already or the name expanded to get a - // leading "s:" then remove it. - lv.ll_name += 2; - lv.ll_name_len -= 2; - len -= 2; - lead = 2; - } - } else { - // Skip over "s:" and "g:". - if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) { - lv.ll_name += 2; - lv.ll_name_len -= 2; - } - len = (int)((const char *)end - lv.ll_name); - } - - size_t sid_buf_len = 0; - char sid_buf[20]; - - // Copy the function name to allocated memory. - // Accept name() inside a script, translate into 123_name(). - // Accept 123_name() outside a script. - if (skip) { - lead = 0; // do nothing - } else if (lead > 0) { - lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) - || eval_fname_sid((const char *)(*pp))) { - // It's "s:" or "". - if (current_sctx.sc_sid <= 0) { - EMSG(_(e_usingsid)); - goto theend; - } - sid_buf_len = snprintf(sid_buf, sizeof(sid_buf), - "%" PRIdSCID "_", current_sctx.sc_sid); - lead += sid_buf_len; - } - } else if (!(flags & TFN_INT) - && builtin_function(lv.ll_name, lv.ll_name_len)) { - EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), - start); - goto theend; - } - - if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { - char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); - - if (cp != NULL && cp < end) { - EMSG2(_("E884: Function name cannot contain a colon: %s"), start); - goto theend; - } - } - - name = xmalloc(len + lead + 1); - if (!skip && lead > 0) { - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; - if (sid_buf_len > 0) { // If it's "" - memcpy(name + 3, sid_buf, sid_buf_len); - } - } - memmove(name + lead, lv.ll_name, len); - name[lead + len] = NUL; - *pp = (char_u *)end; - -theend: - clear_lval(&lv); - return name; -} - -/* - * ":function" - */ -void ex_function(exarg_T *eap) -{ - char_u *theline; - char_u *line_to_free = NULL; - int c; - int saved_did_emsg; - int saved_wait_return = need_wait_return; - char_u *name = NULL; - char_u *p; - char_u *arg; - char_u *line_arg = NULL; - garray_T newargs; - garray_T newlines; - int varargs = false; - int flags = 0; - ufunc_T *fp; - bool overwrite = false; - int indent; - int nesting; - dictitem_T *v; - funcdict_T fudi; - static int func_nr = 0; /* number for nameless function */ - int paren; - hashtab_T *ht; - int todo; - hashitem_T *hi; - linenr_T sourcing_lnum_off; - linenr_T sourcing_lnum_top; - bool is_heredoc = false; - char_u *skip_until = NULL; - char_u *heredoc_trimmed = NULL; - bool show_block = false; - bool do_concat = true; - - /* - * ":function" without argument: list functions. - */ - if (ends_excmd(*eap->arg)) { - if (!eap->skip) { - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (message_filtered(fp->uf_name)) { - continue; - } - if (!func_name_refcount(fp->uf_name)) { - list_func_head(fp, false, false); - } - } - } - } - eap->nextcmd = check_nextcmd(eap->arg); - return; - } - - /* - * ":function /pat": list functions matching pattern. - */ - if (*eap->arg == '/') { - p = skip_regexp(eap->arg + 1, '/', TRUE, NULL); - if (!eap->skip) { - regmatch_T regmatch; - - c = *p; - *p = NUL; - regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); - *p = c; - if (regmatch.regprog != NULL) { - regmatch.rm_ic = p_ic; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (!isdigit(*fp->uf_name) - && vim_regexec(®match, fp->uf_name, 0)) - list_func_head(fp, false, false); - } - } - vim_regfree(regmatch.regprog); - } - } - if (*p == '/') - ++p; - eap->nextcmd = check_nextcmd(p); - return; - } - - // Get the function name. There are these situations: - // func function name - // "name" == func, "fudi.fd_dict" == NULL - // dict.func new dictionary entry - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func - // dict.func existing dict entry with a Funcref - // "name" == func, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // dict.func existing dict entry that's not a Funcref - // "name" == NULL, "fudi.fd_dict" set, - // "fudi.fd_di" set, "fudi.fd_newkey" == NULL - // s:func script-local function name - // g:func global function name, same as "func" - p = eap->arg; - name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); - paren = (vim_strchr(p, '(') != NULL); - if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { - /* - * Return on an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ - if (!aborting()) { - if (fudi.fd_newkey != NULL) { - EMSG2(_(e_dictkey), fudi.fd_newkey); - } - xfree(fudi.fd_newkey); - return; - } else - eap->skip = TRUE; - } - - /* An error in a function call during evaluation of an expression in magic - * braces should not cause the function not to be defined. */ - saved_did_emsg = did_emsg; - did_emsg = FALSE; - - // - // ":function func" with only function name: list function. - // If bang is given: - // - include "!" in function head - // - exclude line numbers from function body - // - if (!paren) { - if (!ends_excmd(*skipwhite(p))) { - EMSG(_(e_trailing)); - goto ret_free; - } - eap->nextcmd = check_nextcmd(p); - if (eap->nextcmd != NULL) - *p = NUL; - if (!eap->skip && !got_int) { - fp = find_func(name); - if (fp != NULL) { - list_func_head(fp, !eap->forceit, eap->forceit); - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { - if (FUNCLINE(fp, j) == NULL) { - continue; - } - msg_putchar('\n'); - if (!eap->forceit) { - msg_outnum((long)j + 1); - if (j < 9) { - msg_putchar(' '); - } - if (j < 99) { - msg_putchar(' '); - } - } - msg_prt_line(FUNCLINE(fp, j), false); - ui_flush(); // show a line at a time - os_breakcheck(); - } - if (!got_int) { - msg_putchar('\n'); - msg_puts(eap->forceit ? "endfunction" : " endfunction"); - } - } else - emsg_funcname(N_("E123: Undefined function: %s"), name); - } - goto ret_free; - } - - /* - * ":function name(arg1, arg2)" Define function. - */ - p = skipwhite(p); - if (*p != '(') { - if (!eap->skip) { - EMSG2(_("E124: Missing '(': %s"), eap->arg); - goto ret_free; - } - /* attempt to continue by skipping some text */ - if (vim_strchr(p, '(') != NULL) - p = vim_strchr(p, '('); - } - p = skipwhite(p + 1); - - ga_init(&newargs, (int)sizeof(char_u *), 3); - ga_init(&newlines, (int)sizeof(char_u *), 3); - - if (!eap->skip) { - /* Check the name of the function. Unless it's a dictionary function - * (that we are overwriting). */ - if (name != NULL) - arg = name; - else - arg = fudi.fd_newkey; - if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { - int j = (*arg == K_SPECIAL) ? 3 : 0; - while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) - : eval_isnamec(arg[j]))) - ++j; - if (arg[j] != NUL) - emsg_funcname((char *)e_invarg2, arg); - } - /* Disallow using the g: dict. */ - if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) - EMSG(_("E862: Cannot use g: here")); - } - - if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { - goto errret_2; - } - - if (KeyTyped && ui_has(kUICmdline)) { - show_block = true; - ui_ext_cmdline_block_append(0, (const char *)eap->cmd); - } - - // find extra arguments "range", "dict", "abort" and "closure" - for (;; ) { - p = skipwhite(p); - if (STRNCMP(p, "range", 5) == 0) { - flags |= FC_RANGE; - p += 5; - } else if (STRNCMP(p, "dict", 4) == 0) { - flags |= FC_DICT; - p += 4; - } else if (STRNCMP(p, "abort", 5) == 0) { - flags |= FC_ABORT; - p += 5; - } else if (STRNCMP(p, "closure", 7) == 0) { - flags |= FC_CLOSURE; - p += 7; - if (current_funccal == NULL) { - emsg_funcname(N_ - ("E932: Closure function should not be at top level: %s"), - name == NULL ? (char_u *)"" : name); - goto erret; - } - } else { - break; - } - } - - /* When there is a line break use what follows for the function body. - * Makes 'exe "func Test()\n...\nendfunc"' work. */ - if (*p == '\n') { - line_arg = p + 1; - } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { - EMSG(_(e_trailing)); - } - - /* - * Read the body of the function, until ":endfunction" is found. - */ - if (KeyTyped) { - /* Check if the function already exists, don't let the user type the - * whole function before telling him it doesn't work! For a script we - * need to skip the body to be able to find what follows. */ - if (!eap->skip && !eap->forceit) { - if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) - EMSG(_(e_funcdict)); - else if (name != NULL && find_func(name) != NULL) - emsg_funcname(e_funcexts, name); - } - - if (!eap->skip && did_emsg) - goto erret; - - if (!ui_has(kUICmdline)) { - msg_putchar('\n'); // don't overwrite the function name - } - cmdline_row = msg_row; - } - - // Save the starting line number. - sourcing_lnum_top = sourcing_lnum; - - indent = 2; - nesting = 0; - for (;; ) { - if (KeyTyped) { - msg_scroll = true; - saved_wait_return = false; - } - need_wait_return = false; - - if (line_arg != NULL) { - /* Use eap->arg, split up in parts by line breaks. */ - theline = line_arg; - p = vim_strchr(theline, '\n'); - if (p == NULL) - line_arg += STRLEN(line_arg); - else { - *p = NUL; - line_arg = p + 1; - } - } else { - xfree(line_to_free); - if (eap->getline == NULL) { - theline = getcmdline(':', 0L, indent, do_concat); - } else { - theline = eap->getline(':', eap->cookie, indent, do_concat); - } - line_to_free = theline; - } - if (KeyTyped) { - lines_left = Rows - 1; - } - if (theline == NULL) { - EMSG(_("E126: Missing :endfunction")); - goto erret; - } - if (show_block) { - assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); - } - - // Detect line continuation: sourcing_lnum increased more than one. - sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); - if (sourcing_lnum < sourcing_lnum_off) { - sourcing_lnum_off -= sourcing_lnum; - } else { - sourcing_lnum_off = 0; - } - - if (skip_until != NULL) { - // Don't check for ":endfunc" between - // * ":append" and "." - // * ":python < 0) { - give_warning2((char_u *)_("W22: Text found after :endfunction: %s"), - p, true); - } - if (nextcmd != NULL) { - // Another command follows. If the line came from "eap" we - // can simply point into it, otherwise we need to change - // "eap->cmdlinep". - eap->nextcmd = nextcmd; - if (line_to_free != NULL) { - xfree(*eap->cmdlinep); - *eap->cmdlinep = line_to_free; - line_to_free = NULL; - } - } - break; - } - - /* Increase indent inside "if", "while", "for" and "try", decrease - * at "end". */ - if (indent > 2 && STRNCMP(p, "end", 3) == 0) - indent -= 2; - else if (STRNCMP(p, "if", 2) == 0 - || STRNCMP(p, "wh", 2) == 0 - || STRNCMP(p, "for", 3) == 0 - || STRNCMP(p, "try", 3) == 0) - indent += 2; - - /* Check for defining a function inside this function. */ - if (checkforcmd(&p, "function", 2)) { - if (*p == '!') { - p = skipwhite(p + 1); - } - p += eval_fname_script((const char *)p); - xfree(trans_function_name(&p, true, 0, NULL, NULL)); - if (*skipwhite(p) == '(') { - nesting++; - indent += 2; - } - } - - // Check for ":append", ":change", ":insert". - p = skip_range(p, NULL); - if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) - || (p[0] == 'c' - && (!ASCII_ISALPHA(p[1]) - || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) - || (p[2] == 'a' - && (STRNCMP(&p[3], "nge", 3) != 0 - || !ASCII_ISALPHA(p[6]))))))) - || (p[0] == 'i' - && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' - && (!ASCII_ISALPHA(p[2]) - || (p[2] == 's')))))) { - skip_until = vim_strsave((char_u *)"."); - } - - // heredoc: Check for ":python < 0) - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; - - /* Check for end of eap->arg. */ - if (line_arg != NULL && *line_arg == NUL) - line_arg = NULL; - } - - /* Don't define the function when skipping commands or when an error was - * detected. */ - if (eap->skip || did_emsg) - goto erret; - - /* - * If there are no errors, add the function - */ - if (fudi.fd_dict == NULL) { - v = find_var((const char *)name, STRLEN(name), &ht, false); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - emsg_funcname(N_("E707: Function name conflicts with variable: %s"), - name); - goto erret; - } - - fp = find_func(name); - if (fp != NULL) { - // Function can be replaced with "function!" and when sourcing the - // same script again, but only once. - if (!eap->forceit - && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid - || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { - emsg_funcname(e_funcexts, name); - goto erret; - } - if (fp->uf_calls > 0) { - emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), - name); - goto erret; - } - if (fp->uf_refcount > 1) { - // This function is referenced somewhere, don't redefine it but - // create a new one. - (fp->uf_refcount)--; - fp->uf_flags |= FC_REMOVED; - fp = NULL; - overwrite = true; - } else { - // redefine existing function - XFREE_CLEAR(name); - func_clear_items(fp); - fp->uf_profiling = false; - fp->uf_prof_initialized = false; - } - } - } else { - char numbuf[20]; - - fp = NULL; - if (fudi.fd_newkey == NULL && !eap->forceit) { - EMSG(_(e_funcdict)); - goto erret; - } - if (fudi.fd_di == NULL) { - if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { - // Can't add a function to a locked dictionary - goto erret; - } - } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { - // Can't change an existing function if it is locked - goto erret; - } - - /* Give the function a sequential number. Can only be used with a - * Funcref! */ - xfree(name); - sprintf(numbuf, "%d", ++func_nr); - name = vim_strsave((char_u *)numbuf); - } - - if (fp == NULL) { - if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { - int slen, plen; - char_u *scriptname; - - /* Check that the autoload name matches the script name. */ - int j = FAIL; - if (sourcing_name != NULL) { - scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); - p = vim_strchr(scriptname, '/'); - plen = (int)STRLEN(p); - slen = (int)STRLEN(sourcing_name); - if (slen > plen && fnamecmp(p, - sourcing_name + slen - plen) == 0) - j = OK; - xfree(scriptname); - } - if (j == FAIL) { - EMSG2(_( - "E746: Function name does not match script file name: %s"), - name); - goto erret; - } - } - - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); - - if (fudi.fd_dict != NULL) { - if (fudi.fd_di == NULL) { - // Add new dict entry - fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); - if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { - xfree(fudi.fd_di); - xfree(fp); - goto erret; - } - } else { - // Overwrite existing dict entry. - tv_clear(&fudi.fd_di->di_tv); - } - fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); - - /* behave like "dict" was used */ - flags |= FC_DICT; - } - - /* insert the new function in the function list */ - STRCPY(fp->uf_name, name); - if (overwrite) { - hi = hash_find(&func_hashtab, name); - hi->hi_key = UF2HIKEY(fp); - } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); - goto erret; - } - fp->uf_refcount = 1; - } - fp->uf_args = newargs; - fp->uf_lines = newlines; - if ((flags & FC_CLOSURE) != 0) { - register_closure(fp); - } else { - fp->uf_scoped = NULL; - } - if (prof_def_func()) { - func_do_profile(fp); - } - fp->uf_varargs = varargs; - if (sandbox) { - flags |= FC_SANDBOX; - } - fp->uf_flags = flags; - fp->uf_calls = 0; - fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; - - goto ret_free; - -erret: - ga_clear_strings(&newargs); -errret_2: - ga_clear_strings(&newlines); -ret_free: - xfree(skip_until); - xfree(line_to_free); - xfree(fudi.fd_newkey); - xfree(name); - did_emsg |= saved_did_emsg; - need_wait_return |= saved_wait_return; - if (show_block) { - ui_ext_cmdline_block_leave(); - } -} // NOLINT(readability/fn_size) - -/* - * Return 5 if "p" starts with "" or "" (ignoring case). - * Return 2 if "p" starts with "s:". - * Return 0 otherwise. - */ -int eval_fname_script(const char *const p) -{ - // Use mb_strnicmp() because in Turkish comparing the "I" may not work with - // the standard library function. - if (p[0] == '<' - && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 - || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { - return 5; - } - if (p[0] == 's' && p[1] == ':') { - return 2; - } - return 0; -} - -bool translated_function_exists(const char *name) -{ - if (builtin_function(name, -1)) { - return find_internal_func((char *)name) != NULL; - } - return find_func((const char_u *)name) != NULL; -} - -/// Check whether function with the given name exists -/// -/// @param[in] name Function name. -/// @param[in] no_deref Whether to dereference a Funcref. -/// -/// @return True if it exists, false otherwise. -bool function_exists(const char *const name, bool no_deref) -{ - const char_u *nm = (const char_u *)name; - bool n = false; - int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; - - if (no_deref) { - flag |= TFN_NO_DEREF; - } - char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL, - NULL); - nm = skipwhite(nm); - - /* Only accept "funcname", "funcname ", "funcname (..." and - * "funcname(...", not "funcname!...". */ - if (p != NULL && (*nm == NUL || *nm == '(')) { - n = translated_function_exists(p); - } - xfree(p); - return n; -} - -/* - * Function given to ExpandGeneric() to obtain the list of user defined - * function names. - */ -char_u *get_user_func_name(expand_T *xp, int idx) -{ - static size_t done; - static hashitem_T *hi; - ufunc_T *fp; - - if (idx == 0) { - done = 0; - hi = func_hashtab.ht_array; - } - assert(hi); - if (done < func_hashtab.ht_used) { - if (done++ > 0) - ++hi; - while (HASHITEM_EMPTY(hi)) - ++hi; - fp = HI2UF(hi); - - if ((fp->uf_flags & FC_DICT) - || STRNCMP(fp->uf_name, "", 8) == 0) { - return (char_u *)""; // don't show dict and lambda functions - } - - if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { - return fp->uf_name; // Prevent overflow. - } - - cat_func_name(IObuff, fp); - if (xp->xp_context != EXPAND_USER_FUNC) { - STRCAT(IObuff, "("); - if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) - STRCAT(IObuff, ")"); - } - return IObuff; - } - return NULL; -} - -/// ":delfunction {name}" -void ex_delfunction(exarg_T *eap) -{ - ufunc_T *fp = NULL; - char_u *p; - char_u *name; - funcdict_T fudi; - - p = eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); - xfree(fudi.fd_newkey); - if (name == NULL) { - if (fudi.fd_dict != NULL && !eap->skip) - EMSG(_(e_funcref)); - return; - } - if (!ends_excmd(*skipwhite(p))) { - xfree(name); - EMSG(_(e_trailing)); - return; - } - eap->nextcmd = check_nextcmd(p); - if (eap->nextcmd != NULL) - *p = NUL; - - if (!eap->skip) - fp = find_func(name); - xfree(name); - - if (!eap->skip) { - if (fp == NULL) { - if (!eap->forceit) { - EMSG2(_(e_nofunc), eap->arg); - } - return; - } - if (fp->uf_calls > 0) { - EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg); - return; - } - // check `uf_refcount > 2` because deleting a function should also reduce - // the reference count, and 1 is the initial refcount. - if (fp->uf_refcount > 2) { - EMSG2(_("Cannot delete function %s: It is being used internally"), - eap->arg); - return; - } - - if (fudi.fd_dict != NULL) { - // Delete the dict item that refers to the function, it will - // invoke func_unref() and possibly delete the function. - tv_dict_item_remove(fudi.fd_dict, fudi.fd_di); - } else { - // A normal function (not a numbered function or lambda) has a - // refcount of 1 for the entry in the hashtable. When deleting - // it and the refcount is more than one, it should be kept. - // A numbered function or lambda should be kept if the refcount is - // one or more. - if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { - // Function is still referenced somewhere. Don't free it but - // do remove it from the hashtable. - if (func_remove(fp)) { - fp->uf_refcount--; - } - fp->uf_flags |= FC_DELETED; - } else { - func_clear_free(fp, false); - } - } - } -} - -/* - * Unreference a Function: decrement the reference count and free it when it - * becomes zero. - */ -void func_unref(char_u *name) -{ - ufunc_T *fp = NULL; - - if (name == NULL || !func_name_refcount(name)) { - return; - } - - fp = find_func(name); - if (fp == NULL && isdigit(*name)) { -#ifdef EXITFREE - if (!entered_free_all_mem) { - internal_error("func_unref()"); - abort(); - } -#else - internal_error("func_unref()"); - abort(); -#endif - } - func_ptr_unref(fp); -} - -/// Unreference a Function: decrement the reference count and free it when it -/// becomes zero. -/// Unreference user function, freeing it if needed -/// -/// Decrements the reference count and frees when it becomes zero. -/// -/// @param fp Function to unreference. -void func_ptr_unref(ufunc_T *fp) -{ - if (fp != NULL && --fp->uf_refcount <= 0) { - // Only delete it when it's not being used. Otherwise it's done - // when "uf_calls" becomes zero. - if (fp->uf_calls == 0) { - func_clear_free(fp, false); - } - } -} - -/// Count a reference to a Function. -void func_ref(char_u *name) -{ - ufunc_T *fp; - - if (name == NULL || !func_name_refcount(name)) { - return; - } - fp = find_func(name); - if (fp != NULL) { - (fp->uf_refcount)++; - } else if (isdigit(*name)) { - // Only give an error for a numbered function. - // Fail silently, when named or lambda function isn't found. - internal_error("func_ref()"); - } -} - -/// Count a reference to a Function. -void func_ptr_ref(ufunc_T *fp) -{ - if (fp != NULL) { - (fp->uf_refcount)++; - } -} - -/// Check whether funccall is still referenced outside -/// -/// It is supposed to be referenced if either it is referenced itself or if l:, -/// a: or a:000 are referenced as all these are statically allocated within -/// funccall structure. -static inline bool fc_referenced(const funccall_T *const fc) - FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT - FUNC_ATTR_NONNULL_ALL -{ - return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) - != DO_NOT_FREE_CNT) - || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT - || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT - || fc->fc_refcount > 0); -} - -/// @return true if items in "fc" do not have "copyID". That means they are not -/// referenced from anywhere that is in use. -static int can_free_funccal(funccall_T *fc, int copyID) -{ - return fc->l_varlist.lv_copyID != copyID - && fc->l_vars.dv_copyID != copyID - && fc->l_avars.dv_copyID != copyID - && fc->fc_copyID != copyID; -} - -/* - * ":return [expr]" - */ -void ex_return(exarg_T *eap) -{ - char_u *arg = eap->arg; - typval_T rettv; - int returning = FALSE; - - if (current_funccal == NULL) { - EMSG(_("E133: :return not inside a function")); - return; - } - - if (eap->skip) - ++emsg_skip; - - eap->nextcmd = NULL; - if ((*arg != NUL && *arg != '|' && *arg != '\n') - && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { - if (!eap->skip) { - returning = do_return(eap, false, true, &rettv); - } else { - tv_clear(&rettv); - } - } else if (!eap->skip) { // It's safer to return also on error. - // In return statement, cause_abort should be force_abort. - update_force_abort(); - - // Return unless the expression evaluation has been cancelled due to an - // aborting error, an interrupt, or an exception. - if (!aborting()) { - returning = do_return(eap, false, true, NULL); - } - } - - /* When skipping or the return gets pending, advance to the next command - * in this line (!returning). Otherwise, ignore the rest of the line. - * Following lines will be ignored by get_func_line(). */ - if (returning) - eap->nextcmd = NULL; - else if (eap->nextcmd == NULL) /* no argument */ - eap->nextcmd = check_nextcmd(arg); - - if (eap->skip) - --emsg_skip; -} - -// TODO(ZyX-I): move to eval/ex_cmds - -/* - * ":1,25call func(arg1, arg2)" function call. - */ -void ex_call(exarg_T *eap) -{ - char_u *arg = eap->arg; - char_u *startarg; - char_u *name; - char_u *tofree; - int len; - typval_T rettv; - linenr_T lnum; - int doesrange; - bool failed = false; - funcdict_T fudi; - partial_T *partial = NULL; - - if (eap->skip) { - // trans_function_name() doesn't work well when skipping, use eval0() - // instead to skip to any following command, e.g. for: - // :if 0 | call dict.foo().bar() | endif. - emsg_skip++; - if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) { - tv_clear(&rettv); - } - emsg_skip--; - return; - } - - tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); - if (fudi.fd_newkey != NULL) { - // Still need to give an error message for missing key. - EMSG2(_(e_dictkey), fudi.fd_newkey); - xfree(fudi.fd_newkey); - } - if (tofree == NULL) { - return; - } - - // Increase refcount on dictionary, it could get deleted when evaluating - // the arguments. - if (fudi.fd_dict != NULL) { - fudi.fd_dict->dv_refcount++; - } - - // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its - // contents. For VAR_PARTIAL get its partial, unless we already have one - // from trans_function_name(). - len = (int)STRLEN(tofree); - name = deref_func_name((const char *)tofree, &len, - partial != NULL ? NULL : &partial, false); - - // Skip white space to allow ":call func ()". Not good, but required for - // backward compatibility. - startarg = skipwhite(arg); - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. - - if (*startarg != '(') { - EMSG2(_("E107: Missing parentheses: %s"), eap->arg); - goto end; - } - - lnum = eap->line1; - for (; lnum <= eap->line2; lnum++) { - if (eap->addr_count > 0) { // -V560 - if (lnum > curbuf->b_ml.ml_line_count) { - // If the function deleted lines or switched to another buffer - // the line number may become invalid. - EMSG(_(e_invrange)); - break; - } - curwin->w_cursor.lnum = lnum; - curwin->w_cursor.col = 0; - curwin->w_cursor.coladd = 0; - } - arg = startarg; - if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, - eap->line1, eap->line2, &doesrange, - true, partial, fudi.fd_dict) == FAIL) { - failed = true; - break; - } - - // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, true, true) - == FAIL) { - failed = true; - break; - } - - tv_clear(&rettv); - if (doesrange) { - break; - } - - // Stop when immediately aborting on error, or when an interrupt - // occurred or an exception was thrown but not caught. - // get_func_tv() returned OK, so that the check for trailing - // characters below is executed. - if (aborting()) { - break; - } - } - - if (!failed) { - // Check for trailing illegal characters and a following command. - if (!ends_excmd(*arg)) { - emsg_severe = TRUE; - EMSG(_(e_trailing)); - } else { - eap->nextcmd = check_nextcmd(arg); - } - } - -end: - tv_dict_unref(fudi.fd_dict); - xfree(tofree); -} - -/* - * Return from a function. Possibly makes the return pending. Also called - * for a pending return at the ":endtry" or after returning from an extra - * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set - * when called due to a ":return" command. "rettv" may point to a typval_T - * with the return rettv. Returns TRUE when the return can be carried out, - * FALSE when the return gets pending. - */ -int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) -{ - int idx; - cstack_T *const cstack = eap->cstack; - - if (reanimate) - /* Undo the return. */ - current_funccal->returned = FALSE; - - /* - * Cleanup (and inactivate) conditionals, but stop when a try conditional - * not in its finally clause (which then is to be executed next) is found. - * In this case, make the ":return" pending for execution at the ":endtry". - * Otherwise, return normally. - */ - idx = cleanup_conditionals(eap->cstack, 0, TRUE); - if (idx >= 0) { - cstack->cs_pending[idx] = CSTP_RETURN; - - if (!is_cmd && !reanimate) - /* A pending return again gets pending. "rettv" points to an - * allocated variable with the rettv of the original ":return"'s - * argument if present or is NULL else. */ - cstack->cs_rettv[idx] = rettv; - else { - /* When undoing a return in order to make it pending, get the stored - * return rettv. */ - if (reanimate) { - assert(current_funccal->rettv); - rettv = current_funccal->rettv; - } - - if (rettv != NULL) { - /* Store the value of the pending return. */ - cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T)); - *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv; - } else - cstack->cs_rettv[idx] = NULL; - - if (reanimate) { - /* The pending return value could be overwritten by a ":return" - * without argument in a finally clause; reset the default - * return value. */ - current_funccal->rettv->v_type = VAR_NUMBER; - current_funccal->rettv->vval.v_number = 0; - } - } - report_make_pending(CSTP_RETURN, rettv); - } else { - current_funccal->returned = TRUE; - - /* If the return is carried out now, store the return value. For - * a return immediately after reanimation, the value is already - * there. */ - if (!reanimate && rettv != NULL) { - tv_clear(current_funccal->rettv); - *current_funccal->rettv = *(typval_T *)rettv; - if (!is_cmd) - xfree(rettv); - } - } - - return idx < 0; -} - -/* - * Generate a return command for producing the value of "rettv". The result - * is an allocated string. Used by report_pending() for verbose messages. - */ -char_u *get_return_cmd(void *rettv) -{ - char_u *s = NULL; - char_u *tofree = NULL; - - if (rettv != NULL) { - tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL); - } - if (s == NULL) { - s = (char_u *)""; - } - - STRCPY(IObuff, ":return "); - STRLCPY(IObuff + 8, s, IOSIZE - 8); - if (STRLEN(s) + 8 >= IOSIZE) - STRCPY(IObuff + IOSIZE - 4, "..."); - xfree(tofree); - return vim_strsave(IObuff); -} - -/* - * Get next function line. - * Called by do_cmdline() to get the next line. - * Returns allocated string, or NULL for end of function. - */ -char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - char_u *retval; - garray_T *gap; /* growarray with function lines */ - - /* If breakpoints have been added/deleted need to check for it. */ - if (fcp->dbg_tick != debug_tick) { - fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, - sourcing_lnum); - fcp->dbg_tick = debug_tick; - } - if (do_profiling == PROF_YES) - func_line_end(cookie); - - gap = &fp->uf_lines; - if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned) - retval = NULL; - else { - /* Skip NULL lines (continuation lines). */ - while (fcp->linenr < gap->ga_len - && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) - ++fcp->linenr; - if (fcp->linenr >= gap->ga_len) - retval = NULL; - else { - retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); - sourcing_lnum = fcp->linenr; - if (do_profiling == PROF_YES) - func_line_start(cookie); - } - } - - /* Did we encounter a breakpoint? */ - if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { - dbg_breakpoint(fp->uf_name, sourcing_lnum); - /* Find next breakpoint. */ - fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, - sourcing_lnum); - fcp->dbg_tick = debug_tick; - } - - return retval; -} - -/* - * Return TRUE if the currently active function should be ended, because a - * return was encountered or an error occurred. Used inside a ":while". - */ -int func_has_ended(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - - /* Ignore the "abort" flag if the abortion behavior has been changed due to - * an error inside a try conditional. */ - return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned; -} - -/* - * return TRUE if cookie indicates a function which "abort"s on errors. - */ -int func_has_abort(void *cookie) -{ - return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; -} - -/// Turn "dict.Func" into a partial for "Func" bound to "dict". -/// Changes "rettv" in-place. -void make_partial(dict_T *const selfdict, typval_T *const rettv) -{ - char_u *fname; - char_u *tofree = NULL; - ufunc_T *fp; - char_u fname_buf[FLEN_FIXED + 1]; - int error; - - if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { - fp = rettv->vval.v_partial->pt_func; - } else { - fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? rettv->vval.v_string - : rettv->vval.v_partial->pt_name; - // Translate "s:func" to the stored function name. - fname = fname_trans_sid(fname, fname_buf, &tofree, &error); - fp = find_func(fname); - xfree(tofree); - } - - // Turn "dict.Func" into a partial for "Func" with "dict". - if (fp != NULL && (fp->uf_flags & FC_DICT)) { - partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); - pt->pt_refcount = 1; - pt->pt_dict = selfdict; - (selfdict->dv_refcount)++; - pt->pt_auto = true; - if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { - // Just a function: Take over the function name and use selfdict. - pt->pt_name = rettv->vval.v_string; - } else { - partial_T *ret_pt = rettv->vval.v_partial; - int i; - - // Partial: copy the function name, use selfdict and copy - // args. Can't take over name or args, the partial might - // be referenced elsewhere. - if (ret_pt->pt_name != NULL) { - pt->pt_name = vim_strsave(ret_pt->pt_name); - func_ref(pt->pt_name); - } else { - pt->pt_func = ret_pt->pt_func; - func_ptr_ref(pt->pt_func); - } - if (ret_pt->pt_argc > 0) { - size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; - pt->pt_argv = (typval_T *)xmalloc(arg_size); - pt->pt_argc = ret_pt->pt_argc; - for (i = 0; i < pt->pt_argc; i++) { - tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); - } - } - partial_unref(ret_pt); - } - rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = pt; - } -} - -/* - * Return the name of the executed function. - */ -char_u *func_name(void *cookie) -{ - return ((funccall_T *)cookie)->func->uf_name; -} - -/* - * Return the address holding the next breakpoint line for a funccall cookie. - */ -linenr_T *func_breakpoint(void *cookie) -{ - return &((funccall_T *)cookie)->breakpoint; -} - -/* - * Return the address holding the debug tick for a funccall cookie. - */ -int *func_dbg_tick(void *cookie) -{ - return &((funccall_T *)cookie)->dbg_tick; -} - -/* - * Return the nesting level for a funccall cookie. - */ -int func_level(void *cookie) -{ - return ((funccall_T *)cookie)->level; -} - -/* - * Return TRUE when a function was ended by a ":return" command. - */ -int current_func_returned(void) -{ - return current_funccal->returned; -} - -bool free_unref_funccal(int copyID, int testing) -{ - bool did_free = false; - bool did_free_funccal = false; - - for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) { - if (can_free_funccal(*pfc, copyID)) { - funccall_T *fc = *pfc; - *pfc = fc->caller; - free_funccal(fc, true); - did_free = true; - did_free_funccal = true; - } else { - pfc = &(*pfc)->caller; - } - } - if (did_free_funccal) { - // When a funccal was freed some more items might be garbage - // collected, so run again. - (void)garbage_collect(testing); - } - return did_free; -} - -// Get function call environment based on backtrace debug level -funccall_T *get_funccal(void) -{ - funccall_T *funccal = current_funccal; - if (debug_backtrace_level > 0) { - for (int i = 0; i < debug_backtrace_level; i++) { - funccall_T *temp_funccal = funccal->caller; - if (temp_funccal) { - funccal = temp_funccal; - } else { - // backtrace level overflow. reset to max - debug_backtrace_level = i; - } - } - } - - return funccal; -} - -/// Return the hashtable used for local variables in the current funccal. -/// Return NULL if there is no current funccal. -hashtab_T *get_funccal_local_ht(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return &get_funccal()->l_vars.dv_hashtab; -} - -/// Return the l: scope variable. -/// Return NULL if there is no current funccal. -dictitem_T *get_funccal_local_var(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return (dictitem_T *)&get_funccal()->l_vars_var; -} - -/// Return the hashtable used for argument in the current funccal. -/// Return NULL if there is no current funccal. -hashtab_T *get_funccal_args_ht(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return &get_funccal()->l_avars.dv_hashtab; -} - -/// Return the a: scope variable. -/// Return NULL if there is no current funccal. -dictitem_T *get_funccal_args_var(void) -{ - if (current_funccal == NULL) { - return NULL; - } - return (dictitem_T *)¤t_funccal->l_avars_var; -} - -/* - * List function variables, if there is a function. - */ -void list_func_vars(int *first) -{ - if (current_funccal != NULL) { - list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, - first); - } -} - -/// If "ht" is the hashtable for local variables in the current funccal, return -/// the dict that contains it. -/// Otherwise return NULL. -dict_T *get_current_funccal_dict(hashtab_T *ht) -{ - if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { - return ¤t_funccal->l_vars; - } - return NULL; -} - -/// Search hashitem in parent scope. -hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) -{ - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { - return NULL; - } - - funccall_T *old_current_funccal = current_funccal; - hashitem_T *hi = NULL; - const size_t namelen = strlen(name); - const char *varname; - - // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; - while (current_funccal != NULL) { - hashtab_T *ht = find_var_ht(name, namelen, &varname); - if (ht != NULL && *varname != NUL) { - hi = hash_find_len(ht, varname, namelen - (varname - name)); - if (!HASHITEM_EMPTY(hi)) { - *pht = ht; - break; - } - } - if (current_funccal == current_funccal->func->uf_scoped) { - break; - } - current_funccal = current_funccal->func->uf_scoped; - } - current_funccal = old_current_funccal; - - return hi; -} - -/// Search variable in parent scope. -dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, - int no_autoload) -{ - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { - return NULL; - } - - dictitem_T *v = NULL; - funccall_T *old_current_funccal = current_funccal; - const char *varname; - - // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; - while (current_funccal) { - hashtab_T *ht = find_var_ht(name, namelen, &varname); - if (ht != NULL && *varname != NUL) { - v = find_var_in_ht(ht, *name, varname, - namelen - (size_t)(varname - name), no_autoload); - if (v != NULL) { - break; - } - } - if (current_funccal == current_funccal->func->uf_scoped) { - break; - } - current_funccal = current_funccal->func->uf_scoped; - } - current_funccal = old_current_funccal; - - return v; -} - -/// Set "copyID + 1" in previous_funccal and callers. -bool set_ref_in_previous_funccal(int copyID) -{ - bool abort = false; - - for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL); - } - return abort; -} - -static bool set_ref_in_funccal(funccall_T *fc, int copyID) -{ - bool abort = false; - - if (fc->fc_copyID != copyID) { - fc->fc_copyID = copyID; - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_func(NULL, fc->func, copyID); - } - return abort; -} - -/// Set "copyID" in all local vars and arguments in the call stack. -bool set_ref_in_call_stack(int copyID) -{ - bool abort = false; - - for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); - } - return abort; -} - -/// Set "copyID" in all functions available by name. -bool set_ref_in_functions(int copyID) -{ - int todo; - hashitem_T *hi = NULL; - bool abort = false; - ufunc_T *fp; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (!func_name_refcount(fp->uf_name)) { - abort = abort || set_ref_in_func(NULL, fp, copyID); - } - } - } - return abort; -} - -/// Set "copyID" in all function arguments. -bool set_ref_in_func_args(int copyID) -{ - bool abort = false; - - for (int i = 0; i < funcargs.ga_len; i++) { - abort = abort || set_ref_in_item(((typval_T **)funcargs.ga_data)[i], - copyID, NULL, NULL); - } - return abort; -} - -/// Mark all lists and dicts referenced through function "name" with "copyID". -/// "list_stack" is used to add lists to be marked. Can be NULL. -/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. -/// -/// @return true if setting references failed somehow. -bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) -{ - ufunc_T *fp = fp_in; - funccall_T *fc; - int error = ERROR_NONE; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; - bool abort = false; - if (name == NULL && fp_in == NULL) { - return false; - } - - if (fp_in == NULL) { - fname = fname_trans_sid(name, fname_buf, &tofree, &error); - fp = find_func(fname); - } - if (fp != NULL) { - for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { - abort = abort || set_ref_in_funccal(fc, copyID); - } - } - xfree(tofree); - return abort; -} diff --git a/src/nvim/eval/user_funcs.h b/src/nvim/eval/user_funcs.h deleted file mode 100644 index b325c2e976..0000000000 --- a/src/nvim/eval/user_funcs.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef NVIM_EVAL_USER_FUNCS_H -#define NVIM_EVAL_USER_FUNCS_H - -#include "nvim/eval/typval.h" -#include "nvim/ex_cmds_defs.h" - -///< 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; - -/// errors for when calling a function -typedef enum { - ERROR_UNKNOWN = 0, - ERROR_TOOMANY, - ERROR_TOOFEW, - ERROR_SCRIPT, - ERROR_DICT, - ERROR_NONE, - ERROR_OTHER, - ERROR_BOTH, - ERROR_DELETED, -} FnameTransError; - -typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, - int called_func_argcount); - -#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] -#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval/user_funcs.h.generated.h" -#endif -#endif // NVIM_EVAL_USER_FUNCS_H diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c new file mode 100644 index 0000000000..2c1cf3eedf --- /dev/null +++ b/src/nvim/eval/userfunc.c @@ -0,0 +1,3363 @@ +// 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 + +// User defined function support + +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/ex_getln.h" +#include "nvim/fileio.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" +#include "nvim/lua/executor.h" +#include "nvim/misc1.h" +#include "nvim/os/input.h" +#include "nvim/regexp.h" +#include "nvim/search.h" +#include "nvim/ui.h" +#include "nvim/vim.h" + +// flags used in uf_flags +#define FC_ABORT 0x01 // abort function on error +#define FC_RANGE 0x02 // function accepts range +#define FC_DICT 0x04 // Dict function, uses "self" +#define FC_CLOSURE 0x08 // closure, uses outer scope variables +#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 +#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 +#define FC_SANDBOX 0x40 // function defined in the sandbox + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "eval/userfunc.c.generated.h" +#endif + +hashtab_T func_hashtab; + +// Used by get_func_tv() +static garray_T funcargs = GA_EMPTY_INIT_VALUE; + +/* pointer to funccal for currently active function */ +funccall_T *current_funccal = NULL; + +// Pointer to list of previously used funccal, still around because some +// item in it is still being used. +funccall_T *previous_funccal = NULL; + +static char *e_funcexts = N_( + "E122: Function %s already exists, add ! to replace it"); +static char *e_funcdict = N_("E717: Dictionary entry already exists"); +static char *e_funcref = N_("E718: Funcref required"); +static char *e_nofunc = N_("E130: Unknown function: %s"); + +void func_init(void) +{ + hash_init(&func_hashtab); +} + +/// Get function arguments. +static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, + int *varargs, bool skip) +{ + bool mustend = false; + char_u *arg = *argp; + char_u *p = arg; + int c; + int i; + + if (newargs != NULL) { + ga_init(newargs, (int)sizeof(char_u *), 3); + } + + if (varargs != NULL) { + *varargs = false; + } + + // Isolate the arguments: "arg1, arg2, ...)" + while (*p != endchar) { + if (p[0] == '.' && p[1] == '.' && p[2] == '.') { + if (varargs != NULL) { + *varargs = true; + } + p += 3; + mustend = true; + } else { + arg = p; + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit(*arg) + || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) + || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (!skip) { + EMSG2(_("E125: Illegal argument: %s"), arg); + } + break; + } + if (newargs != NULL) { + ga_grow(newargs, 1); + c = *p; + *p = NUL; + arg = vim_strsave(arg); + + // Check for duplicate argument name. + for (i = 0; i < newargs->ga_len; i++) { + if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { + EMSG2(_("E853: Duplicate argument name: %s"), arg); + xfree(arg); + goto err_ret; + } + } + ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; + newargs->ga_len++; + + *p = c; + } + if (*p == ',') { + p++; + } else { + mustend = true; + } + } + p = skipwhite(p); + if (mustend && *p != endchar) { + if (!skip) { + EMSG2(_(e_invarg2), *argp); + } + break; + } + } + if (*p != endchar) { + goto err_ret; + } + p++; // skip "endchar" + + *argp = p; + return OK; + +err_ret: + if (newargs != NULL) { + ga_clear_strings(newargs); + } + return FAIL; +} + +/// Register function "fp" as using "current_funccal" as its scope. +static void register_closure(ufunc_T *fp) +{ + if (fp->uf_scoped == current_funccal) { + // no change + return; + } + funccal_unref(fp->uf_scoped, fp, false); + fp->uf_scoped = current_funccal; + current_funccal->fc_refcount++; + ga_grow(¤t_funccal->fc_funcs, 1); + ((ufunc_T **)current_funccal->fc_funcs.ga_data) + [current_funccal->fc_funcs.ga_len++] = fp; +} + +/// Parse a lambda expression and get a Funcref from "*arg". +/// +/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. +int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) +{ + garray_T newargs = GA_EMPTY_INIT_VALUE; + garray_T *pnewargs; + ufunc_T *fp = NULL; + int varargs; + int ret; + char_u *start = skipwhite(*arg + 1); + char_u *s, *e; + static int lambda_no = 0; + bool *old_eval_lavars = eval_lavars_used; + bool eval_lavars = false; + + // First, check if this is a lambda expression. "->" must exists. + ret = get_function_args(&start, '-', NULL, NULL, true); + if (ret == FAIL || *start != '>') { + return NOTDONE; + } + + // Parse the arguments again. + if (evaluate) { + pnewargs = &newargs; + } else { + pnewargs = NULL; + } + *arg = skipwhite(*arg + 1); + ret = get_function_args(arg, '-', pnewargs, &varargs, false); + if (ret == FAIL || **arg != '>') { + goto errret; + } + + // Set up a flag for checking local variables and arguments. + if (evaluate) { + eval_lavars_used = &eval_lavars; + } + + // Get the start and the end of the expression. + *arg = skipwhite(*arg + 1); + s = *arg; + ret = skip_expr(arg); + if (ret == FAIL) { + goto errret; + } + e = *arg; + *arg = skipwhite(*arg); + if (**arg != '}') { + goto errret; + } + (*arg)++; + + if (evaluate) { + int len, flags = 0; + char_u *p; + char_u name[20]; + partial_T *pt; + garray_T newlines; + + lambda_no++; + snprintf((char *)name, sizeof(name), "%d", lambda_no); + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + pt = xcalloc(1, sizeof(partial_T)); + + ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_grow(&newlines, 1); + + // Add "return " before the expression. + len = 7 + e - s + 1; + p = (char_u *)xmalloc(len); + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + STRCPY(p, "return "); + STRLCPY(p + 7, s, e - s + 1); + + fp->uf_refcount = 1; + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + fp->uf_args = newargs; + fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + + if (prof_def_func()) { + func_do_profile(fp); + } + if (sandbox) { + flags |= FC_SANDBOX; + } + fp->uf_varargs = true; + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; + + pt->pt_func = fp; + pt->pt_refcount = 1; + rettv->vval.v_partial = pt; + rettv->v_type = VAR_PARTIAL; + } + + eval_lavars_used = old_eval_lavars; + return OK; + +errret: + ga_clear_strings(&newargs); + xfree(fp); + eval_lavars_used = old_eval_lavars; + return FAIL; +} + +/// Return name of the function corresponding to `name` +/// +/// If `name` points to variable that is either a function or partial then +/// corresponding function name is returned. Otherwise it returns `name` itself. +/// +/// @param[in] name Function name to check. +/// @param[in,out] lenp Location where length of the returned name is stored. +/// Must be set to the length of the `name` argument. +/// @param[out] partialp Location where partial will be stored if found +/// function appears to be a partial. May be NULL if this +/// is not needed. +/// @param[in] no_autoload If true, do not source autoload scripts if function +/// was not found. +/// +/// @return name of the function. +char_u *deref_func_name(const char *name, int *lenp, + partial_T **const partialp, bool no_autoload) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + if (partialp != NULL) { + *partialp = NULL; + } + + dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + if (v->di_tv.vval.v_string == NULL) { // just in case + *lenp = 0; + return (char_u *)""; + } + *lenp = (int)STRLEN(v->di_tv.vval.v_string); + return v->di_tv.vval.v_string; + } + + if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { + partial_T *const pt = v->di_tv.vval.v_partial; + + if (pt == NULL) { // just in case + *lenp = 0; + return (char_u *)""; + } + if (partialp != NULL) { + *partialp = pt; + } + char_u *s = partial_name(pt); + *lenp = (int)STRLEN(s); + return s; + } + + return (char_u *)name; +} + +/// Give an error message with a function name. Handle things. +/// +/// @param ermsg must be passed without translation (use N_() instead of _()). +/// @param name function name +static void emsg_funcname(char *ermsg, const char_u *name) +{ + char_u *p; + + if (*name == K_SPECIAL) { + p = concat_str((char_u *)"", name + 3); + } else { + p = (char_u *)name; + } + + EMSG2(_(ermsg), p); + + if (p != name) { + xfree(p); + } +} + +/* + * Allocate a variable for the result of a function. + * Return OK or FAIL. + */ +int +get_func_tv( + const char_u *name, // name of the function + int len, // length of "name" + typval_T *rettv, + char_u **arg, // argument, pointing to the '(' + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // return: function handled range + int evaluate, + partial_T *partial, // for extra arguments + dict_T *selfdict // Dictionary for "self" +) +{ + char_u *argp; + int ret = OK; + typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */ + int argcount = 0; /* number of arguments found */ + + /* + * Get the arguments. + */ + argp = *arg; + while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + argp = skipwhite(argp + 1); // skip the '(' or ',' + if (*argp == ')' || *argp == ',' || *argp == NUL) { + break; + } + if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { + ret = FAIL; + break; + } + ++argcount; + if (*argp != ',') + break; + } + if (*argp == ')') + ++argp; + else + ret = FAIL; + + if (ret == OK) { + int i = 0; + + if (get_vim_var_nr(VV_TESTING)) { + // Prepare for calling garbagecollect_for_testing(), need to know + // what variables are used on the call stack. + if (funcargs.ga_itemsize == 0) { + ga_init(&funcargs, (int)sizeof(typval_T *), 50); + } + for (i = 0; i < argcount; i++) { + ga_grow(&funcargs, 1); + ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; + } + } + ret = call_func(name, len, rettv, argcount, argvars, NULL, + firstline, lastline, doesrange, evaluate, + partial, selfdict); + + funcargs.ga_len -= i; + } else if (!aborting()) { + if (argcount == MAX_FUNC_ARGS) { + emsg_funcname(N_("E740: Too many arguments for function %s"), name); + } else { + emsg_funcname(N_("E116: Invalid arguments for function %s"), name); + } + } + + while (--argcount >= 0) { + tv_clear(&argvars[argcount]); + } + + *arg = skipwhite(argp); + return ret; +} + +#define FLEN_FIXED 40 + +/// Check whether function name starts with or s: +/// +/// @warning Only works for names previously checked by eval_fname_script(), if +/// it returned non-zero. +/// +/// @param[in] name Name to check. +/// +/// @return true if it starts with or s:, false otherwise. +static inline bool eval_fname_sid(const char *const name) + FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; +} + +/// In a script transform script-local names into actually used names +/// +/// Transforms "" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and +/// "" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have +/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. +/// +/// @param[in] name Name to transform. +/// @param fname_buf Buffer to save resulting function name to, if it fits. +/// Must have at least #FLEN_FIXED + 1 length. +/// @param[out] tofree Location where pointer to an allocated memory is saved +/// in case result does not fit into fname_buf. +/// @param[out] error Location where error type is saved, @see +/// FnameTransError. +/// +/// @return transformed name: either `fname_buf` or a pointer to an allocated +/// memory. +static char_u *fname_trans_sid(const char_u *const name, + char_u *const fname_buf, + char_u **const tofree, int *const error) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + char_u *fname; + const int llen = eval_fname_script((const char *)name); + if (llen > 0) { + fname_buf[0] = K_SPECIAL; + fname_buf[1] = KS_EXTRA; + fname_buf[2] = (int)KE_SNR; + int i = 3; + if (eval_fname_sid((const char *)name)) { // "" or "s:" + if (current_sctx.sc_sid <= 0) { + *error = ERROR_SCRIPT; + } else { + snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + i = (int)STRLEN(fname_buf); + } + } + if (i + STRLEN(name + llen) < FLEN_FIXED) { + STRCPY(fname_buf + i, name + llen); + fname = fname_buf; + } else { + fname = xmalloc(i + STRLEN(name + llen) + 1); + *tofree = fname; + memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); + } + } else { + fname = (char_u *)name; + } + + return fname; +} + +/// Find a function by name, return pointer to it in ufuncs. +/// @return NULL for unknown function. +ufunc_T *find_func(const char_u *name) +{ + hashitem_T *hi; + + hi = hash_find(&func_hashtab, name); + if (!HASHITEM_EMPTY(hi)) + return HI2UF(hi); + return NULL; +} + +/* + * Copy the function name of "fp" to buffer "buf". + * "buf" must be able to hold the function name plus three bytes. + * Takes care of script-local function names. + */ +static void cat_func_name(char_u *buf, ufunc_T *fp) +{ + if (fp->uf_name[0] == K_SPECIAL) { + STRCPY(buf, ""); + STRCAT(buf, fp->uf_name + 3); + } else + STRCPY(buf, fp->uf_name); +} + +/* + * Add a number variable "name" to dict "dp" with value "nr". + */ +static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) +{ +#ifndef __clang_analyzer__ + STRCPY(v->di_key, name); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(dp, v); + v->di_tv.v_type = VAR_NUMBER; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_number = nr; +} + +/* + * Free "fc" and what it contains. + */ +static void +free_funccal( + funccall_T *fc, + int free_val /* a: vars were allocated */ +) +{ + for (int i = 0; i < fc->fc_funcs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + + // When garbage collecting a funccall_T may be freed before the + // function that references it, clear its uf_scoped field. + // The function may have been redefined and point to another + // funccal_T, don't clear it then. + if (fp != NULL && fp->uf_scoped == fc) { + fp->uf_scoped = NULL; + } + } + ga_clear(&fc->fc_funcs); + + // The a: variables typevals may not have been allocated, only free the + // allocated variables. + vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); + + // Free all l: variables. + vars_clear(&fc->l_vars.dv_hashtab); + + // Free the a:000 variables if they were allocated. + if (free_val) { + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); + } + + func_ptr_unref(fc->func); + xfree(fc); +} + +/// Handle the last part of returning from a function: free the local hashtable. +/// Unless it is still in use by a closure. +static void cleanup_function_call(funccall_T *fc) +{ + current_funccal = fc->caller; + + // If the a:000 list and the l: and a: dicts are not referenced and there + // is no closure using it, we can free the funccall_T and what's in it. + if (!fc_referenced(fc)) { + free_funccal(fc, false); + } else { + // "fc" is still in use. This can happen when returning "a:000", + // assigning "l:" to a global variable or defining a closure. + // Link "fc" in the list for garbage collection later. + fc->caller = previous_funccal; + previous_funccal = fc; + + // Make a copy of the a: variables, since we didn't do that above. + TV_DICT_ITER(&fc->l_avars, di, { + tv_copy(&di->di_tv, &di->di_tv); + }); + + // Make a copy of the a:000 items, since we didn't do that above. + TV_LIST_ITER(&fc->l_varlist, li, { + tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); + }); + } +} + +/// Unreference "fc": decrement the reference count and free it when it +/// becomes zero. "fp" is detached from "fc". +/// +/// @param[in] force When true, we are exiting. +static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) +{ + funccall_T **pfc; + int i; + + if (fc == NULL) { + return; + } + + fc->fc_refcount--; + if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { + for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + if (fc == *pfc) { + *pfc = fc->caller; + free_funccal(fc, true); + return; + } + } + } + for (i = 0; i < fc->fc_funcs.ga_len; i++) { + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + } + } +} + +/// Remove the function from the function hashtable. If the function was +/// deleted while it still has references this was already done. +/// +/// @return true if the entry was deleted, false if it wasn't found. +static bool func_remove(ufunc_T *fp) +{ + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + + if (!HASHITEM_EMPTY(hi)) { + hash_remove(&func_hashtab, hi); + return true; + } + + return false; +} + +static void func_clear_items(ufunc_T *fp) +{ + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + + XFREE_CLEAR(fp->uf_tml_count); + XFREE_CLEAR(fp->uf_tml_total); + XFREE_CLEAR(fp->uf_tml_self); +} + +/// Free all things that a function contains. Does not free the function +/// itself, use func_free() for that. +/// +/// param[in] force When true, we are exiting. +static void func_clear(ufunc_T *fp, bool force) +{ + if (fp->uf_cleared) { + return; + } + fp->uf_cleared = true; + + // clear this function + func_clear_items(fp); + funccal_unref(fp->uf_scoped, fp, force); +} + +/// Free a function and remove it from the list of functions. Does not free +/// what a function contains, call func_clear() first. +/// +/// param[in] fp The function to free. +static void func_free(ufunc_T *fp) +{ + // only remove it when not done already, otherwise we would remove a newer + // version of the function + if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { + func_remove(fp); + } + xfree(fp); +} + +/// Free all things that a function contains and free the function itself. +/// +/// param[in] force When true, we are exiting. +static void func_clear_free(ufunc_T *fp, bool force) +{ + func_clear(fp, force); + func_free(fp); +} + +/// Call a user function +/// +/// @param fp Function to call. +/// @param[in] argcount Number of arguments. +/// @param argvars Arguments. +/// @param[out] rettv Return value. +/// @param[in] firstline First line of range. +/// @param[in] lastline Last line of range. +/// @param selfdict Dictionary for "self" for dictionary functions. +void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, + typval_T *rettv, linenr_T firstline, linenr_T lastline, + dict_T *selfdict) + FUNC_ATTR_NONNULL_ARG(1, 3, 4) +{ + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + bool using_sandbox = false; + funccall_T *fc; + int save_did_emsg; + static int depth = 0; + dictitem_T *v; + int fixvar_idx = 0; /* index in fixvar[] */ + int ai; + bool islambda = false; + char_u numbuf[NUMBUFLEN]; + char_u *name; + proftime_T wait_start; + proftime_T call_start; + int started_profiling = false; + bool did_save_redo = false; + save_redo_T save_redo; + + /* If depth of calling is getting too high, don't execute the function */ + if (depth >= p_mfd) { + EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'")); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + return; + } + ++depth; + // Save search patterns and redo buffer. + save_search_patterns(); + if (!ins_compl_active()) { + saveRedobuff(&save_redo); + did_save_redo = true; + } + ++fp->uf_calls; + // check for CTRL-C hit + line_breakcheck(); + // prepare the funccall_T structure + fc = xmalloc(sizeof(funccall_T)); + fc->caller = current_funccal; + current_funccal = fc; + fc->func = fp; + fc->rettv = rettv; + rettv->vval.v_number = 0; + fc->linenr = 0; + fc->returned = FALSE; + fc->level = ex_nesting_level; + /* Check if this function has a breakpoint. */ + fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); + fc->dbg_tick = debug_tick; + + // Set up fields for closure. + fc->fc_refcount = 0; + fc->fc_copyID = 0; + ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); + func_ptr_ref(fp); + + if (STRNCMP(fp->uf_name, "", 8) == 0) { + islambda = true; + } + + // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables + // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free + // each argument variable and saves a lot of time. + // + // Init l: variables. + init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); + if (selfdict != NULL) { + // Set l:self to "selfdict". Use "name" to avoid a warning from + // some compiler that checks the destination size. + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; +#ifndef __clang_analyzer__ + name = v->di_key; + STRCPY(name, "self"); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(&fc->l_vars, v); + v->di_tv.v_type = VAR_DICT; + v->di_tv.v_lock = 0; + v->di_tv.vval.v_dict = selfdict; + ++selfdict->dv_refcount; + } + + /* + * Init a: variables. + * Set a:0 to "argcount". + * Set a:000 to a list with room for the "..." arguments. + */ + init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", + (varnumber_T)(argcount - fp->uf_args.ga_len)); + fc->l_avars.dv_lock = VAR_FIXED; + // Use "name" to avoid a warning from some compiler that checks the + // destination size. + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; +#ifndef __clang_analyzer__ + name = v->di_key; + STRCPY(name, "000"); +#endif + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + tv_dict_add(&fc->l_avars, v); + v->di_tv.v_type = VAR_LIST; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_list = &fc->l_varlist; + tv_list_init_static(&fc->l_varlist); + tv_list_set_lock(&fc->l_varlist, VAR_FIXED); + + // Set a:firstline to "firstline" and a:lastline to "lastline". + // Set a:name to named arguments. + // Set a:N to the "..." arguments. + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "firstline", (varnumber_T)firstline); + add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + "lastline", (varnumber_T)lastline); + for (int i = 0; i < argcount; i++) { + bool addlocal = false; + + ai = i - fp->uf_args.ga_len; + if (ai < 0) { + // named argument a:name + name = FUNCARG(fp, i); + if (islambda) { + addlocal = true; + } + } else { + // "..." argument a:1, a:2, etc. + snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); + name = numbuf; + } + if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { + v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + } else { + v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + } + STRCPY(v->di_key, name); + + // Note: the values are copied directly to avoid alloc/free. + // "argvars" must have VAR_FIXED for v_lock. + v->di_tv = argvars[i]; + v->di_tv.v_lock = VAR_FIXED; + + if (addlocal) { + // Named arguments can be accessed without the "a:" prefix in lambda + // expressions. Add to the l: dict. + tv_copy(&v->di_tv, &v->di_tv); + tv_dict_add(&fc->l_vars, v); + } else { + tv_dict_add(&fc->l_avars, v); + } + + if (ai >= 0 && ai < MAX_FUNC_ARGS) { + tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); + *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; + TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; + } + } + + /* Don't redraw while executing the function. */ + ++RedrawingDisabled; + save_sourcing_name = sourcing_name; + save_sourcing_lnum = sourcing_lnum; + sourcing_lnum = 1; + + if (fp->uf_flags & FC_SANDBOX) { + using_sandbox = true; + sandbox++; + } + + // need space for new sourcing_name: + // * save_sourcing_name + // * "["number"].." or "function " + // * "" + fp->uf_name - 3 + // * terminating NUL + size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) + + STRLEN(fp->uf_name) + 27; + sourcing_name = xmalloc(len); + { + if (save_sourcing_name != NULL + && STRNCMP(save_sourcing_name, "function ", 9) == 0) { + vim_snprintf((char *)sourcing_name, + len, + "%s[%" PRId64 "]..", + save_sourcing_name, + (int64_t)save_sourcing_lnum); + } else { + STRCPY(sourcing_name, "function "); + } + cat_func_name(sourcing_name + STRLEN(sourcing_name), fp); + + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg(_("calling %s"), sourcing_name); + if (p_verbose >= 14) { + msg_puts("("); + for (int i = 0; i < argcount; i++) { + if (i > 0) { + msg_puts(", "); + } + if (argvars[i].v_type == VAR_NUMBER) { + msg_outnum((long)argvars[i].vval.v_number); + } else { + // Do not want errors such as E724 here. + emsg_off++; + char *tofree = encode_tv2string(&argvars[i], NULL); + emsg_off--; + if (tofree != NULL) { + char *s = tofree; + char buf[MSG_BUF_LEN]; + if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { + trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, + sizeof(buf)); + s = buf; + } + msg_puts(s); + xfree(tofree); + } + } + } + msg_puts(")"); + } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + } + + const bool do_profiling_yes = do_profiling == PROF_YES; + + bool func_not_yet_profiling_but_should = + do_profiling_yes + && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); + + if (func_not_yet_profiling_but_should) { + started_profiling = true; + func_do_profile(fp); + } + + bool func_or_func_caller_profiling = + do_profiling_yes + && (fp->uf_profiling + || (fc->caller != NULL && fc->caller->func->uf_profiling)); + + if (func_or_func_caller_profiling) { + ++fp->uf_tm_count; + call_start = profile_start(); + fp->uf_tm_children = profile_zero(); + } + + if (do_profiling_yes) { + script_prof_save(&wait_start); + } + + const sctx_T save_current_sctx = current_sctx; + current_sctx = fp->uf_script_ctx; + save_did_emsg = did_emsg; + did_emsg = FALSE; + + /* call do_cmdline() to execute the lines */ + do_cmdline(NULL, get_func_line, (void *)fc, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + + --RedrawingDisabled; + + // when the function was aborted because of an error, return -1 + if ((did_emsg + && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) { + tv_clear(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + } + + if (func_or_func_caller_profiling) { + call_start = profile_end(call_start); + call_start = profile_sub_wait(wait_start, call_start); // -V614 + fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); + fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, + fp->uf_tm_children); + if (fc->caller != NULL && fc->caller->func->uf_profiling) { + fc->caller->func->uf_tm_children = + profile_add(fc->caller->func->uf_tm_children, call_start); + fc->caller->func->uf_tml_children = + profile_add(fc->caller->func->uf_tml_children, call_start); + } + if (started_profiling) { + // make a ":profdel func" stop profiling the function + fp->uf_profiling = false; + } + } + + /* when being verbose, mention the return value */ + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + if (aborting()) + smsg(_("%s aborted"), sourcing_name); + else if (fc->rettv->v_type == VAR_NUMBER) + smsg(_("%s returning #%" PRId64 ""), + sourcing_name, (int64_t)fc->rettv->vval.v_number); + else { + char_u buf[MSG_BUF_LEN]; + + // The value may be very long. Skip the middle part, so that we + // have some idea how it starts and ends. smsg() would always + // truncate it at the end. Don't want errors such as E724 here. + emsg_off++; + char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL); + char_u *tofree = s; + emsg_off--; + if (s != NULL) { + if (vim_strsize(s) > MSG_BUF_CLEN) { + trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); + s = buf; + } + smsg(_("%s returning %s"), sourcing_name, s); + xfree(tofree); + } + } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + + xfree(sourcing_name); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + current_sctx = save_current_sctx; + if (do_profiling_yes) { + script_prof_restore(&wait_start); + } + if (using_sandbox) { + sandbox--; + } + + if (p_verbose >= 12 && sourcing_name != NULL) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg(_("continuing in %s"), sourcing_name); + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + --no_wait_return; + } + + did_emsg |= save_did_emsg; + depth--; + + cleanup_function_call(fc); + + if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { + // Function was unreferenced while being used, free it now. + func_clear_free(fp, false); + } + // restore search patterns and redo buffer + if (did_save_redo) { + restoreRedobuff(&save_redo); + } + restore_search_patterns(); +} + +/// There are two kinds of function names: +/// 1. ordinary names, function defined with :function +/// 2. numbered functions and lambdas +/// For the first we only count the name stored in func_hashtab as a reference, +/// using function() does not count as a reference, because the function is +/// looked up by name. +static bool func_name_refcount(char_u *name) +{ + return isdigit(*name) || *name == '<'; +} + +/* + * Save the current function call pointer, and set it to NULL. + * Used when executing autocommands and for ":source". + */ +void *save_funccal(void) +{ + funccall_T *fc = current_funccal; + + current_funccal = NULL; + return (void *)fc; +} + +void restore_funccal(void *vfc) +{ + current_funccal = (funccall_T *)vfc; +} + +funccall_T *get_current_funccal(void) +{ + return current_funccal; +} + +#if defined(EXITFREE) +void free_all_functions(void) +{ + hashitem_T *hi; + ufunc_T *fp; + uint64_t skipped = 0; + uint64_t todo = 1; + uint64_t used; + + // Clean up the call stack. + while (current_funccal != NULL) { + tv_clear(current_funccal->rettv); + cleanup_function_call(current_funccal); + } + + // First clear what the functions contain. Since this may lower the + // reference count of a function, it may also free a function and change + // the hash table. Restart if that happens. + while (todo > 0) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + used = func_hashtab.ht_used; + func_clear(fp, true); + if (used != func_hashtab.ht_used) { + skipped = 0; + break; + } + } + todo--; + } + } + } + + // Now actually free the functions. Need to start all over every time, + // because func_free() may change the hash table. + skipped = 0; + while (func_hashtab.ht_used > skipped) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + func_free(fp); + skipped = 0; + break; + } + } + } + } + if (skipped == 0) { + hash_clear(&func_hashtab); + } +} + +#endif + +/// Checks if a builtin function with the given name exists. +/// +/// @param[in] name name of the builtin function to check. +/// @param[in] len length of "name", or -1 for NUL terminated. +/// +/// @return true if "name" looks like a builtin function name: starts with a +/// lower case letter and doesn't contain AUTOLOAD_CHAR. +static bool builtin_function(const char *name, int len) +{ + if (!ASCII_ISLOWER(name[0])) { + return false; + } + + const char *p = (len == -1 + ? strchr(name, AUTOLOAD_CHAR) + : memchr(name, AUTOLOAD_CHAR, (size_t)len)); + + return p == NULL; +} + +int func_call(char_u *name, typval_T *args, partial_T *partial, + dict_T *selfdict, typval_T *rettv) +{ + typval_T argv[MAX_FUNC_ARGS + 1]; + int argc = 0; + int dummy; + int r = 0; + + TV_LIST_ITER(args->vval.v_list, item, { + if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + EMSG(_("E699: Too many arguments")); + goto func_call_skip_call; + } + // Make a copy of each argument. This is needed to be able to set + // v_lock to VAR_FIXED in the copy without changing the original list. + tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); + }); + + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); + +func_call_skip_call: + // Free the arguments. + while (argc > 0) { + tv_clear(&argv[--argc]); + } + + return r; +} + +/// Call a function with its resolved parameters +/// +/// "argv_func", when not NULL, can be used to fill in arguments only when the +/// invoked function uses them. It is called like this: +/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) +/// +/// @return FAIL if function cannot be called, else OK (even if an error +/// occurred while executing the function! Set `msg_list` to capture +/// the error, see do_cmdline()). +int +call_func( + const char_u *funcname, // name of the function + int len, // length of "name" + typval_T *rettv, // [out] value goes here + int argcount_in, // number of "argvars" + typval_T *argvars_in, // vars for arguments, must have "argcount" + // PLUS ONE elements! + ArgvFunc argv_func, // function to fill in argvars + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // [out] function handled range + bool evaluate, + partial_T *partial, // optional, can be NULL + dict_T *selfdict_in // Dictionary for "self" +) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) +{ + int ret = FAIL; + int error = ERROR_NONE; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + char_u *name; + int argcount = argcount_in; + typval_T *argvars = argvars_in; + dict_T *selfdict = selfdict_in; + typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL + int argv_clear = 0; + + // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) + // even when call_func() returns FAIL. + rettv->v_type = VAR_UNKNOWN; + + // Make a copy of the name, if it comes from a funcref variable it could + // be changed or deleted in the called function. + name = vim_strnsave(funcname, len); + + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + + *doesrange = false; + + if (partial != NULL) { + // When the function has a partial with a dict and there is a dict + // argument, use the dict argument. That is backwards compatible. + // When the dict was bound explicitly use the one from the partial. + if (partial->pt_dict != NULL + && (selfdict_in == NULL || !partial->pt_auto)) { + selfdict = partial->pt_dict; + } + if (error == ERROR_NONE && partial->pt_argc > 0) { + for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { + tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]); + } + for (int i = 0; i < argcount_in; i++) { + argv[i + argv_clear] = argvars_in[i]; + } + argvars = argv; + argcount = partial->pt_argc + argcount_in; + } + } + + if (error == ERROR_NONE && evaluate) { + char_u *rfname = fname; + + /* Ignore "g:" before a function name. */ + if (fname[0] == 'g' && fname[1] == ':') { + rfname = fname + 2; + } + + rettv->v_type = VAR_NUMBER; /* default rettv is number zero */ + rettv->vval.v_number = 0; + error = ERROR_UNKNOWN; + + if (is_luafunc(partial)) { + if (len > 0) { + error = ERROR_NONE; + executor_call_lua((const char *)funcname, len, + argvars, argcount, rettv); + } + } else if (!builtin_function((const char *)rfname, -1)) { + // User defined function. + if (partial != NULL && partial->pt_func != NULL) { + fp = partial->pt_func; + } else { + fp = find_func(rfname); + } + + // Trigger FuncUndefined event, may load the function. + if (fp == NULL + && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) + && !aborting()) { + /* executed an autocommand, search for the function again */ + fp = find_func(rfname); + } + // Try loading a package. + if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), + true) && !aborting()) { + // Loaded a package, search for the function again. + fp = find_func(rfname); + } + + if (fp != NULL && (fp->uf_flags & FC_DELETED)) { + error = ERROR_DELETED; + } else if (fp != NULL) { + if (argv_func != NULL) { + argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); + } + if (fp->uf_flags & FC_RANGE) { + *doesrange = true; + } + if (argcount < fp->uf_args.ga_len) { + error = ERROR_TOOFEW; + } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { + error = ERROR_TOOMANY; + } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { + error = ERROR_DICT; + } else { + // Call the user function. + call_user_func(fp, argcount, argvars, rettv, firstline, lastline, + (fp->uf_flags & FC_DICT) ? selfdict : NULL); + error = ERROR_NONE; + } + } + } else { + // Find the function name in the table, call its implementation. + const VimLFuncDef *const fdef = find_internal_func((const char *)fname); + if (fdef != NULL) { + if (argcount < fdef->min_argc) { + error = ERROR_TOOFEW; + } else if (argcount > fdef->max_argc) { + error = ERROR_TOOMANY; + } else { + argvars[argcount].v_type = VAR_UNKNOWN; + fdef->func(argvars, rettv, fdef->data); + error = ERROR_NONE; + } + } + } + /* + * The function call (or "FuncUndefined" autocommand sequence) might + * have been aborted by an error, an interrupt, or an explicitly thrown + * exception that has not been caught so far. This situation can be + * tested for by calling aborting(). For an error in an internal + * function or for the "E132" error in call_user_func(), however, the + * throw point at which the "force_abort" flag (temporarily reset by + * emsg()) is normally updated has not been reached yet. We need to + * update that flag first to make aborting() reliable. + */ + update_force_abort(); + } + if (error == ERROR_NONE) + ret = OK; + + /* + * Report an error unless the argument evaluation or function call has been + * cancelled due to an aborting error, an interrupt, or an exception. + */ + if (!aborting()) { + switch (error) { + case ERROR_UNKNOWN: + emsg_funcname(N_("E117: Unknown function: %s"), name); + break; + case ERROR_DELETED: + emsg_funcname(N_("E933: Function was deleted: %s"), name); + break; + case ERROR_TOOMANY: + emsg_funcname(_(e_toomanyarg), name); + break; + case ERROR_TOOFEW: + emsg_funcname(N_("E119: Not enough arguments for function: %s"), + name); + break; + case ERROR_SCRIPT: + emsg_funcname(N_("E120: Using not in a script context: %s"), + name); + break; + case ERROR_DICT: + emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), + name); + break; + } + } + + while (argv_clear > 0) { + tv_clear(&argv[--argv_clear]); + } + xfree(tofree); + xfree(name); + + return ret; +} + +/// List the head of the function: "name(arg1, arg2)". +/// +/// @param[in] fp Function pointer. +/// @param[in] indent Indent line. +/// @param[in] force Include bang "!" (i.e.: "function!"). +static void list_func_head(ufunc_T *fp, int indent, bool force) +{ + msg_start(); + if (indent) + MSG_PUTS(" "); + MSG_PUTS(force ? "function! " : "function "); + if (fp->uf_name[0] == K_SPECIAL) { + MSG_PUTS_ATTR("", HL_ATTR(HLF_8)); + msg_puts((const char *)fp->uf_name + 3); + } else { + msg_puts((const char *)fp->uf_name); + } + msg_putchar('('); + int j; + for (j = 0; j < fp->uf_args.ga_len; j++) { + if (j) { + msg_puts(", "); + } + msg_puts((const char *)FUNCARG(fp, j)); + } + if (fp->uf_varargs) { + if (j) { + msg_puts(", "); + } + msg_puts("..."); + } + msg_putchar(')'); + if (fp->uf_flags & FC_ABORT) { + msg_puts(" abort"); + } + if (fp->uf_flags & FC_RANGE) { + msg_puts(" range"); + } + if (fp->uf_flags & FC_DICT) { + msg_puts(" dict"); + } + if (fp->uf_flags & FC_CLOSURE) { + msg_puts(" closure"); + } + msg_clr_eos(); + if (p_verbose > 0) { + last_set_msg(fp->uf_script_ctx); + } +} + +/// Get a function name, translating "" and "". +/// Also handles a Funcref in a List or Dictionary. +/// flags: +/// TFN_INT: internal function name OK +/// TFN_QUIET: be quiet +/// TFN_NO_AUTOLOAD: do not use script autoloading +/// TFN_NO_DEREF: do not dereference a Funcref +/// Advances "pp" to just after the function name (if no error). +/// +/// @return the function name in allocated memory, or NULL for failure. +char_u * +trans_function_name( + char_u **pp, + bool skip, // only find the end, don't evaluate + int flags, + funcdict_T *fdp, // return: info about dictionary used + partial_T **partial // return: partial of a FuncRef +) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u *name = NULL; + const char_u *start; + const char_u *end; + int lead; + int len; + lval_T lv; + + if (fdp != NULL) + memset(fdp, 0, sizeof(funcdict_T)); + start = *pp; + + /* Check for hard coded : already translated function ID (from a user + * command). */ + if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA + && (*pp)[2] == (int)KE_SNR) { + *pp += 3; + len = get_id_len((const char **)pp) + 3; + return (char_u *)xmemdupz(start, len); + } + + /* A name starting with "" or "" is local to a script. But + * don't skip over "s:", get_lval() needs it for "s:dict.func". */ + lead = eval_fname_script((const char *)start); + if (lead > 2) { + start += lead; + } + + // Note that TFN_ flags use the same values as GLV_ flags. + end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, + lead > 2 ? 0 : FNE_CHECK_START); + if (end == start) { + if (!skip) + EMSG(_("E129: Function name required")); + goto theend; + } + if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { + /* + * Report an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (end != NULL) { + emsgf(_(e_invarg2), start); + } + } else { + *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR); + } + goto theend; + } + + if (lv.ll_tv != NULL) { + if (fdp != NULL) { + fdp->fd_dict = lv.ll_dict; + fdp->fd_newkey = lv.ll_newkey; + lv.ll_newkey = NULL; + fdp->fd_di = lv.ll_di; + } + if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { + name = vim_strsave(lv.ll_tv->vval.v_string); + *pp = (char_u *)end; + } else if (lv.ll_tv->v_type == VAR_PARTIAL + && lv.ll_tv->vval.v_partial != NULL) { + if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') { + len = check_luafunc_name((const char *)end+1, true); + if (len == 0) { + EMSG2(e_invexpr2, "v:lua"); + goto theend; + } + name = xmallocz(len); + memcpy(name, end+1, len); + *pp = (char_u *)end+1+len; + } else { + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + *pp = (char_u *)end; + } + if (partial != NULL) { + *partial = lv.ll_tv->vval.v_partial; + } + } else { + if (!skip && !(flags & TFN_QUIET) && (fdp == NULL + || lv.ll_dict == NULL + || fdp->fd_newkey == NULL)) { + EMSG(_(e_funcref)); + } else { + *pp = (char_u *)end; + } + name = NULL; + } + goto theend; + } + + if (lv.ll_name == NULL) { + // Error found, but continue after the function name. + *pp = (char_u *)end; + goto theend; + } + + /* Check if the name is a Funcref. If so, use the value. */ + if (lv.ll_exp_name != NULL) { + len = (int)strlen(lv.ll_exp_name); + name = deref_func_name(lv.ll_exp_name, &len, partial, + flags & TFN_NO_AUTOLOAD); + if ((const char *)name == lv.ll_exp_name) { + name = NULL; + } + } else if (!(flags & TFN_NO_DEREF)) { + len = (int)(end - *pp); + name = deref_func_name((const char *)(*pp), &len, partial, + flags & TFN_NO_AUTOLOAD); + if (name == *pp) { + name = NULL; + } + } + if (name != NULL) { + name = vim_strsave(name); + *pp = (char_u *)end; + if (strncmp((char *)name, "", 5) == 0) { + // Change "" to the byte sequence. + name[0] = K_SPECIAL; + name[1] = KS_EXTRA; + name[2] = (int)KE_SNR; + memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); + } + goto theend; + } + + if (lv.ll_exp_name != NULL) { + len = (int)strlen(lv.ll_exp_name); + if (lead <= 2 && lv.ll_name == lv.ll_exp_name + && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) { + // When there was "s:" already or the name expanded to get a + // leading "s:" then remove it. + lv.ll_name += 2; + lv.ll_name_len -= 2; + len -= 2; + lead = 2; + } + } else { + // Skip over "s:" and "g:". + if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) { + lv.ll_name += 2; + lv.ll_name_len -= 2; + } + len = (int)((const char *)end - lv.ll_name); + } + + size_t sid_buf_len = 0; + char sid_buf[20]; + + // Copy the function name to allocated memory. + // Accept name() inside a script, translate into 123_name(). + // Accept 123_name() outside a script. + if (skip) { + lead = 0; // do nothing + } else if (lead > 0) { + lead = 3; + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid((const char *)(*pp))) { + // It's "s:" or "". + if (current_sctx.sc_sid <= 0) { + EMSG(_(e_usingsid)); + goto theend; + } + sid_buf_len = snprintf(sid_buf, sizeof(sid_buf), + "%" PRIdSCID "_", current_sctx.sc_sid); + lead += sid_buf_len; + } + } else if (!(flags & TFN_INT) + && builtin_function(lv.ll_name, lv.ll_name_len)) { + EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), + start); + goto theend; + } + + if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { + char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); + + if (cp != NULL && cp < end) { + EMSG2(_("E884: Function name cannot contain a colon: %s"), start); + goto theend; + } + } + + name = xmalloc(len + lead + 1); + if (!skip && lead > 0) { + name[0] = K_SPECIAL; + name[1] = KS_EXTRA; + name[2] = (int)KE_SNR; + if (sid_buf_len > 0) { // If it's "" + memcpy(name + 3, sid_buf, sid_buf_len); + } + } + memmove(name + lead, lv.ll_name, len); + name[lead + len] = NUL; + *pp = (char_u *)end; + +theend: + clear_lval(&lv); + return name; +} + +/* + * ":function" + */ +void ex_function(exarg_T *eap) +{ + char_u *theline; + char_u *line_to_free = NULL; + int c; + int saved_did_emsg; + int saved_wait_return = need_wait_return; + char_u *name = NULL; + char_u *p; + char_u *arg; + char_u *line_arg = NULL; + garray_T newargs; + garray_T newlines; + int varargs = false; + int flags = 0; + ufunc_T *fp; + bool overwrite = false; + int indent; + int nesting; + dictitem_T *v; + funcdict_T fudi; + static int func_nr = 0; /* number for nameless function */ + int paren; + hashtab_T *ht; + int todo; + hashitem_T *hi; + linenr_T sourcing_lnum_off; + linenr_T sourcing_lnum_top; + bool is_heredoc = false; + char_u *skip_until = NULL; + char_u *heredoc_trimmed = NULL; + bool show_block = false; + bool do_concat = true; + + /* + * ":function" without argument: list functions. + */ + if (ends_excmd(*eap->arg)) { + if (!eap->skip) { + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (message_filtered(fp->uf_name)) { + continue; + } + if (!func_name_refcount(fp->uf_name)) { + list_func_head(fp, false, false); + } + } + } + } + eap->nextcmd = check_nextcmd(eap->arg); + return; + } + + /* + * ":function /pat": list functions matching pattern. + */ + if (*eap->arg == '/') { + p = skip_regexp(eap->arg + 1, '/', TRUE, NULL); + if (!eap->skip) { + regmatch_T regmatch; + + c = *p; + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); + *p = c; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (!isdigit(*fp->uf_name) + && vim_regexec(®match, fp->uf_name, 0)) + list_func_head(fp, false, false); + } + } + vim_regfree(regmatch.regprog); + } + } + if (*p == '/') + ++p; + eap->nextcmd = check_nextcmd(p); + return; + } + + // Get the function name. There are these situations: + // func function name + // "name" == func, "fudi.fd_dict" == NULL + // dict.func new dictionary entry + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func + // dict.func existing dict entry with a Funcref + // "name" == func, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // dict.func existing dict entry that's not a Funcref + // "name" == NULL, "fudi.fd_dict" set, + // "fudi.fd_di" set, "fudi.fd_newkey" == NULL + // s:func script-local function name + // g:func global function name, same as "func" + p = eap->arg; + name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); + paren = (vim_strchr(p, '(') != NULL); + if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { + /* + * Return on an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (fudi.fd_newkey != NULL) { + EMSG2(_(e_dictkey), fudi.fd_newkey); + } + xfree(fudi.fd_newkey); + return; + } else + eap->skip = TRUE; + } + + /* An error in a function call during evaluation of an expression in magic + * braces should not cause the function not to be defined. */ + saved_did_emsg = did_emsg; + did_emsg = FALSE; + + // + // ":function func" with only function name: list function. + // If bang is given: + // - include "!" in function head + // - exclude line numbers from function body + // + if (!paren) { + if (!ends_excmd(*skipwhite(p))) { + EMSG(_(e_trailing)); + goto ret_free; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + if (!eap->skip && !got_int) { + fp = find_func(name); + if (fp != NULL) { + list_func_head(fp, !eap->forceit, eap->forceit); + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { + continue; + } + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum((long)j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } + } + msg_prt_line(FUNCLINE(fp, j), false); + ui_flush(); // show a line at a time + os_breakcheck(); + } + if (!got_int) { + msg_putchar('\n'); + msg_puts(eap->forceit ? "endfunction" : " endfunction"); + } + } else + emsg_funcname(N_("E123: Undefined function: %s"), name); + } + goto ret_free; + } + + /* + * ":function name(arg1, arg2)" Define function. + */ + p = skipwhite(p); + if (*p != '(') { + if (!eap->skip) { + EMSG2(_("E124: Missing '(': %s"), eap->arg); + goto ret_free; + } + /* attempt to continue by skipping some text */ + if (vim_strchr(p, '(') != NULL) + p = vim_strchr(p, '('); + } + p = skipwhite(p + 1); + + ga_init(&newargs, (int)sizeof(char_u *), 3); + ga_init(&newlines, (int)sizeof(char_u *), 3); + + if (!eap->skip) { + /* Check the name of the function. Unless it's a dictionary function + * (that we are overwriting). */ + if (name != NULL) + arg = name; + else + arg = fudi.fd_newkey; + if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { + int j = (*arg == K_SPECIAL) ? 3 : 0; + while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) + : eval_isnamec(arg[j]))) + ++j; + if (arg[j] != NUL) + emsg_funcname((char *)e_invarg2, arg); + } + /* Disallow using the g: dict. */ + if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) + EMSG(_("E862: Cannot use g: here")); + } + + if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { + goto errret_2; + } + + if (KeyTyped && ui_has(kUICmdline)) { + show_block = true; + ui_ext_cmdline_block_append(0, (const char *)eap->cmd); + } + + // find extra arguments "range", "dict", "abort" and "closure" + for (;; ) { + p = skipwhite(p); + if (STRNCMP(p, "range", 5) == 0) { + flags |= FC_RANGE; + p += 5; + } else if (STRNCMP(p, "dict", 4) == 0) { + flags |= FC_DICT; + p += 4; + } else if (STRNCMP(p, "abort", 5) == 0) { + flags |= FC_ABORT; + p += 5; + } else if (STRNCMP(p, "closure", 7) == 0) { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) { + emsg_funcname(N_ + ("E932: Closure function should not be at top level: %s"), + name == NULL ? (char_u *)"" : name); + goto erret; + } + } else { + break; + } + } + + /* When there is a line break use what follows for the function body. + * Makes 'exe "func Test()\n...\nendfunc"' work. */ + if (*p == '\n') { + line_arg = p + 1; + } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { + EMSG(_(e_trailing)); + } + + /* + * Read the body of the function, until ":endfunction" is found. + */ + if (KeyTyped) { + /* Check if the function already exists, don't let the user type the + * whole function before telling him it doesn't work! For a script we + * need to skip the body to be able to find what follows. */ + if (!eap->skip && !eap->forceit) { + if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) + EMSG(_(e_funcdict)); + else if (name != NULL && find_func(name) != NULL) + emsg_funcname(e_funcexts, name); + } + + if (!eap->skip && did_emsg) + goto erret; + + if (!ui_has(kUICmdline)) { + msg_putchar('\n'); // don't overwrite the function name + } + cmdline_row = msg_row; + } + + // Save the starting line number. + sourcing_lnum_top = sourcing_lnum; + + indent = 2; + nesting = 0; + for (;; ) { + if (KeyTyped) { + msg_scroll = true; + saved_wait_return = false; + } + need_wait_return = false; + + if (line_arg != NULL) { + /* Use eap->arg, split up in parts by line breaks. */ + theline = line_arg; + p = vim_strchr(theline, '\n'); + if (p == NULL) + line_arg += STRLEN(line_arg); + else { + *p = NUL; + line_arg = p + 1; + } + } else { + xfree(line_to_free); + if (eap->getline == NULL) { + theline = getcmdline(':', 0L, indent, do_concat); + } else { + theline = eap->getline(':', eap->cookie, indent, do_concat); + } + line_to_free = theline; + } + if (KeyTyped) { + lines_left = Rows - 1; + } + if (theline == NULL) { + EMSG(_("E126: Missing :endfunction")); + goto erret; + } + if (show_block) { + assert(indent >= 0); + ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); + } + + // Detect line continuation: sourcing_lnum increased more than one. + sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); + if (sourcing_lnum < sourcing_lnum_off) { + sourcing_lnum_off -= sourcing_lnum; + } else { + sourcing_lnum_off = 0; + } + + if (skip_until != NULL) { + // Don't check for ":endfunc" between + // * ":append" and "." + // * ":python < 0) { + give_warning2((char_u *)_("W22: Text found after :endfunction: %s"), + p, true); + } + if (nextcmd != NULL) { + // Another command follows. If the line came from "eap" we + // can simply point into it, otherwise we need to change + // "eap->cmdlinep". + eap->nextcmd = nextcmd; + if (line_to_free != NULL) { + xfree(*eap->cmdlinep); + *eap->cmdlinep = line_to_free; + line_to_free = NULL; + } + } + break; + } + + /* Increase indent inside "if", "while", "for" and "try", decrease + * at "end". */ + if (indent > 2 && STRNCMP(p, "end", 3) == 0) + indent -= 2; + else if (STRNCMP(p, "if", 2) == 0 + || STRNCMP(p, "wh", 2) == 0 + || STRNCMP(p, "for", 3) == 0 + || STRNCMP(p, "try", 3) == 0) + indent += 2; + + /* Check for defining a function inside this function. */ + if (checkforcmd(&p, "function", 2)) { + if (*p == '!') { + p = skipwhite(p + 1); + } + p += eval_fname_script((const char *)p); + xfree(trans_function_name(&p, true, 0, NULL, NULL)); + if (*skipwhite(p) == '(') { + nesting++; + indent += 2; + } + } + + // Check for ":append", ":change", ":insert". + p = skip_range(p, NULL); + if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) + || (p[0] == 'c' + && (!ASCII_ISALPHA(p[1]) + || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) + || (p[2] == 'a' + && (STRNCMP(&p[3], "nge", 3) != 0 + || !ASCII_ISALPHA(p[6]))))))) + || (p[0] == 'i' + && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' + && (!ASCII_ISALPHA(p[2]) + || (p[2] == 's')))))) { + skip_until = vim_strsave((char_u *)"."); + } + + // heredoc: Check for ":python < 0) + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; + + /* Check for end of eap->arg. */ + if (line_arg != NULL && *line_arg == NUL) + line_arg = NULL; + } + + /* Don't define the function when skipping commands or when an error was + * detected. */ + if (eap->skip || did_emsg) + goto erret; + + /* + * If there are no errors, add the function + */ + if (fudi.fd_dict == NULL) { + v = find_var((const char *)name, STRLEN(name), &ht, false); + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + emsg_funcname(N_("E707: Function name conflicts with variable: %s"), + name); + goto erret; + } + + fp = find_func(name); + if (fp != NULL) { + // Function can be replaced with "function!" and when sourcing the + // same script again, but only once. + if (!eap->forceit + && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid + || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { + emsg_funcname(e_funcexts, name); + goto erret; + } + if (fp->uf_calls > 0) { + emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), + name); + goto erret; + } + if (fp->uf_refcount > 1) { + // This function is referenced somewhere, don't redefine it but + // create a new one. + (fp->uf_refcount)--; + fp->uf_flags |= FC_REMOVED; + fp = NULL; + overwrite = true; + } else { + // redefine existing function + XFREE_CLEAR(name); + func_clear_items(fp); + fp->uf_profiling = false; + fp->uf_prof_initialized = false; + } + } + } else { + char numbuf[20]; + + fp = NULL; + if (fudi.fd_newkey == NULL && !eap->forceit) { + EMSG(_(e_funcdict)); + goto erret; + } + if (fudi.fd_di == NULL) { + if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, + TV_CSTRING)) { + // Can't add a function to a locked dictionary + goto erret; + } + } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, + TV_CSTRING)) { + // Can't change an existing function if it is locked + goto erret; + } + + /* Give the function a sequential number. Can only be used with a + * Funcref! */ + xfree(name); + sprintf(numbuf, "%d", ++func_nr); + name = vim_strsave((char_u *)numbuf); + } + + if (fp == NULL) { + if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { + int slen, plen; + char_u *scriptname; + + /* Check that the autoload name matches the script name. */ + int j = FAIL; + if (sourcing_name != NULL) { + scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); + p = vim_strchr(scriptname, '/'); + plen = (int)STRLEN(p); + slen = (int)STRLEN(sourcing_name); + if (slen > plen && fnamecmp(p, + sourcing_name + slen - plen) == 0) + j = OK; + xfree(scriptname); + } + if (j == FAIL) { + EMSG2(_( + "E746: Function name does not match script file name: %s"), + name); + goto erret; + } + } + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + + if (fudi.fd_dict != NULL) { + if (fudi.fd_di == NULL) { + // Add new dict entry + fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); + if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { + xfree(fudi.fd_di); + xfree(fp); + goto erret; + } + } else { + // Overwrite existing dict entry. + tv_clear(&fudi.fd_di->di_tv); + } + fudi.fd_di->di_tv.v_type = VAR_FUNC; + fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); + + /* behave like "dict" was used */ + flags |= FC_DICT; + } + + /* insert the new function in the function list */ + STRCPY(fp->uf_name, name); + if (overwrite) { + hi = hash_find(&func_hashtab, name); + hi->hi_key = UF2HIKEY(fp); + } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { + xfree(fp); + goto erret; + } + fp->uf_refcount = 1; + } + fp->uf_args = newargs; + fp->uf_lines = newlines; + if ((flags & FC_CLOSURE) != 0) { + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + if (prof_def_func()) { + func_do_profile(fp); + } + fp->uf_varargs = varargs; + if (sandbox) { + flags |= FC_SANDBOX; + } + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + + goto ret_free; + +erret: + ga_clear_strings(&newargs); +errret_2: + ga_clear_strings(&newlines); +ret_free: + xfree(skip_until); + xfree(line_to_free); + xfree(fudi.fd_newkey); + xfree(name); + did_emsg |= saved_did_emsg; + need_wait_return |= saved_wait_return; + if (show_block) { + ui_ext_cmdline_block_leave(); + } +} // NOLINT(readability/fn_size) + +/* + * Return 5 if "p" starts with "" or "" (ignoring case). + * Return 2 if "p" starts with "s:". + * Return 0 otherwise. + */ +int eval_fname_script(const char *const p) +{ + // Use mb_strnicmp() because in Turkish comparing the "I" may not work with + // the standard library function. + if (p[0] == '<' + && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 + || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { + return 5; + } + if (p[0] == 's' && p[1] == ':') { + return 2; + } + return 0; +} + +bool translated_function_exists(const char *name) +{ + if (builtin_function(name, -1)) { + return find_internal_func((char *)name) != NULL; + } + return find_func((const char_u *)name) != NULL; +} + +/// Check whether function with the given name exists +/// +/// @param[in] name Function name. +/// @param[in] no_deref Whether to dereference a Funcref. +/// +/// @return True if it exists, false otherwise. +bool function_exists(const char *const name, bool no_deref) +{ + const char_u *nm = (const char_u *)name; + bool n = false; + int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; + + if (no_deref) { + flag |= TFN_NO_DEREF; + } + char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL, + NULL); + nm = skipwhite(nm); + + /* Only accept "funcname", "funcname ", "funcname (..." and + * "funcname(...", not "funcname!...". */ + if (p != NULL && (*nm == NUL || *nm == '(')) { + n = translated_function_exists(p); + } + xfree(p); + return n; +} + +/* + * Function given to ExpandGeneric() to obtain the list of user defined + * function names. + */ +char_u *get_user_func_name(expand_T *xp, int idx) +{ + static size_t done; + static hashitem_T *hi; + ufunc_T *fp; + + if (idx == 0) { + done = 0; + hi = func_hashtab.ht_array; + } + assert(hi); + if (done < func_hashtab.ht_used) { + if (done++ > 0) + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + fp = HI2UF(hi); + + if ((fp->uf_flags & FC_DICT) + || STRNCMP(fp->uf_name, "", 8) == 0) { + return (char_u *)""; // don't show dict and lambda functions + } + + if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { + return fp->uf_name; // Prevent overflow. + } + + cat_func_name(IObuff, fp); + if (xp->xp_context != EXPAND_USER_FUNC) { + STRCAT(IObuff, "("); + if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) + STRCAT(IObuff, ")"); + } + return IObuff; + } + return NULL; +} + +/// ":delfunction {name}" +void ex_delfunction(exarg_T *eap) +{ + ufunc_T *fp = NULL; + char_u *p; + char_u *name; + funcdict_T fudi; + + p = eap->arg; + name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + xfree(fudi.fd_newkey); + if (name == NULL) { + if (fudi.fd_dict != NULL && !eap->skip) + EMSG(_(e_funcref)); + return; + } + if (!ends_excmd(*skipwhite(p))) { + xfree(name); + EMSG(_(e_trailing)); + return; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + + if (!eap->skip) + fp = find_func(name); + xfree(name); + + if (!eap->skip) { + if (fp == NULL) { + if (!eap->forceit) { + EMSG2(_(e_nofunc), eap->arg); + } + return; + } + if (fp->uf_calls > 0) { + EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg); + return; + } + // check `uf_refcount > 2` because deleting a function should also reduce + // the reference count, and 1 is the initial refcount. + if (fp->uf_refcount > 2) { + EMSG2(_("Cannot delete function %s: It is being used internally"), + eap->arg); + return; + } + + if (fudi.fd_dict != NULL) { + // Delete the dict item that refers to the function, it will + // invoke func_unref() and possibly delete the function. + tv_dict_item_remove(fudi.fd_dict, fudi.fd_di); + } else { + // A normal function (not a numbered function or lambda) has a + // refcount of 1 for the entry in the hashtable. When deleting + // it and the refcount is more than one, it should be kept. + // A numbered function or lambda should be kept if the refcount is + // one or more. + if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { + // Function is still referenced somewhere. Don't free it but + // do remove it from the hashtable. + if (func_remove(fp)) { + fp->uf_refcount--; + } + fp->uf_flags |= FC_DELETED; + } else { + func_clear_free(fp, false); + } + } + } +} + +/* + * Unreference a Function: decrement the reference count and free it when it + * becomes zero. + */ +void func_unref(char_u *name) +{ + ufunc_T *fp = NULL; + + if (name == NULL || !func_name_refcount(name)) { + return; + } + + fp = find_func(name); + if (fp == NULL && isdigit(*name)) { +#ifdef EXITFREE + if (!entered_free_all_mem) { + internal_error("func_unref()"); + abort(); + } +#else + internal_error("func_unref()"); + abort(); +#endif + } + func_ptr_unref(fp); +} + +/// Unreference a Function: decrement the reference count and free it when it +/// becomes zero. +/// Unreference user function, freeing it if needed +/// +/// Decrements the reference count and frees when it becomes zero. +/// +/// @param fp Function to unreference. +void func_ptr_unref(ufunc_T *fp) +{ + if (fp != NULL && --fp->uf_refcount <= 0) { + // Only delete it when it's not being used. Otherwise it's done + // when "uf_calls" becomes zero. + if (fp->uf_calls == 0) { + func_clear_free(fp, false); + } + } +} + +/// Count a reference to a Function. +void func_ref(char_u *name) +{ + ufunc_T *fp; + + if (name == NULL || !func_name_refcount(name)) { + return; + } + fp = find_func(name); + if (fp != NULL) { + (fp->uf_refcount)++; + } else if (isdigit(*name)) { + // Only give an error for a numbered function. + // Fail silently, when named or lambda function isn't found. + internal_error("func_ref()"); + } +} + +/// Count a reference to a Function. +void func_ptr_ref(ufunc_T *fp) +{ + if (fp != NULL) { + (fp->uf_refcount)++; + } +} + +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + != DO_NOT_FREE_CNT) + || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_refcount > 0); +} + +/// @return true if items in "fc" do not have "copyID". That means they are not +/// referenced from anywhere that is in use. +static int can_free_funccal(funccall_T *fc, int copyID) +{ + return fc->l_varlist.lv_copyID != copyID + && fc->l_vars.dv_copyID != copyID + && fc->l_avars.dv_copyID != copyID + && fc->fc_copyID != copyID; +} + +/* + * ":return [expr]" + */ +void ex_return(exarg_T *eap) +{ + char_u *arg = eap->arg; + typval_T rettv; + int returning = FALSE; + + if (current_funccal == NULL) { + EMSG(_("E133: :return not inside a function")); + return; + } + + if (eap->skip) + ++emsg_skip; + + eap->nextcmd = NULL; + if ((*arg != NUL && *arg != '|' && *arg != '\n') + && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + if (!eap->skip) { + returning = do_return(eap, false, true, &rettv); + } else { + tv_clear(&rettv); + } + } else if (!eap->skip) { // It's safer to return also on error. + // In return statement, cause_abort should be force_abort. + update_force_abort(); + + // Return unless the expression evaluation has been cancelled due to an + // aborting error, an interrupt, or an exception. + if (!aborting()) { + returning = do_return(eap, false, true, NULL); + } + } + + /* When skipping or the return gets pending, advance to the next command + * in this line (!returning). Otherwise, ignore the rest of the line. + * Following lines will be ignored by get_func_line(). */ + if (returning) + eap->nextcmd = NULL; + else if (eap->nextcmd == NULL) /* no argument */ + eap->nextcmd = check_nextcmd(arg); + + if (eap->skip) + --emsg_skip; +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/* + * ":1,25call func(arg1, arg2)" function call. + */ +void ex_call(exarg_T *eap) +{ + char_u *arg = eap->arg; + char_u *startarg; + char_u *name; + char_u *tofree; + int len; + typval_T rettv; + linenr_T lnum; + int doesrange; + bool failed = false; + funcdict_T fudi; + partial_T *partial = NULL; + + if (eap->skip) { + // trans_function_name() doesn't work well when skipping, use eval0() + // instead to skip to any following command, e.g. for: + // :if 0 | call dict.foo().bar() | endif. + emsg_skip++; + if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) { + tv_clear(&rettv); + } + emsg_skip--; + return; + } + + tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); + if (fudi.fd_newkey != NULL) { + // Still need to give an error message for missing key. + EMSG2(_(e_dictkey), fudi.fd_newkey); + xfree(fudi.fd_newkey); + } + if (tofree == NULL) { + return; + } + + // Increase refcount on dictionary, it could get deleted when evaluating + // the arguments. + if (fudi.fd_dict != NULL) { + fudi.fd_dict->dv_refcount++; + } + + // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its + // contents. For VAR_PARTIAL get its partial, unless we already have one + // from trans_function_name(). + len = (int)STRLEN(tofree); + name = deref_func_name((const char *)tofree, &len, + partial != NULL ? NULL : &partial, false); + + // Skip white space to allow ":call func ()". Not good, but required for + // backward compatibility. + startarg = skipwhite(arg); + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. + + if (*startarg != '(') { + EMSG2(_("E107: Missing parentheses: %s"), eap->arg); + goto end; + } + + lnum = eap->line1; + for (; lnum <= eap->line2; lnum++) { + if (eap->addr_count > 0) { // -V560 + if (lnum > curbuf->b_ml.ml_line_count) { + // If the function deleted lines or switched to another buffer + // the line number may become invalid. + EMSG(_(e_invrange)); + break; + } + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + } + arg = startarg; + if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, + eap->line1, eap->line2, &doesrange, + true, partial, fudi.fd_dict) == FAIL) { + failed = true; + break; + } + + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript((const char **)&arg, &rettv, true, true) + == FAIL) { + failed = true; + break; + } + + tv_clear(&rettv); + if (doesrange) { + break; + } + + // Stop when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + // get_func_tv() returned OK, so that the check for trailing + // characters below is executed. + if (aborting()) { + break; + } + } + + if (!failed) { + // Check for trailing illegal characters and a following command. + if (!ends_excmd(*arg)) { + emsg_severe = TRUE; + EMSG(_(e_trailing)); + } else { + eap->nextcmd = check_nextcmd(arg); + } + } + +end: + tv_dict_unref(fudi.fd_dict); + xfree(tofree); +} + +/* + * Return from a function. Possibly makes the return pending. Also called + * for a pending return at the ":endtry" or after returning from an extra + * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set + * when called due to a ":return" command. "rettv" may point to a typval_T + * with the return rettv. Returns TRUE when the return can be carried out, + * FALSE when the return gets pending. + */ +int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) +{ + int idx; + cstack_T *const cstack = eap->cstack; + + if (reanimate) + /* Undo the return. */ + current_funccal->returned = FALSE; + + /* + * Cleanup (and inactivate) conditionals, but stop when a try conditional + * not in its finally clause (which then is to be executed next) is found. + * In this case, make the ":return" pending for execution at the ":endtry". + * Otherwise, return normally. + */ + idx = cleanup_conditionals(eap->cstack, 0, TRUE); + if (idx >= 0) { + cstack->cs_pending[idx] = CSTP_RETURN; + + if (!is_cmd && !reanimate) + /* A pending return again gets pending. "rettv" points to an + * allocated variable with the rettv of the original ":return"'s + * argument if present or is NULL else. */ + cstack->cs_rettv[idx] = rettv; + else { + /* When undoing a return in order to make it pending, get the stored + * return rettv. */ + if (reanimate) { + assert(current_funccal->rettv); + rettv = current_funccal->rettv; + } + + if (rettv != NULL) { + /* Store the value of the pending return. */ + cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T)); + *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv; + } else + cstack->cs_rettv[idx] = NULL; + + if (reanimate) { + /* The pending return value could be overwritten by a ":return" + * without argument in a finally clause; reset the default + * return value. */ + current_funccal->rettv->v_type = VAR_NUMBER; + current_funccal->rettv->vval.v_number = 0; + } + } + report_make_pending(CSTP_RETURN, rettv); + } else { + current_funccal->returned = TRUE; + + /* If the return is carried out now, store the return value. For + * a return immediately after reanimation, the value is already + * there. */ + if (!reanimate && rettv != NULL) { + tv_clear(current_funccal->rettv); + *current_funccal->rettv = *(typval_T *)rettv; + if (!is_cmd) + xfree(rettv); + } + } + + return idx < 0; +} + +/* + * Generate a return command for producing the value of "rettv". The result + * is an allocated string. Used by report_pending() for verbose messages. + */ +char_u *get_return_cmd(void *rettv) +{ + char_u *s = NULL; + char_u *tofree = NULL; + + if (rettv != NULL) { + tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL); + } + if (s == NULL) { + s = (char_u *)""; + } + + STRCPY(IObuff, ":return "); + STRLCPY(IObuff + 8, s, IOSIZE - 8); + if (STRLEN(s) + 8 >= IOSIZE) + STRCPY(IObuff + IOSIZE - 4, "..."); + xfree(tofree); + return vim_strsave(IObuff); +} + +/* + * Get next function line. + * Called by do_cmdline() to get the next line. + * Returns allocated string, or NULL for end of function. + */ +char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + char_u *retval; + garray_T *gap; /* growarray with function lines */ + + /* If breakpoints have been added/deleted need to check for it. */ + if (fcp->dbg_tick != debug_tick) { + fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) + func_line_end(cookie); + + gap = &fp->uf_lines; + if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned) + retval = NULL; + else { + /* Skip NULL lines (continuation lines). */ + while (fcp->linenr < gap->ga_len + && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) + ++fcp->linenr; + if (fcp->linenr >= gap->ga_len) + retval = NULL; + else { + retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); + sourcing_lnum = fcp->linenr; + if (do_profiling == PROF_YES) + func_line_start(cookie); + } + } + + /* Did we encounter a breakpoint? */ + if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { + dbg_breakpoint(fp->uf_name, sourcing_lnum); + /* Find next breakpoint. */ + fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + + return retval; +} + +/* + * Return TRUE if the currently active function should be ended, because a + * return was encountered or an error occurred. Used inside a ":while". + */ +int func_has_ended(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + + /* Ignore the "abort" flag if the abortion behavior has been changed due to + * an error inside a try conditional. */ + return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned; +} + +/* + * return TRUE if cookie indicates a function which "abort"s on errors. + */ +int func_has_abort(void *cookie) +{ + return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; +} + +/// Turn "dict.Func" into a partial for "Func" bound to "dict". +/// Changes "rettv" in-place. +void make_partial(dict_T *const selfdict, typval_T *const rettv) +{ + char_u *fname; + char_u *tofree = NULL; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + int error; + + if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { + fp = rettv->vval.v_partial->pt_func; + } else { + fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING + ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; + // Translate "s:func" to the stored function name. + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); + fp = find_func(fname); + xfree(tofree); + } + + // Turn "dict.Func" into a partial for "Func" with "dict". + if (fp != NULL && (fp->uf_flags & FC_DICT)) { + partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + pt->pt_refcount = 1; + pt->pt_dict = selfdict; + (selfdict->dv_refcount)++; + pt->pt_auto = true; + if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { + // Just a function: Take over the function name and use selfdict. + pt->pt_name = rettv->vval.v_string; + } else { + partial_T *ret_pt = rettv->vval.v_partial; + int i; + + // Partial: copy the function name, use selfdict and copy + // args. Can't take over name or args, the partial might + // be referenced elsewhere. + if (ret_pt->pt_name != NULL) { + pt->pt_name = vim_strsave(ret_pt->pt_name); + func_ref(pt->pt_name); + } else { + pt->pt_func = ret_pt->pt_func; + func_ptr_ref(pt->pt_func); + } + if (ret_pt->pt_argc > 0) { + size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; + pt->pt_argv = (typval_T *)xmalloc(arg_size); + pt->pt_argc = ret_pt->pt_argc; + for (i = 0; i < pt->pt_argc; i++) { + tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); + } + } + partial_unref(ret_pt); + } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } +} + +/* + * Return the name of the executed function. + */ +char_u *func_name(void *cookie) +{ + return ((funccall_T *)cookie)->func->uf_name; +} + +/* + * Return the address holding the next breakpoint line for a funccall cookie. + */ +linenr_T *func_breakpoint(void *cookie) +{ + return &((funccall_T *)cookie)->breakpoint; +} + +/* + * Return the address holding the debug tick for a funccall cookie. + */ +int *func_dbg_tick(void *cookie) +{ + return &((funccall_T *)cookie)->dbg_tick; +} + +/* + * Return the nesting level for a funccall cookie. + */ +int func_level(void *cookie) +{ + return ((funccall_T *)cookie)->level; +} + +/* + * Return TRUE when a function was ended by a ":return" command. + */ +int current_func_returned(void) +{ + return current_funccal->returned; +} + +bool free_unref_funccal(int copyID, int testing) +{ + bool did_free = false; + bool did_free_funccal = false; + + for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) { + if (can_free_funccal(*pfc, copyID)) { + funccall_T *fc = *pfc; + *pfc = fc->caller; + free_funccal(fc, true); + did_free = true; + did_free_funccal = true; + } else { + pfc = &(*pfc)->caller; + } + } + if (did_free_funccal) { + // When a funccal was freed some more items might be garbage + // collected, so run again. + (void)garbage_collect(testing); + } + return did_free; +} + +// Get function call environment based on backtrace debug level +funccall_T *get_funccal(void) +{ + funccall_T *funccal = current_funccal; + if (debug_backtrace_level > 0) { + for (int i = 0; i < debug_backtrace_level; i++) { + funccall_T *temp_funccal = funccal->caller; + if (temp_funccal) { + funccal = temp_funccal; + } else { + // backtrace level overflow. reset to max + debug_backtrace_level = i; + } + } + } + + return funccal; +} + +/// Return the hashtable used for local variables in the current funccal. +/// Return NULL if there is no current funccal. +hashtab_T *get_funccal_local_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_vars.dv_hashtab; +} + +/// Return the l: scope variable. +/// Return NULL if there is no current funccal. +dictitem_T *get_funccal_local_var(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return (dictitem_T *)&get_funccal()->l_vars_var; +} + +/// Return the hashtable used for argument in the current funccal. +/// Return NULL if there is no current funccal. +hashtab_T *get_funccal_args_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_avars.dv_hashtab; +} + +/// Return the a: scope variable. +/// Return NULL if there is no current funccal. +dictitem_T *get_funccal_args_var(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return (dictitem_T *)¤t_funccal->l_avars_var; +} + +/* + * List function variables, if there is a function. + */ +void list_func_vars(int *first) +{ + if (current_funccal != NULL) { + list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, + first); + } +} + +/// If "ht" is the hashtable for local variables in the current funccal, return +/// the dict that contains it. +/// Otherwise return NULL. +dict_T *get_current_funccal_dict(hashtab_T *ht) +{ + if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { + return ¤t_funccal->l_vars; + } + return NULL; +} + +/// Search hashitem in parent scope. +hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + funccall_T *old_current_funccal = current_funccal; + hashitem_T *hi = NULL; + const size_t namelen = strlen(name); + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal != NULL) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + hi = hash_find_len(ht, varname, namelen - (varname - name)); + if (!HASHITEM_EMPTY(hi)) { + *pht = ht; + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return hi; +} + +/// Search variable in parent scope. +dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, + int no_autoload) +{ + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + dictitem_T *v = NULL; + funccall_T *old_current_funccal = current_funccal; + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + v = find_var_in_ht(ht, *name, varname, + namelen - (size_t)(varname - name), no_autoload); + if (v != NULL) { + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return v; +} + +/// Set "copyID + 1" in previous_funccal and callers. +bool set_ref_in_previous_funccal(int copyID) +{ + bool abort = false; + + for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL); + } + return abort; +} + +static bool set_ref_in_funccal(funccall_T *fc, int copyID) +{ + bool abort = false; + + if (fc->fc_copyID != copyID) { + fc->fc_copyID = copyID; + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_func(NULL, fc->func, copyID); + } + return abort; +} + +/// Set "copyID" in all local vars and arguments in the call stack. +bool set_ref_in_call_stack(int copyID) +{ + bool abort = false; + + for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + } + return abort; +} + +/// Set "copyID" in all functions available by name. +bool set_ref_in_functions(int copyID) +{ + int todo; + hashitem_T *hi = NULL; + bool abort = false; + ufunc_T *fp; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + fp = HI2UF(hi); + if (!func_name_refcount(fp->uf_name)) { + abort = abort || set_ref_in_func(NULL, fp, copyID); + } + } + } + return abort; +} + +/// Set "copyID" in all function arguments. +bool set_ref_in_func_args(int copyID) +{ + bool abort = false; + + for (int i = 0; i < funcargs.ga_len; i++) { + abort = abort || set_ref_in_item(((typval_T **)funcargs.ga_data)[i], + copyID, NULL, NULL); + } + return abort; +} + +/// Mark all lists and dicts referenced through function "name" with "copyID". +/// "list_stack" is used to add lists to be marked. Can be NULL. +/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. +/// +/// @return true if setting references failed somehow. +bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +{ + ufunc_T *fp = fp_in; + funccall_T *fc; + int error = ERROR_NONE; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + bool abort = false; + if (name == NULL && fp_in == NULL) { + return false; + } + + if (fp_in == NULL) { + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + fp = find_func(fname); + } + if (fp != NULL) { + for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { + abort = abort || set_ref_in_funccal(fc, copyID); + } + } + xfree(tofree); + return abort; +} diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h new file mode 100644 index 0000000000..db4e02ee98 --- /dev/null +++ b/src/nvim/eval/userfunc.h @@ -0,0 +1,36 @@ +#ifndef NVIM_EVAL_USERFUNC_H +#define NVIM_EVAL_USERFUNC_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" + +///< 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; + +/// errors for when calling a function +typedef enum { + ERROR_UNKNOWN = 0, + ERROR_TOOMANY, + ERROR_TOOFEW, + ERROR_SCRIPT, + ERROR_DICT, + ERROR_NONE, + ERROR_OTHER, + ERROR_BOTH, + ERROR_DELETED, +} FnameTransError; + +typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, + int called_func_argcount); + +#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] +#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/userfunc.h.generated.h" +#endif +#endif // NVIM_EVAL_USERFUNC_H -- cgit From 7ed4837298414488e238763953d40a4c7f2d7976 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 20 Feb 2020 22:53:38 -0500 Subject: vim-patch:8.1.1122: char2nr() does not handle composing characters Problem: char2nr() does not handle composing characters. Solution: Add str2list() and list2str(). (Ozaki Kiichi, closes vim/vim#4190) https://github.com/vim/vim/commit/9d40128afd7fcd038ff6532722b55b1a8c189ce8 'utf8' optional param is noop unlike Vim. --- src/nvim/eval/funcs.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 36782e4223..bd57cec794 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5246,6 +5246,36 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "list2str()" function +static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_invarg)); + return; + } + + list_T *const l = argvars[0].vval.v_list; + if (l == NULL) { + return; // empty list results in empty string + } + + ga_init(&ga, 1, 80); + char_u buf[MB_MAXBYTES + 1]; + + TV_LIST_ITER_CONST(l, li, { + buf[utf_char2bytes(tv_get_number(TV_LIST_ITEM_TV(li)), buf)] = NUL; + ga_concat(&ga, buf); + }); + ga_append(&ga, NUL); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ga.ga_data; +} + /* * "localtime()" function */ @@ -9353,6 +9383,17 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_FLOAT; } +// "str2list()" function +static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, kListLenUnknown); + const char_u *p = (const char_u *)tv_get_string(&argvars[0]); + + for (; *p != NUL; p += utf_ptr2len(p)) { + tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p)); + } +} + // "str2nr()" function static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { -- cgit From ebcde1de42588e697e0f4eaed9f6f0ea6a77a2cd Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 17 Feb 2020 16:33:55 +0100 Subject: nvim:eval: Fix enum declaration for ListLenSpecials Instead of declaring an enum, this creates a global variable. As gcc10 uses -fno-common by default, global variables declared with the same name more than once is not allowed anymore revealing this issue. Each time this header is included, we define the enum name as a global variable. See also https://bugzilla.redhat.com/show_bug.cgi?id=1799680 --- src/nvim/eval/typval.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 008453b87f..5afdedff75 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -33,7 +33,7 @@ typedef double float_T; enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; /// Additional values for tv_list_alloc() len argument -enum { +enum ListLenSpecials { /// List length is not known in advance /// /// To be used when there is neither a way to know how many elements will be @@ -49,7 +49,7 @@ enum { /// /// To be used when it looks impractical to determine list length. kListLenMayKnow = -3, -} ListLenSpecials; +}; /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX -- cgit From 3c12ee333a519c5be1d8f63d7978b483a51a3da7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 2 Feb 2020 15:55:15 -0500 Subject: vim-patch:8.1.0619: :echomsg and :echoerr do not handle List and Dict Problem: :echomsg and :echoerr do not handle List and Dict like :echo does. (Daniel Hahler) Solution: Be more tolerant about the expression result type. https://github.com/vim/vim/commit/461a7fcfce3cd6414f990037e6468af3b5ccf119 Add lua functional tests for :echo,:echon,:echomsg,:echoerr because nvim did not port "test_" functions from Vim that modify internal state. Testing :echoerr via try/catch is sufficient. --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index bd57cec794..be8e35b1de 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -9558,7 +9558,7 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) /* * "string()" function */ -static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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); -- cgit From eba8a9ca1d94f43b7fe2e92d5a2caa144dd68d0d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 21 Feb 2020 22:05:59 -0500 Subject: vim-patch:8.1.1510: a plugin cannot easily expand a command like done internally Problem: A plugin cannot easily expand a command like done internally. Solution: Add the expandcmd() function. (Yegappan Lakshmanan, closes vim/vim#4514) https://github.com/vim/vim/commit/80dad48c5095d30873a42ec82628bdb213125d8e --- src/nvim/eval/funcs.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index be8e35b1de..c89c72c3de 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2103,6 +2103,31 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); } +// "expandcmd()" function +// Expand all the special characters in a command string. +static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *errormsg = NULL; + + rettv->v_type = VAR_STRING; + char_u *cmdstr = (char_u *)xstrdup(tv_get_string(&argvars[0])); + + exarg_T eap = { + .cmd = cmdstr, + .arg = cmdstr, + .usefilter = false, + .nextcmd = NULL, + .cmdidx = CMD_USER, + }; + eap.argt |= NOSPC; + + expand_filename(&eap, &cmdstr, &errormsg); + if (errormsg != NULL && *errormsg != NUL) { + EMSG(errormsg); + } + rettv->vval.v_string = cmdstr; +} + /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function -- cgit From 607240a8bac80f02d812084d3146b4372ddae6dd Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 22 Feb 2020 12:09:21 -0500 Subject: pvs/v1048: variable was assigned same value --- src/nvim/eval/funcs.c | 1 - 1 file changed, 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index c89c72c3de..25e57d54f0 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5297,7 +5297,6 @@ static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) }); ga_append(&ga, NUL); - rettv->v_type = VAR_STRING; rettv->vval.v_string = ga.ga_data; } -- cgit From d846f47cc88cd36da5ad8fd7e716552c80e3b418 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 24 Feb 2020 20:33:43 -0500 Subject: vim-patch:8.1.0881: can execute shell commands in rvim through interfaces Problem: Can execute shell commands in rvim through interfaces. Solution: Disable using interfaces in restricted mode. Allow for writing file with writefile(), histadd() and a few others. https://github.com/vim/vim/commit/8c62a08faf89663e5633dc5036cd8695c80f1075 --- src/nvim/eval/funcs.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 25e57d54f0..6d67279e64 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4317,7 +4317,7 @@ static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) HistoryType histype; rettv->vval.v_number = false; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -7779,8 +7779,7 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() - || check_secure() + if (check_secure() || !tv_check_str_or_nr(&argvars[0])) { return; } @@ -8284,7 +8283,7 @@ static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -11005,7 +11004,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } -- cgit From f61331e184d8db6d8dc965856eba1db321a2d8f3 Mon Sep 17 00:00:00 2001 From: erw7 Date: Wed, 25 Mar 2020 15:50:55 +0900 Subject: Change resolve() to resolve symbolic links on Windows --- src/nvim/eval/funcs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6d67279e64..b4a3e29156 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6709,7 +6709,10 @@ 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); + char *v = os_resolve_shortcut(fname); + if (v == NULL) { + v = os_realpath(fname, v); + } rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); #else # ifdef HAVE_READLINK -- cgit From b687a6c2b2c096a68b46141947aef29c616f7a1f Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 27 Mar 2020 16:07:39 +0900 Subject: Change to canonicalize only when reparse point in included --- src/nvim/eval/funcs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index b4a3e29156..68eca56235 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6711,7 +6711,9 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) #ifdef WIN32 char *v = os_resolve_shortcut(fname); if (v == NULL) { - v = os_realpath(fname, v); + if (os_is_reparse_point_include(fname)) { + v = os_realpath(fname, v); + } } rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); #else -- cgit From 73dc9e943cfea92ec0254f6926c9524029cb0222 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Mar 2020 09:51:46 -0400 Subject: vim-patch:8.1.2378: using old C style comments Problem: Using old C style comments. Solution: Use // comments where appropriate. https://github.com/vim/vim/commit/5d18efecfd6c45d69f55268948a22cd0465bb955 --- src/nvim/eval/funcs.c | 225 +++++++++++++++++++++++++++----------------------- 1 file changed, 120 insertions(+), 105 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6d67279e64..8800e02295 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -971,15 +971,16 @@ static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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) + // '> 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 + } 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. */ + // 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(); @@ -1664,25 +1665,29 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) int filler_lines; int col; - if (lnum < 0) /* ignore type error in {lnum} arg */ + 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. */ + // 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 + 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; @@ -2060,8 +2065,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } 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. */ + // 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; @@ -2480,9 +2485,9 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - /* Find interesting text in this line. */ + // Find interesting text in this line. s = skipwhite(ml_get(lnum)); - /* skip C comment-start */ + // skip C comment-start if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { s = skipwhite(s + 2); if (*skipwhite(s) == NUL && lnum + 1 < foldend) { @@ -2500,7 +2505,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) sprintf((char *)r, txt, dashes, count); len = (int)STRLEN(r); STRCAT(r, s); - /* remove 'foldmarker' and 'commentstring' */ + // remove 'foldmarker' and 'commentstring' foldtext_cleanup(r + len); rettv->vval.v_string = r; } @@ -2925,10 +2930,10 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; if (IS_SPECIAL(n) || mod_mask != 0) { - char_u temp[10]; /* modifier: 3, mbyte-char: 6, NUL: 1 */ + char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 int i = 0; - /* Turn a special key into three bytes, plus modifier. */ + // Turn a special key into three bytes, plus modifier. if (mod_mask != 0) { temp[i++] = K_SPECIAL; temp[i++] = KS_MODIFIER; @@ -3277,7 +3282,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { rettv->vval.v_number = (varnumber_T)filesize; - /* non-perfect check for overflow */ + // non-perfect check for overflow if ((uint64_t)rettv->vval.v_number != filesize) { rettv->vval.v_number = -2; } @@ -4026,7 +4031,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "dialog_con", "diff", "digraphs", - "eval", /* always present, of course! */ + "eval", // always present, of course! "ex_extra", "extra_search", "file_in_path", @@ -4094,7 +4099,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "textobjects", "timers", "title", - "user-commands", /* was accidentally included in 5.4 */ + "user-commands", // was accidentally included in 5.4 "user_commands", "vertsplit", "virtualedit", @@ -4550,9 +4555,9 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } msg_start(); - msg_row = Rows - 1; /* for when 'cmdheight' > 1 */ - lines_left = Rows; /* avoid more prompt */ - msg_scroll = TRUE; + 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, { @@ -5426,7 +5431,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, long idx = 0; char_u *tofree = NULL; - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + // Make 'cpoptions' empty, the 'l' flag should not be used here. save_cpo = p_cpo; p_cpo = (char_u *)""; @@ -5492,12 +5497,12 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, 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) + // 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 { + } else { str += start; len -= start; } @@ -5536,7 +5541,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (l == NULL && !match) break; - /* Advance to just after the match. */ + // Advance to just after the match. if (l != NULL) { li = TV_LIST_ITEM_NEXT(l, li); idx++; @@ -6309,12 +6314,12 @@ 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 */ + 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 */ + 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) { @@ -6353,14 +6358,17 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) char_u *s = NULL; size_t len = p - start; - /* Finished a line. Remove CRs before NL. */ + // 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; + 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); @@ -6372,7 +6380,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) s = xrealloc(prev, prevlen + len + 1); memcpy(s + prevlen, start, len); s[prevlen + len] = NUL; - prev = NULL; /* the list will own the string */ + prev = NULL; // the list will own the string prevlen = prevsize = 0; } @@ -6411,13 +6419,12 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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) + // 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 */ + } else { + // have to shuffle buf to close gap int adjust_prevlen = 0; if (dest < buf) { // -V782 @@ -6433,13 +6440,13 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - } /* for */ + } // 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". */ + // 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 @@ -6454,11 +6461,11 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } prev = xrealloc(prev, prevsize); } - /* Add the line part to end of "prev". */ + // Add the line part to end of "prev". memmove(prev + prevlen, start, p - start); prevlen += (long)(p - start); } - } /* while */ + } // while xfree(prev); fclose(fd); @@ -6950,7 +6957,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) pos_T save_cursor; bool save_p_ws = p_ws; int dir; - int retval = 0; /* default: FAIL */ + int retval = 0; // default: FAIL long lnum_stop = 0; proftime_T tm; long time_limit = 0; @@ -6974,7 +6981,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) options |= SEARCH_COL; } - /* Optional arguments: line number to stop searching and timeout. */ + // 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) { @@ -6988,7 +6995,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } } - /* Set the time limit, if there is one. */ + // Set the time limit, if there is one. tm = profile_setlimit(time_limit); /* @@ -7018,20 +7025,21 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) setpcmark(); curwin->w_cursor = pos; if (match_pos != NULL) { - /* Store the match cursor position */ + // 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 */ + // "/$" 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) + // If 'n' flag is used: restore cursor position. + if (flags & SP_NOMOVE) { curwin->w_cursor = save_cursor; - else - curwin->w_set_curswant = TRUE; + } else { + curwin->w_set_curswant = true; + } theend: p_ws = save_p_ws; @@ -7370,7 +7378,7 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) int thisblock = 0; bool error = false; - rettv->vval.v_number = 1; /* default: FAIL */ + rettv->vval.v_number = 1; // default: FAIL const char *const name = tv_get_string_chk(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { @@ -7528,11 +7536,11 @@ do_searchpair( size_t pat2_len; size_t pat3_len; - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + // 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. */ + // 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 @@ -7580,17 +7588,18 @@ do_searchpair( 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) + // 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 + } else { incl(&pos); + } } foundpos = pos; - /* clear the start flag to avoid getting stuck here */ + // clear the start flag to avoid getting stuck here options &= ~SEARCH_START; // If the skip pattern matches, ignore this match. @@ -7601,7 +7610,7 @@ do_searchpair( const bool r = eval_expr_to_bool(skip, &err); curwin->w_cursor = save_pos; if (err) { - /* Evaluating {skip} caused an error, break here. */ + // Evaluating {skip} caused an error, break here. curwin->w_cursor = save_cursor; retval = -1; break; @@ -7611,49 +7620,54 @@ do_searchpair( } 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 */ + // 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 */ + // 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 + // Found the match: return matchcount or line number. + if (flags & SP_RETCOUNT) { + retval++; + } else { retval = pos.lnum; - if (flags & SP_SETPCMARK) + } + if (flags & SP_SETPCMARK) { setpcmark(); + } curwin->w_cursor = pos; if (!(flags & SP_REPEAT)) break; - nest = 1; /* search for next unmatched */ + nest = 1; // search for next unmatched } } if (match_pos != NULL) { - /* Store the match cursor position */ + // 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) + // 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) + if (p_cpo == empty_option) { p_cpo = save_cpo; - else - /* Darn, evaluating the {skip} expression changed the value. */ + } else { + // Darn, evaluating the {skip} expression changed the value. free_string_option(save_cpo); + } return retval; } @@ -9031,7 +9045,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) info.item_compare_selfdict = NULL; if (argvars[1].v_type != VAR_UNKNOWN) { - /* optional second argument: {func} */ + // 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) { @@ -9292,7 +9306,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool keepempty = false; bool typeerr = false; - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + // Make 'cpoptions' empty, the 'l' flag should not be used here. save_cpo = p_cpo; p_cpo = (char_u *)""; @@ -9480,10 +9494,10 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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) + // MSVC returns NULL for an invalid value of seconds. + if (curtime_ptr == NULL) { rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); - else { + } else { vimconv_T conv; char_u *enc; @@ -10180,7 +10194,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; wp != twin; wp = wp->w_next) { if (wp == NULL) { - /* didn't find it in this tabpage */ + // didn't find it in this tabpage nr = 0; break; } @@ -10769,9 +10783,10 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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])) + // A non-zero number or non-empty string argument: reset mode. + if (non_zero_arg(&argvars[0])) { curbuf->b_visual_mode_eval = NUL; + } } /* -- cgit From 22eb3c9e4933d440f3ad8f55e37e7370a69c765e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 21 Mar 2020 11:04:09 -0400 Subject: vim-patch:8.1.1745: compiler warning for unused argument Problem: Compiler warning for unused argument. Solution: Add UNUSED. Change comments to new style. https://github.com/vim/vim/commit/1f164b19685d8ad709b11f3f1933685469251e30 --- src/nvim/eval/funcs.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8800e02295..d97fba8992 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5648,8 +5648,11 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char grpbuf[NUMBUFLEN]; char patbuf[NUMBUFLEN]; + // group const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); + // pattern const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + // default priority int prio = 10; int id = -1; bool error = false; -- cgit From 366e75b6be57220b28f2abc22add19d5df33b5a3 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Apr 2020 20:31:20 -0400 Subject: vim-patch:8.2.0473: variables declared in an outer scope Problem: Variables declared in an outer scope. Solution: Decleare variables only in the scope where they are used. https://github.com/vim/vim/commit/8601545338581c01e328cdc3a72c0b12d92c54cf --- src/nvim/eval/funcs.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index d97fba8992..8f16855d0b 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2807,11 +2807,12 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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 + buf_T *const save_curbuf = curbuf; + + // set curbuf to be our buf, temporarily + curbuf = buf; + if (varname[1] == NUL) { // get all buffer-local options in a dict dict_T *opts = get_winbuf_options(true); @@ -2824,19 +2825,19 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) // buffer-local-option done = true; } + + // restore previous notion of curbuf + curbuf = save_curbuf; } 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', + dictitem_T *const v = find_var_in_ht(&buf->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--; @@ -7824,10 +7825,9 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) // 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); + buf_T *const save_curbuf = curbuf; curbuf = buf; memcpy(bufvarname, "b:", 2); memcpy(bufvarname + 2, varname, varname_len + 1); -- cgit From 572627255983b7733b6ad05da52b3e704aae8a2f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Apr 2020 20:36:11 -0400 Subject: vim-patch:8.2.0507: getbufvar() may get the wrong dictionary Problem: Getbufvar() may get the wrong dictionary. (David le Blanc) Solution: Check for empty name. (closes vim/vim#5878) https://github.com/vim/vim/commit/5259275347667a90fb88d8ea74331f88ad68edfc --- src/nvim/eval/funcs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8f16855d0b..21a6904674 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2831,8 +2831,10 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { // Look up the variable. // Let getbufvar({nr}, "") return the "b:" dictionary. - dictitem_T *const v = find_var_in_ht(&buf->b_vars->dv_hashtab, 'b', - varname, strlen(varname), false); + dictitem_T *const v = *varname == NUL + ? (dictitem_T *)&buf->b_bufvar + : find_var_in_ht(&buf->b_vars->dv_hashtab, 'b', + varname, strlen(varname), false); if (v != NULL) { tv_copy(&v->di_tv, rettv); done = true; -- cgit From 6da16ac931eec7be2487ee98e7f605fa12b0171d Mon Sep 17 00:00:00 2001 From: Yatao Li Date: Tue, 3 Mar 2020 18:17:37 +0800 Subject: external pum: use floating point geometry; typval: add tv_dict_add_float --- src/nvim/eval/typval.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index c8b5fc294c..773e493d0b 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1634,6 +1634,28 @@ int tv_dict_add_nr(dict_T *const d, const char *const key, return OK; } +/// Add a floating point number entry to dictionary +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param[in] nr Floating point number to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_float(dict_T *const d, const char *const key, + const size_t key_len, const float_T nr) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_type = VAR_FLOAT; + item->di_tv.vval.v_float = nr; + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + return OK; +} + /// Add a special entry to dictionary /// /// @param[out] d Dictionary to add entry to. -- cgit From 4e6531ddbdce9944f9986ca5357b71171a14f05e Mon Sep 17 00:00:00 2001 From: Hirokazu Hata Date: Tue, 28 Apr 2020 23:41:39 +0900 Subject: lsp: use vim.tbl_isempty to check sign (#12190) ref: #12164 fix #12201 sign_getdefined() returns a list, {} if the sign is not defined. --- src/nvim/eval/funcs.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 10f9d51599..bbe6f6de9d 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -8489,6 +8489,7 @@ static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(linehl); xfree(text); xfree(texthl); + xfree(numhl); } /// "sign_getdefined()" function -- cgit From c477b19bdca0cc57e08f5fb93f647767ea84dd3e Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 29 Apr 2020 00:27:14 +0900 Subject: vim-patch:8.2.0084: complete item "user_data" can only be a string Problem: Complete item "user_data" can only be a string. Solution: Accept any type of variable. (closes vim/vim#5412) https://github.com/vim/vim/commit/0892832bb6c7e322fcae8560eaad5a8140ee4a06 --- src/nvim/eval/typval.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 773e493d0b..35130f6f40 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1429,6 +1429,23 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, return TV_DICT_HI2DI(hi); } +/// Get a typval item from a dictionary and copy it into "rettv". +/// +/// @param[in] d Dictionary to check. +/// @param[in] key Dictionary key. +/// @param[in] rettv Return value. +/// @return OK in case of success or FAIL if nothing was found. +int tv_dict_get_tv(dict_T *d, const char *const key, typval_T *rettv) +{ + dictitem_T *const di = tv_dict_find(d, key, -1); + if (di == NULL) { + return FAIL; + } + + tv_copy(&di->di_tv, rettv); + return OK; +} + /// Get a number item from a dictionary /// /// Returns 0 if the entry does not exist. @@ -1588,6 +1605,26 @@ int tv_dict_add_list(dict_T *const d, const char *const key, return OK; } +/// Add a typval entry to dictionary. +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// +/// @return FAIL if out of memory or key already exists. +int tv_dict_add_tv(dict_T *d, const char *key, const size_t key_len, + typval_T *tv) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + tv_copy(tv, &item->di_tv); + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + return OK; +} + /// Add a dictionary entry to dictionary /// /// @param[out] d Dictionary to add entry to. -- cgit From 66369cd9d03bf4b09c354f4a23ed24adad834408 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Apr 2020 00:31:01 -0400 Subject: vim-patch:8.1.0800: may use a lot of memory when a function refers itself Problem: May use a lot of memory when a function creates a cyclic reference. Solution: After saving a funccal many times, invoke the garbage collector. (closes vim/vim#3835) https://github.com/vim/vim/commit/4456ab527a6a5faae9287f3bd2e52cc18966cfb0 --- src/nvim/eval/userfunc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index c55a29c67c..6e3c6c592a 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -592,6 +592,8 @@ static void cleanup_function_call(funccall_T *fc) if (!fc_referenced(fc)) { free_funccal(fc, false); } else { + static int made_copy = 0; + // "fc" is still in use. This can happen when returning "a:000", // assigning "l:" to a global variable or defining a closure. // Link "fc" in the list for garbage collection later. @@ -607,6 +609,15 @@ static void cleanup_function_call(funccall_T *fc) TV_LIST_ITER(&fc->l_varlist, li, { tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); }); + + if (++made_copy == 10000) { + // We have made a lot of copies. This can happen when + // repetitively calling a function that creates a reference to + // itself somehow. Call the garbage collector here to avoid using + // too much memory. + made_copy = 0; + (void)garbage_collect(false); + } } } -- cgit From a89d64b68760094f9da3b8b10442c1f86942351c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Apr 2020 00:35:44 -0400 Subject: vim-patch:8.1.0868: crash if triggering garbage collector after a function call Problem: Crash if triggering garbage collector after a function call. (Michael Henry) Solution: Don't call the garbage collector right away, do it later. (closes vim/vim#3894) https://github.com/vim/vim/commit/889da2f2438c8168f9a25dc776360b81109bad44 --- src/nvim/eval/userfunc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 6e3c6c592a..ae8557a8bc 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -613,10 +613,10 @@ static void cleanup_function_call(funccall_T *fc) if (++made_copy == 10000) { // We have made a lot of copies. This can happen when // repetitively calling a function that creates a reference to - // itself somehow. Call the garbage collector here to avoid using + // itself somehow. Call the garbage collector soon to avoid using // too much memory. made_copy = 0; - (void)garbage_collect(false); + want_garbage_collect = true; } } } -- cgit From d46c4003cfd6b5f1afcc67b07ed9769d73aa1110 Mon Sep 17 00:00:00 2001 From: erw7 Date: Mon, 4 May 2020 17:50:54 +0900 Subject: funcs: Fix a memory leak in f_expand (#12227) :echo expand('%', v:false, v:true) ==423== 28 bytes in 1 blocks are definitely lost in loss record 124 of 420 ==423== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==423== by 0x2AD09C: try_malloc (memory.c:71) ==423== by 0x2AD0B8: xmalloc (memory.c:105) ==423== by 0x2AE05D: xmallocz (memory.c:182) ==423== by 0x1F7CF4: vim_strnsave (strings.c:68) ==423== by 0x318813: eval_vars (ex_docmd.c:8885) ==423== by 0x34F5FC: f_expand (funcs.c:2058) ==423== by 0x36D023: call_func (eval.c:6419) ==423== by 0x370C28: get_func_tv.lto_priv.707 (eval.c:6150) ==423== by 0x372748: eval7 (eval.c:4326) ==423== by 0x37291A: eval6 (eval.c:4036) ==423== by 0x372BF6: eval5 (eval.c:3884) --- src/nvim/eval/funcs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index bbe6f6de9d..26e87df6d4 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2063,8 +2063,10 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (result != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); } - } else + XFREE_CLEAR(result); + } 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. -- cgit From d2766b06c8fc50d06765c5c607744cc6b5f5ef0a Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 5 May 2020 18:15:45 +0300 Subject: vim-patch:8.1.1120: cannot easily get directory entry matches #12222 Problem: Cannot easily get directory entry matches. Solution: Add the readdir() function. (Yasuhiro Matsumoto, closes vim/vim#2439) https://github.com/vim/vim/commit/543c9b1921d7605498b54afdef518e312f1b4515 closes #12212 --- src/nvim/eval/funcs.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 26e87df6d4..217490ad10 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6316,6 +6316,99 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// Evaluate "expr" for readdir(). +static varnumber_T readdir_checkitem(typval_T *expr, const char *name) +{ + typval_T save_val; + typval_T rettv; + typval_T argv[2]; + varnumber_T retval = 0; + bool error = false; + + prepare_vimvar(VV_VAL, &save_val); + set_vim_var_string(VV_VAL, name, -1); + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = (char_u *)name; + + if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) { + goto theend; + } + + retval = tv_get_number_chk(&rettv, &error); + if (error) { + retval = -1; + } + + tv_clear(&rettv); + +theend: + set_vim_var_string(VV_VAL, NULL, 0); + restore_vimvar(VV_VAL, &save_val); + return retval; +} + +// "readdir()" function +static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + typval_T *expr; + const char *path; + garray_T ga; + Directory dir; + + tv_list_alloc_ret(rettv, kListLenUnknown); + path = tv_get_string(&argvars[0]); + expr = &argvars[1]; + ga_init(&ga, (int)sizeof(char *), 20); + + if (!os_scandir(&dir, path)) { + smsg(_(e_notopen), path); + } else { + for (;;) { + bool ignore; + + path = os_scandir_next(&dir); + if (path == NULL) { + break; + } + + ignore = (path[0] == '.' + && (path[1] == NUL || (path[1] == '.' && path[2] == NUL))); + if (!ignore && expr->v_type != VAR_UNKNOWN) { + varnumber_T r = readdir_checkitem(expr, path); + + if (r < 0) { + break; + } + if (r == 0) { + ignore = true; + } + } + + if (!ignore) { + ga_grow(&ga, 1); + ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path); + } + } + + os_closedir(&dir); + } + + rettv->vval.v_list = tv_list_alloc(kListLenShouldKnow); + if (rettv->vval.v_list != NULL) { + tv_list_ref(rettv->vval.v_list); + sort_strings((char_u **)ga.ga_data, ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + path = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, path, -1); + } + } + for (int i = 0; i < ga.ga_len; i++) { + xfree(((uint8_t **)ga.ga_data)[i]); + } + + ga_clear(&ga); +} + /* * "readfile()" function */ -- cgit From 17f067f4b4799dde06be78f9b7c9e7c7d60900a2 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 31 Aug 2019 15:09:30 +0900 Subject: vim-patch:8.1.0475: memory not freed on exit when quit in autocmd Problem: Memory not freed on exit when quit in autocmd. Solution: Remember funccal stack when executing autocmd. https://github.com/vim/vim/commit/27e80c885bcb5c5cf6a6462d71d6c81b06ba2451 --- src/nvim/eval/funcs.c | 8 ++++---- src/nvim/eval/userfunc.c | 35 ++++++++++++++++++++++++----------- src/nvim/eval/userfunc.h | 6 ++++++ 3 files changed, 34 insertions(+), 15 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 217490ad10..ac28e3c3e0 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -7219,7 +7219,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; linenr_T save_sourcing_lnum; int save_autocmd_bufnr; - void *save_funccalp; + funccal_entry_T funccal_entry; if (l_provider_call_nesting) { // If this is called from a provider function, restore the scope @@ -7230,7 +7230,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; save_autocmd_bufnr = autocmd_bufnr; - save_funccalp = save_funccal(); + save_funccal(&funccal_entry); current_sctx = provider_caller_scope.script_ctx; sourcing_name = provider_caller_scope.sourcing_name; @@ -7238,7 +7238,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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); + set_current_funccal((funccall_T *)(provider_caller_scope.funccalp)); } @@ -7256,7 +7256,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; autocmd_bufnr = save_autocmd_bufnr; - restore_funccal(save_funccalp); + restore_funccal(); } if (ERROR_SET(&err)) { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index ae8557a8bc..9e123e505f 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1106,21 +1106,26 @@ static bool func_name_refcount(char_u *name) return isdigit(*name) || *name == '<'; } -/* - * Save the current function call pointer, and set it to NULL. - * Used when executing autocommands and for ":source". - */ -void *save_funccal(void) -{ - funccall_T *fc = current_funccal; +static funccal_entry_T *funccal_stack = NULL; +// Save the current function call pointer, and set it to NULL. +// Used when executing autocommands and for ":source". +void save_funccal(funccal_entry_T *entry) +{ + entry->top_funccal = current_funccal; + entry->next = funccal_stack; + funccal_stack = entry; current_funccal = NULL; - return (void *)fc; } -void restore_funccal(void *vfc) +void restore_funccal(void) { - current_funccal = (funccall_T *)vfc; + if (funccal_stack == NULL) { + IEMSG("INTERNAL: restore_funccal()"); + } else { + current_funccal = funccal_stack->top_funccal; + funccal_stack = funccal_stack->next; + } } funccall_T *get_current_funccal(void) @@ -1128,6 +1133,11 @@ funccall_T *get_current_funccal(void) return current_funccal; } +void set_current_funccal(funccall_T *fc) +{ + current_funccal = fc; +} + #if defined(EXITFREE) void free_all_functions(void) { @@ -1137,10 +1147,13 @@ void free_all_functions(void) uint64_t todo = 1; uint64_t used; - // Clean up the call stack. + // Clean up the current_funccal chain and the funccal stack. while (current_funccal != NULL) { tv_clear(current_funccal->rettv); cleanup_function_call(current_funccal); + if (current_funccal == NULL && funccal_stack != NULL) { + restore_funccal(); + } } // First clear what the functions contain. Since this may lower the diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index ad8e071548..e8ad0bf1da 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -11,6 +11,12 @@ typedef struct { dictitem_T *fd_di; ///< Dictionary item used. } funcdict_T; +typedef struct funccal_entry funccal_entry_T; +struct funccal_entry { + void *top_funccal; + funccal_entry_T *next; +}; + /// errors for when calling a function typedef enum { ERROR_UNKNOWN = 0, -- cgit From 1212390254c5de90513c0314efa94df7b9218590 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sun, 1 Sep 2019 18:44:14 +0900 Subject: vim-patch:8.1.1007: using closure may consume a lot of memory Problem: Using closure may consume a lot of memory. Solution: unreference items that are no longer needed. Add a test. (Ozaki Kiichi, closes vim/vim#3961) https://github.com/vim/vim/commit/209b8e3e3bf7a4a3d102134124120f6c7f57d560 --- src/nvim/eval/typval_encode.c.h | 2 +- src/nvim/eval/userfunc.c | 109 +++++++++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 41 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index af21a6fbe3..94986a64b5 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -607,7 +607,7 @@ _convert_one_value_regular_dict: {} kMPConvDict); TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict, tv->vval.v_dict->dv_hashtab.ht_used); - assert(saved_copyID != copyID && saved_copyID != copyID - 1); + assert(saved_copyID != copyID); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvDict, diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 9e123e505f..de4ca86680 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -43,11 +43,11 @@ hashtab_T func_hashtab; static garray_T funcargs = GA_EMPTY_INIT_VALUE; // pointer to funccal for currently active function -funccall_T *current_funccal = NULL; +static funccall_T *current_funccal = NULL; // Pointer to list of previously used funccal, still around because some // item in it is still being used. -funccall_T *previous_funccal = NULL; +static funccall_T *previous_funccal = NULL; static char *e_funcexts = N_( "E122: Function %s already exists, add ! to replace it"); @@ -541,14 +541,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) v->di_tv.vval.v_number = nr; } -/* - * Free "fc" and what it contains. - */ -static void -free_funccal( - funccall_T *fc, - int free_val // a: vars were allocated -) +// Free "fc" +static void free_funccal(funccall_T *fc) { for (int i = 0; i < fc->fc_funcs.ga_len; i++) { ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; @@ -563,56 +557,89 @@ free_funccal( } ga_clear(&fc->fc_funcs); - // The a: variables typevals may not have been allocated, only free the - // allocated variables. - vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); + func_ptr_unref(fc->func); + xfree(fc); +} +// Free "fc" and what it contains. +// Can be called only when "fc" is kept beyond the period of it called, +// i.e. after cleanup_function_call(fc). +static void free_funccal_contents(funccall_T *fc) +{ // Free all l: variables. vars_clear(&fc->l_vars.dv_hashtab); - // Free the a:000 variables if they were allocated. - if (free_val) { - TV_LIST_ITER(&fc->l_varlist, li, { - tv_clear(TV_LIST_ITEM_TV(li)); - }); - } + // Free all a: variables. + vars_clear(&fc->l_avars.dv_hashtab); - func_ptr_unref(fc->func); - xfree(fc); + // Free the a:000 variables. + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); + + free_funccal(fc); } /// Handle the last part of returning from a function: free the local hashtable. /// Unless it is still in use by a closure. static void cleanup_function_call(funccall_T *fc) { + bool may_free_fc = fc->fc_refcount <= 0; + bool free_fc = true; + current_funccal = fc->caller; - // If the a:000 list and the l: and a: dicts are not referenced and there - // is no closure using it, we can free the funccall_T and what's in it. - if (!fc_referenced(fc)) { - free_funccal(fc, false); + // Free all l: variables if not referred. + if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear(&fc->l_vars.dv_hashtab); } else { - static int made_copy = 0; + free_fc = false; + } - // "fc" is still in use. This can happen when returning "a:000", - // assigning "l:" to a global variable or defining a closure. - // Link "fc" in the list for garbage collection later. - fc->caller = previous_funccal; - previous_funccal = fc; + // If the a:000 list and the l: and a: dicts are not referenced and + // there is no closure using it, we can free the funccall_T and what's + // in it. + if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear_ext(&fc->l_avars.dv_hashtab, false); + } else { + free_fc = false; // Make a copy of the a: variables, since we didn't do that above. TV_DICT_ITER(&fc->l_avars, di, { tv_copy(&di->di_tv, &di->di_tv); }); + } + + if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + == DO_NOT_FREE_CNT) { + fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated) + + } else { + free_fc = false; // Make a copy of the a:000 items, since we didn't do that above. TV_LIST_ITER(&fc->l_varlist, li, { tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); }); + } - if (++made_copy == 10000) { - // We have made a lot of copies. This can happen when - // repetitively calling a function that creates a reference to + if (free_fc) { + free_funccal(fc); + } else { + static int made_copy = 0; + + // "fc" is still in use. This can happen when returning "a:000", + // assigning "l:" to a global variable or defining a closure. + // Link "fc" in the list for garbage collection later. + fc->caller = previous_funccal; + previous_funccal = fc; + + if (want_garbage_collect) { + // If garbage collector is ready, clear count. + made_copy = 0; + } else if (++made_copy >= (int)((4096 * 1024) / sizeof(*fc))) { + // We have made a lot of copies, worth 4 Mbyte. This can happen + // when repetitively calling a function that creates a reference to // itself somehow. Call the garbage collector soon to avoid using // too much memory. made_copy = 0; @@ -639,7 +666,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { if (fc == *pfc) { *pfc = fc->caller; - free_funccal(fc, true); + free_funccal_contents(fc); return; } } @@ -766,7 +793,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // check for CTRL-C hit line_breakcheck(); // prepare the funccall_T structure - fc = xmalloc(sizeof(funccall_T)); + fc = xcalloc(1, sizeof(funccall_T)); fc->caller = current_funccal; current_funccal = fc; fc->func = fp; @@ -881,9 +908,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } if (ai >= 0 && ai < MAX_FUNC_ARGS) { - tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; - TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; + listitem_T *li = &fc->l_listitems[ai]; + + *TV_LIST_ITEM_TV(li) = argvars[i]; + TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; + tv_list_append(&fc->l_varlist, li); } } @@ -3134,7 +3163,7 @@ bool free_unref_funccal(int copyID, int testing) if (can_free_funccal(*pfc, copyID)) { funccall_T *fc = *pfc; *pfc = fc->caller; - free_funccal(fc, true); + free_funccal_contents(fc); did_free = true; did_free_funccal = true; } else { -- cgit From ae01a5e9f8cde646f369bdf9c6cf1f8679204291 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 5 May 2020 22:16:15 -0400 Subject: vim-patch:8.1.1186: readdir() allocates list twice Problem: readdir() allocates list twice. Solution: Remove second allocation. Also check for zero length. https://github.com/vim/vim/commit/334ad415040f9592451ec99498cd99f90d6e33e6 --- src/nvim/eval/funcs.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 217490ad10..c7df1d6753 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6393,20 +6393,14 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) os_closedir(&dir); } - rettv->vval.v_list = tv_list_alloc(kListLenShouldKnow); - if (rettv->vval.v_list != NULL) { - tv_list_ref(rettv->vval.v_list); + if (rettv->vval.v_list != NULL && ga.ga_len > 0) { sort_strings((char_u **)ga.ga_data, ga.ga_len); for (int i = 0; i < ga.ga_len; i++) { path = ((const char **)ga.ga_data)[i]; tv_list_append_string(rettv->vval.v_list, path, -1); } } - for (int i = 0; i < ga.ga_len; i++) { - xfree(((uint8_t **)ga.ga_data)[i]); - } - - ga_clear(&ga); + ga_clear_strings(&ga); } /* -- cgit From d7d69fed18c2cd59d28ff12ef72f6fb2a98a7b66 Mon Sep 17 00:00:00 2001 From: Shougo Date: Sat, 16 May 2020 22:25:51 +0900 Subject: vim-patch:8.1.1084: cannot delete a match from another window (#12325) Problem: Cannot delete a match from another window. (Paul Jolly) Solution: Add window ID argument to matchdelete(), clearmatches(), getmatches() and setmatches(). (Andy Massimino, closes vim/vim#4178) https://github.com/vim/vim/commit/aff749145e23c0f20b5158d1d3a942948ed138e3 --- src/nvim/eval/funcs.c | 53 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index c7df1d6753..8289f9515c 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -93,6 +93,7 @@ PRAGMA_DIAG_POP static char *e_listarg = N_("E686: Argument of %s must be a List"); static char *e_stringreq = N_("E928: String required"); +static char *e_invalwindow = N_("E957: Invalid window number"); /// Dummy va_list for passing to vim_snprintf /// @@ -952,12 +953,30 @@ static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = -1; } +static win_T * get_optional_window(typval_T *argvars, int idx) +{ + win_T *win = curwin; + + if (argvars[idx].v_type != VAR_UNKNOWN) { + win = find_win_by_nr_or_id(&argvars[idx]); + if (win == NULL) { + EMSG(_(e_invalwindow)); + return NULL; + } + } + return win; +} + /* * "clearmatches()" function */ static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - clear_matches(curwin); + win_T *win = get_optional_window(argvars, 0); + + if (win != NULL) { + clear_matches(win); + } } /* @@ -3452,10 +3471,16 @@ static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - matchitem_T *cur = curwin->w_match_head; + matchitem_T *cur; int i; + win_T *win = get_optional_window(argvars, 0); + + if (win == NULL) { + return; + } tv_list_alloc_ret(rettv, kListLenMayKnow); + cur = win->w_match_head; while (cur != NULL) { dict_T *dict = tv_dict_alloc(); if (cur->match.regprog == NULL) { @@ -5771,8 +5796,13 @@ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ 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); + win_T *win = get_optional_window(argvars, 1); + if (win == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = match_delete(curwin, + (int)tv_get_number(&argvars[0]), true); + } } /* @@ -8136,14 +8166,19 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - dict_T *d; - list_T *s = NULL; + dict_T *d; + list_T *s = NULL; + win_T *win = get_optional_window(argvars, 1); rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_LIST) { EMSG(_(e_listreq)); return; } + if (win == NULL) { + return; + } + list_T *const l = argvars[0].vval.v_list; // To some extent make sure that we are dealing with a list from // "getmatches()". @@ -8167,7 +8202,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) li_idx++; }); - clear_matches(curwin); + clear_matches(win); bool match_add_failed = false; TV_LIST_ITER_CONST(l, li, { int i = 0; @@ -8213,13 +8248,13 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) ? tv_get_string(&conceal_di->di_tv) : NULL); if (i == 0) { - if (match_add(curwin, group, + if (match_add(win, 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) { + if (match_add(win, group, NULL, priority, id, s, conceal) != id) { match_add_failed = true; } tv_list_unref(s); -- cgit From 090d3c2813a2fbe6a0407d9f51c2206a713e4dc3 Mon Sep 17 00:00:00 2001 From: Shougo Date: Sun, 17 May 2020 20:55:10 +0900 Subject: matchdelete: fix porting (#12328) really use win and not curwin --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8289f9515c..79a52d9779 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5800,7 +5800,7 @@ static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (win == NULL) { rettv->vval.v_number = -1; } else { - rettv->vval.v_number = match_delete(curwin, + rettv->vval.v_number = match_delete(win, (int)tv_get_number(&argvars[0]), true); } } -- cgit From c3dab08c8fcbb969805f8b8825475822d5e40469 Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 6 Sep 2019 12:38:52 +0900 Subject: vim-patch:8.1.1485: double free when garbage_collect() is used in autocommand Problem: Double free when garbage_collect() is used in autocommand. Solution: Have garbage collection also set the copyID in funccal_stack. https://github.com/vim/vim/commit/c07f67ad0e9c48a07d49f2d67eb63e183a22386a --- src/nvim/eval/userfunc.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index de4ca86680..c054433255 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -3356,9 +3356,18 @@ bool set_ref_in_call_stack(int copyID) bool abort = false; for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { - abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_funccal(fc, copyID); } + + // Also go through the funccal_stack. + for (funccal_entry_T *entry = funccal_stack; entry != NULL; + entry = entry->next) { + for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL; + fc = fc->caller) { + abort = abort || set_ref_in_funccal(fc, copyID); + } + } + return abort; } -- cgit From 2298350884b09e1af45859da23c89d072f7de99b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 31 May 2020 14:58:46 -0400 Subject: vim-patch:8.1.0917: double free when running out of memory Problem: Double free when running out of memory. Solution: Remove one free. (Ken Takata, closes vim/vim#3955) https://github.com/vim/vim/commit/445e71c5ee06015064cf0642cac8190cfe8fbc59 --- src/nvim/eval/userfunc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index c054433255..4d658498c1 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -170,6 +170,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; ufunc_T *fp = NULL; + partial_T *pt = NULL; int varargs; int ret; char_u *start = skipwhite(*arg + 1); @@ -219,7 +220,6 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) int len, flags = 0; char_u *p; char_u name[20]; - partial_T *pt; garray_T newlines; lambda_no++; @@ -274,6 +274,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) errret: ga_clear_strings(&newargs); xfree(fp); + xfree(pt); eval_lavars_used = old_eval_lavars; return FAIL; } -- cgit From 1805fb469a39d998f9bef0415999aa835d051044 Mon Sep 17 00:00:00 2001 From: Billy Su Date: Tue, 28 Apr 2020 23:21:50 +0800 Subject: vim-patch:8.2.0111: VAR_SPECIAL is also used for booleans Problem: VAR_SPECIAL is also used for booleans. Solution: Add VAR_BOOL for better type checking. https://github.com/vim/vim/commit/9b4a15d5dba354d2e1e02871470bad103f34769a --- src/nvim/eval/decode.c | 12 +++++------ src/nvim/eval/encode.c | 7 +++++-- src/nvim/eval/encode.h | 1 + src/nvim/eval/executor.c | 4 +++- src/nvim/eval/funcs.c | 35 ++++++++++++-------------------- src/nvim/eval/typval.c | 44 ++++++++++++++++++++++++++++++++--------- src/nvim/eval/typval.h | 10 ++++++++-- src/nvim/eval/typval_encode.c.h | 15 +++++++++----- 8 files changed, 81 insertions(+), 47 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 42999ddd62..daba304f00 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -795,9 +795,9 @@ json_decode_string_cycle_start: } p += 3; POP(((typval_T) { - .v_type = VAR_SPECIAL, + .v_type = VAR_BOOL, .v_lock = VAR_UNLOCKED, - .vval = { .v_special = kSpecialVarTrue }, + .vval = { .v_bool = kBoolVarTrue }, }), false); break; } @@ -808,9 +808,9 @@ json_decode_string_cycle_start: } p += 4; POP(((typval_T) { - .v_type = VAR_SPECIAL, + .v_type = VAR_BOOL, .v_lock = VAR_UNLOCKED, - .vval = { .v_special = kSpecialVarFalse }, + .vval = { .v_bool = kBoolVarFalse }, }), false); break; } @@ -954,10 +954,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } case MSGPACK_OBJECT_BOOLEAN: { *rettv = (typval_T) { - .v_type = VAR_SPECIAL, + .v_type = VAR_BOOL, .v_lock = VAR_UNLOCKED, .vval = { - .v_special = mobj.via.boolean ? kSpecialVarTrue : kSpecialVarFalse + .v_bool = mobj.via.boolean ? kBoolVarTrue : kBoolVarFalse }, }; break; diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 138f638eb2..137f099df6 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -34,10 +34,13 @@ #define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b)) #define utf_char2len(b) ((size_t)utf_char2len(b)) +const char *const encode_bool_var_names[] = { + [kBoolVarTrue] = "true", + [kBoolVarFalse] = "false", +}; + const char *const encode_special_var_names[] = { [kSpecialVarNull] = "null", - [kSpecialVarTrue] = "true", - [kSpecialVarFalse] = "false", }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index ccea245ab3..596bb49ae0 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -55,6 +55,7 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) } /// Array mapping values from SpecialVarValue enum to names +extern const char *const encode_bool_var_names[]; extern const char *const encode_special_var_names[]; /// First codepoint in high surrogates block diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 8cd21f8d62..da05ecda43 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -28,11 +28,13 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED { // Can't do anything with a Funcref, a Dict or special value on the right. - if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT) { + if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT + && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) { switch (tv1->v_type) { case VAR_DICT: case VAR_FUNC: case VAR_PARTIAL: + case VAR_BOOL: case VAR_SPECIAL: { break; } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 25beb4be50..4526eecaba 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -175,8 +175,8 @@ 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_BOOL + && argvars[0].vval.v_bool == kBoolVarTrue) || (argvars[0].v_type == VAR_STRING && argvars[0].vval.v_string != NULL && *argvars[0].vval.v_string != NUL)); @@ -1758,21 +1758,23 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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: { + case VAR_BOOL: { + switch (argvars[0].vval.v_bool) { + case kBoolVarTrue: { n = false; break; } - case kSpecialVarFalse: - case kSpecialVarNull: { + case kBoolVarFalse: { n = true; break; } } break; } + case VAR_SPECIAL: { + n = argvars[0].vval.v_special == kSpecialVarNull; + break; + } case VAR_UNKNOWN: { internal_error("f_empty(UNKNOWN)"); break; @@ -5189,6 +5191,7 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case VAR_UNKNOWN: + case VAR_BOOL: case VAR_SPECIAL: case VAR_FLOAT: case VAR_PARTIAL: @@ -10808,20 +10811,8 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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_BOOL: n = VAR_TYPE_BOOL; break; + case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break; case VAR_UNKNOWN: { internal_error("f_type(UNKNOWN)"); break; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 35130f6f40..0daaf6c878 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1693,21 +1693,21 @@ int tv_dict_add_float(dict_T *const d, const char *const key, return OK; } -/// Add a special entry to dictionary +/// Add a boolean entry to dictionary /// /// @param[out] d Dictionary to add entry to. /// @param[in] key Key to add. /// @param[in] key_len Key length. -/// @param[in] val SpecialVarValue to add. +/// @param[in] val BoolVarValue to add. /// /// @return OK in case of success, FAIL when key already exists. -int tv_dict_add_special(dict_T *const d, const char *const key, - const size_t key_len, SpecialVarValue val) +int tv_dict_add_bool(dict_T *const d, const char *const key, + const size_t key_len, BoolVarValue val) { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_type = VAR_SPECIAL; - item->di_tv.vval.v_special = val; + item->di_tv.v_type = VAR_BOOL; + item->di_tv.vval.v_bool = val; if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; @@ -2013,12 +2013,15 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) #define TYPVAL_ENCODE_CONV_NIL(tv) \ do { \ - tv->vval.v_special = kSpecialVarFalse; \ + tv->vval.v_special = kSpecialVarNull; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) #define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ - TYPVAL_ENCODE_CONV_NIL(tv) + do { \ + tv->vval.v_bool = kBoolVarFalse; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ do { \ @@ -2293,6 +2296,7 @@ void tv_free(typval_T *tv) tv_dict_unref(tv->vval.v_dict); break; } + case VAR_BOOL: case VAR_SPECIAL: case VAR_NUMBER: case VAR_FLOAT: @@ -2324,6 +2328,7 @@ void tv_copy(const typval_T *const from, typval_T *const to) switch (from->v_type) { case VAR_NUMBER: case VAR_FLOAT: + case VAR_BOOL: case VAR_SPECIAL: { break; } @@ -2425,6 +2430,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) case VAR_STRING: case VAR_FUNC: case VAR_PARTIAL: + case VAR_BOOL: case VAR_SPECIAL: { break; } @@ -2588,6 +2594,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const char *s2 = tv_get_string_buf(tv2, buf2); return mb_strcmp_ic((bool)ic, s1, s2) == 0; } + case VAR_BOOL: { + return tv1->vval.v_bool == tv2->vval.v_bool; + } case VAR_SPECIAL: { return tv1->vval.v_special == tv2->vval.v_special; } @@ -2638,6 +2647,10 @@ bool tv_check_str_or_nr(const typval_T *const tv) EMSG(_("E728: Expected a Number or a String, Dictionary found")); return false; } + case VAR_BOOL: { + EMSG(_("E5299: Expected a Number or a String, Boolean found")); + return false; + } case VAR_SPECIAL: { EMSG(_("E5300: Expected a Number or a String")); return false; @@ -2677,6 +2690,7 @@ bool tv_check_num(const typval_T *const tv) { switch (tv->v_type) { case VAR_NUMBER: + case VAR_BOOL: case VAR_SPECIAL: case VAR_STRING: { return true; @@ -2721,6 +2735,7 @@ bool tv_check_str(const typval_T *const tv) { switch (tv->v_type) { case VAR_NUMBER: + case VAR_BOOL: case VAR_SPECIAL: case VAR_STRING: { return true; @@ -2791,8 +2806,11 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) } return n; } + case VAR_BOOL: { + return tv->vval.v_bool == kBoolVarTrue ? 1 : 0; + } case VAR_SPECIAL: { - return tv->vval.v_special == kSpecialVarTrue ? 1 : 0; + return 0; } case VAR_UNKNOWN: { emsgf(_(e_intern2), "tv_get_number(UNKNOWN)"); @@ -2860,6 +2878,10 @@ float_T tv_get_float(const typval_T *const tv) EMSG(_("E894: Using a Dictionary as a Float")); break; } + case VAR_BOOL: { + EMSG(_("E362: Using a boolean value as a Float")); + break; + } case VAR_SPECIAL: { EMSG(_("E907: Using a special value as a Float")); break; @@ -2897,6 +2919,10 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) } return ""; } + case VAR_BOOL: { + STRCPY(buf, encode_bool_var_names[tv->vval.v_bool]); + return buf; + } case VAR_SPECIAL: { STRCPY(buf, encode_special_var_names[tv->vval.v_special]); return buf; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 4390db1b71..343dd205ff 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -91,10 +91,14 @@ typedef struct dict_watcher { bool busy; // prevent recursion if the dict is changed in the callback } DictWatcher; +/// Bool variable values +typedef enum { + kBoolVarFalse, ///< v:false + kBoolVarTrue, ///< v:true +} BoolVarValue; + /// Special variable values typedef enum { - kSpecialVarFalse, ///< v:false - kSpecialVarTrue, ///< v:true kSpecialVarNull, ///< v:null } SpecialVarValue; @@ -114,6 +118,7 @@ typedef enum { VAR_LIST, ///< List, .v_list is used. VAR_DICT, ///< Dictionary, .v_dict is used. VAR_FLOAT, ///< Floating-point value, .v_float is used. + VAR_BOOL, ///< true, false VAR_SPECIAL, ///< Special value (true, false, null), .v_special ///< is used. VAR_PARTIAL, ///< Partial, .v_partial is used. @@ -125,6 +130,7 @@ typedef struct { VarLockStatus v_lock; ///< Variable lock status. union typval_vval_union { varnumber_T v_number; ///< Number, for VAR_NUMBER. + BoolVarValue v_bool; ///< Bool value, for VAR_BOOL SpecialVarValue v_special; ///< Special value, for VAR_SPECIAL. float_T v_float; ///< Floating-point number, for VAR_FLOAT. char_u *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL. diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 94986a64b5..0aa64b1d5f 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -379,17 +379,22 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack)); break; } + case VAR_BOOL: { + switch (tv->vval.v_bool) { + case kBoolVarTrue: + case kBoolVarFalse: { + TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_bool == kBoolVarTrue); + break; + } + } + break; + } case VAR_SPECIAL: { switch (tv->vval.v_special) { case kSpecialVarNull: { TYPVAL_ENCODE_CONV_NIL(tv); // -V1037 break; } - case kSpecialVarTrue: - case kSpecialVarFalse: { - TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_special == kSpecialVarTrue); - break; - } } break; } -- cgit From d17e38e48209c19b63d809c5b807613f15aa03c8 Mon Sep 17 00:00:00 2001 From: erw7 Date: Wed, 5 Feb 2020 16:04:45 +0900 Subject: Add overlapped option to jobstart When UV_OVERLAPPED_PIPE was used for the pipe passed to the child process, a problem occurred with the standard input of the .Net Framework application (#11809). Therefore, add the overlapped option to jobstart() and change it so that it is set only when necessary --- src/nvim/eval/funcs.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 4526eecaba..1071e75c06 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4862,6 +4862,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool rpc = false; bool pty = false; bool clear_env = false; + bool overlapped = false; CallbackReader on_stdout = CALLBACK_READER_INIT, on_stderr = CALLBACK_READER_INIT; Callback on_exit = CALLBACK_NONE; @@ -4873,12 +4874,23 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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; + overlapped = tv_dict_get_number(job_opts, "overlapped") != 0; + if (pty && rpc) { EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); shell_free_argv(argv); return; } +#ifdef WIN32 + if (pty && overlapped) { + EMSG2(_(e_invarg2), + "job cannot have both 'pty' and 'overlapped' options set"); + shell_free_argv(argv); + return; + } +#endif + char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); if (new_cwd && strlen(new_cwd) > 0) { cwd = new_cwd; @@ -4945,7 +4957,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, - rpc, detach, cwd, width, height, + rpc, overlapped, detach, cwd, width, height, term_name, env, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); @@ -7372,8 +7384,8 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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); + false, true, false, false, NULL, 0, 0, + NULL, NULL, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -10461,7 +10473,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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, + true, false, false, false, cwd, term_width, curwin->w_height_inner, xstrdup("xterm-256color"), NULL, &rettv->vval.v_number); -- cgit From 21453e8f09b72751d396858b9ea86c0584fd6c89 Mon Sep 17 00:00:00 2001 From: erw7 Date: Mon, 22 Jun 2020 23:17:20 +0900 Subject: eval: fix assertion failure in garbage collection (#12436) * eval: fix assertion failure in garbage collection fixes #12387, #12430 Lists with CopyID+1 linked only from previous_funccal may be removed in the garbage collection. Therefore, the terms of the assertions are not correct. This can be confirmed by the following (The l:x with CopyID+1 of the first function call needs to be removed by garbage collection): func! s:f() let l:x = [1] let g:x = l: endfunc for _ in range(2) call s:f() endfor call garbagecollect() " press any key * test: add test for #12387, #12430 --- src/nvim/eval/typval_encode.c.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 0aa64b1d5f..91c948ce7e 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -364,7 +364,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); - assert(saved_copyID != copyID && saved_copyID != copyID - 1); + assert(saved_copyID != copyID); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, .tv = tv, -- cgit From 971a191c4d772493535d55524b994fe385fae546 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Fri, 19 Jun 2020 00:23:30 -0400 Subject: lua: Add ability to pass lua functions directly to vimL --- src/nvim/eval/typval.h | 9 +++++++++ src/nvim/eval/userfunc.c | 52 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 4 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 343dd205ff..cb13bec50d 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -271,6 +271,11 @@ typedef struct { /// Number of fixed variables used for arguments #define FIXVAR_CNT 12 +/// Callback interface for C function reference +typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT +/// Callback to clear cfunc_T +typedef void (*cfunc_free_T)(void *state); + // Structure to hold info for a function that is currently being executed. typedef struct funccall_S funccall_T; @@ -307,6 +312,10 @@ struct ufunc { garray_T uf_lines; ///< function lines int uf_profiling; ///< true when func is being profiled int uf_prof_initialized; + // Managing cfuncs + cfunc_T uf_cb; ///< C function extension callback + cfunc_free_T uf_cb_free; ///< C function extesion free callback + void *uf_cb_state; ///< State of C function extension. // Profiling the function as a whole. int uf_tm_count; ///< nr of calls proftime_T uf_tm_total; ///< time spent in function + children diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 4d658498c1..ca2f4a32ca 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -32,6 +32,7 @@ #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 #define FC_SANDBOX 0x40 // function defined in the sandbox +#define FC_CFUNC 0x80 // C function extension #ifdef INCLUDE_GENERATED_DECLARATIONS #include "eval/userfunc.c.generated.h" @@ -162,6 +163,17 @@ static void register_closure(ufunc_T *fp) [current_funccal->fc_funcs.ga_len++] = fp; } + +/// Get a name for a lambda. Returned in static memory. +char_u * get_lambda_name(void) +{ + static char_u name[30]; + static int lambda_no = 0; + + snprintf((char *)name, sizeof(name), "%d", ++lambda_no); + return name; +} + /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. @@ -175,7 +187,6 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) int ret; char_u *start = skipwhite(*arg + 1); char_u *s, *e; - static int lambda_no = 0; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; @@ -219,11 +230,9 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) if (evaluate) { int len, flags = 0; char_u *p; - char_u name[20]; garray_T newlines; - lambda_no++; - snprintf((char *)name, sizeof(name), "%d", lambda_no); + char_u *name = get_lambda_name(); fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); pt = xcalloc(1, sizeof(partial_T)); @@ -700,6 +709,11 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_lines)); + if (fp->uf_cb_free != NULL) { + fp->uf_cb_free(fp->uf_cb_state); + fp->uf_cb_free = NULL; + } + XFREE_CLEAR(fp->uf_tml_count); XFREE_CLEAR(fp->uf_tml_total); XFREE_CLEAR(fp->uf_tml_self); @@ -1408,6 +1422,9 @@ call_func( if (fp != NULL && (fp->uf_flags & FC_DELETED)) { error = ERROR_DELETED; + } else if (fp != NULL && (fp->uf_flags & FC_CFUNC)) { + cfunc_T cb = fp->uf_cb; + error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state); } else if (fp != NULL) { if (argv_func != NULL) { // postponed filling in the arguments, do it now @@ -3435,3 +3452,30 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) xfree(tofree); return abort; } + +/// Registers a C extension user function. +char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state) +{ + char_u *name = get_lambda_name(); + ufunc_T *fp = NULL; + int flags = FC_CFUNC; + + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + if (fp == NULL) { + return NULL; + } + + fp->uf_refcount = 1; + fp->uf_varargs = true; + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ctx = current_sctx; + fp->uf_cb = cb; + fp->uf_cb_free = cb_free; + fp->uf_cb_state = state; + + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + + return name; +} -- cgit From 6360cf7ce87407bd8a519b9a17f45b2148291904 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Tue, 30 Jun 2020 02:05:06 -0400 Subject: lua: Add ability to pass tables with __call vim-patch:8.2.1054: not so easy to pass a lua function to Vim vim-patch:8.2.1084: Lua: registering function has useless code I think I have also opened up the possibility for people to use these callbacks elsewhere, since I've added a new struct that we should be able to use. Also, this should allow us to determine what the state of a list is in Lua or a dictionary in Lua, since we now can track the luaref as we go. --- src/nvim/eval/funcs.c | 3 +++ src/nvim/eval/typval.c | 3 +++ src/nvim/eval/typval.h | 9 +++++++-- src/nvim/eval/userfunc.c | 5 ++--- 4 files changed, 15 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 1071e75c06..99014d1a09 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -824,9 +824,12 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (argvars[0].v_type == VAR_PARTIAL) { partial = argvars[0].vval.v_partial; func = partial_name(partial); + } else if (nlua_is_table_from_lua(&argvars[0])) { + func = nlua_register_table_as_callable(&argvars[0]); } else { func = (char_u *)tv_get_string(&argvars[0]); } + if (*func == NUL) { return; // type error or empty name } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0daaf6c878..89ca2db59b 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -16,6 +16,7 @@ #include "nvim/eval/typval_encode.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" +#include "nvim/lua/executor.h" #include "nvim/types.h" #include "nvim/assert.h" #include "nvim/memory.h" @@ -301,6 +302,7 @@ void tv_list_free_list(list_T *const l) } list_log(l, NULL, NULL, "freelist"); + nlua_free_typval_list(l); xfree(l); } @@ -1374,6 +1376,7 @@ void tv_dict_free_dict(dict_T *const d) d->dv_used_next->dv_used_prev = d->dv_used_prev; } + nlua_free_typval_dict(d); xfree(d); } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index cb13bec50d..503a32a81e 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -180,6 +180,8 @@ struct listvar_S { int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx]. int lv_copyID; ///< ID used by deepcopy(). VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED. + + LuaRef lua_table_ref; }; // Static list with 10 items. Use tv_list_init_static10() to initialize. @@ -245,6 +247,8 @@ struct dictvar_S { dict_T *dv_used_next; ///< Next dictionary in used dictionaries list. dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list. QUEUE watchers; ///< Dictionary key watchers set by user code. + + LuaRef lua_table_ref; }; /// Type used for script ID @@ -271,9 +275,10 @@ typedef struct { /// Number of fixed variables used for arguments #define FIXVAR_CNT 12 -/// Callback interface for C function reference +/// Callback interface for C function reference> +/// Used for managing functions that were registered with |register_cfunc| typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT -/// Callback to clear cfunc_T +/// Callback to clear cfunc_T and any associated state. typedef void (*cfunc_free_T)(void *state); // Structure to hold info for a function that is currently being executed. diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index ca2f4a32ca..229f0e8dde 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -3458,7 +3458,6 @@ char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state) { char_u *name = get_lambda_name(); ufunc_T *fp = NULL; - int flags = FC_CFUNC; fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); if (fp == NULL) { @@ -3467,7 +3466,7 @@ char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state) fp->uf_refcount = 1; fp->uf_varargs = true; - fp->uf_flags = flags; + fp->uf_flags = FC_CFUNC; fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; fp->uf_cb = cb; @@ -3477,5 +3476,5 @@ char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state) STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); - return name; + return fp->uf_name; } -- cgit From 6420615e3f703870ed898083f84d6e3515a8c279 Mon Sep 17 00:00:00 2001 From: Cédric Barreteau <> Date: Tue, 30 Jun 2020 18:39:54 +0200 Subject: vim-patch:8.2.0935: flattening a list with existing code is slow Problem: Flattening a list with existing code is slow. Solution: Add flatten(). (Mopp, closes vim/vim#3676) https://github.com/vim/vim/commit/077a1e670ad69ef4cefc22103ca6635bd269e764 --- src/nvim/eval/funcs.c | 36 ++++++++++++++++++++++++++++++++++++ src/nvim/eval/typval.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 99014d1a09..95686b97bf 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2160,6 +2160,42 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = cmdstr; } + +/// "flatten(list[, {maxdepth}])" function +static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + list_T *list; + long maxdepth; + bool error = false; + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "flatten()"); + return; + } + + if (argvars[1].v_type == VAR_UNKNOWN) { + maxdepth = 999999; + } else { + maxdepth = (long)tv_get_number_chk(&argvars[1], &error); + if (error) { + return; + } + if (maxdepth < 0) { + EMSG(_("E900: maxdepth must be non-negative number")); + return; + } + } + + list = argvars[0].vval.v_list; + if (list != NULL + && !tv_check_lock(tv_list_locked(list), + N_("flatten() argument"), + TV_TRANSLATE) + && tv_list_flatten(list, maxdepth) == OK) { + tv_copy(&argvars[0], rettv); + } +} + /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 89ca2db59b..576948f052 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -640,6 +640,53 @@ tv_list_copy_error: return NULL; } +/// Flatten "list" in place to depth "maxdepth". +/// Does nothing if "maxdepth" is 0. +/// +/// @param[in] list List to flatten +/// @param[in] maxdepth Maximum depth that will be flattened +/// +/// @return OK or FAIL +int tv_list_flatten(list_T *list, long maxdepth) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + listitem_T *item; + int n; + if (maxdepth == 0) { + return OK; + } + + n = 0; + item = list->lv_first; + while (item != NULL) { + fast_breakcheck(); + if (got_int) { + return FAIL; + } + if (item->li_tv.v_type == VAR_LIST) { + listitem_T *next = item->li_next; + + tv_list_drop_items(list, item, item); + tv_list_extend(list, item->li_tv.vval.v_list, next); + + if (item->li_prev == NULL) { + item = list->lv_first; + } else { + item = item->li_prev->li_next; + } + + if (++n >= maxdepth) { + n = 0; + item = next; + } + } else { + n = 0; + item = item->li_next; + } + } + return OK; +} + /// Extend first list with the second /// /// @param[out] l1 List to extend. -- cgit From fd57d69970e7947da0d69d744c13121668f91bd8 Mon Sep 17 00:00:00 2001 From: Cédric Barreteau <> Date: Tue, 30 Jun 2020 18:45:58 +0200 Subject: vim-patch:8.2.0937: asan failure in the flatten() test Problem: Asan failure in the flatten() test. Solution: Free the flattened list. https://github.com/vim/vim/commit/dcf59c37d0e1517439c4c0c4a6a5ca09c90157ad --- src/nvim/eval/typval.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 576948f052..d5db15afcc 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -651,6 +651,7 @@ int tv_list_flatten(list_T *list, long maxdepth) FUNC_ATTR_WARN_UNUSED_RESULT { listitem_T *item; + listitem_T *to_free; int n; if (maxdepth == 0) { return OK; @@ -668,12 +669,15 @@ int tv_list_flatten(list_T *list, long maxdepth) tv_list_drop_items(list, item, item); tv_list_extend(list, item->li_tv.vval.v_list, next); + tv_clear(&item->li_tv); + to_free = item; if (item->li_prev == NULL) { item = list->lv_first; } else { item = item->li_prev->li_next; } + xfree(to_free); if (++n >= maxdepth) { n = 0; -- cgit From 9f7f42ea0a4ba85aa27f9d8dc3833e854ce54703 Mon Sep 17 00:00:00 2001 From: Cédric Barreteau <> Date: Tue, 7 Jul 2020 21:02:22 +0200 Subject: Prevent `flatten` from taking a null list --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index d5db15afcc..e47403c656 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -648,7 +648,7 @@ tv_list_copy_error: /// /// @return OK or FAIL int tv_list_flatten(list_T *list, long maxdepth) - FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { listitem_T *item; listitem_T *to_free; -- cgit From 93f2dc0d034677a47c9ba7032b3537c91a718096 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 Jul 2020 10:17:30 -0400 Subject: vim-patch:8.2.0893: assert_equalfile() does not take a third argument Problem: Assert_equalfile() does not take a third argument. Solution: Implement the third argument. (Gary Johnson) https://github.com/vim/vim/commit/fb517bac2384798bb5142ed1f75f965f93984c0a --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 99014d1a09..831167a489 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -415,7 +415,7 @@ 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 +// "assert_equalfile(fname-one, fname-two[, msg])" function static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = assert_equalfile(argvars); -- cgit From e16f2cbd123b5a7bd75b5810ec7641a93c99c009 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 Jul 2020 19:04:35 -0400 Subject: vim-patch:8.2.0539: comparing two NULL list fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Comparing two NULL list fails. Solution: Change the order of comparing two lists. https://github.com/vim/vim/commit/7b293c730b07d1586688e622b8d9cbbb4a52379b N/A patches for version.c: vim-patch:8.2.1187: terminal2 test sometimes hangs in the GUI on Travis Problem: Terminal2 test sometimes hangs in the GUI on Travis. Solution: Disable Test_zz2_terminal_guioptions_bang() for now. https://github.com/vim/vim/commit/c85156bb897085d7f5a8e4e180287f87bf19b948 vim-patch:8.2.1188: memory leak with invalid json input Problem: Memory leak with invalid json input. Solution: Free all keys at the end. (Dominique Pellé, closes vim/vim#6443, closes vim/vim#6442) https://github.com/vim/vim/commit/6d3a7213f58da834b0fc869d05f87e86010c66cf vim-patch:8.2.1196: build failure with normal features Problem: Build failure with normal features. Solution: Add #ifdef. https://github.com/vim/vim/commit/83e7450053399942e1c9efa802c568b51d948541 vim-patch:8.2.1198: terminal2 test sometimes hangs in the GUI on Travis Problem: Terminal2 test sometimes hangs in the GUI on Travis. Solution: Move test function to terminal3 to see if the problem moves too. https://github.com/vim/vim/commit/a4b442614c5ca4ebf32acf5cf0b7b718496f1c94 --- src/nvim/eval/typval.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 89ca2db59b..2394eb8099 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -799,10 +799,14 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, if (l1 == l2) { return true; } - if (l1 == NULL || l2 == NULL) { + if (tv_list_len(l1) != tv_list_len(l2)) { return false; } - if (tv_list_len(l1) != tv_list_len(l2)) { + if (tv_list_len(l1) == 0) { + // empty and NULL list are considered equal + return true; + } + if (l1 == NULL || l2 == NULL) { return false; } -- cgit From 9464399c8c8af44eea9c723f090e782af0346a1a Mon Sep 17 00:00:00 2001 From: Cédric Barreteau <> Date: Mon, 20 Jul 2020 20:21:49 +0200 Subject: Fix documentation The list parameter is an an [in,out] rather than just an [in]. --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index e47403c656..dcfd456ce3 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -643,7 +643,7 @@ tv_list_copy_error: /// Flatten "list" in place to depth "maxdepth". /// Does nothing if "maxdepth" is 0. /// -/// @param[in] list List to flatten +/// @param[in,out] list List to flatten /// @param[in] maxdepth Maximum depth that will be flattened /// /// @return OK or FAIL -- cgit From e0afec8a626d72d074f80bdbbc2cc753802ecaa4 Mon Sep 17 00:00:00 2001 From: erw7 Date: Thu, 23 Jul 2020 12:58:22 +0900 Subject: typval: fix incompatibility with vim The dict_add_string of vim accepts NULL as the value to add. But tv_dict_add_str didn't accept it. Change it to accept NULL as well as vim. --- src/nvim/eval/typval.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ef8e66a992..8dde78de3d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1779,9 +1779,9 @@ int tv_dict_add_bool(dict_T *const d, const char *const key, int tv_dict_add_str(dict_T *const d, const char *const key, const size_t key_len, const char *const val) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1, 2) { - return tv_dict_add_allocated_str(d, key, key_len, xstrdup(val)); + return tv_dict_add_str_len(d, key, key_len, val, -1); } /// Add a string entry to dictionary @@ -1795,10 +1795,10 @@ int tv_dict_add_str(dict_T *const d, /// @return OK in case of success, FAIL when key already exists. int tv_dict_add_str_len(dict_T *const d, const char *const key, const size_t key_len, - char *const val, int len) + const char *const val, int len) FUNC_ATTR_NONNULL_ARG(1, 2) { - char *s = val ? val : ""; + char *s = NULL; if (val != NULL) { s = (len < 0) ? xstrdup(val) : xstrndup(val, (size_t)len); } @@ -1821,7 +1821,7 @@ int tv_dict_add_str_len(dict_T *const d, int tv_dict_add_allocated_str(dict_T *const d, const char *const key, const size_t key_len, char *const val) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1, 2) { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); -- cgit From 2d34ddb3c3a3ebbc515e2f8c37c63d0c7fed1387 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 21 Jul 2020 19:55:05 -0400 Subject: vim-patch:8.2.1265: crash with EXITFREE when split() fails Problem: Crash with EXITFREE when split() fails. Solution: Restore 'cpoptions'. https://github.com/vim/vim/commit/7d5e744162c1e971e5a863e89787cadc8e56051c --- src/nvim/eval/funcs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e350d09935..b97bd6b10d 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -9519,7 +9519,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); if (typeerr) { - return; + goto theend; } regmatch_T regmatch = { @@ -9563,6 +9563,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) vim_regfree(regmatch.regprog); } +theend: p_cpo = save_cpo; } -- cgit