aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/map.txt27
-rw-r--r--src/nvim/edit.c4
-rw-r--r--src/nvim/ex_docmd.c4
-rw-r--r--src/nvim/ex_getln.c8
-rw-r--r--src/nvim/getchar.c67
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/keymap.c1
-rw-r--r--src/nvim/keymap.h2
-rw-r--r--src/nvim/normal.c32
-rw-r--r--src/nvim/screen.c14
-rw-r--r--src/nvim/terminal.c4
-rw-r--r--test/functional/ex_cmds/cmd_map_spec.lua771
12 files changed, 916 insertions, 24 deletions
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 9b61fa6527..038e90fab0 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -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*
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index cf4328fe0a..b772a944f4 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -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:
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index eb6d7ad815..99495aaa61 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -8156,6 +8156,10 @@ static void ex_startinsert(exarg_T *eap)
restart_edit = 'i';
curwin->w_curswant = 0; /* avoid MAXCOL */
}
+
+ if (VIsual_active) {
+ showmode();
+ }
}
/*
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 9601e0f3b2..698419405a 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -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) {
- multiqueue_process_events(main_loop.events);
+ 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;
}
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 7df1bf8429..5d4e61d56a 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -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;
+}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 2db22f6cbf..e857f5ff5b 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -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"));
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index 5e56e5f41b..628bfef221 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -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.
};
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index 04fc93e29e..00e9cf6ed3 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -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 */
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index fbbc8248e8..e4310de5d8 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -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,9 +4554,9 @@ 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,
- cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
+ // 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 */
if (p_im != old_p_im) {
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index e624acaed5..22de08041a 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -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);
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index c5beec5a34..276b47536f 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -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;
diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua
new file mode 100644
index 0000000000..5b7f0942b1
--- /dev/null
+++ b/test/functional/ex_cmds/cmd_map_spec.lua
@@ -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)
+