changeset 26175:6b4f017d7005 v8.2.3619

patch 8.2.3619: cannot use a lambda for 'operatorfunc' Commit: https://github.com/vim/vim/commit/777175b0df8c5ec3cd30d19a2e887e661ac209c8 Author: Yegappan Lakshmanan <yegappan@yahoo.com> 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)
author Bram Moolenaar <Bram@vim.org>
date Thu, 18 Nov 2021 23:15:04 +0100
parents 00e8f1ac9c6d
children 16e7f5c5ad54
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 src/version.c
diffstat 10 files changed, 185 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- 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 <F4> <Cmd>let &opfunc='{t ->
+				\ getline(".")
+				\ ->split("\\zs")
+				\ ->insert("\"", col("'']"))
+				\ ->insert("\"", col("''[") - 1)
+				\ ->join("")
+				\ ->setline(".")}'<CR>g@
+
 ==============================================================================
 2. Abbreviations			*abbreviations* *Abbreviations*
 
--- 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.
--- 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;
--- 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
+}
--- 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
--- 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 : */
--- 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 : */
--- 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);
 }
 
 /*
--- 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 <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=
--- 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,