]> granicus.if.org Git - vim/commitdiff
patch 8.2.1978: making a mapping work in all modes is complicated v8.2.1978
authorBram Moolenaar <Bram@vim.org>
Thu, 12 Nov 2020 13:21:06 +0000 (14:21 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 12 Nov 2020 13:21:06 +0000 (14:21 +0100)
Problem:    Making a mapping work in all modes is complicated.
Solution:   Add the <Cmd> special key. (Yegappan Lakshmanan, closes #7282,
            closes 4784, based on patch by Bjorn Linse)

19 files changed:
runtime/doc/autocmd.txt
runtime/doc/eval.txt
runtime/doc/map.txt
src/edit.c
src/errors.h
src/ex_docmd.c
src/ex_getln.c
src/getchar.c
src/insexpand.c
src/keymap.h
src/map.c
src/misc2.c
src/normal.c
src/ops.c
src/proto/getchar.pro
src/screen.c
src/terminal.c
src/testdir/test_mapping.vim
src/version.c

index a5407046a0a5fcb29408e874f7f98bbd97f6da72..0a668c195ac096d3d144ef926b93cdfd878f68aa 100644 (file)
@@ -551,12 +551,15 @@ CmdlineChanged                    After a change was made to the text in the
                                                        *CmdlineEnter*
 CmdlineEnter                   After moving the cursor to the command line,
                                where the user can type a command or search
-                               string.
+                               string; including non-interactive use of ":"
+                               in a mapping, but not when using |<Cmd>|.
                                <afile> is set to a single character,
                                indicating the type of command-line.
                                |cmdwin-char|
                                                        *CmdlineLeave*
-CmdlineLeave                   Before leaving the command line.
+CmdlineLeave                   Before leaving the command line; including
+                               non-interactive use of ":" in a mapping, but
+                               not when using |<Cmd>|.
                                Also when abandoning the command line, after
                                typing CTRL-C or <Esc>.
                                When the commands result in an error the
index c08b75bb8199133f01f1259e4d017d0a69085bf4..3303cab3a667b0527fce0b8ad5ff4075db769299 100644 (file)
@@ -8660,6 +8660,7 @@ screencol()                                                       *screencol()*
                the following mappings: >
                        nnoremap <expr> GG ":echom ".screencol()."\n"
                        nnoremap <silent> GG :echom screencol()<CR>
+                       nnoremap GG <Cmd>echom screencol()<CR>
 <
 screenpos({winid}, {lnum}, {col})                              *screenpos()*
                The result is a Dict with the screen position of the text
index 276ab505092a57c94c797679012c049d160c7dcd..83a5428ce654ff88e78e6fc5da32eea55a2ab23c 100644 (file)
@@ -1,4 +1,4 @@
-*map.txt*       For Vim version 8.2.  Last change: 2020 Oct 07
+*map.txt*       For Vim version 8.2.  Last change: 2020 Nov 12
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -271,7 +271,7 @@ For this reason the following is blocked:
 - The |:normal| command.
 - Moving the cursor is allowed, but it is restored afterwards.
 If you want the mapping to do any of these let the returned characters do
-that.
+that, or use a |<Cmd>| mapping instead.
 
 You can use getchar(), it consumes typeahead if there is any. E.g., if you
 have these mappings: >
@@ -303,6 +303,40 @@ empty string, so that nothing is inserted.
 Note that using 0x80 as a single byte before other text does not work, it will
 be seen as a special key.
 
+                                               *<Cmd>* *:map-cmd*
+The special text <Cmd> begins a "command mapping", it executes the command
+directly without changing modes.  Where you might use ":...<CR>" in the
+{rhs} of a mapping, you can instead use "<Cmd>...<CR>".
+Example: >
+       noremap x <Cmd>echo mode(1)<CR>
+<
+This is more flexible than `:<C-U>` in Visual and Operator-pending mode, or
+`<C-O>:` in Insert mode, because the commands are executed directly in the
+current mode, instead of always going to Normal mode.  Visual mode is
+preserved, so tricks with |gv| are not needed.  Commands can be invoked
+directly in Command-line mode (which would otherwise require timer hacks).
+Example of using <Cmd> halfway Insert mode: >
+       nnoremap <F3> aText <Cmd>echo mode(1)<CR> Added<Esc>
+
+Unlike <expr> mappings, there are no special restrictions on the <Cmd>
+command: it is executed as if an (unrestricted) |autocmd| was invoked.
+
+Note:
+- Because <Cmd> avoids 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.
+- In Select mode, |:map| and |:vmap| command mappings are executed in
+  Visual mode.  Use |:smap| to handle Select mode differently.
+
+                                                       *E1135* *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.
+
+                                                       *E1137*
+<Cmd> commands can have only normal characters and cannot contain special
+characters like function keys.
+
 
 1.3 MAPPING AND MODES                                  *:map-modes*
                        *mapmode-nvo* *mapmode-n* *mapmode-v* *mapmode-o*
index 934f6c1983d45c1fdf315ba9eee0972a4577d017..d92a9513981c0ae0f37e4baf67b532ac7587dd74 100644 (file)
@@ -1031,6 +1031,10 @@ doESCkey:
        case K_IGNORE:  // Something mapped to nothing
            break;
 
+       case K_COMMAND:         // <Cmd>command<CR>
+           do_cmdline(NULL, getcmdkeycmd, NULL, 0);
+           break;
+
        case K_CURSORHOLD:      // Didn't type something for a while.
            ins_apply_autocmds(EVENT_CURSORHOLDI);
            did_cursorhold = TRUE;
index 426164998d959f3491d36033cf944483eb2031d9..969301f9c4228854d2a153dbadcc95b39b813261 100644 (file)
@@ -295,3 +295,9 @@ EXTERN char e_cannot_extend_null_list[]
 EXTERN char e_using_string_as_bool_str[]
        INIT(= N_("E1135: Using a String as a Bool: \"%s\""));
 #endif
+EXTERN char e_cmd_mapping_must_end_with_cr[]
+       INIT(=N_("E1135: <Cmd> mapping must end with <CR>"));
+EXTERN char e_cmd_mapping_must_end_with_cr_before_second_cmd[]
+       INIT(=N_("E1136: <Cmd> mapping must end with <CR> before second <Cmd>"));
+EXTERN char e_cmd_maping_must_not_include_str_key[]
+       INIT(= N_("E1137: <Cmd> mapping must not include %s key"));
index 627dae71e9578d7f17aadbf3ff9e258f78603a7b..dbaf21847d1863f14c642a2ddf2c8a76d5dde76e 100644 (file)
@@ -8148,6 +8148,9 @@ ex_startinsert(exarg_T *eap)
            restart_edit = 'i';
        curwin->w_curswant = 0;     // avoid MAXCOL
     }
+
+    if (VIsual_active)
+       showmode();
 }
 
 /*
index 1325f77d1e81a429cdd598201a351802b32f72da..a00d6deb32abc259a6bc66382bc501cd7bb33007 100644 (file)
@@ -1711,6 +1711,10 @@ getcmdline_int(
            c = safe_vgetc();
        while (c == K_IGNORE || c == K_NOP);
 
+       if (c == K_COMMAND
+                  && do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT) == OK)
+           goto cmdline_changed;
+
        if (KeyTyped)
        {
            some_key_typed = TRUE;
index b0cc8c2317d27d28f6dfb976c91aa9e1234689d5..3d73a6c7b63f87be3d2de44927c54a789fe523fe 100644 (file)
@@ -3619,3 +3619,96 @@ input_available(void)
            );
 }
 #endif
+
+/*
+ * Function passed to do_cmdline() to get the command after a <Cmd> key from
+ * typeahead.
+ */
+    char_u *
+getcmdkeycmd(
+       int             promptc UNUSED,
+       void            *cookie UNUSED,
+       int             indent UNUSED,
+       getline_opt_T   do_concat UNUSED)
+{
+    garray_T   line_ga;
+    int                c1 = -1;
+    int                c2;
+    int                cmod = 0;
+    int                aborted = FALSE;
+
+    ga_init2(&line_ga, 1, 32);
+
+    // no mapping for these characters
+    no_mapping++;
+
+    got_int = FALSE;
+    while (c1 != NUL && !aborted)
+    {
+       ga_grow(&line_ga, 32);
+
+       if (vgetorpeek(FALSE) == NUL)
+       {
+           // incomplete <Cmd> is an error, because there is not much the user
+           // could do in this state.
+           emsg(_(e_cmd_mapping_must_end_with_cr));
+           aborted = TRUE;
+           break;
+       }
+
+       // Get one character at a time.
+       c1 = vgetorpeek(TRUE);
+
+       // Get two extra bytes for special keys
+       if (c1 == K_SPECIAL)
+       {
+           c1 = vgetorpeek(TRUE);
+           c2 = vgetorpeek(TRUE);
+           if (c1 == KS_MODIFIER)
+           {
+               cmod = c2;
+               continue;
+           }
+           c1 = TO_SPECIAL(c1, c2);
+       }
+
+       if (got_int)
+           aborted = TRUE;
+       else if (c1 == '\r' || c1 == '\n')
+           c1 = NUL;  // end the line
+       else if (c1 == ESC)
+           aborted = TRUE;
+       else if (c1 == K_COMMAND)
+       {
+           // give a nicer error message for this special case
+           emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd));
+           aborted = TRUE;
+       }
+       else if (IS_SPECIAL(c1))
+       {
+           if (c1 == K_SNR)
+           {
+               ga_append(&line_ga, (char)K_SPECIAL);
+               ga_append(&line_ga, (char)KS_EXTRA);
+               ga_append(&line_ga, (char)KE_SNR);
+           }
+           else
+           {
+               semsg(e_cmd_maping_must_not_include_str_key,
+                                              get_special_key_name(c1, cmod));
+               aborted = TRUE;
+           }
+       }
+       else
+           ga_append(&line_ga, (char)c1);
+
+       cmod = 0;
+    }
+
+    no_mapping--;
+
+    if (aborted)
+       ga_clear(&line_ga);
+
+    return (char_u *)line_ga.ga_data;
+}
index 7e61e618becb7e19fc92d9b98b2a8fc40a465f0b..eebf01dc474154f63b3e061901d4fff3d0ce7d5f 100644 (file)
@@ -1822,7 +1822,7 @@ 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_MOUSELEFT || c == K_MOUSERIGHT || c == K_COMMAND)
        return retval;
 
 #ifdef FEAT_PROP_POPUP
index 4583c6c67a1149b10665c1ab91fc14355f254c5e..6efb13d0651badc50b10382ca7f39c525d440c18 100644 (file)
@@ -274,6 +274,7 @@ enum key_extra
     , KE_FOCUSLOST = 99                // focus lost
     , KE_MOUSEMOVE = 100       // mouse moved with no button down
     , KE_CANCEL = 101          // return from vgetc()
+    , KE_COMMAND = 102         // <Cmd> special key
 };
 
 /*
@@ -449,11 +450,11 @@ enum key_extra
 #define K_RIGHTMOUSE   TERMCAP2KEY(KS_EXTRA, KE_RIGHTMOUSE)
 #define K_RIGHTDRAG    TERMCAP2KEY(KS_EXTRA, KE_RIGHTDRAG)
 #define K_RIGHTRELEASE TERMCAP2KEY(KS_EXTRA, KE_RIGHTRELEASE)
-#define K_X1MOUSE       TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE)
-#define K_X1MOUSE       TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE)
+#define K_X1MOUSE      TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE)
+#define K_X1MOUSE      TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE)
 #define K_X1DRAG       TERMCAP2KEY(KS_EXTRA, KE_X1DRAG)
 #define K_X1RELEASE     TERMCAP2KEY(KS_EXTRA, KE_X1RELEASE)
-#define K_X2MOUSE       TERMCAP2KEY(KS_EXTRA, KE_X2MOUSE)
+#define K_X2MOUSE      TERMCAP2KEY(KS_EXTRA, KE_X2MOUSE)
 #define K_X2DRAG       TERMCAP2KEY(KS_EXTRA, KE_X2DRAG)
 #define K_X2RELEASE     TERMCAP2KEY(KS_EXTRA, KE_X2RELEASE)
 
@@ -477,6 +478,8 @@ enum key_extra
 
 #define K_CURSORHOLD   TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
 
+#define K_COMMAND      TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
+
 // Bits for modifier mask
 // 0x01 cannot be used, because the modifier must be 0x02 or higher
 #define MOD_MASK_SHIFT     0x02
index b46ba3781d48589efc16a129cdd9073a890abcaf..229095bc1472b2f8dd124f319eda94fd60df0f49 100644 (file)
--- a/src/map.c
+++ b/src/map.c
@@ -1639,8 +1639,7 @@ eval_map_expr(
  * Returns NULL when out of memory.
  */
     char_u *
-vim_strsave_escape_csi(
-    char_u *p)
+vim_strsave_escape_csi(char_u *p)
 {
     char_u     *res;
     char_u     *s, *d;
index 48cc4b7489e01555c884176d8b71b9b315e22b8d..52e0499bc8476d154daffc12b7e59253717373a3 100644 (file)
@@ -2530,6 +2530,7 @@ static struct key_name_entry
     {K_PLUG,           (char_u *)"Plug"},
     {K_CURSORHOLD,     (char_u *)"CursorHold"},
     {K_IGNORE,         (char_u *)"Ignore"},
+    {K_COMMAND,                (char_u *)"Cmd"},
     {0,                        NULL}
     // NOTE: When adding a long name update MAX_KEY_NAME_LEN.
 };
index af4d3b2885459b40a0fa39f13403e44cd1604249..87f1956fa9ed5e3e8612ae4339e3684ae99c5689 100644 (file)
@@ -375,6 +375,7 @@ static const struct nv_cmd
 #endif
     {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG,          0},
     {K_PS,     nv_edit,        0,                      0},
+    {K_COMMAND,        nv_colon,       0,                      0},
 };
 
 // Number of commands in nv_cmds[].
@@ -3312,10 +3313,11 @@ nv_exmode(cmdarg_T *cap)
     static void
 nv_colon(cmdarg_T *cap)
 {
-    int            old_p_im;
-    int            cmd_result;
+    int        old_p_im;
+    int        cmd_result;
+    int        is_cmdkey = cap->cmdchar == K_COMMAND;
 
-    if (VIsual_active)
+    if (VIsual_active && !is_cmdkey)
        nv_operator(cap);
     else
     {
@@ -3325,7 +3327,7 @@ nv_colon(cmdarg_T *cap)
            cap->oap->motion_type = MCHAR;
            cap->oap->inclusive = FALSE;
        }
-       else if (cap->count0)
+       else if (cap->count0 && !is_cmdkey)
        {
            // translate "count:" into ":.,.+(count - 1)"
            stuffcharReadbuff('.');
@@ -3343,7 +3345,7 @@ nv_colon(cmdarg_T *cap)
        old_p_im = p_im;
 
        // get a command line and execute it
-       cmd_result = do_cmdline(NULL, getexline, NULL,
+       cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
                            cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
 
        // If 'insertmode' changed, enter or exit Insert mode
index 5abe151d4c0fd2c458e5f55b5fd5ff32a867d9b3..cc25683072ab0d7df1be94b17756654e770feb86 100644 (file)
--- a/src/ops.c
+++ b/src/ops.c
@@ -3490,7 +3490,7 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
                    AppendToRedobuffLit(cap->searchbuf, -1);
                AppendToRedobuff(NL_STR);
            }
-           else if (cap->cmdchar == ':')
+           else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
            {
                // do_cmdline() has stored the first typed line in
                // "repeat_cmdline".  When several lines are typed repeating
index bf131a5acea75d3b04f96dbc3422a6ce5b231196..cb8b7507e14d24b4e893892a3d885f452118b7d9 100644 (file)
@@ -51,4 +51,5 @@ 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);
 /* vim: set ft=c : */
index b204de9bf30c38948da6f7f18187214e078cf1f5..7c6fe63e1ad8cb31db667b5b63646d041a45e819 100644 (file)
@@ -4192,7 +4192,8 @@ showmode(void)
 #endif
                    msg_puts_attr(_(" INSERT"), attr);
                }
-               else if (restart_edit == 'I' || restart_edit == 'A')
+               else if (restart_edit == 'I' || restart_edit == 'i' ||
+                       restart_edit == 'a' || restart_edit == 'A')
                    msg_puts_attr(_(" (insert)"), attr);
                else if (restart_edit == 'R')
                    msg_puts_attr(_(" (replace)"), attr);
index e81754cca1e8ac676c404f188f47d8a8f0fc0e15..0fb9f485a616d5095f41a7e308ffa8784e60c805 100644 (file)
@@ -2180,6 +2180,10 @@ send_keys_to_term(term_T *term, int c, int modmask, int typed)
                    return FAIL;
                }
            }
+           break;
+
+       case K_COMMAND:
+           return do_cmdline(NULL, getcmdkeycmd, NULL, 0);
     }
     if (typed)
        mouse_was_outside = FALSE;
index 5bec95a11c40b35e7a9af8dbf3bf5c81c2c9a8cd..dbd0a7e320c9bd5b893d41ef416d09b356439a7d 100644 (file)
@@ -3,6 +3,7 @@
 source shared.vim
 source check.vim
 source screendump.vim
+source term_util.vim
 
 func Test_abbreviation()
   " abbreviation with 0x80 should work
@@ -856,4 +857,471 @@ func Test_map_cpo_special_keycode()
   mapclear!
 endfunc
 
+" Test for <Cmd> key in maps to execute commands
+func Test_map_cmdkey()
+  new
+
+  " Error cases
+  let x = 0
+  noremap <F3> <Cmd><Cmd>let x = 1<CR>
+  call assert_fails('call feedkeys("\<F3>", "xt")', 'E1136:')
+  call assert_equal(0, x)
+
+  noremap <F3> <Cmd><F3>let x = 2<CR>
+  call assert_fails('call feedkeys("\<F3>", "xt")', 'E1137:')
+  call assert_equal(0, x)
+
+  noremap <F3> <Cmd>let x = 3
+  call assert_fails('call feedkeys("\<F3>", "xt!")', 'E1135:')
+  call assert_equal(0, x)
+
+  " works in various modes and sees the correct mode()
+  noremap <F3> <Cmd>let m = mode(1)<CR>
+  noremap! <F3> <Cmd>let m = mode(1)<CR>
+
+  " normal mode
+  call feedkeys("\<F3>", 'xt')
+  call assert_equal('n', m)
+
+  " visual mode
+  call feedkeys("v\<F3>", 'xt!')
+  call assert_equal('v', m)
+  " shouldn't leave the visual mode
+  call assert_equal('v', mode(1))
+  call feedkeys("\<Esc>", 'xt')
+  call assert_equal('n', mode(1))
+
+  " visual mapping in select mode
+  call feedkeys("gh\<F3>", 'xt!')
+  call assert_equal('v', m)
+  " shouldn't leave select mode
+  call assert_equal('s', mode(1))
+  call feedkeys("\<Esc>", 'xt')
+  call assert_equal('n', mode(1))
+
+  " select mode mapping
+  snoremap <F3> <Cmd>let m = mode(1)<cr>
+  call feedkeys("gh\<F3>", 'xt!')
+  call assert_equal('s', m)
+  " shouldn't leave select mode
+  call assert_equal('s', mode(1))
+  call feedkeys("\<Esc>", 'xt')
+  call assert_equal('n', mode(1))
+
+  " operator-pending mode
+  call feedkeys("d\<F3>", 'xt!')
+  call assert_equal('no', m)
+  " leaves the operator-pending mode
+  call assert_equal('n', mode(1))
+
+  " insert mode
+  call feedkeys("i\<F3>abc", 'xt')
+  call assert_equal('i', m)
+  call assert_equal('abc', getline('.'))
+
+  " replace mode
+  call feedkeys("0R\<F3>two", 'xt')
+  call assert_equal('R', m)
+  call assert_equal('two', getline('.'))
+
+  " virtual replace mode
+  call setline('.', "one\ttwo")
+  call feedkeys("4|gR\<F3>xxx", 'xt')
+  call assert_equal('Rv', m)
+  call assert_equal("onexxx\ttwo", getline('.'))
+
+  " cmdline mode
+  call feedkeys(":\<F3>\"xxx\<CR>", 'xt!')
+  call assert_equal('c', m)
+  call assert_equal('"xxx', @:)
+
+  " terminal mode
+  if CanRunVimInTerminal()
+    tnoremap <F3> <Cmd>let m = mode(1)<CR>
+    let buf = Run_shell_in_terminal({})
+    call feedkeys("\<F3>", 'xt')
+    call assert_equal('t', m)
+    call assert_equal('t', mode(1))
+    call StopShellInTerminal(buf)
+    call TermWait(buf)
+    close!
+    tunmap <F3>
+  endif
+
+  " invoke cmdline mode recursively
+  noremap! <F2> <Cmd>norm! :foo<CR>
+  %d
+  call setline(1, ['some short lines', 'of test text'])
+  call feedkeys(":bar\<F2>x\<C-B>\"\r", 'xt')
+  call assert_equal('"barx', @:)
+  unmap! <F2>
+
+  " test for calling a <SID> function
+  let lines =<< trim END
+    map <F2> <Cmd>call <SID>do_it()<CR>
+    func s:do_it()
+      let g:x = 32
+    endfunc
+  END
+  call writefile(lines, 'Xscript')
+  source Xscript
+  call feedkeys("\<F2>", 'xt')
+  call assert_equal(32, g:x)
+  call delete('Xscript')
+
+  unmap <F3>
+  unmap! <F3>
+  %bw!
+endfunc
+
+" text object enters visual mode
+func TextObj()
+  if mode() !=# "v"
+    normal! v
+  end
+  call cursor(1, 3)
+  normal! o
+  call cursor(2, 4)
+endfunc
+
+func s:cmdmap(lhs, rhs)
+  exe 'noremap ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
+  exe 'noremap! ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
+endfunc
+
+func s:cmdunmap(lhs)
+  exe 'unmap ' .. a:lhs
+  exe 'unmap! ' .. a:lhs
+endfunc
+
+" Map various <Fx> keys used by the <Cmd> key tests
+func s:setupMaps()
+  call s:cmdmap('<F3>', 'let m = mode(1)')
+  call s:cmdmap('<F4>', 'normal! ww')
+  call s:cmdmap('<F5>', 'normal! "ay')
+  call s:cmdmap('<F6>', 'throw "very error"')
+  call s:cmdmap('<F7>', 'call TextObj()')
+  call s:cmdmap('<F8>', 'startinsert')
+  call s:cmdmap('<F9>', 'stopinsert')
+endfunc
+
+" Remove the mappings setup by setupMaps()
+func s:cleanupMaps()
+  call s:cmdunmap('<F3>')
+  call s:cmdunmap('<F4>')
+  call s:cmdunmap('<F5>')
+  call s:cmdunmap('<F6>')
+  call s:cmdunmap('<F7>')
+  call s:cmdunmap('<F8>')
+  call s:cmdunmap('<F9>')
+endfunc
+
+" Test for <Cmd> mapping in normal mode
+func Test_map_cmdkey_normal_mode()
+  new
+  call s:setupMaps()
+
+  " check v:count and v:register works
+  call s:cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]')
+  call feedkeys("\<F2>", 'xt')
+  call assert_equal(['n', 0, '"'], s)
+  call feedkeys("7\<F2>", 'xt')
+  call assert_equal(['n', 7, '"'], s)
+  call feedkeys("\"e\<F2>", 'xt')
+  call assert_equal(['n', 0, 'e'], s)
+  call feedkeys("5\"k\<F2>", 'xt')
+  call assert_equal(['n', 5, 'k'], s)
+  call s:cmdunmap('<F2>')
+
+  call setline(1, ['some short lines', 'of test text'])
+  call feedkeys("\<F7>y", 'xt')
+  call assert_equal("me short lines\nof t", @")
+  call assert_equal('v', getregtype('"'))
+  call assert_equal([0, 1, 3, 0], getpos("'<"))
+  call assert_equal([0, 2, 4, 0], getpos("'>"))
+
+  " startinsert
+  %d
+  call feedkeys("\<F8>abc", 'xt')
+  call assert_equal('abc', getline(1))
+
+  " feedkeys are not executed immediately
+  noremap ,a <Cmd>call feedkeys("aalpha") \| let g:a = getline(2)<CR>
+  %d
+  call setline(1, ['some short lines', 'of test text'])
+  call cursor(2, 3)
+  call feedkeys(",a\<F3>", 'xt')
+  call assert_equal('of test text', g:a)
+  call assert_equal('n', m)
+  call assert_equal(['some short lines', 'of alphatest text'], getline(1, '$'))
+  nunmap ,a
+
+  " feedkeys(..., 'x') is executed immediately, but insert mode is aborted
+  noremap ,b <Cmd>call feedkeys("abeta", 'x') \| let g:b = getline(2)<CR>
+  call feedkeys(",b\<F3>", 'xt')
+  call assert_equal('n', m)
+  call assert_equal('of alphabetatest text', g:b)
+  nunmap ,b
+
+  call s:cleanupMaps()
+  %bw!
+endfunc
+
+" Test for <Cmd> mapping with the :normal command
+func Test_map_cmdkey_normal_cmd()
+  new
+  noremap ,x <Cmd>call append(1, "xx") \| call append(1, "aa")<CR>
+  noremap ,f <Cmd>nosuchcommand<CR>
+  noremap ,e <Cmd>throw "very error" \| call append(1, "yy")<CR>
+  noremap ,m <Cmd>echoerr "The message." \| call append(1, "zz")<CR>
+  noremap ,w <Cmd>for i in range(5) \| if i==1 \| echoerr "Err" \| endif \| call append(1, i) \| endfor<CR>
+
+  call setline(1, ['some short lines', 'of test text'])
+  exe "norm ,x\r"
+  call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
+
+  call assert_fails('norm ,f', 'E492:')
+  call assert_fails('norm ,e', 'very error')
+  call assert_fails('norm ,m', 'The message.')
+  call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
+
+  %d
+  let caught_err = 0
+  try
+    exe "normal ,w"
+  catch /Vim(echoerr):Err/
+    let caught_err = 1
+  endtry
+  call assert_equal(1, caught_err)
+  call assert_equal(['', '0'], getline(1, '$'))
+
+  %d
+  call assert_fails('normal ,w', 'Err')
+  call assert_equal(['', '4', '3', '2' ,'1', '0'], getline(1, '$'))
+  call assert_equal(1, line('.'))
+
+  nunmap ,x
+  nunmap ,f
+  nunmap ,e
+  nunmap ,m
+  nunmap ,w
+  %bw!
+endfunc
+
+" Test for <Cmd> mapping in visual mode
+func Test_map_cmdkey_visual_mode()
+  new
+  set showmode
+  call s:setupMaps()
+
+  call setline(1, ['some short lines', 'of test text'])
+  call feedkeys("v\<F4>", 'xt!')
+  call assert_equal(['v', 1, 12], [mode(1), col('v'), col('.')])
+
+  " can invoke an opeartor, ending the visual mode
+  let @a = ''
+  call feedkeys("\<F5>", 'xt!')
+  call assert_equal('n', mode(1))
+  call assert_equal('some short l', @a)
+
+  " error doesn't interrupt visual mode
+  call assert_fails('call feedkeys("ggvw\<F6>", "xt!")', 'E605:')
+  call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
+  call feedkeys("\<F7>", 'xt!')
+  call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+  " startinsert gives "-- (insert) VISUAL --" mode
+  call feedkeys("\<F8>", 'xt!')
+  call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+  redraw!
+  call assert_match('^-- (insert) VISUAL --', Screenline(&lines))
+  call feedkeys("\<Esc>new ", 'x')
+  call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
+
+  call s:cleanupMaps()
+  set showmode&
+  %bw!
+endfunc
+
+" Test for <Cmd> mapping in select mode
+func Test_map_cmdkey_select_mode()
+  new
+  set showmode
+  call s:setupMaps()
+
+  snoremap <F1> <cmd>throw "very error"<CR>
+  snoremap <F2> <cmd>normal! <c-g>"by<CR>
+  call setline(1, ['some short lines', 'of test text'])
+
+  call feedkeys("gh\<F4>", "xt!")
+  call assert_equal(['s', 1, 12], [mode(1), col('v'), col('.')])
+  redraw!
+  call assert_match('^-- SELECT --', Screenline(&lines))
+
+  " visual mapping in select mode restarts select mode after operator
+  let @a = ''
+  call feedkeys("\<F5>", 'xt!')
+  call assert_equal('s', mode(1))
+  call assert_equal('some short l', @a)
+
+  " select mode mapping works, and does not restart select mode
+  let @b = ''
+  call feedkeys("\<F2>", 'xt!')
+  call assert_equal('n', mode(1))
+  call assert_equal('some short l', @b)
+
+  " error doesn't interrupt temporary visual mode
+  call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F6>", "xt!")', 'E605:')
+  redraw!
+  call assert_match('^-- VISUAL --', Screenline(&lines))
+  " quirk: restoration of select mode is not performed
+  call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
+
+  " error doesn't interrupt select mode
+  call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F1>", "xt!")', 'E605:')
+  redraw!
+  call assert_match('^-- SELECT --', Screenline(&lines))
+  call assert_equal(['s', 1, 6], [mode(1), col('v'), col('.')])
+
+  call feedkeys("\<F7>", 'xt!')
+  redraw!
+  call assert_match('^-- SELECT --', Screenline(&lines))
+  call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+  " startinsert gives "-- SELECT (insert) --" mode
+  call feedkeys("\<F8>", 'xt!')
+  redraw!
+  call assert_match('^-- (insert) SELECT --', Screenline(&lines))
+  call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+  call feedkeys("\<Esc>new ", 'x')
+  call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
+
+  sunmap <F1>
+  sunmap <F2>
+  call s:cleanupMaps()
+  set showmode&
+  %bw!
+endfunc
+
+" Test for <Cmd> mapping in operator-pending mode
+func Test_map_cmdkey_op_pending_mode()
+  new
+  call s:setupMaps()
+
+  call setline(1, ['some short lines', 'of test text'])
+  call feedkeys("d\<F4>", 'xt')
+  call assert_equal(['lines', 'of test text'], getline(1, '$'))
+  call assert_equal(['some short '], getreg('"', 1, 1))
+  " create a new undo point
+  let &undolevels = &undolevels
+
+  call feedkeys(".", 'xt')
+  call assert_equal(['test text'], getline(1, '$'))
+  call assert_equal(['lines', 'of '], getreg('"', 1, 1))
+  " create a new undo point
+  let &undolevels = &undolevels
+
+  call feedkeys("uu", 'xt')
+  call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
+
+  " error aborts operator-pending, operator not performed
+  call assert_fails('call feedkeys("d\<F6>", "xt")', 'E605:')
+  call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
+
+  call feedkeys("\"bd\<F7>", 'xt')
+  call assert_equal(['soest text'], getline(1, '$'))
+  call assert_equal(['me short lines', 'of t'], getreg('b', 1, 1))
+
+  " startinsert aborts operator
+  call feedkeys("d\<F8>cc", 'xt')
+  call assert_equal(['soccest text'], getline(1, '$'))
+
+  call s:cleanupMaps()
+  %bw!
+endfunc
+
+" Test for <Cmd> mapping in insert mode
+func Test_map_cmdkey_insert_mode()
+  new
+  call s:setupMaps()
+
+  call setline(1, ['some short lines', 'of test text'])
+  " works the same as <C-O>w<C-O>w
+  call feedkeys("iindeed \<F4>little ", 'xt')
+  call assert_equal(['indeed some short little lines', 'of test text'], getline(1, '$'))
+  call assert_fails('call feedkeys("i\<F6> 2", "xt")', 'E605:')
+  call assert_equal(['indeed some short little 2 lines', 'of test text'], getline(1, '$'))
+
+  " Note when entering visual mode from InsertEnter autocmd, an async event,
+  " or a <Cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode.
+  call feedkeys("i\<F7>stuff ", 'xt')
+  call assert_equal(['indeed some short little 2 lines', 'of stuff test text'], getline(1, '$'))
+  call assert_equal(['v', 1, 3, 2, 9], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+  call feedkeys("\<F5>", 'xt')
+  call assert_equal(['deed some short little 2 lines', 'of stuff '], getreg('a', 1, 1))
+
+  " also works as part of abbreviation
+  abbr foo <Cmd>let g:y = 17<CR>bar
+  exe "normal i\<space>foo "
+  call assert_equal(17, g:y)
+  call assert_equal('in bar deed some short little 2 lines', getline(1))
+  unabbr foo
+
+  " :startinsert does nothing
+  call setline(1, 'foo bar')
+  call feedkeys("ggi\<F8>vim", 'xt')
+  call assert_equal('vimfoo bar', getline(1))
+
+  " :stopinsert works
+  call feedkeys("ggi\<F9>Abc", 'xt')
+  call assert_equal('vimfoo barbc', getline(1))
+
+  call s:cleanupMaps()
+  %bw!
+endfunc
+
+" Test for <Cmd> mapping in insert-completion mode
+func Test_map_cmdkey_insert_complete_mode()
+  new
+  call s:setupMaps()
+
+  call setline(1, 'some short lines')
+  call feedkeys("os\<C-X>\<C-N>\<F3>\<C-N> ", 'xt')
+  call assert_equal('ic', m)
+  call assert_equal(['some short lines', 'short '], getline(1, '$'))
+
+  call s:cleanupMaps()
+  %bw!
+endfunc
+
+" Test for <Cmd> mapping in cmdline mode
+func Test_map_cmdkey_cmdline_mode()
+  new
+  call s:setupMaps()
+
+  call setline(1, ['some short lines', 'of test text'])
+  let x = 0
+  call feedkeys(":let x\<F3>= 10\r", 'xt')
+  call assert_equal('c', m)
+  call assert_equal(10, x)
+
+  " exception doesn't leave cmdline mode
+  call assert_fails('call feedkeys(":let x\<F6>= 20\r", "xt")', 'E605:')
+  call assert_equal(20, x)
+
+  " move cursor in the buffer from cmdline mode
+  call feedkeys(":let x\<F4>= 30\r", 'xt')
+  call assert_equal(30, x)
+  call assert_equal(12, col('.'))
+
+  " :startinsert takes effect after leaving cmdline mode
+  call feedkeys(":let x\<F8>= 40\rnew ", 'xt')
+  call assert_equal(40, x)
+  call assert_equal('some short new lines', getline(1))
+
+  call s:cleanupMaps()
+  %bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index 092801cc1fc9069de88f5bfdbdcca89bff6764fb..caf4db89ae2f29d7bbc941cad53ca4fa7cd6f57c 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1978,
 /**/
     1977,
 /**/