summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2021-11-18 22:08:57 +0000
committerBram Moolenaar <Bram@vim.org>2021-11-18 22:08:57 +0000
commit777175b0df8c5ec3cd30d19a2e887e661ac209c8 (patch)
tree58806429ad4998391464a42f4f4321779642bd2a
parent851c7a699ae00bdc14a4db874cf722b7b7393b53 (diff)
patch 8.2.3619: cannot use a lambda for 'operatorfunc'v8.2.3619
Problem: Cannot use a lambda for 'operatorfunc'. Solution: Support using a lambda or partial. (Yegappan Lakshmanan, closes #8775)
-rw-r--r--runtime/doc/map.txt14
-rw-r--r--runtime/doc/options.txt20
-rw-r--r--src/ops.c27
-rw-r--r--src/option.c47
-rw-r--r--src/optionstr.c10
-rw-r--r--src/proto/ops.pro2
-rw-r--r--src/proto/option.pro3
-rw-r--r--src/quickfix.c37
-rw-r--r--src/testdir/test_normal.vim64
-rw-r--r--src/version.c2
10 files changed, 185 insertions, 41 deletions
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 4b5dd63305..4d23f4a5dc 100644
--- 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*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index a0bbd3babb..b5bccebec3 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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.
diff --git a/src/ops.c b/src/ops.c
index 5a48550146..aa3b5dec4c 100644
--- 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;
diff --git a/src/option.c b/src/option.c
index e8afa7c254..4f080a3739 100644
--- 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
+}
diff --git a/src/optionstr.c b/src/optionstr.c
index 2c4b2b826d..100b0f465f 100644
--- 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
diff --git a/src/proto/ops.pro b/src/proto/ops.pro
index cbe49cc548..c46af05a04 100644
--- a/src/proto/ops.pro
+++ b/src/proto/ops.pro
@@ -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 : */
diff --git a/src/proto/option.pro b/src/proto/option.pro
index 13b9c1b7ed..ea6baa7e4f 100644
--- 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 : */
diff --git a/src/quickfix.c b/src/quickfix.c
index 3fb921ff27..4405d4b64b 100644
--- 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);
}
/*
diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim
index c437315ae9..3e3f663638 100644
--- 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=
diff --git a/src/version.c b/src/version.c
index 6125df90cd..c40cff31a1 100644
--- 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,