diff options
-rw-r--r-- | runtime/doc/options.txt | 14 | ||||
-rw-r--r-- | src/nvim/buffer.c | 3 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 3 | ||||
-rw-r--r-- | src/nvim/eval.c | 19 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 18 | ||||
-rw-r--r-- | src/nvim/insexpand.c | 100 | ||||
-rw-r--r-- | src/nvim/option.c | 5 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 12 | ||||
-rw-r--r-- | src/nvim/tag.c | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_ins_complete.vim | 499 | ||||
-rw-r--r-- | src/nvim/testdir/test_tagfunc.vim | 35 |
11 files changed, 677 insertions, 33 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index a14dc1b99e..37f8ea4b20 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -314,8 +314,8 @@ 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') @@ -1454,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. @@ -4421,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 @@ -6576,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/src/nvim/buffer.c b/src/nvim/buffer.c index 4c8faccaa7..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); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index d8e86a75cf..6448c6b6f6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -675,7 +675,9 @@ 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' @@ -749,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 c983388450..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. diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index e6b038a335..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) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 7ff4735480..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,6 +2316,19 @@ static char_u *get_complete_funcname(int type) } } +/// 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". /// @@ -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 1eabe8c540..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" @@ -4381,11 +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); - buf_set_tfu_callback(buf); + 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 abae338daa..b088a4c8c7 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -1473,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; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index e38fbb7da9..d6e62cff6d 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -149,7 +149,7 @@ void free_tagfunc_option(void) /// Copy the global 'tagfunc' callback function to the buffer-local 'tagfunc' /// callback for 'buf'. -void buf_set_tfu_callback(buf_T *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) { 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 29ca69278d..e3085b9395 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -117,6 +117,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() @@ -159,6 +165,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] @@ -167,30 +188,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='' |