Patch 8.2.1978
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)
Files:      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


*** ../vim-8.2.1977/runtime/doc/autocmd.txt     2020-10-21 12:19:50.080854732 
+0200
--- runtime/doc/autocmd.txt     2020-11-12 13:28:53.302992440 +0100
***************
*** 551,562 ****
                                                        *CmdlineEnter*
  CmdlineEnter                  After moving the cursor to the command line,
                                where the user can type a command or search
!                               string.
                                <afile> is set to a single character,
                                indicating the type of command-line.
                                |cmdwin-char|
                                                        *CmdlineLeave*
! CmdlineLeave                  Before leaving the command line.
                                Also when abandoning the command line, after
                                typing CTRL-C or <Esc>.
                                When the commands result in an error the
--- 551,565 ----
                                                        *CmdlineEnter*
  CmdlineEnter                  After moving the cursor to the command line,
                                where the user can type a command or search
!                               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; 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
*** ../vim-8.2.1977/runtime/doc/eval.txt        2020-11-09 18:31:30.544791868 
+0100
--- runtime/doc/eval.txt        2020-11-12 13:23:14.947599257 +0100
***************
*** 8592,8597 ****
--- 8662,8668 ----
                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
*** ../vim-8.2.1977/runtime/doc/map.txt 2020-10-07 17:28:47.473370302 +0200
--- runtime/doc/map.txt 2020-11-12 13:56:22.455089311 +0100
***************
*** 254,260 ****
  - 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.
  
  You can use getchar(), it consumes typeahead if there is any. E.g., if you
  have these mappings: >
--- 271,277 ----
  - 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, or use a |<Cmd>| mapping instead.
  
  You can use getchar(), it consumes typeahead if there is any. E.g., if you
  have these mappings: >
***************
*** 283,297 ****
  CTRL-L inserts the next number, CTRL-R resets the count.  CTRL-R returns an
  empty string, so that nothing is inserted.
  
! Note that there are some tricks to make special keys work and escape CSI bytes
! in the text.  The |:map| command also does this, thus you must avoid that it
! is done twice.  This does not work: >
!       :imap <expr> <F3> "<Char-0x611B>"
! Because the <Char- sequence is escaped for being a |:imap| argument and then
! again for using <expr>.  This does work: >
!       :imap <expr> <F3> "\u611B"
! Using 0x80 as a single byte before other text does not work, it will be seen
! as a special key.
  
  
  1.3 MAPPING AND MODES                                 *:map-modes*
--- 300,341 ----
  CTRL-L inserts the next number, CTRL-R resets the count.  CTRL-R returns an
  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*
*** ../vim-8.2.1977/src/edit.c  2020-11-11 20:52:36.970181354 +0100
--- src/edit.c  2020-11-12 13:56:56.759026220 +0100
***************
*** 1031,1036 ****
--- 1031,1040 ----
        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;
*** ../vim-8.2.1977/src/errors.h        2020-11-12 12:08:47.982254065 +0100
--- src/errors.h        2020-11-12 13:59:27.146751553 +0100
***************
*** 295,297 ****
--- 295,303 ----
  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"));
*** ../vim-8.2.1977/src/ex_docmd.c      2020-11-07 18:40:47.132725219 +0100
--- src/ex_docmd.c      2020-11-12 13:23:14.955599247 +0100
***************
*** 8148,8153 ****
--- 8148,8156 ----
            restart_edit = 'i';
        curwin->w_curswant = 0;     // avoid MAXCOL
      }
+ 
+     if (VIsual_active)
+       showmode();
  }
  
  /*
*** ../vim-8.2.1977/src/ex_getln.c      2020-10-24 20:49:37.494683051 +0200
--- src/ex_getln.c      2020-11-12 14:00:20.286655120 +0100
***************
*** 1711,1716 ****
--- 1711,1720 ----
            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;
*** ../vim-8.2.1977/src/getchar.c       2020-11-04 11:03:08.376891850 +0100
--- src/getchar.c       2020-11-12 14:04:36.753604783 +0100
***************
*** 3619,3621 ****
--- 3619,3714 ----
            );
  }
  #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;
+ }
*** ../vim-8.2.1977/src/insexpand.c     2020-10-28 20:19:56.372057081 +0100
--- src/insexpand.c     2020-11-12 13:23:14.959599239 +0100
***************
*** 1822,1828 ****
  
      // 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)
        return retval;
  
  #ifdef FEAT_PROP_POPUP
--- 1822,1828 ----
  
      // 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)
        return retval;
  
  #ifdef FEAT_PROP_POPUP
*** ../vim-8.2.1977/src/keymap.h        2019-11-30 18:40:33.000000000 +0100
--- src/keymap.h        2020-11-12 14:05:18.421372489 +0100
***************
*** 274,279 ****
--- 274,280 ----
      , 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,459 ****
  #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_X1DRAG      TERMCAP2KEY(KS_EXTRA, KE_X1DRAG)
  #define K_X1RELEASE     TERMCAP2KEY(KS_EXTRA, KE_X1RELEASE)
! #define K_X2MOUSE       TERMCAP2KEY(KS_EXTRA, KE_X2MOUSE)
  #define K_X2DRAG      TERMCAP2KEY(KS_EXTRA, KE_X2DRAG)
  #define K_X2RELEASE     TERMCAP2KEY(KS_EXTRA, KE_X2RELEASE)
  
--- 450,460 ----
  #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_X1DRAG      TERMCAP2KEY(KS_EXTRA, KE_X1DRAG)
  #define K_X1RELEASE     TERMCAP2KEY(KS_EXTRA, KE_X1RELEASE)
! #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,482 ****
--- 478,485 ----
  
  #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
*** ../vim-8.2.1977/src/map.c   2020-10-01 21:37:17.798009505 +0200
--- src/map.c   2020-11-12 13:23:14.963599234 +0100
***************
*** 1639,1646 ****
   * Returns NULL when out of memory.
   */
      char_u *
! vim_strsave_escape_csi(
!     char_u *p)
  {
      char_u    *res;
      char_u    *s, *d;
--- 1639,1645 ----
   * Returns NULL when out of memory.
   */
      char_u *
! vim_strsave_escape_csi(char_u *p)
  {
      char_u    *res;
      char_u    *s, *d;
*** ../vim-8.2.1977/src/misc2.c 2020-10-28 13:53:46.549128959 +0100
--- src/misc2.c 2020-11-12 13:23:14.963599234 +0100
***************
*** 2530,2535 ****
--- 2530,2536 ----
      {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.
  };
*** ../vim-8.2.1977/src/normal.c        2020-10-28 20:19:56.376057067 +0100
--- src/normal.c        2020-11-12 13:23:14.967599227 +0100
***************
*** 375,380 ****
--- 375,381 ----
  #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,3321 ****
      static void
  nv_colon(cmdarg_T *cap)
  {
!     int           old_p_im;
!     int           cmd_result;
  
!     if (VIsual_active)
        nv_operator(cap);
      else
      {
--- 3313,3323 ----
      static void
  nv_colon(cmdarg_T *cap)
  {
!     int       old_p_im;
!     int       cmd_result;
!     int       is_cmdkey = cap->cmdchar == K_COMMAND;
  
!     if (VIsual_active && !is_cmdkey)
        nv_operator(cap);
      else
      {
***************
*** 3325,3331 ****
            cap->oap->motion_type = MCHAR;
            cap->oap->inclusive = FALSE;
        }
!       else if (cap->count0)
        {
            // translate "count:" into ":.,.+(count - 1)"
            stuffcharReadbuff('.');
--- 3327,3333 ----
            cap->oap->motion_type = MCHAR;
            cap->oap->inclusive = FALSE;
        }
!       else if (cap->count0 && !is_cmdkey)
        {
            // translate "count:" into ":.,.+(count - 1)"
            stuffcharReadbuff('.');
***************
*** 3343,3349 ****
        old_p_im = p_im;
  
        // get a command line and execute it
!       cmd_result = do_cmdline(NULL, getexline, NULL,
                            cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
  
        // If 'insertmode' changed, enter or exit Insert mode
--- 3345,3351 ----
        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);
  
        // If 'insertmode' changed, enter or exit Insert mode
*** ../vim-8.2.1977/src/ops.c   2020-10-24 20:49:37.498683038 +0200
--- src/ops.c   2020-11-12 13:23:14.967599227 +0100
***************
*** 3490,3496 ****
                    AppendToRedobuffLit(cap->searchbuf, -1);
                AppendToRedobuff(NL_STR);
            }
!           else if (cap->cmdchar == ':')
            {
                // do_cmdline() has stored the first typed line in
                // "repeat_cmdline".  When several lines are typed repeating
--- 3490,3496 ----
                    AppendToRedobuffLit(cap->searchbuf, -1);
                AppendToRedobuff(NL_STR);
            }
!           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
*** ../vim-8.2.1977/src/proto/getchar.pro       2020-06-06 22:36:20.464116743 
+0200
--- src/proto/getchar.pro       2020-11-12 14:05:48.405211967 +0100
***************
*** 51,54 ****
--- 51,55 ----
  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 : */
*** ../vim-8.2.1977/src/screen.c        2020-11-06 17:58:31.727138982 +0100
--- src/screen.c        2020-11-12 13:23:14.971599222 +0100
***************
*** 4192,4198 ****
  #endif
                    msg_puts_attr(_(" INSERT"), attr);
                }
!               else if (restart_edit == 'I' || restart_edit == 'A')
                    msg_puts_attr(_(" (insert)"), attr);
                else if (restart_edit == 'R')
                    msg_puts_attr(_(" (replace)"), attr);
--- 4192,4199 ----
  #endif
                    msg_puts_attr(_(" INSERT"), attr);
                }
!               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);
*** ../vim-8.2.1977/src/terminal.c      2020-11-06 17:58:31.727138982 +0100
--- src/terminal.c      2020-11-12 13:23:14.975599214 +0100
***************
*** 2180,2185 ****
--- 2180,2189 ----
                    return FAIL;
                }
            }
+           break;
+ 
+       case K_COMMAND:
+           return do_cmdline(NULL, getcmdkeycmd, NULL, 0);
      }
      if (typed)
        mouse_was_outside = FALSE;
*** ../vim-8.2.1977/src/testdir/test_mapping.vim        2020-10-28 
20:19:56.376057067 +0100
--- src/testdir/test_mapping.vim        2020-11-12 13:23:14.975599214 +0100
***************
*** 3,8 ****
--- 3,9 ----
  source shared.vim
  source check.vim
  source screendump.vim
+ source term_util.vim
  
  func Test_abbreviation()
    " abbreviation with 0x80 should work
***************
*** 856,859 ****
--- 857,1327 ----
    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
*** ../vim-8.2.1977/src/version.c       2020-11-12 12:08:47.986254110 +0100
--- src/version.c       2020-11-12 13:26:21.555313427 +0100
***************
*** 752,753 ****
--- 752,755 ----
  {   /* Add new patch number below this line */
+ /**/
+     1978,
  /**/

-- 
hundred-and-one symptoms of being an internet addict:
238. You think faxes are old-fashioned.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202011121322.0ACDMU3q036279%40masaka.moolenaar.net.

Raspunde prin e-mail lui