summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-01-15 18:26:04 +0000
committerBram Moolenaar <Bram@vim.org>2022-01-15 18:26:04 +0000
commite32c3c462ce9b3163a4a4bffd985897910885d29 (patch)
tree92ade56bf13a5d7fe440e20f3e35e57250f19b29
parent069613c9e8645acea3a128c15ebdbf56e2219d44 (diff)
patch 8.2.4099: Vim9: cannot use Vim9 syntax in mappingv8.2.4099
Problem: Vim9: cannot use Vim9 syntax in mapping. Solution: Add <ScriptCmd> to use the script context for a command.
-rw-r--r--runtime/doc/map.txt32
-rw-r--r--src/edit.c5
-rw-r--r--src/ex_getln.c4
-rw-r--r--src/getchar.c41
-rw-r--r--src/insexpand.c3
-rw-r--r--src/keymap.h2
-rw-r--r--src/misc2.c1
-rw-r--r--src/normal.c12
-rw-r--r--src/ops.c15
-rw-r--r--src/proto/getchar.pro3
-rw-r--r--src/terminal.c3
-rw-r--r--src/testdir/test_vim9_import.vim12
-rw-r--r--src/version.c2
13 files changed, 113 insertions, 22 deletions
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index e87d4308b4..1072d56ce9 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -284,6 +284,10 @@ This can be solved by inserting <Ignore> before the character that is
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 mode: >
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*
diff --git a/src/edit.c b/src/edit.c
index 739f0786e4..0363129303 100644
--- 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.
diff --git a/src/ex_getln.c b/src/ex_getln.c
index c63c8960f5..5dc43d845d 100644
--- 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;
diff --git a/src/getchar.c b/src/getchar.c
index 08c1a95578..6434b4dc41 100644
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -83,6 +83,10 @@ static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap
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
diff --git a/src/insexpand.c b/src/insexpand.c
index 51177fb2c7..b7b6c0256c 100644
--- 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
diff --git a/src/keymap.h b/src/keymap.h
index 4ce5144d15..12268dde59 100644
--- 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
diff --git a/src/misc2.c b/src/misc2.c
index aa39822781..971fbdf797 100644
--- 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}
diff --git a/src/normal.c b/src/normal.c
index df7ae561dd..efc7cfd8a0 100644
--- 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)
diff --git a/src/ops.c b/src/ops.c
index 8b0f79c6e3..6a378442c8 100644
--- 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 old_col, int gui_yank)
&& ((!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 old_col, int gui_yank)
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 old_col, int gui_yank)
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);
diff --git a/src/proto/getchar.pro b/src/proto/getchar.pro
index 9745ddfd85..2d791e5c48 100644
--- 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 : */
diff --git a/src/terminal.c b/src/terminal.c
index 281c673e8b..f32d3adea3 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -2229,7 +2229,8 @@ send_keys_to_term(term_T *term, int c, int modmask, int typed)
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;
diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim
index 2e57fe2527..f5e449714f 100644
--- 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')
diff --git a/src/version.c b/src/version.c
index 879a0e8b47..5bf6df495e 100644
--- 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,