mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 11:15:14 -07:00
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:
parent
ebd5720901
commit
e8618df7f8
@ -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*
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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=
|
||||
|
Loading…
Reference in New Issue
Block a user