changeset 27140:a9eeb18e749c v8.2.4099

patch 8.2.4099: Vim9: cannot use Vim9 syntax in mapping Commit: https://github.com/vim/vim/commit/e32c3c462ce9b3163a4a4bffd985897910885d29 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Jan 15 18:26:04 2022 +0000 patch 8.2.4099: Vim9: cannot use Vim9 syntax in mapping Problem: Vim9: cannot use Vim9 syntax in mapping. Solution: Add <ScriptCmd> to use the script context for a command.
author Bram Moolenaar <Bram@vim.org>
date Sat, 15 Jan 2022 19:30:04 +0100
parents fd4193f6d59e
children 5d0ddfc61e67
files runtime/doc/map.txt src/edit.c src/ex_getln.c src/getchar.c src/insexpand.c src/keymap.h src/misc2.c src/normal.c src/ops.c src/proto/getchar.pro src/terminal.c src/testdir/test_vim9_import.vim src/version.c
diffstat 13 files changed, 113 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -284,6 +284,10 @@ This can be solved by inserting <Ignore>
 expression-mapped: >
 	nmap ! f!<Ignore>x
 
+When defining a mapping in a |Vim9| script, the expression will be evaluated
+in the context of that script.  This means that script-local items can be
+accessed in the expression.
+
 Be very careful about side effects!  The expression is evaluated while
 obtaining characters, you may very well make the command dysfunctional.
 For this reason the following is blocked:
@@ -342,9 +346,24 @@ Example of using <Cmd> halfway Insert mo
 Unlike <expr> mappings, there are no special restrictions on the <Cmd>
 command: it is executed as if an (unrestricted) |autocommand| was invoked.
 
+						*<ScriptCmd>*
+<ScriptCmd> is like <Cmd> but sets the context to the script the mapping was
+defined in, for the duration of the command execution.  This is especially
+useful for |Vim9| script.  It also works to access an import, which is useful
+in a plugin using an autoload script: >
+	vim9script
+	import autoload 'implementation.vim' as impl
+	nnoremap <silent> <F4> <ScriptCmd>impl.DoTheWork()<CR>
+
+No matter where <F4> is typed, the "impl" import will be found in the script
+context of where the mapping was defined.  And since it's an autoload import,
+the "implementation.vim" script will only be loaded once <F4> is typed, not
+when the mapping is defined.
+
 Note:
-- Because <Cmd> avoids mode-changes it does not trigger |CmdlineEnter| and
-  |CmdlineLeave| events, because no user interaction is expected.
+- Because <Cmd> and <ScriptCmd> avoid mode-changes it does not trigger
+  |CmdlineEnter| and |CmdlineLeave| events, because no user interaction is
+  expected.
 - For the same reason, |keycodes| like <C-R><C-W> are interpreted as plain,
   unmapped keys.
 - The command is not echo'ed, no need for <silent>.
@@ -356,12 +375,13 @@ Note:
   Visual mode.  Use |:smap| to handle Select mode differently.
 
 							*E1255* *E1136*
-<Cmd> commands must terminate, that is, they must be followed by <CR> in the
-{rhs} of the mapping definition.  |Command-line| mode is never entered.
+<Cmd> and <ScriptCmd> commands must terminate, that is, they must be followed
+by <CR> in the {rhs} of the mapping definition.  |Command-line| mode is never
+entered.
 
 							*E1137*
-<Cmd> commands can have only normal characters and cannot contain special
-characters like function keys.
+<Cmd> and <ScriptCmd> commands can have only normal characters and cannot
+contain special characters like function keys.
 
 
 1.3 MAPPING AND MODES					*:map-modes*
--- a/src/edit.c
+++ b/src/edit.c
@@ -1055,8 +1055,9 @@ doESCkey:
 	case K_IGNORE:	// Something mapped to nothing
 	    break;
 
-	case K_COMMAND:		// <Cmd>command<CR>
-	    do_cmdline(NULL, getcmdkeycmd, NULL, 0);
+	case K_COMMAND:		    // <Cmd>command<CR>
+	case K_SCRIPT_COMMAND:	    // <ScriptCmd>command<CR>
+	    do_cmdkey_command(c, 0);
 #ifdef FEAT_TERMINAL
 	    if (term_use_loop())
 		// Started a terminal that gets the input, exit Insert mode.
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -1772,11 +1772,11 @@ getcmdline_int(
 	    c = safe_vgetc();
 	} while (c == K_IGNORE || c == K_NOP);
 
-	if (c == K_COMMAND)
+	if (c == K_COMMAND || c == K_SCRIPT_COMMAND)
 	{
 	    int	    clen = ccline.cmdlen;
 
-	    if (do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT) == OK)
+	    if (do_cmdkey_command(c, DOCMD_NOWAIT) == OK)
 	    {
 		if (clen == ccline.cmdlen)
 		    trigger_cmdlinechanged = FALSE;
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -83,6 +83,10 @@ static char_u	noremapbuf_init[TYPELEN_IN
 
 static int	last_recorded_len = 0;	// number of last recorded chars
 
+#ifdef FEAT_EVAL
+mapblock_T	*last_used_map = NULL;
+#endif
+
 static int	read_readbuf(buffheader_T *buf, int advance);
 static void	init_typebuf(void);
 static void	may_sync_undo(void);
@@ -2893,6 +2897,7 @@ handle_mapping(
 #ifdef FEAT_EVAL
 	    if (save_m_expr)
 		vim_free(map_str);
+	    last_used_map = mp;
 #endif
 	}
 #ifdef FEAT_EVAL
@@ -3708,7 +3713,7 @@ input_available(void)
  * Function passed to do_cmdline() to get the command after a <Cmd> key from
  * typeahead.
  */
-    char_u *
+    static char_u *
 getcmdkeycmd(
 	int		promptc UNUSED,
 	void		*cookie UNUSED,
@@ -3774,7 +3779,7 @@ getcmdkeycmd(
 	    c1 = NUL;  // end the line
 	else if (c1 == ESC)
 	    aborted = TRUE;
-	else if (c1 == K_COMMAND)
+	else if (c1 == K_COMMAND || c1 == K_SCRIPT_COMMAND)
 	{
 	    // give a nicer error message for this special case
 	    emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd));
@@ -3804,3 +3809,35 @@ getcmdkeycmd(
 
     return (char_u *)line_ga.ga_data;
 }
+
+    int
+do_cmdkey_command(int key, int flags)
+{
+    int	    res;
+#ifdef FEAT_EVAL
+    sctx_T  save_current_sctx = {0, 0, 0, 0};
+
+    if (key == K_SCRIPT_COMMAND && last_used_map != NULL)
+    {
+	save_current_sctx = current_sctx;
+	current_sctx = last_used_map->m_script_ctx;
+    }
+#endif
+
+    res = do_cmdline(NULL, getcmdkeycmd, NULL, flags);
+
+#ifdef FEAT_EVAL
+    if (save_current_sctx.sc_sid > 0)
+	current_sctx = save_current_sctx;
+#endif
+
+    return res;
+}
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+    void
+reset_last_used_map(void)
+{
+    last_used_map = NULL;
+}
+#endif
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -2281,7 +2281,8 @@ ins_compl_prep(int c)
 
     // Ignore end of Select mode mapping and mouse scroll buttons.
     if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
-	    || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_COMMAND)
+	    || c == K_MOUSELEFT || c == K_MOUSERIGHT
+	    || c == K_COMMAND || c == K_SCRIPT_COMMAND)
 	return retval;
 
 #ifdef FEAT_PROP_POPUP
--- a/src/keymap.h
+++ b/src/keymap.h
@@ -276,6 +276,7 @@ enum key_extra
     , KE_MOUSEMOVE_XY = 101	// KE_MOUSEMOVE with coordinates
     , KE_CANCEL = 102		// return from vgetc()
     , KE_COMMAND = 103		// <Cmd> special key
+    , KE_SCRIPT_COMMAND = 104	// <ScriptCmd> special key
 };
 
 /*
@@ -480,6 +481,7 @@ enum key_extra
 #define K_CURSORHOLD	TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
 
 #define K_COMMAND	TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
+#define K_SCRIPT_COMMAND TERMCAP2KEY(KS_EXTRA, KE_SCRIPT_COMMAND)
 
 // Bits for modifier mask
 // 0x01 cannot be used, because the modifier must be 0x02 or higher
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -1057,6 +1057,7 @@ static struct key_name_entry
     {K_CURSORHOLD,	(char_u *)"CursorHold"},
     {K_IGNORE,		(char_u *)"Ignore"},
     {K_COMMAND,		(char_u *)"Cmd"},
+    {K_SCRIPT_COMMAND,	(char_u *)"ScriptCmd"},
     {K_FOCUSGAINED,	(char_u *)"FocusGained"},
     {K_FOCUSLOST,	(char_u *)"FocusLost"},
     {0,			NULL}
--- a/src/normal.c
+++ b/src/normal.c
@@ -373,6 +373,7 @@ static const struct nv_cmd
     {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG,		0},
     {K_PS,	nv_edit,	0,			0},
     {K_COMMAND,	nv_colon,	0,			0},
+    {K_SCRIPT_COMMAND, nv_colon, 0,			0},
 };
 
 // Number of commands in nv_cmds[].
@@ -3429,7 +3430,9 @@ nv_colon(cmdarg_T *cap)
 {
     int	old_p_im;
     int	cmd_result;
-    int	is_cmdkey = cap->cmdchar == K_COMMAND;
+    int	is_cmdkey = cap->cmdchar == K_COMMAND
+					   || cap->cmdchar == K_SCRIPT_COMMAND;
+    int	flags;
 
     if (VIsual_active && !is_cmdkey)
 	nv_operator(cap);
@@ -3459,8 +3462,11 @@ nv_colon(cmdarg_T *cap)
 	old_p_im = p_im;
 
 	// get a command line and execute it
-	cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
-			    cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
+	flags = cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0;
+	if (is_cmdkey)
+	    cmd_result = do_cmdkey_command(cap->cmdchar, flags);
+	else
+	    cmd_result = do_cmdline(NULL, getexline, NULL, flags);
 
 	// If 'insertmode' changed, enter or exit Insert mode
 	if (p_im != old_p_im)
--- a/src/ops.c
+++ b/src/ops.c
@@ -3501,6 +3501,14 @@ typedef struct {
     int		rv_arg;		// extra argument
 } redo_VIsual_T;
 
+    static int
+is_ex_cmdchar(cmdarg_T *cap)
+{
+    return cap->cmdchar == ':'
+	|| cap->cmdchar == K_COMMAND
+	|| cap->cmdchar == K_SCRIPT_COMMAND;
+}
+
 /*
  * Handle an operator after Visual mode or when the movement is finished.
  * "gui_yank" is true when yanking text for the clipboard.
@@ -3583,8 +3591,7 @@ do_pending_operator(cmdarg_T *cap, int o
 		&& ((!VIsual_active || oap->motion_force)
 		    // Also redo Operator-pending Visual mode mappings
 		    || (VIsual_active
-			  && (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
-						  && oap->op_type != OP_COLON))
+			    && is_ex_cmdchar(cap) && oap->op_type != OP_COLON))
 		&& cap->cmdchar != 'D'
 #ifdef FEAT_FOLDING
 		&& oap->op_type != OP_FOLD
@@ -3608,7 +3615,7 @@ do_pending_operator(cmdarg_T *cap, int o
 		    AppendToRedobuffLit(cap->searchbuf, -1);
 		AppendToRedobuff(NL_STR);
 	    }
-	    else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
+	    else if (is_ex_cmdchar(cap))
 	    {
 		// do_cmdline() has stored the first typed line in
 		// "repeat_cmdline".  When several lines are typed repeating
@@ -3806,7 +3813,7 @@ do_pending_operator(cmdarg_T *cap, int o
 			    get_op_char(oap->op_type),
 			    get_extra_op_char(oap->op_type),
 			    oap->motion_force, cap->cmdchar, cap->nchar);
-		else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND)
+		else if (!is_ex_cmdchar(cap))
 		{
 		    int opchar = get_op_char(oap->op_type);
 		    int extra_opchar = get_extra_op_char(oap->op_type);
--- a/src/proto/getchar.pro
+++ b/src/proto/getchar.pro
@@ -52,5 +52,6 @@ void parse_queued_messages(void);
 void vungetc(int c);
 int fix_input_buffer(char_u *buf, int len);
 int input_available(void);
-char_u *getcmdkeycmd(int promptc, void *cookie, int indent, getline_opt_T do_concat);
+int do_cmdkey_command(int key, int flags);
+void reset_last_used_map(void);
 /* vim: set ft=c : */
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -2229,7 +2229,8 @@ send_keys_to_term(term_T *term, int c, i
 	    break;
 
 	case K_COMMAND:
-	    return do_cmdline(NULL, getcmdkeycmd, NULL, 0);
+	case K_SCRIPT_COMMAND:
+	    return do_cmdkey_command(c, 0);
     }
     if (typed)
 	mouse_was_outside = FALSE;
--- a/src/testdir/test_vim9_import.vim
+++ b/src/testdir/test_vim9_import.vim
@@ -1337,6 +1337,9 @@ def Test_autoload_mapping()
       export def Toggle(): string
         return ":g:toggle_called = 'yes'\<CR>"
       enddef
+      export def Doit()
+        g:doit_called = 'yes'
+      enddef
   END
   writefile(lines, 'Xdir/autoload/toggle.vim')
 
@@ -1346,6 +1349,8 @@ def Test_autoload_mapping()
       import autoload 'toggle.vim'
 
       nnoremap <silent> <expr> tt toggle.Toggle() 
+      nnoremap <silent> xx <ScriptCmd>toggle.Doit()<CR>
+      nnoremap <silent> yy <Cmd>toggle.Doit()<CR>
   END
   CheckScriptSuccess(lines)
   assert_false(exists("g:toggle_loaded"))
@@ -1355,7 +1360,14 @@ def Test_autoload_mapping()
   assert_equal('yes', g:toggle_loaded)
   assert_equal('yes', g:toggle_called)
 
+  feedkeys("xx", 'xt')
+  assert_equal('yes', g:doit_called)
+
+  assert_fails('call feedkeys("yy", "xt")', 'E121: Undefined variable: toggle')
+
   nunmap tt
+  nunmap xx
+  nunmap yy
   unlet g:toggle_loaded
   unlet g:toggle_called
   delete('Xdir', 'rf')
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4099,
+/**/
     4098,
 /**/
     4097,