0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 8.2.1978: making a mapping work in all modes is complicated

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)
This commit is contained in:
Bram Moolenaar 2020-11-12 14:21:06 +01:00
parent ea2d407f9c
commit 957cf67d50
19 changed files with 646 additions and 17 deletions

View File

@ -551,12 +551,15 @@ CmdlineChanged After a change was made to the text in the
*CmdlineEnter* *CmdlineEnter*
CmdlineEnter After moving the cursor to the command line, CmdlineEnter After moving the cursor to the command line,
where the user can type a command or search where the user can type a command or search
string. string; including non-interactive use of ":"
in a mapping, but not when using |<Cmd>|.
<afile> is set to a single character, <afile> is set to a single character,
indicating the type of command-line. indicating the type of command-line.
|cmdwin-char| |cmdwin-char|
*CmdlineLeave* *CmdlineLeave*
CmdlineLeave Before leaving the command line. CmdlineLeave Before leaving the command line; including
non-interactive use of ":" in a mapping, but
not when using |<Cmd>|.
Also when abandoning the command line, after Also when abandoning the command line, after
typing CTRL-C or <Esc>. typing CTRL-C or <Esc>.
When the commands result in an error the When the commands result in an error the

View File

@ -8660,6 +8660,7 @@ screencol() *screencol()*
the following mappings: > the following mappings: >
nnoremap <expr> GG ":echom ".screencol()."\n" nnoremap <expr> GG ":echom ".screencol()."\n"
nnoremap <silent> GG :echom screencol()<CR> nnoremap <silent> GG :echom screencol()<CR>
nnoremap GG <Cmd>echom screencol()<CR>
< <
screenpos({winid}, {lnum}, {col}) *screenpos()* screenpos({winid}, {lnum}, {col}) *screenpos()*
The result is a Dict with the screen position of the text The result is a Dict with the screen position of the text

View File

@ -1,4 +1,4 @@
*map.txt* For Vim version 8.2. Last change: 2020 Oct 07 *map.txt* For Vim version 8.2. Last change: 2020 Nov 12
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -271,7 +271,7 @@ For this reason the following is blocked:
- The |:normal| command. - The |:normal| command.
- Moving the cursor is allowed, but it is restored afterwards. - 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 If you want the mapping to do any of these let the returned characters do
that. that, or use a |<Cmd>| mapping instead.
You can use getchar(), it consumes typeahead if there is any. E.g., if you You can use getchar(), it consumes typeahead if there is any. E.g., if you
have these mappings: > have these mappings: >
@ -303,6 +303,40 @@ empty string, so that nothing is inserted.
Note that using 0x80 as a single byte before other text does not work, it will Note that using 0x80 as a single byte before other text does not work, it will
be seen as a special key. 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* 1.3 MAPPING AND MODES *:map-modes*
*mapmode-nvo* *mapmode-n* *mapmode-v* *mapmode-o* *mapmode-nvo* *mapmode-n* *mapmode-v* *mapmode-o*

View File

@ -1031,6 +1031,10 @@ doESCkey:
case K_IGNORE: // Something mapped to nothing case K_IGNORE: // Something mapped to nothing
break; break;
case K_COMMAND: // <Cmd>command<CR>
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
break;
case K_CURSORHOLD: // Didn't type something for a while. case K_CURSORHOLD: // Didn't type something for a while.
ins_apply_autocmds(EVENT_CURSORHOLDI); ins_apply_autocmds(EVENT_CURSORHOLDI);
did_cursorhold = TRUE; did_cursorhold = TRUE;

View File

@ -295,3 +295,9 @@ EXTERN char e_cannot_extend_null_list[]
EXTERN char e_using_string_as_bool_str[] EXTERN char e_using_string_as_bool_str[]
INIT(= N_("E1135: Using a String as a Bool: \"%s\"")); INIT(= N_("E1135: Using a String as a Bool: \"%s\""));
#endif #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"));

View File

@ -8148,6 +8148,9 @@ ex_startinsert(exarg_T *eap)
restart_edit = 'i'; restart_edit = 'i';
curwin->w_curswant = 0; // avoid MAXCOL curwin->w_curswant = 0; // avoid MAXCOL
} }
if (VIsual_active)
showmode();
} }
/* /*

View File

@ -1711,6 +1711,10 @@ getcmdline_int(
c = safe_vgetc(); c = safe_vgetc();
while (c == K_IGNORE || c == K_NOP); while (c == K_IGNORE || c == K_NOP);
if (c == K_COMMAND
&& do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT) == OK)
goto cmdline_changed;
if (KeyTyped) if (KeyTyped)
{ {
some_key_typed = TRUE; some_key_typed = TRUE;

View File

@ -3619,3 +3619,96 @@ input_available(void)
); );
} }
#endif #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;
}

View File

@ -1822,7 +1822,7 @@ ins_compl_prep(int c)
// Ignore end of Select mode mapping and mouse scroll buttons. // Ignore end of Select mode mapping and mouse scroll buttons.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSELEFT || c == K_MOUSERIGHT) || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_COMMAND)
return retval; return retval;
#ifdef FEAT_PROP_POPUP #ifdef FEAT_PROP_POPUP

View File

@ -274,6 +274,7 @@ enum key_extra
, KE_FOCUSLOST = 99 // focus lost , KE_FOCUSLOST = 99 // focus lost
, KE_MOUSEMOVE = 100 // mouse moved with no button down , KE_MOUSEMOVE = 100 // mouse moved with no button down
, KE_CANCEL = 101 // return from vgetc() , KE_CANCEL = 101 // return from vgetc()
, KE_COMMAND = 102 // <Cmd> special key
}; };
/* /*
@ -449,11 +450,11 @@ enum key_extra
#define K_RIGHTMOUSE TERMCAP2KEY(KS_EXTRA, KE_RIGHTMOUSE) #define K_RIGHTMOUSE TERMCAP2KEY(KS_EXTRA, KE_RIGHTMOUSE)
#define K_RIGHTDRAG TERMCAP2KEY(KS_EXTRA, KE_RIGHTDRAG) #define K_RIGHTDRAG TERMCAP2KEY(KS_EXTRA, KE_RIGHTDRAG)
#define K_RIGHTRELEASE TERMCAP2KEY(KS_EXTRA, KE_RIGHTRELEASE) #define K_RIGHTRELEASE TERMCAP2KEY(KS_EXTRA, KE_RIGHTRELEASE)
#define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE) #define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE)
#define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE) #define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE)
#define K_X1DRAG TERMCAP2KEY(KS_EXTRA, KE_X1DRAG) #define K_X1DRAG TERMCAP2KEY(KS_EXTRA, KE_X1DRAG)
#define K_X1RELEASE TERMCAP2KEY(KS_EXTRA, KE_X1RELEASE) #define K_X1RELEASE TERMCAP2KEY(KS_EXTRA, KE_X1RELEASE)
#define K_X2MOUSE TERMCAP2KEY(KS_EXTRA, KE_X2MOUSE) #define K_X2MOUSE TERMCAP2KEY(KS_EXTRA, KE_X2MOUSE)
#define K_X2DRAG TERMCAP2KEY(KS_EXTRA, KE_X2DRAG) #define K_X2DRAG TERMCAP2KEY(KS_EXTRA, KE_X2DRAG)
#define K_X2RELEASE TERMCAP2KEY(KS_EXTRA, KE_X2RELEASE) #define K_X2RELEASE TERMCAP2KEY(KS_EXTRA, KE_X2RELEASE)
@ -477,6 +478,8 @@ enum key_extra
#define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD) #define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
// Bits for modifier mask // Bits for modifier mask
// 0x01 cannot be used, because the modifier must be 0x02 or higher // 0x01 cannot be used, because the modifier must be 0x02 or higher
#define MOD_MASK_SHIFT 0x02 #define MOD_MASK_SHIFT 0x02

View File

@ -1639,8 +1639,7 @@ eval_map_expr(
* Returns NULL when out of memory. * Returns NULL when out of memory.
*/ */
char_u * char_u *
vim_strsave_escape_csi( vim_strsave_escape_csi(char_u *p)
char_u *p)
{ {
char_u *res; char_u *res;
char_u *s, *d; char_u *s, *d;

View File

@ -2530,6 +2530,7 @@ static struct key_name_entry
{K_PLUG, (char_u *)"Plug"}, {K_PLUG, (char_u *)"Plug"},
{K_CURSORHOLD, (char_u *)"CursorHold"}, {K_CURSORHOLD, (char_u *)"CursorHold"},
{K_IGNORE, (char_u *)"Ignore"}, {K_IGNORE, (char_u *)"Ignore"},
{K_COMMAND, (char_u *)"Cmd"},
{0, NULL} {0, NULL}
// NOTE: When adding a long name update MAX_KEY_NAME_LEN. // NOTE: When adding a long name update MAX_KEY_NAME_LEN.
}; };

View File

@ -375,6 +375,7 @@ static const struct nv_cmd
#endif #endif
{K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0}, {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0},
{K_PS, nv_edit, 0, 0}, {K_PS, nv_edit, 0, 0},
{K_COMMAND, nv_colon, 0, 0},
}; };
// Number of commands in nv_cmds[]. // Number of commands in nv_cmds[].
@ -3312,10 +3313,11 @@ nv_exmode(cmdarg_T *cap)
static void static void
nv_colon(cmdarg_T *cap) nv_colon(cmdarg_T *cap)
{ {
int old_p_im; int old_p_im;
int cmd_result; int cmd_result;
int is_cmdkey = cap->cmdchar == K_COMMAND;
if (VIsual_active) if (VIsual_active && !is_cmdkey)
nv_operator(cap); nv_operator(cap);
else else
{ {
@ -3325,7 +3327,7 @@ nv_colon(cmdarg_T *cap)
cap->oap->motion_type = MCHAR; cap->oap->motion_type = MCHAR;
cap->oap->inclusive = FALSE; cap->oap->inclusive = FALSE;
} }
else if (cap->count0) else if (cap->count0 && !is_cmdkey)
{ {
// translate "count:" into ":.,.+(count - 1)" // translate "count:" into ":.,.+(count - 1)"
stuffcharReadbuff('.'); stuffcharReadbuff('.');
@ -3343,7 +3345,7 @@ nv_colon(cmdarg_T *cap)
old_p_im = p_im; old_p_im = p_im;
// get a command line and execute it // get a command line and execute it
cmd_result = do_cmdline(NULL, getexline, NULL, cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
// If 'insertmode' changed, enter or exit Insert mode // If 'insertmode' changed, enter or exit Insert mode

View File

@ -3490,7 +3490,7 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
AppendToRedobuffLit(cap->searchbuf, -1); AppendToRedobuffLit(cap->searchbuf, -1);
AppendToRedobuff(NL_STR); AppendToRedobuff(NL_STR);
} }
else if (cap->cmdchar == ':') else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
{ {
// do_cmdline() has stored the first typed line in // do_cmdline() has stored the first typed line in
// "repeat_cmdline". When several lines are typed repeating // "repeat_cmdline". When several lines are typed repeating

View File

@ -51,4 +51,5 @@ void parse_queued_messages(void);
void vungetc(int c); void vungetc(int c);
int fix_input_buffer(char_u *buf, int len); int fix_input_buffer(char_u *buf, int len);
int input_available(void); int input_available(void);
char_u *getcmdkeycmd(int promptc, void *cookie, int indent, getline_opt_T do_concat);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@ -4192,7 +4192,8 @@ showmode(void)
#endif #endif
msg_puts_attr(_(" INSERT"), attr); msg_puts_attr(_(" INSERT"), attr);
} }
else if (restart_edit == 'I' || restart_edit == 'A') else if (restart_edit == 'I' || restart_edit == 'i' ||
restart_edit == 'a' || restart_edit == 'A')
msg_puts_attr(_(" (insert)"), attr); msg_puts_attr(_(" (insert)"), attr);
else if (restart_edit == 'R') else if (restart_edit == 'R')
msg_puts_attr(_(" (replace)"), attr); msg_puts_attr(_(" (replace)"), attr);

View File

@ -2180,6 +2180,10 @@ send_keys_to_term(term_T *term, int c, int modmask, int typed)
return FAIL; return FAIL;
} }
} }
break;
case K_COMMAND:
return do_cmdline(NULL, getcmdkeycmd, NULL, 0);
} }
if (typed) if (typed)
mouse_was_outside = FALSE; mouse_was_outside = FALSE;

View File

@ -3,6 +3,7 @@
source shared.vim source shared.vim
source check.vim source check.vim
source screendump.vim source screendump.vim
source term_util.vim
func Test_abbreviation() func Test_abbreviation()
" abbreviation with 0x80 should work " abbreviation with 0x80 should work
@ -856,4 +857,471 @@ func Test_map_cpo_special_keycode()
mapclear! mapclear!
endfunc 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: shiftwidth=2 sts=2 expandtab

View File

@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1978,
/**/ /**/
1977, 1977,
/**/ /**/