# HG changeset patch # User Bram Moolenaar # Date 1637273704 -3600 # Node ID 6b4f017d70055d963d6d44e2815df7481cfea1f9 # Parent 00e8f1ac9c6dae8ac898b6a34b365e132f1ea812 patch 8.2.3619: cannot use a lambda for 'operatorfunc' Commit: https://github.com/vim/vim/commit/777175b0df8c5ec3cd30d19a2e887e661ac209c8 Author: Yegappan Lakshmanan Date: Thu Nov 18 22:08:57 2021 +0000 patch 8.2.3619: cannot use a lambda for 'operatorfunc' Problem: Cannot use a lambda for 'operatorfunc'. Solution: Support using a lambda or partial. (Yegappan Lakshmanan, closes #8775) diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -1009,6 +1009,20 @@ or `unnamedplus`. The `mode()` function will return the state as it will be after applying the operator. +The `mode()` function will return the state as it will be after applying the +operator. + +Here is an example for using a lambda function to create a normal-mode +operator to add quotes around text in the current line: > + + nnoremap let &opfunc='{t -> + \ getline(".") + \ ->split("\\zs") + \ ->insert("\"", col("'']")) + \ ->insert("\"", col("''[") - 1) + \ ->join("") + \ ->setline(".")}'g@ + ============================================================================== 2. Abbreviations *abbreviations* *Abbreviations* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -371,6 +371,17 @@ Note: In the future more global options ":setlocal" on a global option might work differently then. + *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: +> + set opfunc=MyOpFunc + set opfunc=function("MyOpFunc") + set opfunc=funcref("MyOpFunc") + set opfunc={t\ ->\ MyOpFunc(t)} +< + Setting the filetype :setf[iletype] [FALLBACK] {filetype} *:setf* *:setfiletype* @@ -5623,7 +5634,9 @@ A jump table for the options with a shor 'operatorfunc' 'opfunc' string (default: empty) global This option specifies a function to be called by the |g@| operator. - See |:map-operator| for more info and an example. + See |:map-operator| for more info and an example. 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. @@ -6023,8 +6036,9 @@ A jump table for the options with a shor customize the information displayed in the quickfix or location window for each entry in the corresponding quickfix or location list. See |quickfix-window-function| for an explanation of how to write the - function and an example. The value can be the name of a function or a - lambda. + 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. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. diff --git a/src/ops.c b/src/ops.c --- a/src/ops.c +++ b/src/ops.c @@ -3305,6 +3305,29 @@ op_colon(oparg_T *oap) // do_cmdline() does the rest } +// callback function for 'operatorfunc' +static callback_T opfunc_cb; + +/* + * Process the 'operatorfunc' option value. + * Returns OK or FAIL. + */ + int +set_operatorfunc_option(void) +{ + return option_set_callback_func(p_opfunc, &opfunc_cb); +} + +#if defined(EXITFREE) || defined(PROTO) + void +free_operatorfunc_option(void) +{ +# ifdef FEAT_EVAL + free_callback(&opfunc_cb); +# endif +} +#endif + /* * Handle the "g@" operator: call 'operatorfunc'. */ @@ -3317,6 +3340,7 @@ op_function(oparg_T *oap UNUSED) int save_finish_op = finish_op; pos_T orig_start = curbuf->b_op_start; pos_T orig_end = curbuf->b_op_end; + typval_T rettv; if (*p_opfunc == NUL) emsg(_("E774: 'operatorfunc' is empty")); @@ -3345,7 +3369,8 @@ op_function(oparg_T *oap UNUSED) // Reset finish_op so that mode() returns the right value. finish_op = FALSE; - (void)call_func_noret(p_opfunc, 1, argv); + if (call_callback(&opfunc_cb, 0, &rettv, 1, argv) != FAIL) + clear_tv(&rettv); virtual_op = save_virtual_op; finish_op = save_finish_op; diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -809,6 +809,7 @@ free_all_options(void) // buffer-local option: free global value clear_string_option((char_u **)options[i].var); } + free_operatorfunc_option(); } #endif @@ -7184,3 +7185,49 @@ magic_isset(void) #endif return p_magic; } + +/* + * Set the callback function value for an option that accepts a function name, + * lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.) + * Returns OK if the option is successfully set to a function, otherwise + * returns FAIL. + */ + int +option_set_callback_func(char_u *optval UNUSED, callback_T *optcb UNUSED) +{ +#ifdef FEAT_EVAL + typval_T *tv; + callback_T cb; + + if (optval == NULL || *optval == NUL) + { + free_callback(optcb); + return OK; + } + + if (*optval == '{' + || (STRNCMP(optval, "function(", 9) == 0) + || (STRNCMP(optval, "funcref(", 8) == 0)) + // Lambda expression or a funcref + tv = eval_expr(optval, NULL); + else + // treat everything else as a function name string + tv = alloc_string_tv(vim_strsave(optval)); + if (tv == NULL) + return FAIL; + + cb = get_callback(tv); + if (cb.cb_name == NULL) + { + free_tv(tv); + return FAIL; + } + + free_callback(optcb); + set_callback(optcb, &cb); + free_tv(tv); + return OK; +#else + return FAIL; +#endif +} diff --git a/src/optionstr.c b/src/optionstr.c --- a/src/optionstr.c +++ b/src/optionstr.c @@ -2320,10 +2320,18 @@ ambw_end: # endif #endif + // 'operatorfunc' + else if (varp == &p_opfunc) + { + if (set_operatorfunc_option() == FAIL) + errmsg = e_invarg; + } + #ifdef FEAT_QUICKFIX + // 'quickfixtextfunc' else if (varp == &p_qftf) { - if (qf_process_qftf_option() == FALSE) + if (qf_process_qftf_option() == FAIL) errmsg = e_invarg; } #endif diff --git a/src/proto/ops.pro b/src/proto/ops.pro --- a/src/proto/ops.pro +++ b/src/proto/ops.pro @@ -17,5 +17,7 @@ void block_prep(oparg_T *oap, struct blo void op_addsub(oparg_T *oap, linenr_T Prenum1, int g_cmd); void clear_oparg(oparg_T *oap); void cursor_pos_info(dict_T *dict); +int set_operatorfunc_option(void); +void free_operatorfunc_option(void); void do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank); /* vim: set ft=c : */ diff --git a/src/proto/option.pro b/src/proto/option.pro --- a/src/proto/option.pro +++ b/src/proto/option.pro @@ -10,7 +10,7 @@ void set_init_3(void); void set_helplang_default(char_u *lang); void set_title_defaults(void); void ex_set(exarg_T *eap); -int do_set(char_u *arg, int opt_flags); +int do_set(char_u *arg_start, int opt_flags); void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked); int string_to_key(char_u *arg, int multi_byte); void did_set_title(void); @@ -78,4 +78,5 @@ char_u *get_showbreak_value(win_T *win); dict_T *get_winbuf_options(int bufopt); int fill_culopt_flags(char_u *val, win_T *wp); int magic_isset(void); +int option_set_callback_func(char_u *optval, callback_T *optcb); /* vim: set ft=c : */ diff --git a/src/quickfix.c b/src/quickfix.c --- a/src/quickfix.c +++ b/src/quickfix.c @@ -4437,45 +4437,12 @@ qf_find_buf(qf_info_T *qi) /* * Process the 'quickfixtextfunc' option value. + * Returns OK or FAIL. */ int qf_process_qftf_option(void) { - typval_T *tv; - callback_T cb; - - if (p_qftf == NULL || *p_qftf == NUL) - { - free_callback(&qftf_cb); - return TRUE; - } - - if (*p_qftf == '{') - { - // Lambda expression - tv = eval_expr(p_qftf, NULL); - if (tv == NULL) - return FALSE; - } - else - { - // treat everything else as a function name string - tv = alloc_string_tv(vim_strsave(p_qftf)); - if (tv == NULL) - return FALSE; - } - - cb = get_callback(tv); - if (cb.cb_name == NULL) - { - free_tv(tv); - return FALSE; - } - - free_callback(&qftf_cb); - set_callback(&qftf_cb, &cb); - free_tv(tv); - return TRUE; + return option_set_callback_func(p_qftf, &qftf_cb); } /* diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim --- a/src/testdir/test_normal.vim +++ b/src/testdir/test_normal.vim @@ -386,6 +386,70 @@ func Test_normal09_operatorfunc() norm V10j,, call assert_equal(22, g:a) + " Use a lambda function for 'opfunc' + unmap ,, + call cursor(1, 1) + let g:a=0 + nmap ,, :set opfunc={type\ ->\ CountSpaces(type)}g@ + vmap ,, :call CountSpaces(visualmode(), 1) + 50 + norm V2j,, + call assert_equal(6, g:a) + norm V,, + call assert_equal(2, g:a) + norm ,,l + call assert_equal(0, g:a) + 50 + exe "norm 0\10j2l,," + call assert_equal(11, g:a) + 50 + norm V10j,, + call assert_equal(22, g:a) + + " use a partial function for 'opfunc' + let g:OpVal = 0 + func! Test_opfunc1(x, y, type) + let g:OpVal = a:x + a:y + endfunc + set opfunc=function('Test_opfunc1',\ [5,\ 7]) + normal! g@l + call assert_equal(12, g:OpVal) + " delete the function and try to use g@ + delfunc Test_opfunc1 + call test_garbagecollect_now() + call assert_fails('normal! g@l', 'E117:') + set opfunc= + + " use a funcref for 'opfunc' + let g:OpVal = 0 + func! Test_opfunc2(x, y, type) + let g:OpVal = a:x + a:y + endfunc + set opfunc=funcref('Test_opfunc2',\ [4,\ 3]) + normal! g@l + call assert_equal(7, g:OpVal) + " delete the function and try to use g@ + delfunc Test_opfunc2 + call test_garbagecollect_now() + call assert_fails('normal! g@l', 'E933:') + set opfunc= + + " Try to use a function with two arguments for 'operatorfunc' + let g:OpVal = 0 + func! Test_opfunc3(x, y) + let g:OpVal = 4 + endfunc + set opfunc=Test_opfunc3 + call assert_fails('normal! g@l', 'E119:') + call assert_equal(0, g:OpVal) + set opfunc= + delfunc Test_opfunc3 + unlet g:OpVal + + " Try to use a lambda function with two arguments for 'operatorfunc' + set opfunc={x,\ y\ ->\ 'done'} + call assert_fails('normal! g@l', 'E119:') + " clean up unmap ,, set opfunc= diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3619, +/**/ 3618, /**/ 3617,