0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

patch 8.2.3619: cannot use a lambda for 'operatorfunc'

Problem:    Cannot use a lambda for 'operatorfunc'.
Solution:   Support using a lambda or partial. (Yegappan Lakshmanan,
            closes #8775)
This commit is contained in:
Yegappan Lakshmanan
2021-11-18 22:08:57 +00:00
committed by Bram Moolenaar
parent 851c7a699a
commit 777175b0df
10 changed files with 185 additions and 41 deletions

View File

@@ -1009,6 +1009,20 @@ 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.
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* 2. Abbreviations *abbreviations* *Abbreviations*

View 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. ":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*
@@ -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) '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.
@@ -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 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

@@ -3305,6 +3305,29 @@ op_colon(oparg_T *oap)
// do_cmdline() does the rest // 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'. * Handle the "g@" operator: call 'operatorfunc'.
*/ */
@@ -3317,6 +3340,7 @@ op_function(oparg_T *oap UNUSED)
int save_finish_op = finish_op; int save_finish_op = finish_op;
pos_T orig_start = curbuf->b_op_start; pos_T orig_start = curbuf->b_op_start;
pos_T orig_end = curbuf->b_op_end; pos_T orig_end = curbuf->b_op_end;
typval_T rettv;
if (*p_opfunc == NUL) if (*p_opfunc == NUL)
emsg(_("E774: 'operatorfunc' is empty")); 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. // Reset finish_op so that mode() returns the right value.
finish_op = FALSE; 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; virtual_op = save_virtual_op;
finish_op = save_finish_op; finish_op = save_finish_op;

View File

@@ -809,6 +809,7 @@ free_all_options(void)
// buffer-local option: free global value // buffer-local option: free global value
clear_string_option((char_u **)options[i].var); clear_string_option((char_u **)options[i].var);
} }
free_operatorfunc_option();
} }
#endif #endif
@@ -7184,3 +7185,49 @@ magic_isset(void)
#endif #endif
return p_magic; 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
}

View File

@@ -2320,10 +2320,18 @@ ambw_end:
# endif # endif
#endif #endif
// 'operatorfunc'
else if (varp == &p_opfunc)
{
if (set_operatorfunc_option() == FAIL)
errmsg = e_invarg;
}
#ifdef FEAT_QUICKFIX #ifdef FEAT_QUICKFIX
// 'quickfixtextfunc'
else if (varp == &p_qftf) else if (varp == &p_qftf)
{ {
if (qf_process_qftf_option() == FALSE) if (qf_process_qftf_option() == FAIL)
errmsg = e_invarg; errmsg = e_invarg;
} }
#endif #endif

View 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 op_addsub(oparg_T *oap, linenr_T Prenum1, int g_cmd);
void clear_oparg(oparg_T *oap); void clear_oparg(oparg_T *oap);
void cursor_pos_info(dict_T *dict); 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); void do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@@ -10,7 +10,7 @@ void set_init_3(void);
void set_helplang_default(char_u *lang); void set_helplang_default(char_u *lang);
void set_title_defaults(void); void set_title_defaults(void);
void ex_set(exarg_T *eap); 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); 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); int string_to_key(char_u *arg, int multi_byte);
void did_set_title(void); void did_set_title(void);
@@ -78,4 +78,5 @@ char_u *get_showbreak_value(win_T *win);
dict_T *get_winbuf_options(int bufopt); dict_T *get_winbuf_options(int bufopt);
int fill_culopt_flags(char_u *val, win_T *wp); int fill_culopt_flags(char_u *val, win_T *wp);
int magic_isset(void); int magic_isset(void);
int option_set_callback_func(char_u *optval, callback_T *optcb);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@@ -4437,45 +4437,12 @@ qf_find_buf(qf_info_T *qi)
/* /*
* Process the 'quickfixtextfunc' option value. * Process the 'quickfixtextfunc' option value.
* Returns OK or FAIL.
*/ */
int int
qf_process_qftf_option(void) qf_process_qftf_option(void)
{ {
typval_T *tv; return option_set_callback_func(p_qftf, &qftf_cb);
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;
} }
/* /*

View File

@@ -386,6 +386,70 @@ func Test_normal09_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=

View File

@@ -757,6 +757,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
3619,
/**/ /**/
3618, 3618,
/**/ /**/