summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2021-12-03 11:09:29 +0000
committerBram Moolenaar <Bram@vim.org>2021-12-03 11:09:29 +0000
commit8658c759f05b317707d56e3b65a5ef63930c7498 (patch)
tree124fcda187b5515dfaeba4c86aa7a5e0f0888686 /src
parent021ef351c2127cf8aa5668a881435a0a12774644 (diff)
patch 8.2.3725: cannot use a lambda for 'completefunc' and 'omnifunc'v8.2.3725
Problem: Cannot use a lambda for 'completefunc' and 'omnifunc'. Solution: Implement lambda support. (Yegappan Lakshmanan, closes #9257)
Diffstat (limited to 'src')
-rw-r--r--src/buffer.c3
-rw-r--r--src/insexpand.c137
-rw-r--r--src/option.c4
-rw-r--r--src/optionstr.c23
-rw-r--r--src/proto/insexpand.pro6
-rw-r--r--src/proto/tag.pro2
-rw-r--r--src/proto/userfunc.pro1
-rw-r--r--src/structs.h3
-rw-r--r--src/tag.c4
-rw-r--r--src/testdir/test_ins_complete.vim500
-rw-r--r--src/testdir/test_tagfunc.vim35
-rw-r--r--src/userfunc.c23
-rw-r--r--src/version.c2
13 files changed, 730 insertions, 13 deletions
diff --git a/src/buffer.c b/src/buffer.c
index 20a0d66606..f86ecdd269 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2326,8 +2326,11 @@ free_buf_options(
clear_string_option(&buf->b_p_cpt);
#ifdef FEAT_COMPL_FUNC
clear_string_option(&buf->b_p_cfu);
+ free_callback(&buf->b_cfu_cb);
clear_string_option(&buf->b_p_ofu);
+ free_callback(&buf->b_ofu_cb);
clear_string_option(&buf->b_p_tsrfu);
+ free_callback(&buf->b_tsrfu_cb);
#endif
#ifdef FEAT_QUICKFIX
clear_string_option(&buf->b_p_gp);
diff --git a/src/insexpand.c b/src/insexpand.c
index 630548bec1..92f0731eb5 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -2237,6 +2237,113 @@ ins_compl_next_buf(buf_T *buf, int flag)
}
#ifdef FEAT_COMPL_FUNC
+
+# ifdef FEAT_EVAL
+static callback_T cfu_cb; // 'completefunc' callback function
+static callback_T ofu_cb; // 'omnifunc' callback function
+static callback_T tsrfu_cb; // 'thesaurusfunc' callback function
+# endif
+
+/*
+ * Copy a global callback function to a buffer local callback.
+ */
+ static void
+copy_global_to_buflocal_cb(callback_T *globcb, callback_T *bufcb)
+{
+ free_callback(bufcb);
+ if (globcb->cb_name != NULL && *globcb->cb_name != NUL)
+ copy_callback(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;
+
+ 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 UNUSED)
+{
+# ifdef FEAT_EVAL
+ copy_global_to_buflocal_cb(&cfu_cb, &buf->b_cfu_cb);
+# endif
+}
+
+/*
+ * 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;
+
+ 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 UNUSED)
+{
+# ifdef FEAT_EVAL
+ copy_global_to_buflocal_cb(&ofu_cb, &buf->b_ofu_cb);
+# endif
+}
+
+/*
+ * 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
+ free_callback(&curbuf->b_tsrfu_cb);
+ retval = option_set_callback_func(curbuf->b_p_tsrfu,
+ &curbuf->b_tsrfu_cb);
+ }
+ else
+ {
+ // global option set
+ free_callback(&tsrfu_cb);
+ retval = option_set_callback_func(p_tsrfu, &tsrfu_cb);
+ }
+
+ return retval;
+}
+
+
/*
* Get the user-defined completion function name for completion 'type'
*/
@@ -2257,6 +2364,20 @@ get_complete_funcname(int type)
}
/*
+ * Get the callback to use for insert mode completion.
+ */
+ callback_T *
+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".
* "type" is either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS.
@@ -2269,8 +2390,10 @@ expand_by_function(int type, char_u *base)
typval_T args[3];
char_u *funcname;
pos_T pos;
+ callback_T *cb;
typval_T rettv;
int save_State = State;
+ int retval;
funcname = get_complete_funcname(type);
if (*funcname == NUL)
@@ -2289,8 +2412,11 @@ expand_by_function(int type, char_u *base)
// Insert mode in another buffer.
++textwinlock;
+ cb = get_insert_callback(type);
+ retval = call_callback(cb, 0, &rettv, 2, args);
+
// Call a function, which returns a list or dict.
- if (call_vim_function(funcname, 2, args, &rettv) == OK)
+ if (retval == OK)
{
switch (rettv.v_type)
{
@@ -3971,6 +4097,7 @@ ins_complete(int c, int enable_pum)
char_u *funcname;
pos_T pos;
int save_State = State;
+ callback_T *cb;
// Call 'completefunc' or 'omnifunc' and get pattern length as a
// string
@@ -3991,7 +4118,8 @@ ins_complete(int c, int enable_pum)
args[2].v_type = VAR_UNKNOWN;
pos = curwin->w_cursor;
++textwinlock;
- col = call_func_retnr(funcname, 2, args);
+ cb = get_insert_callback(ctrl_x_mode);
+ col = call_callback_retnr(cb, 2, args);
--textwinlock;
State = save_State;
@@ -4339,6 +4467,11 @@ quote_meta(char_u *dest, char_u *src, int len)
free_insexpand_stuff(void)
{
VIM_CLEAR(compl_orig_text);
+# ifdef FEAT_EVAL
+ free_callback(&cfu_cb);
+ free_callback(&ofu_cb);
+ free_callback(&tsrfu_cb);
+# endif
}
#endif
diff --git a/src/option.c b/src/option.c
index 94468d2bac..22dcfc5d1f 100644
--- a/src/option.c
+++ b/src/option.c
@@ -5927,13 +5927,15 @@ buf_copy_options(buf_T *buf, int flags)
#ifdef FEAT_COMPL_FUNC
buf->b_p_cfu = vim_strsave(p_cfu);
COPY_OPT_SCTX(buf, BV_CFU);
+ set_buflocal_cfu_callback(buf);
buf->b_p_ofu = vim_strsave(p_ofu);
COPY_OPT_SCTX(buf, BV_OFU);
+ set_buflocal_ofu_callback(buf);
#endif
#ifdef FEAT_EVAL
buf->b_p_tfu = vim_strsave(p_tfu);
COPY_OPT_SCTX(buf, BV_TFU);
- buf_set_tfu_callback(buf);
+ set_buflocal_tfu_callback(buf);
#endif
buf->b_p_sts = p_sts;
COPY_OPT_SCTX(buf, BV_STS);
diff --git a/src/optionstr.c b/src/optionstr.c
index 1b0d8a1675..fe62cb360d 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -2307,6 +2307,29 @@ ambw_end:
# endif
#endif
+#ifdef FEAT_COMPL_FUNC
+ // 'completefunc'
+ else if (gvarp == &p_cfu)
+ {
+ if (set_completefunc_option() == FAIL)
+ errmsg = e_invarg;
+ }
+
+ // 'omnifunc'
+ else if (gvarp == &p_ofu)
+ {
+ if (set_omnifunc_option() == FAIL)
+ errmsg = e_invarg;
+ }
+
+ // 'thesaurusfunc'
+ else if (gvarp == &p_tsrfu)
+ {
+ if (set_thesaurusfunc_option() == FAIL)
+ errmsg = e_invarg;
+ }
+#endif
+
// 'operatorfunc'
else if (varp == &p_opfunc)
{
diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro
index 90b5a0732c..d455a98fd7 100644
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -39,6 +39,12 @@ int ins_compl_bs(void);
void ins_compl_addleader(int c);
void ins_compl_addfrommatch(void);
int ins_compl_prep(int c);
+int set_completefunc_option(void);
+void set_buflocal_cfu_callback(buf_T *buf);
+int set_omnifunc_option(void);
+void set_buflocal_ofu_callback(buf_T *buf);
+int set_thesaurusfunc_option(void);
+callback_T *get_insert_callback(int type);
void f_complete(typval_T *argvars, typval_T *rettv);
void f_complete_add(typval_T *argvars, typval_T *rettv);
void f_complete_check(typval_T *argvars, typval_T *rettv);
diff --git a/src/proto/tag.pro b/src/proto/tag.pro
index e9827ea7e4..c8ef72101e 100644
--- a/src/proto/tag.pro
+++ b/src/proto/tag.pro
@@ -1,7 +1,7 @@
/* tag.c */
int set_tagfunc_option(void);
void free_tagfunc_option(void);
-void buf_set_tfu_callback(buf_T *buf);
+void set_buflocal_tfu_callback(buf_T *buf);
int do_tag(char_u *tag, int type, int count, int forceit, int verbose);
void tag_freematch(void);
void do_tags(exarg_T *eap);
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index dcc5b34d05..b5ea9b6662 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -28,6 +28,7 @@ int builtin_function(char_u *name, int len);
int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv);
int get_callback_depth(void);
int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars);
+varnumber_T call_callback_retnr(callback_T *callback, int argcount, typval_T *argvars);
void user_func_error(int error, char_u *name);
int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
char_u *printable_func_name(ufunc_T *fp);
diff --git a/src/structs.h b/src/structs.h
index 48a531b86a..be444e312e 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2876,7 +2876,9 @@ struct file_buffer
#endif
#ifdef FEAT_COMPL_FUNC
char_u *b_p_cfu; // 'completefunc'
+ callback_T b_cfu_cb; // 'completefunc' callback
char_u *b_p_ofu; // 'omnifunc'
+ callback_T b_ofu_cb; // 'omnifunc' callback
#endif
#ifdef FEAT_EVAL
char_u *b_p_tfu; // 'tagfunc' option value
@@ -2982,6 +2984,7 @@ struct file_buffer
char_u *b_p_tsr; // 'thesaurus' local value
#ifdef FEAT_COMPL_FUNC
char_u *b_p_tsrfu; // 'thesaurusfunc' local value
+ callback_T b_tsrfu_cb; // 'thesaurusfunc' callback
#endif
long b_p_ul; // 'undolevels' local value
#ifdef FEAT_PERSISTENT_UNDO
diff --git a/src/tag.c b/src/tag.c
index c0601e8806..a4acbacd86 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -115,7 +115,7 @@ static callback_T tfu_cb; // 'tagfunc' callback function
* a function (string), or function(<name>) or funcref(<name>) or a lambda.
*/
int
-set_tagfunc_option()
+set_tagfunc_option(void)
{
#ifdef FEAT_EVAL
free_callback(&tfu_cb);
@@ -148,7 +148,7 @@ free_tagfunc_option(void)
* callback for 'buf'.
*/
void
-buf_set_tfu_callback(buf_T *buf UNUSED)
+set_buflocal_tfu_callback(buf_T *buf UNUSED)
{
#ifdef FEAT_EVAL
free_callback(&buf->b_tfu_cb);
diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim
index 3d6271a42c..083fa68f77 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -2,6 +2,7 @@
source screendump.vim
source check.vim
+source vim9.vim
" Test for insert expansion
func Test_ins_complete()
@@ -867,4 +868,503 @@ func Test_z1_complete_no_history()
close!
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
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tagfunc.vim b/src/testdir/test_tagfunc.vim
index e29e08edc1..1a1226ace1 100644
--- a/src/testdir/test_tagfunc.vim
+++ b/src/testdir/test_tagfunc.vim
@@ -119,6 +119,12 @@ 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()
@@ -161,6 +167,21 @@ func Test_tagfunc_callback()
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]
@@ -169,30 +190,30 @@ func Test_tagfunc_callback()
set tagfunc={a,\ b,\ c\ ->\ MytagFunc3(a,\ b,\ c)}
new | only
let g:MytagFunc3_args = []
- call assert_fails('tag a15', 'E433:')
- call assert_equal(['a15', '', {}], 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 a16', 'E433:')
- call assert_equal(['a16', '', {}], 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 a17", "E433:")
- call assert_equal(['a17', '', {}], 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 a17", "E987:")
+ call assert_fails("tag a20", "E987:")
" Test for clearing the 'tagfunc' option
set tagfunc=''
diff --git a/src/userfunc.c b/src/userfunc.c
index 3edbf891ef..18af66c6e7 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3169,6 +3169,29 @@ call_callback(
}
/*
+ * 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.
+ */
+ varnumber_T
+call_callback_retnr(
+ callback_T *callback,
+ int argcount, // number of "argvars"
+ typval_T *argvars) // vars for arguments, must have "argcount"
+ // PLUS ONE elements!
+{
+ typval_T rettv;
+ varnumber_T retval;
+
+ if (call_callback(callback, 0, &rettv, argcount, argvars) == FAIL)
+ return -1;
+
+ retval = tv_get_number_chk(&rettv, NULL);
+ clear_tv(&rettv);
+ return retval;
+}
+
+/*
* Give an error message for the result of a function.
* Nothing if "error" is FCERR_NONE.
*/
diff --git a/src/version.c b/