Skip to content

Commit 777175b

Browse files
yegappanbrammool
authored andcommitted
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)
1 parent 851c7a6 commit 777175b

File tree

10 files changed

+185
-41
lines changed

10 files changed

+185
-41
lines changed

runtime/doc/map.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,20 @@ or `unnamedplus`.
10091009
The `mode()` function will return the state as it will be after applying the
10101010
operator.
10111011

1012+
The `mode()` function will return the state as it will be after applying the
1013+
operator.
1014+
1015+
Here is an example for using a lambda function to create a normal-mode
1016+
operator to add quotes around text in the current line: >
1017+
1018+
nnoremap <F4> <Cmd>let &opfunc='{t ->
1019+
\ getline(".")
1020+
\ ->split("\\zs")
1021+
\ ->insert("\"", col("'']"))
1022+
\ ->insert("\"", col("''[") - 1)
1023+
\ ->join("")
1024+
\ ->setline(".")}'<CR>g@
1025+
10121026
==============================================================================
10131027
2. Abbreviations *abbreviations* *Abbreviations*
10141028

runtime/doc/options.txt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,17 @@ Note: In the future more global options can be made global-local. Using
371371
":setlocal" on a global option might work differently then.
372372

373373

374+
*option-value-function*
375+
Some options ('completefunc', 'imactivatefunc', 'imstatusfunc', 'omnifunc',
376+
'operatorfunc', 'quickfixtextfunc' and 'tagfunc') are set to a function name
377+
or a function reference or a lambda function. Examples:
378+
>
379+
set opfunc=MyOpFunc
380+
set opfunc=function("MyOpFunc")
381+
set opfunc=funcref("MyOpFunc")
382+
set opfunc={t\ ->\ MyOpFunc(t)}
383+
<
384+
374385
Setting the filetype
375386

376387
: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|.
56235634
'operatorfunc' 'opfunc' string (default: empty)
56245635
global
56255636
This option specifies a function to be called by the |g@| operator.
5626-
See |:map-operator| for more info and an example.
5637+
See |:map-operator| for more info and an example. The value can be
5638+
the name of a function, a |lambda| or a |Funcref|. See
5639+
|option-value-function| for more information.
56275640

56285641
This option cannot be set from a |modeline| or in the |sandbox|, for
56295642
security reasons.
@@ -6023,8 +6036,9 @@ A jump table for the options with a short description can be found at |Q_op|.
60236036
customize the information displayed in the quickfix or location window
60246037
for each entry in the corresponding quickfix or location list. See
60256038
|quickfix-window-function| for an explanation of how to write the
6026-
function and an example. The value can be the name of a function or a
6027-
lambda.
6039+
function and an example. The value can be the name of a function, a
6040+
|lambda| or a |Funcref|. See |option-value-function| for more
6041+
information.
60286042

60296043
This option cannot be set from a |modeline| or in the |sandbox|, for
60306044
security reasons.

src/ops.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3305,6 +3305,29 @@ op_colon(oparg_T *oap)
33053305
// do_cmdline() does the rest
33063306
}
33073307

3308+
// callback function for 'operatorfunc'
3309+
static callback_T opfunc_cb;
3310+
3311+
/*
3312+
* Process the 'operatorfunc' option value.
3313+
* Returns OK or FAIL.
3314+
*/
3315+
int
3316+
set_operatorfunc_option(void)
3317+
{
3318+
return option_set_callback_func(p_opfunc, &opfunc_cb);
3319+
}
3320+
3321+
#if defined(EXITFREE) || defined(PROTO)
3322+
void
3323+
free_operatorfunc_option(void)
3324+
{
3325+
# ifdef FEAT_EVAL
3326+
free_callback(&opfunc_cb);
3327+
# endif
3328+
}
3329+
#endif
3330+
33083331
/*
33093332
* Handle the "g@" operator: call 'operatorfunc'.
33103333
*/
@@ -3317,6 +3340,7 @@ op_function(oparg_T *oap UNUSED)
33173340
int save_finish_op = finish_op;
33183341
pos_T orig_start = curbuf->b_op_start;
33193342
pos_T orig_end = curbuf->b_op_end;
3343+
typval_T rettv;
33203344

33213345
if (*p_opfunc == NUL)
33223346
emsg(_("E774: 'operatorfunc' is empty"));
@@ -3345,7 +3369,8 @@ op_function(oparg_T *oap UNUSED)
33453369
// Reset finish_op so that mode() returns the right value.
33463370
finish_op = FALSE;
33473371

3348-
(void)call_func_noret(p_opfunc, 1, argv);
3372+
if (call_callback(&opfunc_cb, 0, &rettv, 1, argv) != FAIL)
3373+
clear_tv(&rettv);
33493374

33503375
virtual_op = save_virtual_op;
33513376
finish_op = save_finish_op;

src/option.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,7 @@ free_all_options(void)
809809
// buffer-local option: free global value
810810
clear_string_option((char_u **)options[i].var);
811811
}
812+
free_operatorfunc_option();
812813
}
813814
#endif
814815

@@ -7184,3 +7185,49 @@ magic_isset(void)
71847185
#endif
71857186
return p_magic;
71867187
}
7188+
7189+
/*
7190+
* Set the callback function value for an option that accepts a function name,
7191+
* lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.)
7192+
* Returns OK if the option is successfully set to a function, otherwise
7193+
* returns FAIL.
7194+
*/
7195+
int
7196+
option_set_callback_func(char_u *optval UNUSED, callback_T *optcb UNUSED)
7197+
{
7198+
#ifdef FEAT_EVAL
7199+
typval_T *tv;
7200+
callback_T cb;
7201+
7202+
if (optval == NULL || *optval == NUL)
7203+
{
7204+
free_callback(optcb);
7205+
return OK;
7206+
}
7207+
7208+
if (*optval == '{'
7209+
|| (STRNCMP(optval, "function(", 9) == 0)
7210+
|| (STRNCMP(optval, "funcref(", 8) == 0))
7211+
// Lambda expression or a funcref
7212+
tv = eval_expr(optval, NULL);
7213+
else
7214+
// treat everything else as a function name string
7215+
tv = alloc_string_tv(vim_strsave(optval));
7216+
if (tv == NULL)
7217+
return FAIL;
7218+
7219+
cb = get_callback(tv);
7220+
if (cb.cb_name == NULL)
7221+
{
7222+
free_tv(tv);
7223+
return FAIL;
7224+
}
7225+
7226+
free_callback(optcb);
7227+
set_callback(optcb, &cb);
7228+
free_tv(tv);
7229+
return OK;
7230+
#else
7231+
return FAIL;
7232+
#endif
7233+
}

src/optionstr.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2320,10 +2320,18 @@ did_set_string_option(
23202320
# endif
23212321
#endif
23222322

2323+
// 'operatorfunc'
2324+
else if (varp == &p_opfunc)
2325+
{
2326+
if (set_operatorfunc_option() == FAIL)
2327+
errmsg = e_invarg;
2328+
}
2329+
23232330
#ifdef FEAT_QUICKFIX
2331+
// 'quickfixtextfunc'
23242332
else if (varp == &p_qftf)
23252333
{
2326-
if (qf_process_qftf_option() == FALSE)
2334+
if (qf_process_qftf_option() == FAIL)
23272335
errmsg = e_invarg;
23282336
}
23292337
#endif

src/proto/ops.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, int is_del);
1717
void op_addsub(oparg_T *oap, linenr_T Prenum1, int g_cmd);
1818
void clear_oparg(oparg_T *oap);
1919
void cursor_pos_info(dict_T *dict);
20+
int set_operatorfunc_option(void);
21+
void free_operatorfunc_option(void);
2022
void do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank);
2123
/* vim: set ft=c : */

src/proto/option.pro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ void set_init_3(void);
1010
void set_helplang_default(char_u *lang);
1111
void set_title_defaults(void);
1212
void ex_set(exarg_T *eap);
13-
int do_set(char_u *arg, int opt_flags);
13+
int do_set(char_u *arg_start, int opt_flags);
1414
void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked);
1515
int string_to_key(char_u *arg, int multi_byte);
1616
void did_set_title(void);
@@ -78,4 +78,5 @@ char_u *get_showbreak_value(win_T *win);
7878
dict_T *get_winbuf_options(int bufopt);
7979
int fill_culopt_flags(char_u *val, win_T *wp);
8080
int magic_isset(void);
81+
int option_set_callback_func(char_u *optval, callback_T *optcb);
8182
/* vim: set ft=c : */

src/quickfix.c

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4437,45 +4437,12 @@ qf_find_buf(qf_info_T *qi)
44374437

44384438
/*
44394439
* Process the 'quickfixtextfunc' option value.
4440+
* Returns OK or FAIL.
44404441
*/
44414442
int
44424443
qf_process_qftf_option(void)
44434444
{
4444-
typval_T *tv;
4445-
callback_T cb;
4446-
4447-
if (p_qftf == NULL || *p_qftf == NUL)
4448-
{
4449-
free_callback(&qftf_cb);
4450-
return TRUE;
4451-
}
4452-
4453-
if (*p_qftf == '{')
4454-
{
4455-
// Lambda expression
4456-
tv = eval_expr(p_qftf, NULL);
4457-
if (tv == NULL)
4458-
return FALSE;
4459-
}
4460-
else
4461-
{
4462-
// treat everything else as a function name string
4463-
tv = alloc_string_tv(vim_strsave(p_qftf));
4464-
if (tv == NULL)
4465-
return FALSE;
4466-
}
4467-
4468-
cb = get_callback(tv);
4469-
if (cb.cb_name == NULL)
4470-
{
4471-
free_tv(tv);
4472-
return FALSE;
4473-
}
4474-
4475-
free_callback(&qftf_cb);
4476-
set_callback(&qftf_cb, &cb);
4477-
free_tv(tv);
4478-
return TRUE;
4445+
return option_set_callback_func(p_qftf, &qftf_cb);
44794446
}
44804447

44814448
/*

src/testdir/test_normal.vim

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,70 @@ func Test_normal09_operatorfunc()
386386
norm V10j,,
387387
call assert_equal(22, g:a)
388388

389+
" Use a lambda function for 'opfunc'
390+
unmap <buffer> ,,
391+
call cursor(1, 1)
392+
let g:a=0
393+
nmap <buffer><silent> ,, :set opfunc={type\ ->\ CountSpaces(type)}<CR>g@
394+
vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR>
395+
50
396+
norm V2j,,
397+
call assert_equal(6, g:a)
398+
norm V,,
399+
call assert_equal(2, g:a)
400+
norm ,,l
401+
call assert_equal(0, g:a)
402+
50
403+
exe "norm 0\<c-v>10j2l,,"
404+
call assert_equal(11, g:a)
405+
50
406+
norm V10j,,
407+
call assert_equal(22, g:a)
408+
409+
" use a partial function for 'opfunc'
410+
let g:OpVal = 0
411+
func! Test_opfunc1(x, y, type)
412+
let g:OpVal = a:x + a:y
413+
endfunc
414+
set opfunc=function('Test_opfunc1',\ [5,\ 7])
415+
normal! g@l
416+
call assert_equal(12, g:OpVal)
417+
" delete the function and try to use g@
418+
delfunc Test_opfunc1
419+
call test_garbagecollect_now()
420+
call assert_fails('normal! g@l', 'E117:')
421+
set opfunc=
422+
423+
" use a funcref for 'opfunc'
424+
let g:OpVal = 0
425+
func! Test_opfunc2(x, y, type)
426+
let g:OpVal = a:x + a:y
427+
endfunc
428+
set opfunc=funcref('Test_opfunc2',\ [4,\ 3])
429+
normal! g@l
430+
call assert_equal(7, g:OpVal)
431+
" delete the function and try to use g@
432+
delfunc Test_opfunc2
433+
call test_garbagecollect_now()
434+
call assert_fails('normal! g@l', 'E933:')
435+
set opfunc=
436+
437+
" Try to use a function with two arguments for 'operatorfunc'
438+
let g:OpVal = 0
439+
func! Test_opfunc3(x, y)
440+
let g:OpVal = 4
441+
endfunc
442+
set opfunc=Test_opfunc3
443+
call assert_fails('normal! g@l', 'E119:')
444+
call assert_equal(0, g:OpVal)
445+
set opfunc=
446+
delfunc Test_opfunc3
447+
unlet g:OpVal
448+
449+
" Try to use a lambda function with two arguments for 'operatorfunc'
450+
set opfunc={x,\ y\ ->\ 'done'}
451+
call assert_fails('normal! g@l', 'E119:')
452+
389453
" clean up
390454
unmap <buffer> ,,
391455
set opfunc=

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,8 @@ static char *(features[]) =
757757

758758
static int included_patches[] =
759759
{ /* Add new patch number below this line */
760+
/**/
761+
3619,
760762
/**/
761763
3618,
762764
/**/

0 commit comments

Comments
 (0)