# HG changeset patch # User Bram Moolenaar # Date 1642271404 -3600 # Node ID a9eeb18e749c45731e0973ba2f38f7bc0b2b6524 # Parent fd4193f6d59ef5fcbbe43ddc7811d964868e63c2 patch 8.2.4099: Vim9: cannot use Vim9 syntax in mapping Commit: https://github.com/vim/vim/commit/e32c3c462ce9b3163a4a4bffd985897910885d29 Author: Bram Moolenaar 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 to use the script context for a command. diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -284,6 +284,10 @@ This can be solved by inserting expression-mapped: > nmap ! f!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 halfway Insert mo Unlike mappings, there are no special restrictions on the command: it is executed as if an (unrestricted) |autocommand| was invoked. + ** + is like 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 impl.DoTheWork() + +No matter where 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 is typed, not +when the mapping is defined. + Note: -- Because avoids mode-changes it does not trigger |CmdlineEnter| and - |CmdlineLeave| events, because no user interaction is expected. +- Because and avoid mode-changes it does not trigger + |CmdlineEnter| and |CmdlineLeave| events, because no user interaction is + expected. - For the same reason, |keycodes| like are interpreted as plain, unmapped keys. - The command is not echo'ed, no need for . @@ -356,12 +375,13 @@ Note: Visual mode. Use |:smap| to handle Select mode differently. *E1255* *E1136* - commands must terminate, that is, they must be followed by in the -{rhs} of the mapping definition. |Command-line| mode is never entered. + and commands must terminate, that is, they must be followed +by in the {rhs} of the mapping definition. |Command-line| mode is never +entered. *E1137* - commands can have only normal characters and cannot contain special -characters like function keys. + and 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 --- a/src/edit.c +++ b/src/edit.c @@ -1055,8 +1055,9 @@ doESCkey: case K_IGNORE: // Something mapped to nothing break; - case K_COMMAND: // command - do_cmdline(NULL, getcmdkeycmd, NULL, 0); + case K_COMMAND: // command + case K_SCRIPT_COMMAND: // command + 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 --- 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 --- 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 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 --- 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 --- 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 // special key + , KE_SCRIPT_COMMAND = 104 // 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 --- 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 --- 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 --- 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); diff --git a/src/proto/getchar.pro b/src/proto/getchar.pro --- 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 --- 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; diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim --- 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'\" 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 tt toggle.Toggle() + nnoremap xx toggle.Doit() + nnoremap yy toggle.Doit() 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 --- 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,