aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2022-08-20 06:12:02 +0800
committerGitHub <noreply@github.com>2022-08-20 06:12:02 +0800
commite8618df7f826d2ca4d524f12fc712d7c52ea158c (patch)
tree4b1e84ade34a61efbaae47a356eee62bd02145a9
parentebd57209018ed310836a8196ed4710e64a6d8ee5 (diff)
downloadrneovim-e8618df7f826d2ca4d524f12fc712d7c52ea158c.tar.gz
rneovim-e8618df7f826d2ca4d524f12fc712d7c52ea158c.tar.bz2
rneovim-e8618df7f826d2ca4d524f12fc712d7c52ea158c.zip
vim-patch:8.2.3619: cannot use a lambda for 'operatorfunc' (#19846)
Problem: Cannot use a lambda for 'operatorfunc'. Solution: Support using a lambda or partial. (Yegappan Lakshmanan, closes vim/vim#8775) https://github.com/vim/vim/commit/777175b0df8c5ec3cd30d19a2e887e661ac209c8 Omit duplicate docs. It's removed in patch 8.2.3623. Nvim doesn't seem to need callback_set() as it was omitted when patch 8.1.1437 was first ported.
-rw-r--r--runtime/doc/map.txt11
-rw-r--r--runtime/doc/options.txt20
-rw-r--r--src/nvim/ops.c22
-rw-r--r--src/nvim/option.c48
-rw-r--r--src/nvim/quickfix.c35
-rw-r--r--src/nvim/testdir/test_normal.vim64
6 files changed, 163 insertions, 37 deletions
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 2b2bfec6c7..ca1ddaabd4 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -905,6 +905,17 @@ 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*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index a1f2eac5ed..27aa06e18b 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -312,6 +312,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*
@@ -4402,7 +4413,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.
@@ -4696,8 +4709,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/nvim/ops.c b/src/nvim/ops.c
index c3edc5b315..b4fc7534bc 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -6136,6 +6136,23 @@ static void op_colon(oparg_T *oap)
// do_cmdline() does the rest
}
+/// callback function for 'operatorfunc'
+static Callback opfunc_cb;
+
+/// Process the 'operatorfunc' option value.
+/// @return OK or FAIL
+int set_operatorfunc_option(void)
+{
+ return option_set_callback_func(p_opfunc, &opfunc_cb);
+}
+
+#if defined(EXITFREE)
+void free_operatorfunc_option(void)
+{
+ callback_free(&opfunc_cb);
+}
+#endif
+
/// Handle the "g@" operator: call 'operatorfunc'.
static void op_function(const oparg_T *oap)
FUNC_ATTR_NONNULL_ALL
@@ -6173,7 +6190,10 @@ static void op_function(const oparg_T *oap)
// Reset finish_op so that mode() returns the right value.
finish_op = false;
- (void)call_func_retnr((char *)p_opfunc, 1, argv);
+ typval_T rettv;
+ if (callback_call(&opfunc_cb, 1, argv, &rettv) != FAIL) {
+ tv_clear(&rettv);
+ }
virtual_op = save_virtual_op;
finish_op = save_finish_op;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 09793cbdcf..e85a0d9ee4 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -66,6 +66,7 @@
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/normal.h"
+#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os/os.h"
#include "nvim/os_unix.h"
@@ -782,6 +783,7 @@ void free_all_options(void)
clear_string_option((char_u **)options[i].var);
}
}
+ free_operatorfunc_option();
}
#endif
@@ -3264,8 +3266,12 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c
}
}
}
- } else if (varp == &p_qftf) {
- if (!qf_process_qftf_option()) {
+ } else if (varp == &p_opfunc) { // 'operatorfunc'
+ if (set_operatorfunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
+ } else if (varp == &p_qftf) { // 'quickfixtextfunc'
+ if (qf_process_qftf_option() == FAIL) {
errmsg = e_invarg;
}
} else {
@@ -6917,6 +6923,44 @@ static int fill_culopt_flags(char_u *val, win_T *wp)
return OK;
}
+/// Set the callback function value for an option that accepts a function name,
+/// lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.)
+/// @return OK if the option is successfully set to a function, otherwise FAIL
+int option_set_callback_func(char_u *optval, Callback *optcb)
+{
+ if (optval == NULL || *optval == NUL) {
+ callback_free(optcb);
+ return OK;
+ }
+
+ typval_T *tv;
+ if (*optval == '{'
+ || (STRNCMP(optval, "function(", 9) == 0)
+ || (STRNCMP(optval, "funcref(", 8) == 0)) {
+ // Lambda expression or a funcref
+ tv = eval_expr((char *)optval);
+ if (tv == NULL) {
+ return FAIL;
+ }
+ } else {
+ // treat everything else as a function name string
+ tv = xcalloc(1, sizeof(*tv));
+ tv->v_type = VAR_STRING;
+ tv->vval.v_string = (char *)vim_strsave(optval);
+ }
+
+ Callback cb;
+ if (!callback_from_typval(&cb, tv)) {
+ tv_free(tv);
+ return FAIL;
+ }
+
+ callback_free(optcb);
+ *optcb = cb;
+ tv_free(tv);
+ return OK;
+}
+
/// Check an option that can be a range of string values.
///
/// @param list when true: accept a list of values
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 1c416a872b..d04c2bc470 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -3829,38 +3829,11 @@ static buf_T *qf_find_buf(qf_info_T *qi)
return NULL;
}
-// Process the 'quickfixtextfunc' option value.
-bool qf_process_qftf_option(void)
+/// Process the 'quickfixtextfunc' option value.
+/// @return OK or FAIL
+int qf_process_qftf_option(void)
{
- if (p_qftf == NULL || *p_qftf == NUL) {
- callback_free(&qftf_cb);
- return true;
- }
-
- typval_T *tv;
- if (*p_qftf == '{') {
- // Lambda expression
- tv = eval_expr((char *)p_qftf);
- if (tv == NULL) {
- return false;
- }
- } else {
- // treat everything else as a function name string
- tv = xcalloc(1, sizeof(*tv));
- tv->v_type = VAR_STRING;
- tv->vval.v_string = (char *)vim_strsave(p_qftf);
- }
-
- Callback cb;
- if (!callback_from_typval(&cb, tv)) {
- tv_free(tv);
- return false;
- }
-
- callback_free(&qftf_cb);
- qftf_cb = cb;
- tv_free(tv);
- return true;
+ return option_set_callback_func(p_qftf, &qftf_cb);
}
/// Update the w:quickfix_title variable in the quickfix/location list window in
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 347404a579..2092b508ea 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -352,6 +352,70 @@ func Test_normal09a_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=