diff options
-rw-r--r-- | runtime/doc/map.txt | 32 | ||||
-rw-r--r-- | src/edit.c | 5 | ||||
-rw-r--r-- | src/ex_getln.c | 4 | ||||
-rw-r--r-- | src/getchar.c | 41 | ||||
-rw-r--r-- | src/insexpand.c | 3 | ||||
-rw-r--r-- | src/keymap.h | 2 | ||||
-rw-r--r-- | src/misc2.c | 1 | ||||
-rw-r--r-- | src/normal.c | 12 | ||||
-rw-r--r-- | src/ops.c | 15 | ||||
-rw-r--r-- | src/proto/getchar.pro | 3 | ||||
-rw-r--r-- | src/terminal.c | 3 | ||||
-rw-r--r-- | src/testdir/test_vim9_import.vim | 12 | ||||
-rw-r--r-- | src/version.c | 2 |
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) @@ -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, |