# HG changeset patch # User Bram Moolenaar # Date 1638530104 -3600 # Node ID 8aba638e91eba2e035c1fe8a81339f3d1e0c5cc8 # Parent b6eb7e295973331d45e53dd87cb82ddf1b2d6c21 patch 8.2.3725: cannot use a lambda for 'completefunc' and 'omnifunc' Commit: https://github.com/vim/vim/commit/8658c759f05b317707d56e3b65a5ef63930c7498 Author: Yegappan Lakshmanan Date: Fri Dec 3 11:09:29 2021 +0000 patch 8.2.3725: cannot use a lambda for 'completefunc' and 'omnifunc' Problem: Cannot use a lambda for 'completefunc' and 'omnifunc'. Solution: Implement lambda support. (Yegappan Lakshmanan, closes https://github.com/vim/vim/issues/9257) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -373,8 +373,8 @@ Note: In the future more global options *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') @@ -1939,7 +1939,9 @@ A jump table for the options with a shor 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. @@ -5616,7 +5618,9 @@ A jump table for the options with a shor 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 @@ -8075,6 +8079,8 @@ A jump table for the options with a shor feature} 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/src/buffer.c b/src/buffer.c --- 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 --- 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() or funcref() 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() or funcref() 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() or funcref() 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 *bas 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 *bas // 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, in 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 --- 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 --- 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 --- 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 --- 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 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -28,6 +28,7 @@ int builtin_function(char_u *name, int l 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 --- 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 --- a/src/tag.c +++ b/src/tag.c @@ -115,7 +115,7 @@ static callback_T tfu_cb; // 'tagfun * a function (string), or function() or funcref() 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 --- 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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", "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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", "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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", '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\\\", "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\\\", "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\\\", "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\\\", "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 --- 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=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 --- 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/src/version.c --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3725, +/**/ 3724, /**/ 3723,