changeset 26408:8f17f8f327f3 v8.2.3735

patch 8.2.3735: cannot use a lambda for 'imactivatefunc' Commit: https://github.com/vim/vim/commit/7645da568c5e3b4ee339a2e99c3b3af790619787 Author: Yegappan Lakshmanan <yegappan@yahoo.com> 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)
author Bram Moolenaar <Bram@vim.org>
date Sat, 04 Dec 2021 15:15:03 +0100
parents bff05ef0dbc0
children 0227198bdc48
files runtime/doc/options.txt src/alloc.c src/gui_xim.c src/optionstr.c src/proto/gui_xim.pro src/testdir/test_iminsert.vim src/testdir/test_ins_complete.vim src/version.c
diffstat 8 files changed, 215 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- 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: >
--- 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();
--- 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)
--- 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)
     {
--- 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);
--- 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
--- 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\<C-X>\<C-U>\<Esc>", '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\<C-X>\<C-U>\<Esc>", '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 = []
--- 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,