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)
777175b0df

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.
This commit is contained in:
zeertzjq 2022-08-20 06:12:02 +08:00 committed by GitHub
parent ebd5720901
commit e8618df7f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 37 deletions

View File

@ -905,6 +905,17 @@ or `unnamedplus`.
The `mode()` function will return the state as it will be after applying the The `mode()` function will return the state as it will be after applying the
operator. 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* 2. Abbreviations *abbreviations* *Abbreviations*

View File

@ -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. ":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 Setting the filetype
:setf[iletype] [FALLBACK] {filetype} *:setf* *:setfiletype* :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) 'operatorfunc' 'opfunc' string (default: empty)
global global
This option specifies a function to be called by the |g@| operator. 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 This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons. 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 customize the information displayed in the quickfix or location window
for each entry in the corresponding quickfix or location list. See for each entry in the corresponding quickfix or location list. See
|quickfix-window-function| for an explanation of how to write the |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 function and an example. The value can be the name of a function, a
lambda. |lambda| or a |Funcref|. See |option-value-function| for more
information.
This option cannot be set from a |modeline| or in the |sandbox|, for This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons. security reasons.

View File

@ -6136,6 +6136,23 @@ static void op_colon(oparg_T *oap)
// do_cmdline() does the rest // 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'. /// Handle the "g@" operator: call 'operatorfunc'.
static void op_function(const oparg_T *oap) static void op_function(const oparg_T *oap)
FUNC_ATTR_NONNULL_ALL 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. // Reset finish_op so that mode() returns the right value.
finish_op = false; 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; virtual_op = save_virtual_op;
finish_op = save_finish_op; finish_op = save_finish_op;

View File

@ -66,6 +66,7 @@
#include "nvim/mouse.h" #include "nvim/mouse.h"
#include "nvim/move.h" #include "nvim/move.h"
#include "nvim/normal.h" #include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h" #include "nvim/option.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/os_unix.h" #include "nvim/os_unix.h"
@ -782,6 +783,7 @@ void free_all_options(void)
clear_string_option((char_u **)options[i].var); clear_string_option((char_u **)options[i].var);
} }
} }
free_operatorfunc_option();
} }
#endif #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) { } else if (varp == &p_opfunc) { // 'operatorfunc'
if (!qf_process_qftf_option()) { if (set_operatorfunc_option() == FAIL) {
errmsg = e_invarg;
}
} else if (varp == &p_qftf) { // 'quickfixtextfunc'
if (qf_process_qftf_option() == FAIL) {
errmsg = e_invarg; errmsg = e_invarg;
} }
} else { } else {
@ -6917,6 +6923,44 @@ static int fill_culopt_flags(char_u *val, win_T *wp)
return OK; 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. /// Check an option that can be a range of string values.
/// ///
/// @param list when true: accept a list of values /// @param list when true: accept a list of values

View File

@ -3829,38 +3829,11 @@ static buf_T *qf_find_buf(qf_info_T *qi)
return NULL; return NULL;
} }
// Process the 'quickfixtextfunc' option value. /// Process the 'quickfixtextfunc' option value.
bool qf_process_qftf_option(void) /// @return OK or FAIL
int qf_process_qftf_option(void)
{ {
if (p_qftf == NULL || *p_qftf == NUL) { return option_set_callback_func(p_qftf, &qftf_cb);
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;
} }
/// Update the w:quickfix_title variable in the quickfix/location list window in /// Update the w:quickfix_title variable in the quickfix/location list window in

View File

@ -352,6 +352,70 @@ func Test_normal09a_operatorfunc()
norm V10j,, norm V10j,,
call assert_equal(22, g:a) 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 " clean up
unmap <buffer> ,, unmap <buffer> ,,
set opfunc= set opfunc=