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/src/ops.c b/src/ops.c
index 5a48550..aa3b5de 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -3305,6 +3305,29 @@
// 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 @@
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 @@
// 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
index e8afa7c..4f080a3 100644
--- a/src/option.c
+++ b/src/option.c
@@ -809,6 +809,7 @@
// buffer-local option: free global value
clear_string_option((char_u **)options[i].var);
}
+ free_operatorfunc_option();
}
#endif
@@ -7184,3 +7185,49 @@
#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
index 2c4b2b8..100b0f4 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -2320,10 +2320,18 @@
# 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
index cbe49cc..c46af05 100644
--- a/src/proto/ops.pro
+++ b/src/proto/ops.pro
@@ -17,5 +17,7 @@
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
index 13b9c1b..ea6baa7 100644
--- a/src/proto/option.pro
+++ b/src/proto/option.pro
@@ -10,7 +10,7 @@
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 @@
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
index 3fb921f..4405d4b 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -4437,45 +4437,12 @@
/*
* 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
index c437315..3e3f663 100644
--- a/src/testdir/test_normal.vim
+++ b/src/testdir/test_normal.vim
@@ -386,6 +386,70 @@
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=
diff --git a/src/version.c b/src/version.c
index 6125df9..c40cff3 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 3619,
+/**/
3618,
/**/
3617,