To: vim_dev@googlegroups.com Subject: Patch 8.2.3619 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.3619 Problem: Cannot use a lambda for 'operatorfunc'. Solution: Support using a lambda or partial. (Yegappan Lakshmanan, closes #8775) Files: runtime/doc/map.txt, runtime/doc/options.txt, src/ops.c, src/option.c, src/optionstr.c, src/proto/ops.pro, src/proto/option.pro, src/quickfix.c, src/testdir/test_normal.vim *** ../vim-8.2.3618/runtime/doc/map.txt 2021-11-12 11:25:06.291264320 +0000 --- runtime/doc/map.txt 2021-11-18 21:54:34.311812618 +0000 *************** *** 985,990 **** --- 1006,1025 ---- clobbering the `"*` or `"+` registers, if its value contains the item `unnamed` or `unnamedplus`. + 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* *** ../vim-8.2.3618/runtime/doc/options.txt 2021-11-15 17:13:07.342685617 +0000 --- runtime/doc/options.txt 2021-11-18 21:58:38.707549469 +0000 *************** *** 364,369 **** --- 371,387 ---- ":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* *************** *** 5607,5613 **** '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. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. --- 5634,5642 ---- '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. 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. *************** *** 6005,6012 **** 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. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. --- 6036,6044 ---- 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, 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. *** ../vim-8.2.3618/src/ops.c 2021-10-19 11:15:36.114656487 +0100 --- src/ops.c 2021-11-18 22:02:17.182423735 +0000 *************** *** 3305,3310 **** --- 3305,3333 ---- // 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,3322 **** --- 3340,3346 ---- 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,3351 **** // Reset finish_op so that mode() returns the right value. finish_op = FALSE; ! (void)call_func_noret(p_opfunc, 1, argv); virtual_op = save_virtual_op; finish_op = save_finish_op; --- 3369,3376 ---- // Reset finish_op so that mode() returns the right value. finish_op = FALSE; ! if (call_callback(&opfunc_cb, 0, &rettv, 1, argv) != FAIL) ! clear_tv(&rettv); virtual_op = save_virtual_op; finish_op = save_finish_op; *** ../vim-8.2.3618/src/option.c 2021-10-17 14:13:04.832665843 +0100 --- src/option.c 2021-11-18 22:04:08.233515803 +0000 *************** *** 809,814 **** --- 809,815 ---- // buffer-local option: free global value clear_string_option((char_u **)options[i].var); } + free_operatorfunc_option(); } #endif *************** *** 7184,7186 **** --- 7185,7233 ---- #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 + } *** ../vim-8.2.3618/src/optionstr.c 2021-11-12 19:52:44.508731448 +0000 --- src/optionstr.c 2021-11-18 22:02:29.070320056 +0000 *************** *** 2320,2329 **** # endif #endif #ifdef FEAT_QUICKFIX else if (varp == &p_qftf) { ! if (qf_process_qftf_option() == FALSE) errmsg = e_invarg; } #endif --- 2320,2337 ---- # 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() == FAIL) errmsg = e_invarg; } #endif *** ../vim-8.2.3618/src/proto/ops.pro 2020-05-01 13:26:17.132949262 +0100 --- src/proto/ops.pro 2021-11-18 21:54:34.315812615 +0000 *************** *** 17,21 **** --- 17,23 ---- 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 : */ *** ../vim-8.2.3618/src/proto/option.pro 2021-07-26 21:19:05.380122574 +0100 --- src/proto/option.pro 2021-11-18 22:04:50.789200768 +0000 *************** *** 10,16 **** 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); 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); --- 10,16 ---- void set_helplang_default(char_u *lang); void set_title_defaults(void); void ex_set(exarg_T *eap); ! 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,81 **** --- 78,82 ---- 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 : */ *** ../vim-8.2.3618/src/quickfix.c 2021-10-20 21:58:37.235792695 +0100 --- src/quickfix.c 2021-11-18 22:02:02.654552771 +0000 *************** *** 4437,4481 **** /* * Process the 'quickfixtextfunc' option value. */ 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; } /* --- 4437,4448 ---- /* * Process the 'quickfixtextfunc' option value. + * Returns OK or FAIL. */ int qf_process_qftf_option(void) { ! return option_set_callback_func(p_qftf, &qftf_cb); } /* *** ../vim-8.2.3618/src/testdir/test_normal.vim 2021-11-04 13:28:26.082236210 +0000 --- src/testdir/test_normal.vim 2021-11-18 21:54:34.319812609 +0000 *************** *** 386,391 **** --- 386,455 ---- 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= *** ../vim-8.2.3618/src/version.c 2021-11-18 20:47:25.814140290 +0000 --- src/version.c 2021-11-18 21:56:22.087699612 +0000 *************** *** 759,760 **** --- 759,762 ---- { /* Add new patch number below this line */ + /**/ + 3619, /**/ -- ARTHUR: Listen, old crone! Unless you tell us where we can buy a shrubbery, my friend and I will ... we will say "Ni!" CRONE: Do your worst! "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///