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 <F4> <Cmd>let &opfunc='{t ->
+ \ getline(".")
+ \ ->split("\\zs")
+ \ ->insert("\"", col("'']"))
+ \ ->insert("\"", col("''[") - 1)
+ \ ->join("")
+ \ ->setline(".")}'<CR>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 <buffer> ,,
+ call cursor(1, 1)
+ let g:a=0
+ nmap <buffer><silent> ,, :set opfunc={type\ ->\ CountSpaces(type)}<CR>g@
+ vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR>
+ 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\<c-v>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 <buffer> ,,
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 -- Br...@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 ///