# HG changeset patch # User Bram Moolenaar # Date 1638627303 -3600 # Node ID 8f17f8f327f334132eb8b4ae8eda2c9a4d1c490c # Parent bff05ef0dbc0d802dc961a6655001c78d24ee02d patch 8.2.3735: cannot use a lambda for 'imactivatefunc' Commit: https://github.com/vim/vim/commit/7645da568c5e3b4ee339a2e99c3b3af790619787 Author: Yegappan Lakshmanan Date: Sat Dec 4 14:02:30 2021 +0000 patch 8.2.3735: cannot use a lambda for 'imactivatefunc' Problem: Cannot use a lambda for 'imactivatefunc'. Solution: Add lambda support for 'imactivatefunc' and 'imstatusfunc'. (Yegappan Lakshmanan, closes #9275) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4242,7 +4242,9 @@ A jump table for the options with a shor 'imactivatefunc' 'imaf' string (default "") global This option specifies a function that will be called to - activate or deactivate the Input Method. + activate or deactivate the Input Method. The value can be the name of + a function, a |lambda| or a |Funcref|. See |option-value-function| for + more information. It is not used in the MS-Windows GUI version. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. @@ -4352,6 +4354,8 @@ A jump table for the options with a shor global This option specifies a function that is called to obtain the status of Input Method. It must return a positive number when IME is active. + The value can be the name of a function, a |lambda| or a |Funcref|. + See |option-value-function| for more information. It is not used in the MS-Windows GUI version. Example: > diff --git a/src/alloc.c b/src/alloc.c --- a/src/alloc.c +++ b/src/alloc.c @@ -440,6 +440,7 @@ free_all_mem(void) free_prev_shellcmd(); free_regexp_stuff(); free_tag_stuff(); + free_xim_stuff(); free_cd_dir(); # ifdef FEAT_SIGNS free_signs(); diff --git a/src/gui_xim.c b/src/gui_xim.c --- a/src/gui_xim.c +++ b/src/gui_xim.c @@ -67,8 +67,24 @@ xim_log(char *s, ...) # define USE_IMSTATUSFUNC (*p_imsf != NUL) #endif -#if defined(FEAT_EVAL) && \ - (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL)) +#if (defined(FEAT_EVAL) && \ + (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL))) || \ + defined(PROTO) +static callback_T imaf_cb; // 'imactivatefunc' callback function +static callback_T imsf_cb; // 'imstatusfunc' callback function + + int +set_imactivatefunc_option(void) +{ + return option_set_callback_func(p_imaf, &imaf_cb); +} + + int +set_imstatusfunc_option(void) +{ + return option_set_callback_func(p_imsf, &imsf_cb); +} + static void call_imactivatefunc(int active) { @@ -77,7 +93,7 @@ call_imactivatefunc(int active) argv[0].v_type = VAR_NUMBER; argv[0].vval.v_number = active ? 1 : 0; argv[1].v_type = VAR_UNKNOWN; - (void)call_func_retnr(p_imaf, 1, argv); + (void)call_callback_retnr(&imaf_cb, 1, argv); } static int @@ -91,12 +107,25 @@ call_imstatusfunc(void) // FIXME: :py print 'xxx' is shown duplicate result. // Use silent to avoid it. ++msg_silent; - is_active = call_func_retnr(p_imsf, 0, NULL); + is_active = call_callback_retnr(&imsf_cb, 0, NULL); --msg_silent; return (is_active > 0); } #endif +#if defined(EXITFREE) || defined(PROTO) + void +free_xim_stuff(void) +{ +#if defined(FEAT_EVAL) && \ + (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL)) + free_callback(&imaf_cb); + free_callback(&imsf_cb); +# endif +} +#endif + + #if defined(FEAT_XIM) || defined(PROTO) # if defined(FEAT_GUI_GTK) || defined(PROTO) diff --git a/src/optionstr.c b/src/optionstr.c --- a/src/optionstr.c +++ b/src/optionstr.c @@ -2330,6 +2330,23 @@ ambw_end: } #endif +#if defined(FEAT_EVAL) && \ + (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL)) + // 'imactivatefunc' + else if (gvarp == &p_imaf) + { + if (set_imactivatefunc_option() == FAIL) + errmsg = e_invarg; + } + + // 'imstatusfunc' + else if (gvarp == &p_imsf) + { + if (set_imstatusfunc_option() == FAIL) + errmsg = e_invarg; + } +#endif + // 'operatorfunc' else if (varp == &p_opfunc) { diff --git a/src/proto/gui_xim.pro b/src/proto/gui_xim.pro --- a/src/proto/gui_xim.pro +++ b/src/proto/gui_xim.pro @@ -1,4 +1,7 @@ /* gui_xim.c */ +int set_imactivatefunc_option(void); +int set_imstatusfunc_option(void); +void free_xim_stuff(void); void im_set_active(int active); void xim_set_focus(int focus); void im_set_position(int row, int col); diff --git a/src/testdir/test_iminsert.vim b/src/testdir/test_iminsert.vim --- a/src/testdir/test_iminsert.vim +++ b/src/testdir/test_iminsert.vim @@ -2,6 +2,7 @@ source view_util.vim source check.vim +source vim9.vim let s:imactivatefunc_called = 0 let s:imstatusfunc_called = 0 @@ -107,4 +108,143 @@ func Test_iminsert_toggle() close! endfunc +" Test for different ways of setting the 'imactivatefunc' and 'imstatusfunc' +" options +func Test_imactivatefunc_imstatusfunc_callback() + CheckNotMSWindows + func IMactivatefunc1(active) + let g:IMactivatefunc_called += 1 + endfunc + func IMstatusfunc1() + let g:IMstatusfunc_called += 1 + return 1 + endfunc + let g:IMactivatefunc_called = 0 + let g:IMstatusfunc_called = 0 + set iminsert=2 + + " Test for using a function() + set imactivatefunc=function('IMactivatefunc1') + set imstatusfunc=function('IMstatusfunc1') + normal! i + + " Using a funcref variable to set 'completefunc' + let Fn1 = function('IMactivatefunc1') + let &imactivatefunc = string(Fn1) + let Fn2 = function('IMstatusfunc1') + let &imstatusfunc = string(Fn2) + normal! i + call assert_fails('let &imactivatefunc = Fn1', 'E729:') + call assert_fails('let &imstatusfunc = Fn2', 'E729:') + + " Test for using a funcref() + set imactivatefunc=funcref('IMactivatefunc1') + set imstatusfunc=funcref('IMstatusfunc1') + normal! i + + " Using a funcref variable to set 'imactivatefunc' + let Fn1 = funcref('IMactivatefunc1') + let &imactivatefunc = string(Fn1) + let Fn2 = funcref('IMstatusfunc1') + let &imstatusfunc = string(Fn2) + normal! i + call assert_fails('let &imactivatefunc = Fn1', 'E729:') + call assert_fails('let &imstatusfunc = Fn2', 'E729:') + + " Test for using a lambda function + set imactivatefunc={a\ ->\ IMactivatefunc1(a)} + set imstatusfunc={\ ->\ IMstatusfunc1()} + normal! i + + " Set 'imactivatefunc' and 'imstatusfunc' to a lambda expression + let &imactivatefunc = '{a -> IMactivatefunc1(a)}' + let &imstatusfunc = '{ -> IMstatusfunc1()}' + normal! i + + " Set 'imactivatefunc' 'imstatusfunc' to a variable with a lambda expression + let Lambda1 = {a -> IMactivatefunc1(a)} + let Lambda2 = { -> IMstatusfunc1()} + let &imactivatefunc = string(Lambda1) + let &imstatusfunc = string(Lambda2) + normal! i + call assert_fails('let &imactivatefunc = Lambda1', 'E729:') + call assert_fails('let &imstatusfunc = Lambda2', 'E729:') + + " Test for clearing the 'completefunc' option + set imactivatefunc='' imstatusfunc='' + set imactivatefunc& imstatusfunc& + + call assert_fails("set imactivatefunc=function('abc')", "E700:") + call assert_fails("set imstatusfunc=function('abc')", "E700:") + call assert_fails("set imactivatefunc=funcref('abc')", "E700:") + call assert_fails("set imstatusfunc=funcref('abc')", "E700:") + + call assert_equal(7, g:IMactivatefunc_called) + call assert_equal(14, g:IMstatusfunc_called) + + " Vim9 tests + let lines =<< trim END + vim9script + + # Test for using function() + def IMactivatefunc1(active: number): any + g:IMactivatefunc_called += 1 + return 1 + enddef + def IMstatusfunc1(): number + g:IMstatusfunc_called += 1 + return 1 + enddef + g:IMactivatefunc_called = 0 + g:IMstatusfunc_called = 0 + set iminsert=2 + set imactivatefunc=function('IMactivatefunc1') + set imstatusfunc=function('IMstatusfunc1') + normal! i + + # Test for using a lambda + &imactivatefunc = '(a) => IMactivatefunc1(a)' + &imstatusfunc = '() => IMstatusfunc1()' + normal! i + + # Test for using a variable with a lambda expression + var Fn1: func = (active) => { + g:IMactivatefunc_called += 1 + return 1 + } + var Fn2: func = () => { + g:IMstatusfunc_called += 1 + return 1 + } + &imactivatefunc = string(Fn1) + &imstatusfunc = string(Fn2) + normal! i + + assert_equal(3, g:IMactivatefunc_called) + assert_equal(6, g:IMstatusfunc_called) + + set iminsert=0 + set imactivatefunc= + set imstatusfunc= + END + call CheckScriptSuccess(lines) + + " Using Vim9 lambda expression in legacy context should fail + set imactivatefunc=(a)\ =>\ IMactivatefunc1(a) + set imstatusfunc=IMstatusfunc1 + call assert_fails('normal! i', 'E117:') + set imactivatefunc=IMactivatefunc1 + set imstatusfunc=()\ =>\ IMstatusfunc1(a) + call assert_fails('normal! i', 'E117:') + + " cleanup + delfunc IMactivatefunc1 + delfunc IMstatusfunc1 + set iminsert=0 + set imactivatefunc= + set imstatusfunc= + + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -923,7 +923,7 @@ func Test_completefunc_callback() call add(g:MycompleteFunc3_args, [a:findstart, a:base]) return a:findstart ? 0 : [] endfunc - set completefunc={a,\ b,\ ->\ MycompleteFunc3(a,\ b,)} + set completefunc={a,\ b\ ->\ MycompleteFunc3(a,\ b)} new | only call setline(1, 'five') let g:MycompleteFunc3_args = [] @@ -986,26 +986,29 @@ func Test_completefunc_callback() bw! # Test for using a lambda - def MycompleteFunc2(findstart: number, base: string): any - add(g:MycompleteFunc2_args, [findstart, base]) + def LambdaComplete1(findstart: number, base: string): any + add(g:LambdaComplete1_args, [findstart, base]) return findstart ? 0 : [] enddef - &completefunc = '(a, b) => MycompleteFunc2(a, b)' + &completefunc = '(a, b) => LambdaComplete1(a, b)' new | only setline(1, 'two') - g:MycompleteFunc2_args = [] + g:LambdaComplete1_args = [] feedkeys("A\\\", 'x') - assert_equal([[1, ''], [0, 'two']], g:MycompleteFunc2_args) + assert_equal([[1, ''], [0, 'two']], g:LambdaComplete1_args) bw! # Test for using a variable with a lambda expression - var Fn: func = (a, b) => MycompleteFunc2(a, b) + var Fn: func = (findstart, base) => { + add(g:LambdaComplete2_args, [findstart, base]) + return findstart ? 0 : [] + } &completefunc = string(Fn) new | only setline(1, 'three') - g:MycompleteFunc2_args = [] + g:LambdaComplete2_args = [] feedkeys("A\\\", 'x') - assert_equal([[1, ''], [0, 'three']], g:MycompleteFunc2_args) + assert_equal([[1, ''], [0, 'three']], g:LambdaComplete2_args) bw! END call CheckScriptSuccess(lines) @@ -1080,7 +1083,7 @@ func Test_omnifunc_callback() call add(g:MyomniFunc3_args, [a:findstart, a:base]) return a:findstart ? 0 : [] endfunc - set omnifunc={a,\ b,\ ->\ MyomniFunc3(a,\ b,)} + set omnifunc={a,\ b\ ->\ MyomniFunc3(a,\ b)} new | only call setline(1, 'five') let g:MyomniFunc3_args = [] @@ -1237,7 +1240,7 @@ func Test_thesaurusfunc_callback() call add(g:MytsrFunc3_args, [a:findstart, a:base]) return a:findstart ? 0 : [] endfunc - set thesaurusfunc={a,\ b,\ ->\ MytsrFunc3(a,\ b,)} + set thesaurusfunc={a,\ b\ ->\ MytsrFunc3(a,\ b)} new | only call setline(1, 'five') let g:MytsrFunc3_args = [] diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3735, +/**/ 3734, /**/ 3733,