aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2022-11-07 08:51:54 +0800
committerGitHub <noreply@github.com>2022-11-07 08:51:54 +0800
commitd6497c33b7039d143cfdb61cea5c71fa3d49aa32 (patch)
tree33d9c5f6e49c1d4ffcefcc70be6e3cfb46e47bb5
parent897186f409e6f376e52a5e680d307008ba5be5cd (diff)
parent84881674fd702cad5b7572ac868f6d40965a2806 (diff)
downloadrneovim-d6497c33b7039d143cfdb61cea5c71fa3d49aa32.tar.gz
rneovim-d6497c33b7039d143cfdb61cea5c71fa3d49aa32.tar.bz2
rneovim-d6497c33b7039d143cfdb61cea5c71fa3d49aa32.zip
Merge pull request #20944 from zeertzjq/vim-8.2.3705
vim-patch:8.2.{3665,3705,3712,3725},9.0.{0246,0389}
-rw-r--r--runtime/doc/options.txt32
-rw-r--r--runtime/doc/tagsrch.txt2
-rw-r--r--src/nvim/buffer.c4
-rw-r--r--src/nvim/buffer_defs.h4
-rw-r--r--src/nvim/eval.c24
-rw-r--r--src/nvim/eval/userfunc.c48
-rw-r--r--src/nvim/insexpand.c106
-rw-r--r--src/nvim/option.c6
-rw-r--r--src/nvim/optionstr.c17
-rw-r--r--src/nvim/tag.c59
-rw-r--r--src/nvim/testdir/test_expr.vim7
-rw-r--r--src/nvim/testdir/test_ins_complete.vim499
-rw-r--r--src/nvim/testdir/test_tagfunc.vim181
13 files changed, 945 insertions, 44 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 818c6d0115..37f8ea4b20 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -314,13 +314,21 @@ Note: In the future more global options can be made |global-local|. Using
*option-value-function*
Some options ('completefunc', 'imactivatefunc', 'imstatusfunc', 'omnifunc',
-'operatorfunc', 'quickfixtextfunc' and 'tagfunc') are set to a function name
-or a function reference or a lambda function. Examples:
+'operatorfunc', 'quickfixtextfunc', 'tagfunc' and 'thesaurusfunc') are set to
+a function name or a function reference or a lambda function. Examples:
>
set opfunc=MyOpFunc
- set opfunc=function("MyOpFunc")
- set opfunc=funcref("MyOpFunc")
- set opfunc={t\ ->\ MyOpFunc(t)}
+ set opfunc=function('MyOpFunc')
+ set opfunc=funcref('MyOpFunc')
+ set opfunc={a\ ->\ MyOpFunc(a)}
+ " set using a funcref variable
+ let Fn = function('MyTagFunc')
+ let &tagfunc = string(Fn)
+ " set using a lambda expression
+ let &tagfunc = "{t -> MyTagFunc(t)}"
+ " set using a variable with lambda expression
+ let L = {a, b, c -> MyTagFunc(a, b , c)}
+ let &tagfunc = string(L)
<
Setting the filetype
@@ -1446,7 +1454,9 @@ A jump table for the options with a short description can be found at |Q_op|.
This option specifies a function to be used for Insert mode completion
with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U|
See |complete-functions| for an explanation of how the function is
- invoked and what it should return.
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
@@ -4413,7 +4423,9 @@ A jump table for the options with a short description can be found at |Q_op|.
This option specifies a function to be used for Insert mode omni
completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O|
See |complete-functions| for an explanation of how the function is
- invoked and what it should return.
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
This option is usually set by a filetype plugin:
|:filetype-plugin-on|
This option cannot be set from a |modeline| or in the |sandbox|, for
@@ -6443,7 +6455,9 @@ A jump table for the options with a short description can be found at |Q_op|.
This option specifies a function to be used to perform tag searches.
The function gets the tag pattern and should return a List of matching
tags. See |tag-function| for an explanation of how to write the
- function and an example.
+ function and an example. The value can be the name of a function, a
+ |lambda| or a |Funcref|. See |option-value-function| for more
+ information.
*'taglength'* *'tl'*
'taglength' 'tl' number (default 0)
@@ -6566,6 +6580,8 @@ A jump table for the options with a short description can be found at |Q_op|.
global or local to buffer |global-local|
This option specifies a function to be used for thesaurus completion
with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|.
+ The value can be the name of a function, a |lambda| or a |Funcref|.
+ See |option-value-function| for more information.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index d079db0717..aab6f78315 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -909,6 +909,8 @@ If the function returns |v:null| instead of a List, a standard tag lookup will
be performed instead.
It is not allowed to change the tagstack from inside 'tagfunc'. *E986*
+It is not allowed to close a window or change window from inside 'tagfunc'.
+*E1299*
The following is a hypothetical example of a function used for 'tagfunc'. It
uses the output of |taglist()| to generate the result: a list of tags in the
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index ade5c35450..2c87677925 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1971,8 +1971,11 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_cinw);
clear_string_option(&buf->b_p_cpt);
clear_string_option(&buf->b_p_cfu);
+ callback_free(&buf->b_cfu_cb);
clear_string_option(&buf->b_p_ofu);
+ callback_free(&buf->b_ofu_cb);
clear_string_option(&buf->b_p_tsrfu);
+ callback_free(&buf->b_tsrfu_cb);
clear_string_option(&buf->b_p_gp);
clear_string_option(&buf->b_p_mp);
clear_string_option(&buf->b_p_efm);
@@ -1981,6 +1984,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_tags);
clear_string_option(&buf->b_p_tc);
clear_string_option(&buf->b_p_tfu);
+ callback_free(&buf->b_tfu_cb);
clear_string_option(&buf->b_p_dict);
clear_string_option(&buf->b_p_tsr);
clear_string_option(&buf->b_p_qe);
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 2b42289858..6448c6b6f6 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -675,8 +675,11 @@ struct file_buffer {
char *b_p_csl; ///< 'completeslash'
#endif
char *b_p_cfu; ///< 'completefunc'
+ Callback b_cfu_cb; ///< 'completefunc' callback
char *b_p_ofu; ///< 'omnifunc'
+ Callback b_ofu_cb; ///< 'omnifunc' callback
char *b_p_tfu; ///< 'tagfunc'
+ Callback b_tfu_cb; ///< 'tagfunc' callback
int b_p_eof; ///< 'endoffile'
int b_p_eol; ///< 'endofline'
int b_p_fixeol; ///< 'fixendofline'
@@ -748,6 +751,7 @@ struct file_buffer {
char *b_p_dict; ///< 'dictionary' local value
char *b_p_tsr; ///< 'thesaurus' local value
char *b_p_tsrfu; ///< 'thesaurusfunc' local value
+ Callback b_tsrfu_cb; ///< 'thesaurusfunc' callback
long b_p_ul; ///< 'undolevels' local value
int b_p_udf; ///< 'undofile'
char *b_p_lw; ///< 'lispwords' local value
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index fe4ae92834..c578d9fd39 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1110,25 +1110,7 @@ fail:
return ret;
}
-/// Call Vim script function and return the result as a number
-///
-/// @param[in] func Function name.
-/// @param[in] argc Number of arguments.
-/// @param[in] argv Array with typval_T arguments.
-///
-/// @return -1 when calling function fails, result of function otherwise.
-varnumber_T call_func_retnr(const char *func, int argc, typval_T *argv)
- FUNC_ATTR_NONNULL_ALL
-{
- typval_T rettv;
- if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) {
- return -1;
- }
- varnumber_T retval = tv_get_number_chk(&rettv, NULL);
- tv_clear(&rettv);
- return retval;
-}
/// Call Vim script function and return the result as a string
///
/// @param[in] func Function name.
@@ -1151,6 +1133,7 @@ char *call_func_retstr(const char *const func, int argc, typval_T *argv)
tv_clear(&rettv);
return retval;
}
+
/// Call Vim script function and return the result as a List
///
/// @param[in] func Function name.
@@ -5029,9 +5012,8 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) {
name = s;
- trans_name = (char *)trans_function_name(&name, false,
- TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD
- | TFN_NO_DEREF, NULL, NULL);
+ trans_name = save_function_name(&name, false,
+ TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL);
if (*name != NUL) {
s = NULL;
}
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index ef74ca58e3..d07cfe0bd9 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -1393,6 +1393,24 @@ func_call_skip_call:
return r;
}
+/// call the 'callback' function and return the result as a number.
+/// Returns -1 when calling the function fails. Uses argv[0] to argv[argc - 1]
+/// for the function arguments. argv[argc] should have type VAR_UNKNOWN.
+///
+/// @param argcount number of "argvars"
+/// @param argvars vars for arguments, must have "argcount" PLUS ONE elements!
+varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argvars)
+{
+ typval_T rettv;
+ if (!callback_call(callback, argcount, argvars, &rettv)) {
+ return -1;
+ }
+
+ varnumber_T retval = tv_get_number_chk(&rettv, NULL);
+ tv_clear(&rettv);
+ return retval;
+}
+
/// Give an error message for the result of a function.
/// Nothing if "error" is FCERR_NONE.
static void user_func_error(int error, const char_u *name)
@@ -1886,6 +1904,27 @@ theend:
return (char_u *)name;
}
+/// Call trans_function_name(), except that a lambda is returned as-is.
+/// Returns the name in allocated memory.
+char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
+{
+ char *p = *name;
+ char *saved;
+
+ if (strncmp(p, "<lambda>", 8) == 0) {
+ p += 8;
+ (void)getdigits(&p, false, 0);
+ saved = xstrndup(*name, (size_t)(p - *name));
+ if (fudi != NULL) {
+ CLEAR_POINTER(fudi);
+ }
+ } else {
+ saved = (char *)trans_function_name(&p, skip, flags, fudi, NULL);
+ }
+ *name = p;
+ return saved;
+}
+
#define MAX_FUNC_NESTING 50
/// List functions.
@@ -2000,14 +2039,7 @@ void ex_function(exarg_T *eap)
// s:func script-local function name
// g:func global function name, same as "func"
p = eap->arg;
- if (strncmp(p, "<lambda>", 8) == 0) {
- p += 8;
- (void)getdigits(&p, false, 0);
- name = xstrndup(eap->arg, (size_t)(p - eap->arg));
- CLEAR_FIELD(fudi);
- } else {
- name = (char *)trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
- }
+ name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
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
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 9f4c02da19..1acdddedef 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -18,6 +18,7 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
@@ -2224,6 +2225,82 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag)
return buf;
}
+static Callback cfu_cb; ///< 'completefunc' callback function
+static Callback ofu_cb; ///< 'omnifunc' callback function
+static Callback tsrfu_cb; ///< 'thesaurusfunc' callback function
+
+/// Copy a global callback function to a buffer local callback.
+static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb)
+{
+ callback_free(bufcb);
+ if (globcb->data.funcref != NULL && *globcb->data.funcref != NUL) {
+ callback_copy(bufcb, globcb);
+ }
+}
+
+/// Parse the 'completefunc' option value and set the callback function.
+/// Invoked when the 'completefunc' option is set. The option value can be a
+/// name of a function (string), or function(<name>) or funcref(<name>) or a
+/// lambda expression.
+int set_completefunc_option(void)
+{
+ int retval = option_set_callback_func(curbuf->b_p_cfu, &cfu_cb);
+ if (retval == OK) {
+ set_buflocal_cfu_callback(curbuf);
+ }
+
+ return retval;
+}
+
+/// Copy the global 'completefunc' callback function to the buffer-local
+/// 'completefunc' callback for "buf".
+void set_buflocal_cfu_callback(buf_T *buf)
+{
+ copy_global_to_buflocal_cb(&cfu_cb, &buf->b_cfu_cb);
+}
+
+/// Parse the 'omnifunc' option value and set the callback function.
+/// Invoked when the 'omnifunc' option is set. The option value can be a
+/// name of a function (string), or function(<name>) or funcref(<name>) or a
+/// lambda expression.
+int set_omnifunc_option(void)
+{
+ int retval = option_set_callback_func(curbuf->b_p_ofu, &ofu_cb);
+ if (retval == OK) {
+ set_buflocal_ofu_callback(curbuf);
+ }
+
+ return retval;
+}
+
+/// Copy the global 'omnifunc' callback function to the buffer-local 'omnifunc'
+/// callback for "buf".
+void set_buflocal_ofu_callback(buf_T *buf)
+{
+ copy_global_to_buflocal_cb(&ofu_cb, &buf->b_ofu_cb);
+}
+
+/// Parse the 'thesaurusfunc' option value and set the callback function.
+/// Invoked when the 'thesaurusfunc' option is set. The option value can be a
+/// name of a function (string), or function(<name>) or funcref(<name>) or a
+/// lambda expression.
+int set_thesaurusfunc_option(void)
+{
+ int retval;
+
+ if (*curbuf->b_p_tsrfu != NUL) {
+ // buffer-local option set
+ callback_free(&curbuf->b_tsrfu_cb);
+ retval = option_set_callback_func(curbuf->b_p_tsrfu, &curbuf->b_tsrfu_cb);
+ } else {
+ // global option set
+ callback_free(&tsrfu_cb);
+ retval = option_set_callback_func(p_tsrfu, &tsrfu_cb);
+ }
+
+ return retval;
+}
+
/// Get the user-defined completion function name for completion "type"
static char_u *get_complete_funcname(int type)
{
@@ -2239,10 +2316,23 @@ static char_u *get_complete_funcname(int type)
}
}
-/// Execute user defined complete function 'completefunc' or 'omnifunc', and
-/// get matches in "matches".
+/// Get the callback to use for insert mode completion.
+static Callback *get_insert_callback(int type)
+{
+ if (type == CTRL_X_FUNCTION) {
+ return &curbuf->b_cfu_cb;
+ }
+ if (type == CTRL_X_OMNI) {
+ return &curbuf->b_ofu_cb;
+ }
+ // CTRL_X_THESAURUS
+ return (*curbuf->b_p_tsrfu != NUL) ? &curbuf->b_tsrfu_cb : &tsrfu_cb;
+}
+
+/// Execute user defined complete function 'completefunc', 'omnifunc' or
+/// 'thesaurusfunc', and get matches in "matches".
///
-/// @param type CTRL_X_OMNI or CTRL_X_FUNCTION
+/// @param type either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS
static void expand_by_function(int type, char_u *base)
{
list_T *matchlist = NULL;
@@ -2272,8 +2362,10 @@ static void expand_by_function(int type, char_u *base)
// Insert mode in another buffer.
textlock++;
+ Callback *cb = get_insert_callback(type);
+
// Call a function, which returns a list or dict.
- if (call_vim_function((char *)funcname, 2, args, &rettv) == OK) {
+ if (callback_call(cb, 2, args, &rettv)) {
switch (rettv.v_type) {
case VAR_LIST:
matchlist = rettv.vval.v_list;
@@ -3851,7 +3943,8 @@ static int get_userdefined_compl_info(colnr_T curs_col)
pos_T pos = curwin->w_cursor;
textlock++;
- colnr_T col = (colnr_T)call_func_retnr((char *)funcname, 2, args);
+ Callback *cb = get_insert_callback(ctrl_x_mode);
+ colnr_T col = (colnr_T)callback_call_retnr(cb, 2, args);
textlock--;
State = save_State;
@@ -4354,6 +4447,9 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len)
void free_insexpand_stuff(void)
{
XFREE_CLEAR(compl_orig_text);
+ callback_free(&cfu_cb);
+ callback_free(&ofu_cb);
+ callback_free(&tsrfu_cb);
}
#endif
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 523ae13e52..da3fad0d61 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -51,6 +51,7 @@
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
+#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/locale.h"
#include "nvim/macros.h"
@@ -78,6 +79,7 @@
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
+#include "nvim/tag.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
@@ -577,6 +579,7 @@ void free_all_options(void)
}
}
free_operatorfunc_option();
+ free_tagfunc_option();
}
#endif
@@ -4379,10 +4382,13 @@ void buf_copy_options(buf_T *buf, int flags)
#endif
buf->b_p_cfu = xstrdup(p_cfu);
COPY_OPT_SCTX(buf, BV_CFU);
+ set_buflocal_cfu_callback(buf);
buf->b_p_ofu = xstrdup(p_ofu);
COPY_OPT_SCTX(buf, BV_OFU);
+ set_buflocal_ofu_callback(buf);
buf->b_p_tfu = xstrdup(p_tfu);
COPY_OPT_SCTX(buf, BV_TFU);
+ set_buflocal_tfu_callback(buf);
buf->b_p_sts = p_sts;
COPY_OPT_SCTX(buf, BV_STS);
buf->b_p_sts_nopaste = p_sts_nopaste;
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 65bc9f60df..b088a4c8c7 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -38,6 +38,7 @@
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
+#include "nvim/tag.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
#include "nvim/window.h"
@@ -1472,6 +1473,18 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf
}
}
}
+ } else if (gvarp == &p_cfu) { // 'completefunc'
+ if (set_completefunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
+ } else if (gvarp == &p_ofu) { // 'omnifunc'
+ if (set_omnifunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
+ } else if (gvarp == &p_tsrfu) { // 'thesaurusfunc'
+ if (set_thesaurusfunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
} else if (varp == &p_opfunc) { // 'operatorfunc'
if (set_operatorfunc_option() == FAIL) {
errmsg = e_invarg;
@@ -1480,6 +1493,10 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf
if (qf_process_qftf_option() == FAIL) {
errmsg = e_invarg;
}
+ } else if (gvarp == &p_tfu) { // 'tagfunc'
+ if (set_tagfunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
} else {
// Options that are a list of flags.
p = NULL;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 264f961b43..16d6629c2e 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -106,6 +106,8 @@ static char_u *recurmsg
= (char_u *)N_("E986: cannot modify the tag stack within tagfunc");
static char_u *tfu_inv_ret_msg
= (char_u *)N_("E987: invalid return value from tagfunc");
+static char e_window_unexpectedly_close_while_searching_for_tags[]
+ = N_("E1299: Window unexpectedly closed while searching for tags");
static char *tagmatchname = NULL; // name of last used tag
@@ -114,10 +116,49 @@ static char *tagmatchname = NULL; // name of last used tag
static taggy_T ptag_entry = { NULL, INIT_FMARK, 0, 0, NULL };
static int tfu_in_use = false; // disallow recursive call of tagfunc
+static Callback tfu_cb; // 'tagfunc' callback function
// Used instead of NUL to separate tag fields in the growarrays.
#define TAG_SEP 0x02
+/// Reads the 'tagfunc' option value and convert that to a callback value.
+/// Invoked when the 'tagfunc' option is set. The option value can be a name of
+/// a function (string), or function(<name>) or funcref(<name>) or a lambda.
+int set_tagfunc_option(void)
+{
+ callback_free(&tfu_cb);
+ callback_free(&curbuf->b_tfu_cb);
+
+ if (*curbuf->b_p_tfu == NUL) {
+ return OK;
+ }
+
+ if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL) {
+ return FAIL;
+ }
+
+ callback_copy(&curbuf->b_tfu_cb, &tfu_cb);
+
+ return OK;
+}
+
+#if defined(EXITFREE)
+void free_tagfunc_option(void)
+{
+ callback_free(&tfu_cb);
+}
+#endif
+
+/// Copy the global 'tagfunc' callback function to the buffer-local 'tagfunc'
+/// callback for 'buf'.
+void set_buflocal_tfu_callback(buf_T *buf)
+{
+ callback_free(&buf->b_tfu_cb);
+ if (tfu_cb.data.funcref != NULL && *tfu_cb.data.funcref != NUL) {
+ callback_copy(&buf->b_tfu_cb, &tfu_cb);
+ }
+}
+
/// Jump to tag; handling of tag commands and tag stack
///
/// *tag != NUL: ":tag {tag}", jump to new tag, add to tag stack
@@ -160,6 +201,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
int skip_msg = false;
char_u *buf_ffname = (char_u *)curbuf->b_ffname; // name for priority computation
int use_tfu = 1;
+ char *tofree = NULL;
// remember the matches for the last used tag
static int num_matches = 0;
@@ -411,7 +453,10 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// When desired match not found yet, try to find it (and others).
if (use_tagstack) {
- name = tagstack[tagstackidx].tagname;
+ // make a copy, the tagstack may change in 'tagfunc'
+ name = xstrdup(tagstack[tagstackidx].tagname);
+ xfree(tofree);
+ tofree = name;
} else if (g_do_tagpreview != 0) {
name = ptag_entry.tagname;
} else {
@@ -458,6 +503,15 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// found: all matches found.
}
+ // A tag function may do anything, which may cause various
+ // information to become invalid. At least check for the tagstack
+ // to still be the same.
+ if (tagstack != curwin->w_tagstack) {
+ emsg(_(e_window_unexpectedly_close_while_searching_for_tags));
+ FreeWild(new_num_matches, new_matches);
+ break;
+ }
+
// If there already were some matches for the same name, move them
// to the start. Avoids that the order changes when using
// ":tnext" and jumping to another file.
@@ -642,6 +696,7 @@ end_do_tag:
}
postponed_split = 0; // don't split next time
g_do_tagpreview = 0; // don't do tag preview next time
+ xfree(tofree);
}
// List all the matching tags.
@@ -1129,7 +1184,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl
flags & TAG_REGEXP ? "r": "");
save_pos = curwin->w_cursor;
- result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv);
+ result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv);
curwin->w_cursor = save_pos; // restore the cursor position
d->dv_refcount--;
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index ea874cc398..9dbc923b96 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -496,6 +496,13 @@ func Test_function_with_funcref()
call assert_fails("call function('foo()')", 'E475:')
call assert_fails("call function('foo()')", 'foo()')
call assert_fails("function('')", 'E129:')
+
+ let Len = {s -> strlen(s)}
+ call assert_equal(6, Len('foobar'))
+ let name = string(Len)
+ " can evaluate "function('<lambda>99')"
+ call execute('let Ref = ' .. name)
+ call assert_equal(4, Ref('text'))
endfunc
func Test_funcref()
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index f706322a85..fc612dcbe2 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -1285,6 +1285,505 @@ func Test_no_mapping_for_ctrl_x_key()
bwipe!
endfunc
+" Test for different ways of setting the 'completefunc' option
+func Test_completefunc_callback()
+ " Test for using a function()
+ func MycompleteFunc1(findstart, base)
+ call add(g:MycompleteFunc1_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set completefunc=function('MycompleteFunc1')
+ new | only
+ call setline(1, 'one')
+ let g:MycompleteFunc1_args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'one']], g:MycompleteFunc1_args)
+ bw!
+
+ " Using a funcref variable to set 'completefunc'
+ let Fn = function('MycompleteFunc1')
+ let &completefunc = string(Fn)
+ new | only
+ call setline(1, 'two')
+ let g:MycompleteFunc1_args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'two']], g:MycompleteFunc1_args)
+ call assert_fails('let &completefunc = Fn', 'E729:')
+ bw!
+
+ " Test for using a funcref()
+ func MycompleteFunc2(findstart, base)
+ call add(g:MycompleteFunc2_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set completefunc=funcref('MycompleteFunc2')
+ new | only
+ call setline(1, 'three')
+ let g:MycompleteFunc2_args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'three']], g:MycompleteFunc2_args)
+ bw!
+
+ " Using a funcref variable to set 'completefunc'
+ let Fn = funcref('MycompleteFunc2')
+ let &completefunc = string(Fn)
+ new | only
+ call setline(1, 'four')
+ let g:MycompleteFunc2_args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'four']], g:MycompleteFunc2_args)
+ call assert_fails('let &completefunc = Fn', 'E729:')
+ bw!
+
+ " Test for using a lambda function
+ func MycompleteFunc3(findstart, base)
+ call add(g:MycompleteFunc3_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set completefunc={a,\ b,\ ->\ MycompleteFunc3(a,\ b,)}
+ new | only
+ call setline(1, 'five')
+ let g:MycompleteFunc3_args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'five']], g:MycompleteFunc3_args)
+ bw!
+
+ " Set 'completefunc' to a lambda expression
+ let &completefunc = '{a, b -> MycompleteFunc3(a, b)}'
+ new | only
+ call setline(1, 'six')
+ let g:MycompleteFunc3_args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'six']], g:MycompleteFunc3_args)
+ bw!
+
+ " Set 'completefunc' to a variable with a lambda expression
+ let Lambda = {a, b -> MycompleteFunc3(a, b)}
+ let &completefunc = string(Lambda)
+ new | only
+ call setline(1, 'seven')
+ let g:MycompleteFunc3_args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'seven']], g:MycompleteFunc3_args)
+ call assert_fails('let &completefunc = Lambda', 'E729:')
+ bw!
+
+ " Test for using a lambda function with incorrect return value
+ let Lambda = {s -> strlen(s)}
+ let &completefunc = string(Lambda)
+ new | only
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ bw!
+
+ " Test for clearing the 'completefunc' option
+ set completefunc=''
+ set completefunc&
+
+ call assert_fails("set completefunc=function('abc')", "E700:")
+ call assert_fails("set completefunc=funcref('abc')", "E700:")
+ let &completefunc = "{a -> 'abc'}"
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using function()
+ def MycompleteFunc1(findstart: number, base: string): any
+ add(g:MycompleteFunc1_args, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ set completefunc=function('MycompleteFunc1')
+ new | only
+ setline(1, 'one')
+ g:MycompleteFunc1_args = []
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'one']], g:MycompleteFunc1_args)
+ bw!
+
+ # Test for using a lambda
+ def MycompleteFunc2(findstart: number, base: string): any
+ add(g:MycompleteFunc2_args, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ &completefunc = '(a, b) => MycompleteFunc2(a, b)'
+ new | only
+ setline(1, 'two')
+ g:MycompleteFunc2_args = []
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'two']], g:MycompleteFunc2_args)
+ bw!
+
+ # Test for using a variable with a lambda expression
+ var Fn: func = (a, b) => MycompleteFunc2(a, b)
+ &completefunc = string(Fn)
+ new | only
+ setline(1, 'three')
+ g:MycompleteFunc2_args = []
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'three']], g:MycompleteFunc2_args)
+ bw!
+ END
+ " call CheckScriptSuccess(lines)
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set completefunc=(a,\ b)\ =>\ g:MycompleteFunc2(a,\ b)
+ " new | only
+ " let g:MycompleteFunc2_args = []
+ " call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:')
+ " call assert_equal([], g:MycompleteFunc2_args)
+
+ " cleanup
+ delfunc MycompleteFunc1
+ delfunc MycompleteFunc2
+ delfunc MycompleteFunc3
+ set completefunc&
+ %bw!
+endfunc
+
+" Test for different ways of setting the 'omnifunc' option
+func Test_omnifunc_callback()
+ " Test for using a function()
+ func MyomniFunc1(findstart, base)
+ call add(g:MyomniFunc1_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set omnifunc=function('MyomniFunc1')
+ new | only
+ call setline(1, 'one')
+ let g:MyomniFunc1_args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'one']], g:MyomniFunc1_args)
+ bw!
+
+ " Using a funcref variable to set 'omnifunc'
+ let Fn = function('MyomniFunc1')
+ let &omnifunc = string(Fn)
+ new | only
+ call setline(1, 'two')
+ let g:MyomniFunc1_args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'two']], g:MyomniFunc1_args)
+ call assert_fails('let &omnifunc = Fn', 'E729:')
+ bw!
+
+ " Test for using a funcref()
+ func MyomniFunc2(findstart, base)
+ call add(g:MyomniFunc2_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set omnifunc=funcref('MyomniFunc2')
+ new | only
+ call setline(1, 'three')
+ let g:MyomniFunc2_args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'three']], g:MyomniFunc2_args)
+ bw!
+
+ " Using a funcref variable to set 'omnifunc'
+ let Fn = funcref('MyomniFunc2')
+ let &omnifunc = string(Fn)
+ new | only
+ call setline(1, 'four')
+ let g:MyomniFunc2_args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'four']], g:MyomniFunc2_args)
+ call assert_fails('let &omnifunc = Fn', 'E729:')
+ bw!
+
+ " Test for using a lambda function
+ func MyomniFunc3(findstart, base)
+ call add(g:MyomniFunc3_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set omnifunc={a,\ b,\ ->\ MyomniFunc3(a,\ b,)}
+ new | only
+ call setline(1, 'five')
+ let g:MyomniFunc3_args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'five']], g:MyomniFunc3_args)
+ bw!
+
+ " Set 'omnifunc' to a lambda expression
+ let &omnifunc = '{a, b -> MyomniFunc3(a, b)}'
+ new | only
+ call setline(1, 'six')
+ let g:MyomniFunc3_args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'six']], g:MyomniFunc3_args)
+ bw!
+
+ " Set 'omnifunc' to a variable with a lambda expression
+ let Lambda = {a, b -> MyomniFunc3(a, b)}
+ let &omnifunc = string(Lambda)
+ new | only
+ call setline(1, 'seven')
+ let g:MyomniFunc3_args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'seven']], g:MyomniFunc3_args)
+ call assert_fails('let &omnifunc = Lambda', 'E729:')
+ bw!
+
+ " Test for using a lambda function with incorrect return value
+ let Lambda = {s -> strlen(s)}
+ let &omnifunc = string(Lambda)
+ new | only
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ bw!
+
+ " Test for clearing the 'omnifunc' option
+ set omnifunc=''
+ set omnifunc&
+
+ call assert_fails("set omnifunc=function('abc')", "E700:")
+ call assert_fails("set omnifunc=funcref('abc')", "E700:")
+ let &omnifunc = "{a -> 'abc'}"
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using function()
+ def MyomniFunc1(findstart: number, base: string): any
+ add(g:MyomniFunc1_args, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ set omnifunc=function('MyomniFunc1')
+ new | only
+ setline(1, 'one')
+ g:MyomniFunc1_args = []
+ feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'one']], g:MyomniFunc1_args)
+ bw!
+
+ # Test for using a lambda
+ def MyomniFunc2(findstart: number, base: string): any
+ add(g:MyomniFunc2_args, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ &omnifunc = '(a, b) => MyomniFunc2(a, b)'
+ new | only
+ setline(1, 'two')
+ g:MyomniFunc2_args = []
+ feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'two']], g:MyomniFunc2_args)
+ bw!
+
+ # Test for using a variable with a lambda expression
+ var Fn: func = (a, b) => MyomniFunc2(a, b)
+ &omnifunc = string(Fn)
+ new | only
+ setline(1, 'three')
+ g:MyomniFunc2_args = []
+ feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'three']], g:MyomniFunc2_args)
+ bw!
+ END
+ " call CheckScriptSuccess(lines)
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set omnifunc=(a,\ b)\ =>\ g:MyomniFunc2(a,\ b)
+ " new | only
+ " let g:MyomniFunc2_args = []
+ " call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:')
+ " call assert_equal([], g:MyomniFunc2_args)
+
+ " cleanup
+ delfunc MyomniFunc1
+ delfunc MyomniFunc2
+ delfunc MyomniFunc3
+ set omnifunc&
+ %bw!
+endfunc
+
+" Test for different ways of setting the 'thesaurusfunc' option
+func Test_thesaurusfunc_callback()
+ " Test for using a function()
+ func MytsrFunc1(findstart, base)
+ call add(g:MytsrFunc1_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set thesaurusfunc=function('MytsrFunc1')
+ new | only
+ call setline(1, 'one')
+ let g:MytsrFunc1_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'one']], g:MytsrFunc1_args)
+ bw!
+
+ " Using a funcref variable to set 'thesaurusfunc'
+ let Fn = function('MytsrFunc1')
+ let &thesaurusfunc = string(Fn)
+ new | only
+ call setline(1, 'two')
+ let g:MytsrFunc1_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'two']], g:MytsrFunc1_args)
+ call assert_fails('let &thesaurusfunc = Fn', 'E729:')
+ bw!
+
+ " Test for using a funcref()
+ func MytsrFunc2(findstart, base)
+ call add(g:MytsrFunc2_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set thesaurusfunc=funcref('MytsrFunc2')
+ new | only
+ call setline(1, 'three')
+ let g:MytsrFunc2_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'three']], g:MytsrFunc2_args)
+ bw!
+
+ " Using a funcref variable to set 'thesaurusfunc'
+ let Fn = funcref('MytsrFunc2')
+ let &thesaurusfunc = string(Fn)
+ new | only
+ call setline(1, 'four')
+ let g:MytsrFunc2_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'four']], g:MytsrFunc2_args)
+ call assert_fails('let &thesaurusfunc = Fn', 'E729:')
+ bw!
+
+ " Test for using a lambda function
+ func MytsrFunc3(findstart, base)
+ call add(g:MytsrFunc3_args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set thesaurusfunc={a,\ b,\ ->\ MytsrFunc3(a,\ b,)}
+ new | only
+ call setline(1, 'five')
+ let g:MytsrFunc3_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'five']], g:MytsrFunc3_args)
+ bw!
+
+ " Set 'thesaurusfunc' to a lambda expression
+ let &thesaurusfunc = '{a, b -> MytsrFunc3(a, b)}'
+ new | only
+ call setline(1, 'six')
+ let g:MytsrFunc3_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'six']], g:MytsrFunc3_args)
+ bw!
+
+ " Set 'thesaurusfunc' to a variable with a lambda expression
+ let Lambda = {a, b -> MytsrFunc3(a, b)}
+ let &thesaurusfunc = string(Lambda)
+ new | only
+ call setline(1, 'seven')
+ let g:MytsrFunc3_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'seven']], g:MytsrFunc3_args)
+ call assert_fails('let &thesaurusfunc = Lambda', 'E729:')
+ bw!
+
+ " Test for using a lambda function with incorrect return value
+ let Lambda = {s -> strlen(s)}
+ let &thesaurusfunc = string(Lambda)
+ new | only
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ bw!
+
+ " Test for clearing the 'thesaurusfunc' option
+ set thesaurusfunc=''
+ set thesaurusfunc&
+
+ call assert_fails("set thesaurusfunc=function('abc')", "E700:")
+ call assert_fails("set thesaurusfunc=funcref('abc')", "E700:")
+ let &thesaurusfunc = "{a -> 'abc'}"
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using function()
+ def MytsrFunc1(findstart: number, base: string): any
+ add(g:MytsrFunc1_args, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ set thesaurusfunc=function('MytsrFunc1')
+ new | only
+ setline(1, 'one')
+ g:MytsrFunc1_args = []
+ feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'one']], g:MytsrFunc1_args)
+ bw!
+
+ # Test for using a lambda
+ def MytsrFunc2(findstart: number, base: string): any
+ add(g:MytsrFunc2_args, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ &thesaurusfunc = '(a, b) => MytsrFunc2(a, b)'
+ new | only
+ setline(1, 'two')
+ g:MytsrFunc2_args = []
+ feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'two']], g:MytsrFunc2_args)
+ bw!
+
+ # Test for using a variable with a lambda expression
+ var Fn: func = (a, b) => MytsrFunc2(a, b)
+ &thesaurusfunc = string(Fn)
+ new | only
+ setline(1, 'three')
+ g:MytsrFunc2_args = []
+ feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'three']], g:MytsrFunc2_args)
+ bw!
+ END
+ " call CheckScriptSuccess(lines)
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set thesaurusfunc=(a,\ b)\ =>\ g:MytsrFunc2(a,\ b)
+ " new | only
+ " let g:MytsrFunc2_args = []
+ " call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:')
+ " call assert_equal([], g:MytsrFunc2_args)
+ " bw!
+
+ " Use a buffer-local value and a global value
+ func MytsrFunc4(findstart, base)
+ call add(g:MytsrFunc4_args, [a:findstart, a:base])
+ return a:findstart ? 0 : ['sunday']
+ endfunc
+ set thesaurusfunc&
+ setlocal thesaurusfunc=function('MytsrFunc4')
+ call setline(1, 'sun')
+ let g:MytsrFunc4_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sunday', getline(1))
+ call assert_equal([[1, ''], [0, 'sun']], g:MytsrFunc4_args)
+ new
+ call setline(1, 'sun')
+ let g:MytsrFunc4_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sun', getline(1))
+ call assert_equal([], g:MytsrFunc4_args)
+ set thesaurusfunc=function('MytsrFunc1')
+ wincmd w
+ call setline(1, 'sun')
+ let g:MytsrFunc4_args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sunday', getline(1))
+ call assert_equal([[1, ''], [0, 'sun']], g:MytsrFunc4_args)
+
+ " cleanup
+ set thesaurusfunc&
+ delfunc MytsrFunc1
+ delfunc MytsrFunc2
+ delfunc MytsrFunc3
+ delfunc MytsrFunc4
+ %bw!
+endfunc
+
func FooBarComplete(findstart, base)
if a:findstart
return col('.') - 1
diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim
index bdf5afa5b2..8690e30e77 100644
--- a/src/nvim/testdir/test_tagfunc.vim
+++ b/src/nvim/testdir/test_tagfunc.vim
@@ -117,4 +117,185 @@ func Test_tagfunc_settagstack()
delfunc Mytagfunc2
endfunc
+" Script local tagfunc callback function
+func s:ScriptLocalTagFunc(pat, flags, info)
+ let g:ScriptLocalFuncArgs = [a:pat, a:flags, a:info]
+ return v:null
+endfunc
+
+" Test for different ways of setting the 'tagfunc' option
+func Test_tagfunc_callback()
+ " Test for using a function()
+ func MytagFunc1(pat, flags, info)
+ let g:MytagFunc1_args = [a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+ set tagfunc=function('MytagFunc1')
+ new | only
+ let g:MytagFunc1_args = []
+ call assert_fails('tag a11', 'E433:')
+ call assert_equal(['a11', '', {}], g:MytagFunc1_args)
+
+ " Using a funcref variable to set 'tagfunc'
+ let Fn = function('MytagFunc1')
+ let &tagfunc = string(Fn)
+ new | only
+ let g:MytagFunc1_args = []
+ call assert_fails('tag a12', 'E433:')
+ call assert_equal(['a12', '', {}], g:MytagFunc1_args)
+ call assert_fails('let &tagfunc = Fn', 'E729:')
+
+ " Test for using a funcref()
+ func MytagFunc2(pat, flags, info)
+ let g:MytagFunc2_args = [a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+ set tagfunc=funcref('MytagFunc2')
+ new | only
+ let g:MytagFunc2_args = []
+ call assert_fails('tag a13', 'E433:')
+ call assert_equal(['a13', '', {}], g:MytagFunc2_args)
+
+ " Using a funcref variable to set 'tagfunc'
+ let Fn = funcref('MytagFunc2')
+ let &tagfunc = string(Fn)
+ new | only
+ let g:MytagFunc2_args = []
+ call assert_fails('tag a14', 'E433:')
+ call assert_equal(['a14', '', {}], g:MytagFunc2_args)
+ call assert_fails('let &tagfunc = Fn', 'E729:')
+
+ " Test for using a script local function
+ set tagfunc=<SID>ScriptLocalTagFunc
+ new | only
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a15', 'E433:')
+ call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs)
+
+ " Test for using a script local funcref variable
+ let Fn = function("s:ScriptLocalTagFunc")
+ let &tagfunc= string(Fn)
+ new | only
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a16', 'E433:')
+ call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
+
+ " Test for using a lambda function
+ func MytagFunc3(pat, flags, info)
+ let g:MytagFunc3_args = [a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+ set tagfunc={a,\ b,\ c\ ->\ MytagFunc3(a,\ b,\ c)}
+ new | only
+ let g:MytagFunc3_args = []
+ call assert_fails('tag a17', 'E433:')
+ call assert_equal(['a17', '', {}], g:MytagFunc3_args)
+
+ " Set 'tagfunc' to a lambda expression
+ let &tagfunc = '{a, b, c -> MytagFunc3(a, b, c)}'
+ new | only
+ let g:MytagFunc3_args = []
+ call assert_fails('tag a18', 'E433:')
+ call assert_equal(['a18', '', {}], g:MytagFunc3_args)
+
+ " Set 'tagfunc' to a variable with a lambda expression
+ let Lambda = {a, b, c -> MytagFunc3(a, b, c)}
+ let &tagfunc = string(Lambda)
+ new | only
+ let g:MytagFunc3_args = []
+ call assert_fails("tag a19", "E433:")
+ call assert_equal(['a19', '', {}], g:MytagFunc3_args)
+ call assert_fails('let &tagfunc = Lambda', 'E729:')
+
+ " Test for using a lambda function with incorrect return value
+ let Lambda = {s -> strlen(s)}
+ let &tagfunc = string(Lambda)
+ new | only
+ call assert_fails("tag a20", "E987:")
+
+ " Test for clearing the 'tagfunc' option
+ set tagfunc=''
+ set tagfunc&
+
+ call assert_fails("set tagfunc=function('abc')", "E700:")
+ call assert_fails("set tagfunc=funcref('abc')", "E700:")
+ let &tagfunc = "{a -> 'abc'}"
+ call assert_fails("echo taglist('a')", "E987:")
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using function()
+ def MytagFunc1(pat: string, flags: string, info: dict<any>): any
+ g:MytagFunc1_args = [pat, flags, info]
+ return null
+ enddef
+ set tagfunc=function('MytagFunc1')
+ new | only
+ g:MytagFunc1_args = []
+ assert_fails('tag a10', 'E433:')
+ assert_equal(['a10', '', {}], g:MytagFunc1_args)
+
+ # Test for using a lambda
+ def MytagFunc2(pat: string, flags: string, info: dict<any>): any
+ g:MytagFunc2_args = [pat, flags, info]
+ return null
+ enddef
+ &tagfunc = '(a, b, c) => MytagFunc2(a, b, c)'
+ new | only
+ g:MytagFunc2_args = []
+ assert_fails('tag a20', 'E433:')
+ assert_equal(['a20', '', {}], g:MytagFunc2_args)
+
+ # Test for using a variable with a lambda expression
+ var Fn: func = (a, b, c) => MytagFunc2(a, b, c)
+ &tagfunc = string(Fn)
+ new | only
+ g:MytagFunc2_args = []
+ assert_fails('tag a30', 'E433:')
+ assert_equal(['a30', '', {}], g:MytagFunc2_args)
+ END
+ " call CheckScriptSuccess(lines)
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set tagfunc=(a,\ b,\ c)\ =>\ g:MytagFunc2(a,\ b,\ c)
+ " new | only
+ " let g:MytagFunc3_args = []
+ " call assert_fails("tag a17", "E117:")
+ " call assert_equal([], g:MytagFunc3_args)
+
+ " cleanup
+ delfunc MytagFunc1
+ delfunc MytagFunc2
+ delfunc MytagFunc3
+ set tagfunc&
+ %bw!
+endfunc
+
+func Test_tagfunc_wipes_buffer()
+ func g:Tag0unc0(t,f,o)
+ bwipe
+ endfunc
+ set tagfunc=g:Tag0unc0
+ new
+ cal assert_fails('tag 0', 'E987:')
+
+ delfunc g:Tag0unc0
+ set tagfunc=
+endfunc
+
+func Test_tagfunc_closes_window()
+ split any
+ func MytagfuncClose(pat, flags, info)
+ close
+ return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
+ endfunc
+ set tagfunc=MytagfuncClose
+ call assert_fails('tag xyz', 'E1299:')
+
+ set tagfunc=
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab