]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.4099: Vim9: cannot use Vim9 syntax in mapping v8.2.4099
authorBram Moolenaar <Bram@vim.org>
Sat, 15 Jan 2022 18:26:04 +0000 (18:26 +0000)
committerBram Moolenaar <Bram@vim.org>
Sat, 15 Jan 2022 18:26:04 +0000 (18:26 +0000)
Problem:    Vim9: cannot use Vim9 syntax in mapping.
Solution:   Add <ScriptCmd> to use the script context for a command.

13 files changed:
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

index e87d4308b40599e90116d096be8985bc3c1d460e..1072d56ce91397861379f6aceec0912e53bdf926 100644 (file)
@@ -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*
index 739f0786e40d6f57f530a8817a3dd7eee5db237f..03631293034f886d70bb38b3af7614b9d9ef0fad 100644 (file)
@@ -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.
index c63c8960f5136a6d0df2c9b235e89ca5cee74768..5dc43d845d0e1b657efd7a4bd6439d86a10fa1df 100644 (file)
@@ -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;
index 08c1a955781600cff414e032178ef3d237c545dd..6434b4dc41ec37672edadc7a7d15e8cff0b1b000 100644 (file)
@@ -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
index 51177fb2c7e9198b62ac7421570c3a9b1429023c..b7b6c0256cae0a3d1147ba30f7b85248e9f3ef2d 100644 (file)
@@ -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
index 4ce5144d15ef7202748d00d8d04c3dcfa1adf6e6..12268dde593075da0c7866e6ad6ec1e28f1020e8 100644 (file)
@@ -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
index aa398227813839993ad5744a51a47b26e21a3d19..971fbdf797e813cb68384901e24ccbff5fc86d32 100644 (file)
@@ -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}
index df7ae561dd6cc5b23ea46eb8dd407fdb83f50109..efc7cfd8a09b7f99923cc172916d1c0aa8d77b18 100644 (file)
@@ -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)
index 8b0f79c6e3535659895ef5fb66106c0810970ee9..6a378442c875b26f5f40266e897adbad8c9bbd9f 100644 (file)
--- 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);
index 9745ddfd85c699671a513caa811443c6902527c3..2d791e5c489e2601972fee8977d2996de5eb7fd3 100644 (file)
@@ -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 : */
index 281c673e8bf36255e84cdc0dc3724215569bdc9d..f32d3adea3497ef85a992afba2a2a350ce833afa 100644 (file)
@@ -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;
index 2e57fe2527491f6973e566241610660f5c4cef49..f5e449714f179037c98962fd8de9f6452c28ad31 100644 (file)
@@ -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')
index 879a0e8b47fb02b5eadd259c97b3aadd5ea70f62..5bf6df495e66e1d5dda45a226d7dc7a06381668b 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4099,
 /**/
     4098,
 /**/