]> granicus.if.org Git - vim/commitdiff
patch 8.2.3619: cannot use a lambda for 'operatorfunc' v8.2.3619
authorYegappan Lakshmanan <yegappan@yahoo.com>
Thu, 18 Nov 2021 22:08:57 +0000 (22:08 +0000)
committerBram Moolenaar <Bram@vim.org>
Thu, 18 Nov 2021 22:08:57 +0000 (22:08 +0000)
Problem:    Cannot use a lambda for 'operatorfunc'.
Solution:   Support using a lambda or partial. (Yegappan Lakshmanan,
            closes #8775)

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

index 4b5dd63305411630d30e2ec9a6297811ef13afca..4d23f4a5dc1dc34238cf8a3d3044f0665ac17177 100644 (file)
@@ -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*
 
index a0bbd3babbb0965349af7f594cb038b2a94a722e..b5bccebec37098ffed2749de9dde1d0d359975ad 100644 (file)
@@ -371,6 +371,17 @@ Note: In the future more global options can be made global-local.  Using
 ":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 short description can be found at |Q_op|.
 '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 short description can be found at |Q_op|.
        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.
index 5a48550146bdfefb886ff78dae5834083f08aa39..aa3b5dec4cd3d5f86d64d5ea64aa70cec30226e7 100644 (file)
--- 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;
index e8afa7c2543f450dfd774a7fe9da59eff5746914..4f080a3739d2dca2c6d5cf4f5ce11ee232cf0e54 100644 (file)
@@ -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
+}
index 2c4b2b826d166e0b40174015356bc863090eca63..100b0f465fe29b77835ee9ec56b196843636010a 100644 (file)
@@ -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
index cbe49cc548fc702895988a81725272a7a39b0e7a..c46af05a046fee18fe48bdc767540f733f50f0c9 100644 (file)
@@ -17,5 +17,7 @@ void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, int is_del);
 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 : */
index 13b9c1b7edf3a95712ca6500de49514ce40ed134..ea6baa7e4fef84c2edc5551a488de3427ce320a9 100644 (file)
@@ -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 : */
index 3fb921ff27f71971a408e394586da8d2df36aaa5..4405d4b64bb671ca3ec0ec43d082b9a72d7a6f05 100644 (file)
@@ -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);
 }
 
 /*
index c437315ae9f653f51f4cb31f09c6d138271acd27..3e3f6636387ff08ae2c989fa19b48df4b8697ce2 100644 (file)
@@ -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=
index 6125df90cdfb6ffb6a9a03f7d77cf52dc34e4a0e..c40cff31a1ad5a8ab591f87efb8c128a950b29ad 100644 (file)
@@ -757,6 +757,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3619,
 /**/
     3618,
 /**/