mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	Merge #4419 'implement <Cmd> key'
This commit is contained in:
		| @@ -232,8 +232,10 @@ For this reason the following is blocked: | ||||
| - Editing another buffer. | ||||
| - The |:normal| command. | ||||
| - Moving the cursor is allowed, but it is restored afterwards. | ||||
| - If the cmdline is changed, the old text and cursor position are restored. | ||||
| If you want the mapping to do any of these let the returned characters do | ||||
| that. | ||||
| that. Alternatively use a |<Cmd>| mapping which doesn't have these | ||||
| restrictions. | ||||
|  | ||||
| You can use getchar(), it consumes typeahead if there is any. E.g., if you | ||||
| have these mappings: > | ||||
| @@ -272,6 +274,29 @@ again for using <expr>.  This does work: > | ||||
| Using 0x80 as a single byte before other text does not work, it will be seen | ||||
| as a special key. | ||||
|  | ||||
| 						*<Cmd>* *:map-command* | ||||
| A command mapping is a mapping that directly executes a command. Command | ||||
| mappings are written by placing a command in between <Cmd> and <CR> in the | ||||
| rhs of a mapping (in any mode): > | ||||
| 	noremap <f3> <Cmd>echo mode(1)<cr> | ||||
| < | ||||
| 							*E5520* | ||||
| The command must be complete and ended with a <CR>. If the command is | ||||
| incomplete, an error is raised. |Command-line| mode is never entered. | ||||
|  | ||||
| This is more flexible than using `:<c-u>` in visual and operator pending | ||||
| mode, or `<c-o>:` in insert mode, as the commands are exectued directly in the | ||||
| mode, and not normal mode. Also visual mode is not aborted. Commands can be | ||||
| invoked directly in cmdline mode, which is not simple otherwise (a timer has | ||||
| to be used). Unlike <expr> mappings, there are not any specific restrictions | ||||
| what the command can do, except for what is normally possible to do in every | ||||
| specific mode. The command should be executed the same way as if an | ||||
| (unrestricted) |autocmd| was invoked or an async event event was processed. | ||||
|  | ||||
| Note: In select mode, |:map| or |:vmap| command mappings will be executed in | ||||
| visual mode. If a mapping is intended to work in select mode, it is | ||||
| recomendend to map it using |:smap|, possibly in addition to the same mapping | ||||
| with |:map| or |:xmap|. | ||||
|  | ||||
| 1.3 MAPPING AND MODES					*:map-modes* | ||||
| 		*mapmode-nvo* *mapmode-n* *mapmode-v* *mapmode-o* *mapmode-t* | ||||
|   | ||||
| @@ -974,6 +974,10 @@ static int insert_handle_key(InsertState *s) | ||||
|     multiqueue_process_events(main_loop.events); | ||||
|     break; | ||||
|  | ||||
|   case K_COMMAND:       // some command | ||||
|     do_cmdline(NULL, getcmdkeycmd, NULL, 0); | ||||
|     break; | ||||
|  | ||||
|   case K_HOME:        // <Home> | ||||
|   case K_KHOME: | ||||
|   case K_S_HOME: | ||||
|   | ||||
| @@ -8156,6 +8156,10 @@ static void ex_startinsert(exarg_T *eap) | ||||
|       restart_edit = 'i'; | ||||
|     curwin->w_curswant = 0;         /* avoid MAXCOL */ | ||||
|   } | ||||
|  | ||||
|   if (VIsual_active) { | ||||
|     showmode(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* | ||||
|   | ||||
| @@ -512,8 +512,12 @@ static int command_line_execute(VimState *state, int key) | ||||
|   CommandLineState *s = (CommandLineState *)state; | ||||
|   s->c = key; | ||||
|  | ||||
|   if (s->c == K_EVENT || s->c == K_COMMAND) { | ||||
|     if (s->c == K_EVENT) { | ||||
|       multiqueue_process_events(main_loop.events); | ||||
|     } else { | ||||
|       do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); | ||||
|     } | ||||
|     redrawcmdline(); | ||||
|     return 1; | ||||
|   } | ||||
|   | ||||
| @@ -4247,3 +4247,70 @@ mapblock_T *get_maphash(int index, buf_T *buf) | ||||
|  | ||||
|   return (buf == NULL) ? maphash[index] : buf->b_maphash[index]; | ||||
| } | ||||
|  | ||||
| /// Get command argument for <Cmd> key | ||||
| char_u * getcmdkeycmd(int promptc, void *cookie, int indent) | ||||
| { | ||||
|   garray_T line_ga; | ||||
|   int c1 = -1, c2; | ||||
|   int cmod = 0; | ||||
|   bool aborted = false; | ||||
|  | ||||
|   ga_init(&line_ga, 1, 32); | ||||
|  | ||||
|   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_cmdmap_err); | ||||
|       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);          // no mapping for these chars | ||||
|       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) { | ||||
|       // special case to give nicer error message | ||||
|       EMSG(e_cmdmap_repeated); | ||||
|       aborted = true; | ||||
|     } else if (IS_SPECIAL(c1)) { | ||||
|       EMSG2(e_cmdmap_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; | ||||
| } | ||||
|   | ||||
| @@ -1154,6 +1154,12 @@ EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); | ||||
| EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); | ||||
| EXTERN char_u e_autocmd_err[] INIT(=N_( | ||||
|     "E5500: autocmd has thrown an exception: %s")); | ||||
| EXTERN char_u e_cmdmap_err[] INIT(=N_( | ||||
|     "E5520: <Cmd> mapping must end with <CR>")); | ||||
| EXTERN char_u e_cmdmap_repeated[] INIT(=N_( | ||||
|     "E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); | ||||
| EXTERN char_u e_cmdmap_key[] INIT(=N_( | ||||
|     "E5522: <Cmd> mapping must not include %s key")); | ||||
|  | ||||
|  | ||||
| EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); | ||||
|   | ||||
| @@ -285,6 +285,7 @@ static const struct key_name_entry { | ||||
|   { K_SNR,             "SNR" }, | ||||
|   { K_PLUG,            "Plug" }, | ||||
|   { K_PASTE,           "Paste" }, | ||||
|   { K_COMMAND,         "Cmd" }, | ||||
|   { 0,                 NULL } | ||||
|   // NOTE: When adding a long name update MAX_KEY_NAME_LEN. | ||||
| }; | ||||
|   | ||||
| @@ -243,6 +243,7 @@ enum key_extra { | ||||
|   , KE_EVENT            // event | ||||
|   , KE_PASTE            // special key to toggle the 'paste' option. | ||||
|                         // sent only by UIs | ||||
|   , KE_COMMAND          // special key to execute command in any mode | ||||
| }; | ||||
|  | ||||
| /* | ||||
| @@ -431,6 +432,7 @@ enum key_extra { | ||||
|  | ||||
| #define K_EVENT         TERMCAP2KEY(KS_EXTRA, KE_EVENT) | ||||
| #define K_PASTE         TERMCAP2KEY(KS_EXTRA, KE_PASTE) | ||||
| #define K_COMMAND       TERMCAP2KEY(KS_EXTRA, KE_COMMAND) | ||||
|  | ||||
| /* Bits for modifier mask */ | ||||
| /* 0x01 cannot be used, because the modifier must be 0x02 or higher */ | ||||
|   | ||||
| @@ -345,6 +345,7 @@ static const struct nv_cmd { | ||||
|   { K_F8,      farsi_f8,       0,                      0 }, | ||||
|   { K_F9,      farsi_f9,       0,                      0 }, | ||||
|   { K_EVENT,   nv_event,       NV_KEEPREG,             0 }, | ||||
|   { K_COMMAND, nv_colon,       0,                      0 }, | ||||
| }; | ||||
|  | ||||
| /* Number of commands in nv_cmds[]. */ | ||||
| @@ -1473,13 +1474,13 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) | ||||
|           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 | ||||
|          * won't be possible. */ | ||||
|         if (repeat_cmdline == NULL) | ||||
|       } 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 | ||||
|         // won't be possible. | ||||
|         if (repeat_cmdline == NULL) { | ||||
|           ResetRedobuff(); | ||||
|         else { | ||||
|         } else { | ||||
|           AppendToRedobuffLit(repeat_cmdline, -1); | ||||
|           AppendToRedobuff(NL_STR); | ||||
|           xfree(repeat_cmdline); | ||||
| @@ -4524,23 +4525,22 @@ static void nv_exmode(cmdarg_T *cap) | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Handle a ":" command. | ||||
|  */ | ||||
| /// Handle a ":" command and <Cmd>. | ||||
| static void nv_colon(cmdarg_T *cap) | ||||
| { | ||||
|   int old_p_im; | ||||
|   bool cmd_result; | ||||
|   bool is_cmdkey = cap->cmdchar == K_COMMAND; | ||||
|  | ||||
|   if (VIsual_active) | ||||
|   if (VIsual_active && !is_cmdkey) { | ||||
|     nv_operator(cap); | ||||
|   else { | ||||
|   } else { | ||||
|     if (cap->oap->op_type != OP_NOP) { | ||||
|       // Using ":" as a movement is characterwise exclusive. | ||||
|       cap->oap->motion_type = kMTCharWise; | ||||
|       cap->oap->inclusive = false; | ||||
|     } else if (cap->count0) { | ||||
|       /* translate "count:" into ":.,.+(count - 1)" */ | ||||
|     } else if (cap->count0 && !is_cmdkey) { | ||||
|       // translate "count:" into ":.,.+(count - 1)" | ||||
|       stuffcharReadbuff('.'); | ||||
|       if (cap->count0 > 1) { | ||||
|         stuffReadbuff(",.+"); | ||||
| @@ -4554,8 +4554,8 @@ static void nv_colon(cmdarg_T *cap) | ||||
|  | ||||
|     old_p_im = p_im; | ||||
|  | ||||
|     /* get a command line and execute it */ | ||||
|     cmd_result = do_cmdline(NULL, getexline, NULL, | ||||
|     // 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 */ | ||||
|   | ||||
| @@ -6748,16 +6748,20 @@ int showmode(void) | ||||
|           if (p_ri) | ||||
|             MSG_PUTS_ATTR(_(" REVERSE"), attr); | ||||
|           MSG_PUTS_ATTR(_(" INSERT"), attr); | ||||
|         } else if (restart_edit == 'I') | ||||
|         } else if (restart_edit == 'I' || restart_edit == 'i' | ||||
|                    || restart_edit == 'a') { | ||||
|           MSG_PUTS_ATTR(_(" (insert)"), attr); | ||||
|         else if (restart_edit == 'R') | ||||
|         } else if (restart_edit == 'R') { | ||||
|           MSG_PUTS_ATTR(_(" (replace)"), attr); | ||||
|         else if (restart_edit == 'V') | ||||
|         } else if (restart_edit == 'V') { | ||||
|           MSG_PUTS_ATTR(_(" (vreplace)"), attr); | ||||
|         if (p_hkmap) | ||||
|         } | ||||
|         if (p_hkmap) { | ||||
|           MSG_PUTS_ATTR(_(" Hebrew"), attr); | ||||
|         if (p_fkmap) | ||||
|         } | ||||
|         if (p_fkmap) { | ||||
|           MSG_PUTS_ATTR(farsi_text_5, attr); | ||||
|         } | ||||
|         if (State & LANGMAP) { | ||||
|           if (curwin->w_p_arab) { | ||||
|             MSG_PUTS_ATTR(_(" Arabic"), attr); | ||||
|   | ||||
| @@ -461,6 +461,10 @@ static int terminal_execute(VimState *state, int key) | ||||
|       } | ||||
|       break; | ||||
|  | ||||
|     case K_COMMAND: | ||||
|       do_cmdline(NULL, getcmdkeycmd, NULL, 0); | ||||
|       break; | ||||
|  | ||||
|     case Ctrl_N: | ||||
|       if (s->got_bsl) { | ||||
|         return 0; | ||||
|   | ||||
							
								
								
									
										771
									
								
								test/functional/ex_cmds/cmd_map_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										771
									
								
								test/functional/ex_cmds/cmd_map_spec.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,771 @@ | ||||
| local helpers = require('test.functional.helpers')(after_each) | ||||
| local clear = helpers.clear | ||||
| local feed_command = helpers.feed_command | ||||
| local feed = helpers.feed | ||||
| local eq = helpers.eq | ||||
| local expect = helpers.expect | ||||
| local eval = helpers.eval | ||||
| local funcs = helpers.funcs | ||||
| local insert = helpers.insert | ||||
| local exc_exec = helpers.exc_exec | ||||
| local Screen = require('test.functional.ui.screen') | ||||
|  | ||||
| describe('mappings with <Cmd>', function() | ||||
|   local screen | ||||
|   local function cmdmap(lhs, rhs) | ||||
|     feed_command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') | ||||
|     feed_command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') | ||||
|   end | ||||
|  | ||||
|   before_each(function() | ||||
|     clear() | ||||
|     screen = Screen.new(65, 8) | ||||
|     screen:set_default_attr_ids({ | ||||
|       [1] = {bold = true, foreground = Screen.colors.Blue1}, | ||||
|       [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, | ||||
|       [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, | ||||
|       [4] = {bold = true}, | ||||
|       [5] = {background = Screen.colors.LightGrey}, | ||||
|       [6] = {foreground = Screen.colors.Blue1}, | ||||
|     }) | ||||
|     screen:attach() | ||||
|  | ||||
|     cmdmap('<F3>', 'let m = mode(1)') | ||||
|     cmdmap('<F4>', 'normal! ww') | ||||
|     cmdmap('<F5>', 'normal! "ay') | ||||
|     cmdmap('<F6>', 'throw "very error"') | ||||
|     feed_command([[ | ||||
|         function! TextObj() | ||||
|             if mode() !=# "v" | ||||
|                 normal! v | ||||
|             end | ||||
|             call cursor(1,3) | ||||
|             normal! o | ||||
|             call cursor(2,4) | ||||
|         endfunction]]) | ||||
|     cmdmap('<F7>', 'call TextObj()') | ||||
|     insert([[ | ||||
|         some short lines | ||||
|         of test text]]) | ||||
|     feed('gg') | ||||
|     cmdmap('<F8>', 'startinsert') | ||||
|     cmdmap('<F9>', 'stopinsert') | ||||
|     feed_command("abbr foo <Cmd>let g:y = 17<cr>bar") | ||||
|   end) | ||||
|  | ||||
|   it('can be displayed', function() | ||||
|     feed_command('map <F3>') | ||||
|     screen:expect([[ | ||||
|       ^some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|          {6:<F3>}        {6:*} {6:<Cmd>}let m = mode(1){6:<CR>}                        | | ||||
|     ]]) | ||||
|   end) | ||||
|  | ||||
|   it('handles invalid mappings', function() | ||||
|     feed_command('let x = 0') | ||||
|     feed_command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') | ||||
|     feed('<F3>') | ||||
|     screen:expect([[ | ||||
|       ^some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>}      | | ||||
|     ]]) | ||||
|  | ||||
|     feed_command('noremap <F3> <Cmd><F3>let x = 2<cr>') | ||||
|     feed('<F3>') | ||||
|     screen:expect([[ | ||||
|       ^some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:E5522: <Cmd> mapping must not include <F3> key}                   | | ||||
|     ]]) | ||||
|  | ||||
|     feed_command('noremap <F3> <Cmd>let x = 3') | ||||
|     feed('<F3>') | ||||
|     screen:expect([[ | ||||
|       ^some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:E5520: <Cmd> mapping must end with <CR>}                          | | ||||
|     ]]) | ||||
|     eq(0, eval('x')) | ||||
|   end) | ||||
|  | ||||
|   it('works in various modes and sees correct `mode()` value', function() | ||||
|     -- normal mode | ||||
|     feed('<F3>') | ||||
|     eq('n', eval('m')) | ||||
|  | ||||
|     -- visual mode | ||||
|     feed('v<F3>') | ||||
|     eq('v', eval('m')) | ||||
|     -- didn't leave visual mode | ||||
|     eq('v', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     -- visual mapping in select mode | ||||
|     feed('gh<F3>') | ||||
|     eq('v', eval('m')) | ||||
|     -- didn't leave select mode | ||||
|     eq('s', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     -- select mode mapping | ||||
|     feed_command('snoremap <F3> <Cmd>let m = mode(1)<cr>') | ||||
|     feed('gh<F3>') | ||||
|     eq('s', eval('m')) | ||||
|     -- didn't leave select mode | ||||
|     eq('s', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     -- operator-pending mode | ||||
|     feed("d<F3>") | ||||
|     eq('no', eval('m')) | ||||
|     -- did leave operator-pending mode | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     --insert mode | ||||
|     feed('i<F3>') | ||||
|     eq('i', eval('m')) | ||||
|     eq('i', eval('mode(1)')) | ||||
|  | ||||
|     -- replace mode | ||||
|     feed("<Ins><F3>") | ||||
|     eq('R', eval('m')) | ||||
|     eq('R', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     -- virtual replace mode | ||||
|     feed("gR<F3>") | ||||
|     eq('Rv', eval('m')) | ||||
|     eq('Rv', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     -- langmap works, but is not distinguished in mode(1) | ||||
|     feed(":set iminsert=1<cr>i<F3>") | ||||
|     eq('i', eval('m')) | ||||
|     eq('i', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     feed(':<F3>') | ||||
|     eq('c', eval('m')) | ||||
|     eq('c', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     -- terminal mode | ||||
|     feed_command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') | ||||
|     feed_command('split | terminal') | ||||
|     feed('i') | ||||
|     eq('t', eval('mode(1)')) | ||||
|     feed('<F3>') | ||||
|     eq('t', eval('m')) | ||||
|     eq('t', eval('mode(1)')) | ||||
|   end) | ||||
|  | ||||
|   it('works in normal mode', function() | ||||
|     cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]') | ||||
|  | ||||
|     -- check v:count and v:register works | ||||
|     feed('<F2>') | ||||
|     eq({'n', 0, '"'}, eval('s')) | ||||
|     feed('7<F2>') | ||||
|     eq({'n', 7, '"'}, eval('s')) | ||||
|     feed('"e<F2>') | ||||
|     eq({'n', 0, 'e'}, eval('s')) | ||||
|     feed('5"k<F2>') | ||||
|     eq({'n', 5, 'k'}, eval('s')) | ||||
|     feed('"+2<F2>') | ||||
|     eq({'n', 2, '+'}, eval('s')) | ||||
|  | ||||
|     -- text object enters visual mode | ||||
|     feed('<F7>') | ||||
|     screen:expect([[ | ||||
|       so{5:me short lines}                                                 | | ||||
|       {5:of }^test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- VISUAL --}                                                     | | ||||
|     ]]) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     -- startinsert | ||||
|     feed('<F8>') | ||||
|     eq('i', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     eq('n', eval('mode(1)')) | ||||
|     cmdmap(',a', 'call feedkeys("aalpha") \\| let g:a = getline(2)') | ||||
|     cmdmap(',b', 'call feedkeys("abeta", "x") \\| let g:b = getline(2)') | ||||
|  | ||||
|     feed(',a<F3>') | ||||
|     screen:expect([[ | ||||
|       some short lines                                                 | | ||||
|       of alpha^test text                                                | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- INSERT --}                                                     | | ||||
|     ]]) | ||||
|     -- feedkeys were not executed immediately | ||||
|     eq({'n', 'of test text'}, eval('[m,a]')) | ||||
|     eq('i', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     feed(',b<F3>') | ||||
|     screen:expect([[ | ||||
|       some short lines                                                 | | ||||
|       of alphabet^atest text                                            | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|                                                                        | | ||||
|     ]]) | ||||
|     -- feedkeys(..., 'x') was executed immediately, but insert mode gets aborted | ||||
|     eq({'n', 'of alphabetatest text'}, eval('[m,b]')) | ||||
|     eq('n', eval('mode(1)')) | ||||
|   end) | ||||
|  | ||||
|   it('works in :normal command', function() | ||||
|     feed_command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') | ||||
|     feed_command('noremap ,f <Cmd>nosuchcommand<cr>') | ||||
|     feed_command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') | ||||
|     feed_command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') | ||||
|     feed_command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') | ||||
|  | ||||
|     feed(":normal ,x<cr>") | ||||
|     screen:expect([[ | ||||
|       ^some short lines                                                 | | ||||
|       aa                                                               | | ||||
|       xx                                                               | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|                                                                        | | ||||
|     ]]) | ||||
|  | ||||
|     eq('Vim:E492: Not an editor command: nosuchcommand', exc_exec("normal ,f")) | ||||
|     eq('very error', exc_exec("normal ,e")) | ||||
|     eq('Vim(echoerr):The message.', exc_exec("normal ,m")) | ||||
|     feed('w') | ||||
|     screen:expect([[ | ||||
|       some ^short lines                                                 | | ||||
|       aa                                                               | | ||||
|       xx                                                               | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|                                                                        | | ||||
|     ]]) | ||||
|  | ||||
|     feed_command(':%d') | ||||
|     eq('Vim(echoerr):Err', exc_exec("normal ,w")) | ||||
|     screen:expect([[ | ||||
|       ^                                                                 | | ||||
|       0                                                                | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       --No lines in buffer--                                           | | ||||
|     ]]) | ||||
|  | ||||
|     feed_command(':%d') | ||||
|     feed_command(':normal ,w') | ||||
|     screen:expect([[ | ||||
|       ^                                                                 | | ||||
|       4                                                                | | ||||
|       3                                                                | | ||||
|       2                                                                | | ||||
|       1                                                                | | ||||
|       0                                                                | | ||||
|       {1:~                                                                }| | ||||
|       {2:Err}                                                              | | ||||
|     ]]) | ||||
|   end) | ||||
|  | ||||
|   it('works in visual mode', function() | ||||
|     -- can extend visual mode | ||||
|     feed('v<F4>') | ||||
|     screen:expect([[ | ||||
|       {5:some short }^lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- VISUAL --}                                                     | | ||||
|     ]]) | ||||
|     eq('v', funcs.mode(1)) | ||||
|  | ||||
|     -- can invoke operator, ending visual mode | ||||
|     feed('<F5>') | ||||
|     eq('n', funcs.mode(1)) | ||||
|     eq({'some short l'}, funcs.getreg('a',1,1)) | ||||
|  | ||||
|     -- error doesn't interrupt visual mode | ||||
|     feed('ggvw<F6>') | ||||
|     screen:expect([[ | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:Error detected while processing :}                                | | ||||
|       {2:E605: Exception not caught: very error}                           | | ||||
|       {3:Press ENTER or type command to continue}^                          | | ||||
|     ]]) | ||||
|     feed('<cr>') | ||||
|     eq('E605: Exception not caught: very error', eval('v:errmsg')) | ||||
|     -- still in visual mode, <cr> was consumed by the error prompt | ||||
|     screen:expect([[ | ||||
|       {5:some }^short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- VISUAL --}                                                     | | ||||
|     ]]) | ||||
|     eq('v', funcs.mode(1)) | ||||
|     feed('<F7>') | ||||
|     screen:expect([[ | ||||
|       so{5:me short lines}                                                 | | ||||
|       {5:of }^test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- VISUAL --}                                                     | | ||||
|     ]]) | ||||
|     eq('v', funcs.mode(1)) | ||||
|  | ||||
|     -- startinsert gives "-- (insert) VISUAL --" mode | ||||
|     feed('<F8>') | ||||
|     screen:expect([[ | ||||
|       so{5:me short lines}                                                 | | ||||
|       {5:of }^test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- (insert) VISUAL --}                                            | | ||||
|     ]]) | ||||
|     eq('v', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('i', eval('mode(1)')) | ||||
|   end) | ||||
|  | ||||
|   it('works in select mode', function() | ||||
|     feed_command('snoremap <F1> <cmd>throw "very error"<cr>') | ||||
|     feed_command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') | ||||
|     -- can extend select mode | ||||
|     feed('gh<F4>') | ||||
|     screen:expect([[ | ||||
|       {5:some short }^lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- SELECT --}                                                     | | ||||
|     ]]) | ||||
|     eq('s', funcs.mode(1)) | ||||
|  | ||||
|     -- visual mapping in select mode restart selct mode after operator | ||||
|     feed('<F5>') | ||||
|     eq('s', funcs.mode(1)) | ||||
|     eq({'some short l'}, funcs.getreg('a',1,1)) | ||||
|  | ||||
|     -- select mode mapping works, and does not restart select mode | ||||
|     feed('<F2>') | ||||
|     eq('n', funcs.mode(1)) | ||||
|     eq({'some short l'}, funcs.getreg('b',1,1)) | ||||
|  | ||||
|     -- error doesn't interrupt temporary visual mode | ||||
|     feed('<esc>ggvw<c-g><F6>') | ||||
|     screen:expect([[ | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:Error detected while processing :}                                | | ||||
|       {2:E605: Exception not caught: very error}                           | | ||||
|       {3:Press ENTER or type command to continue}^                          | | ||||
|     ]]) | ||||
|     feed('<cr>') | ||||
|     eq('E605: Exception not caught: very error', eval('v:errmsg')) | ||||
|     -- still in visual mode, <cr> was consumed by the error prompt | ||||
|     screen:expect([[ | ||||
|       {5:some }^short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- VISUAL --}                                                     | | ||||
|     ]]) | ||||
|     -- quirk: restoration of select mode is not performed | ||||
|     eq('v', funcs.mode(1)) | ||||
|  | ||||
|     -- error doesn't interrupt select mode | ||||
|     feed('<esc>ggvw<c-g><F1>') | ||||
|     screen:expect([[ | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:Error detected while processing :}                                | | ||||
|       {2:E605: Exception not caught: very error}                           | | ||||
|       {3:Press ENTER or type command to continue}^                          | | ||||
|     ]]) | ||||
|     feed('<cr>') | ||||
|     eq('E605: Exception not caught: very error', eval('v:errmsg')) | ||||
|     -- still in select mode, <cr> was consumed by the error prompt | ||||
|     screen:expect([[ | ||||
|       {5:some }^short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- SELECT --}                                                     | | ||||
|     ]]) | ||||
|     -- quirk: restoration of select mode is not performed | ||||
|     eq('s', funcs.mode(1)) | ||||
|  | ||||
|     feed('<F7>') | ||||
|     screen:expect([[ | ||||
|       so{5:me short lines}                                                 | | ||||
|       {5:of }^test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- SELECT --}                                                     | | ||||
|     ]]) | ||||
|     eq('s', funcs.mode(1)) | ||||
|  | ||||
|     -- startinsert gives "-- SELECT (insert) --" mode | ||||
|     feed('<F8>') | ||||
|     screen:expect([[ | ||||
|       so{5:me short lines}                                                 | | ||||
|       {5:of }^test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- (insert) SELECT --}                                            | | ||||
|     ]]) | ||||
|     eq('s', eval('mode(1)')) | ||||
|     feed('<esc>') | ||||
|     eq('i', eval('mode(1)')) | ||||
|   end) | ||||
|  | ||||
|  | ||||
|   it('works in operator-pending mode', function() | ||||
|     feed('d<F4>') | ||||
|     expect([[ | ||||
|         lines | ||||
|         of test text]]) | ||||
|     eq({'some short '}, funcs.getreg('"',1,1)) | ||||
|     feed('.') | ||||
|     expect([[ | ||||
|         test text]]) | ||||
|     eq({'lines', 'of '}, funcs.getreg('"',1,1)) | ||||
|     feed('uu') | ||||
|     expect([[ | ||||
|         some short lines | ||||
|         of test text]]) | ||||
|  | ||||
|     -- error aborts operator-pending, operator not performed | ||||
|     feed('d<F6>') | ||||
|     screen:expect([[ | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:Error detected while processing :}                                | | ||||
|       {2:E605: Exception not caught: very error}                           | | ||||
|       {3:Press ENTER or type command to continue}^                          | | ||||
|     ]]) | ||||
|     feed('<cr>') | ||||
|     eq('E605: Exception not caught: very error', eval('v:errmsg')) | ||||
|     expect([[ | ||||
|         some short lines | ||||
|         of test text]]) | ||||
|  | ||||
|     feed('"bd<F7>') | ||||
|     expect([[ | ||||
|         soest text]]) | ||||
|     eq(funcs.getreg('b',1,1), {'me short lines', 'of t'}) | ||||
|  | ||||
|     -- startinsert aborts operator | ||||
|     feed('d<F8>') | ||||
|     eq('i', eval('mode(1)')) | ||||
|     expect([[ | ||||
|         soest text]]) | ||||
|   end) | ||||
|  | ||||
|   it('works in insert mode', function() | ||||
|  | ||||
|     -- works the same as <c-o>w<c-o>w | ||||
|     feed('iindeed <F4>little ') | ||||
|     screen:expect([[ | ||||
|       indeed some short little ^lines                                   | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- INSERT --}                                                     | | ||||
|     ]]) | ||||
|  | ||||
|     feed('<F6>') | ||||
|     screen:expect([[ | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:Error detected while processing :}                                | | ||||
|       {2:E605: Exception not caught: very error}                           | | ||||
|       {3:Press ENTER or type command to continue}^                          | | ||||
|     ]]) | ||||
|  | ||||
|  | ||||
|     feed('<cr>') | ||||
|     eq('E605: Exception not caught: very error', eval('v:errmsg')) | ||||
|     -- still in insert | ||||
|     screen:expect([[ | ||||
|       indeed some short little ^lines                                   | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- INSERT --}                                                     | | ||||
|     ]]) | ||||
|     eq('i', eval('mode(1)')) | ||||
|  | ||||
|     -- When entering visual mode from InsertEnter autocmd, an async event, or | ||||
|     -- a <cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode. If a | ||||
|     -- vim patch decides to disable this mode, this test is expected to fail. | ||||
|     feed('<F7>stuff ') | ||||
|     screen:expect([[ | ||||
|       in{5:deed some short little lines}                                   | | ||||
|       {5:of stuff }^test text                                               | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- INSERT VISUAL --}                                              | | ||||
|     ]]) | ||||
|     expect([[ | ||||
|       indeed some short little lines | ||||
|       of stuff test text]]) | ||||
|  | ||||
|     feed('<F5>') | ||||
|     eq(funcs.getreg('a',1,1), {'deed some short little lines', 'of stuff t'}) | ||||
|  | ||||
|     -- still in insert | ||||
|     screen:expect([[ | ||||
|       in^deed some short little lines                                   | | ||||
|       of stuff test text                                               | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- INSERT --}                                                     | | ||||
|     ]]) | ||||
|     eq('i', eval('mode(1)')) | ||||
|  | ||||
|     -- also works as part of abbreviation | ||||
|     feed('<space>foo ') | ||||
|     screen:expect([[ | ||||
|       in bar ^deed some short little lines                              | | ||||
|       of stuff test text                                               | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- INSERT --}                                                     | | ||||
|     ]]) | ||||
|     eq(17, eval('g:y')) | ||||
|  | ||||
|     -- :startinsert does nothing | ||||
|     feed('<F8>') | ||||
|     eq('i', eval('mode(1)')) | ||||
|  | ||||
|     -- :stopinsert works | ||||
|     feed('<F9>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|   end) | ||||
|  | ||||
|   it('works in cmdline mode', function() | ||||
|     cmdmap('<F2>', 'call setcmdpos(2)') | ||||
|     feed(':text<F3>') | ||||
|     eq('c', eval('m')) | ||||
|     -- didn't leave cmdline mode | ||||
|     eq('c', eval('mode(1)')) | ||||
|     feed('<cr>') | ||||
|     eq('n', eval('mode(1)')) | ||||
|     screen:expect([[ | ||||
|       ^some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {2:E492: Not an editor command: text}                                | | ||||
|     ]]) | ||||
|  | ||||
|     feed(':echo 2<F6>') | ||||
|     screen:expect([[ | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       :echo 2                                                          | | ||||
|       {2:Error detected while processing :}                                | | ||||
|       {2:E605: Exception not caught: very error}                           | | ||||
|       :echo 2^                                                          | | ||||
|     ]]) | ||||
|     eq('E605: Exception not caught: very error', eval('v:errmsg')) | ||||
|     -- didn't leave cmdline mode | ||||
|     eq('c', eval('mode(1)')) | ||||
|     feed('+2<cr>') | ||||
|     screen:expect([[ | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       :echo 2                                                          | | ||||
|       {2:Error detected while processing :}                                | | ||||
|       {2:E605: Exception not caught: very error}                           | | ||||
|       4                                                                | | ||||
|       {3:Press ENTER or type command to continue}^                          | | ||||
|     ]]) | ||||
|     -- however, message scrolling may cause extra CR prompt | ||||
|     -- This is consistent with output from async events. | ||||
|     feed('<cr>') | ||||
|     screen:expect([[ | ||||
|       ^some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|                                                                        | | ||||
|     ]]) | ||||
|     eq('n', eval('mode(1)')) | ||||
|  | ||||
|     feed(':let g:x = 3<F4>') | ||||
|     screen:expect([[ | ||||
|       some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       :let g:x = 3^                                                     | | ||||
|     ]]) | ||||
|     feed('+2<cr>') | ||||
|     -- cursor was moved in the background | ||||
|     screen:expect([[ | ||||
|       some short ^lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       :let g:x = 3+2                                                   | | ||||
|     ]]) | ||||
|     eq(5, eval('g:x')) | ||||
|  | ||||
|     feed(':let g:y = 7<F8>') | ||||
|     screen:expect([[ | ||||
|       some short lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       :let g:y = 7^                                                     | | ||||
|     ]]) | ||||
|     eq('c', eval('mode(1)')) | ||||
|     feed('+2<cr>') | ||||
|     -- startinsert takes effect after leaving cmdline mode | ||||
|     screen:expect([[ | ||||
|       some short ^lines                                                 | | ||||
|       of test text                                                     | | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {1:~                                                                }| | ||||
|       {4:-- INSERT --}                                                     | | ||||
|     ]]) | ||||
|     eq('i', eval('mode(1)')) | ||||
|     eq(9, eval('g:y')) | ||||
|  | ||||
|   end) | ||||
|  | ||||
| end) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Justin M. Keyes
					Justin M. Keyes