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

View File

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

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
@ -271,7 +271,7 @@ For this reason the following is blocked:
- The |:normal| command.
- Moving the cursor is allowed, but it is restored afterwards.
If you want the mapping to do any of these let the returned characters do
that.
that, or use a |<Cmd>| mapping instead.
You can use getchar(), it consumes typeahead if there is any. E.g., if you
have these mappings: >
@ -303,6 +303,40 @@ empty string, so that nothing is inserted.
Note that using 0x80 as a single byte before other text does not work, it will
be seen as a special key.
*<Cmd>* *:map-cmd*
The special text <Cmd> begins a "command mapping", it executes the command
directly without changing modes. Where you might use ":...<CR>" in the
{rhs} of a mapping, you can instead use "<Cmd>...<CR>".
Example: >
noremap x <Cmd>echo mode(1)<CR>
<
This is more flexible than `:<C-U>` in Visual and Operator-pending mode, or
`<C-O>:` in Insert mode, because the commands are executed directly in the
current mode, instead of always going to Normal mode. Visual mode is
preserved, so tricks with |gv| are not needed. Commands can be invoked
directly in Command-line mode (which would otherwise require timer hacks).
Example of using <Cmd> halfway Insert mode: >
nnoremap <F3> aText <Cmd>echo mode(1)<CR> Added<Esc>
Unlike <expr> mappings, there are no special restrictions on the <Cmd>
command: it is executed as if an (unrestricted) |autocmd| was invoked.
Note:
- Because <Cmd> avoids mode-changes it does not trigger |CmdlineEnter| and
|CmdlineLeave| events, because no user interaction is expected.
- For the same reason, |keycodes| like <C-R><C-W> are interpreted as plain,
unmapped keys.
- In Select mode, |:map| and |:vmap| command mappings are executed in
Visual mode. Use |:smap| to handle Select mode differently.
*E1135* *E1136*
<Cmd> commands must terminate, that is, they must be followed by <CR> in the
{rhs} of the mapping definition. |Command-line| mode is never entered.
*E1137*
<Cmd> commands can have only normal characters and cannot contain special
characters like function keys.
1.3 MAPPING AND MODES *:map-modes*
*mapmode-nvo* *mapmode-n* *mapmode-v* *mapmode-o*

View File

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

View File

@ -295,3 +295,9 @@ EXTERN char e_cannot_extend_null_list[]
EXTERN char e_using_string_as_bool_str[]
INIT(= N_("E1135: Using a String as a Bool: \"%s\""));
#endif
EXTERN char e_cmd_mapping_must_end_with_cr[]
INIT(=N_("E1135: <Cmd> mapping must end with <CR>"));
EXTERN char e_cmd_mapping_must_end_with_cr_before_second_cmd[]
INIT(=N_("E1136: <Cmd> mapping must end with <CR> before second <Cmd>"));
EXTERN char e_cmd_maping_must_not_include_str_key[]
INIT(= N_("E1137: <Cmd> mapping must not include %s key"));

View File

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

View File

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

View File

@ -3619,3 +3619,96 @@ input_available(void)
);
}
#endif
/*
* Function passed to do_cmdline() to get the command after a <Cmd> key from
* typeahead.
*/
char_u *
getcmdkeycmd(
int promptc UNUSED,
void *cookie UNUSED,
int indent UNUSED,
getline_opt_T do_concat UNUSED)
{
garray_T line_ga;
int c1 = -1;
int c2;
int cmod = 0;
int aborted = FALSE;
ga_init2(&line_ga, 1, 32);
// no mapping for these characters
no_mapping++;
got_int = FALSE;
while (c1 != NUL && !aborted)
{
ga_grow(&line_ga, 32);
if (vgetorpeek(FALSE) == NUL)
{
// incomplete <Cmd> is an error, because there is not much the user
// could do in this state.
emsg(_(e_cmd_mapping_must_end_with_cr));
aborted = TRUE;
break;
}
// Get one character at a time.
c1 = vgetorpeek(TRUE);
// Get two extra bytes for special keys
if (c1 == K_SPECIAL)
{
c1 = vgetorpeek(TRUE);
c2 = vgetorpeek(TRUE);
if (c1 == KS_MODIFIER)
{
cmod = c2;
continue;
}
c1 = TO_SPECIAL(c1, c2);
}
if (got_int)
aborted = TRUE;
else if (c1 == '\r' || c1 == '\n')
c1 = NUL; // end the line
else if (c1 == ESC)
aborted = TRUE;
else if (c1 == K_COMMAND)
{
// give a nicer error message for this special case
emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd));
aborted = TRUE;
}
else if (IS_SPECIAL(c1))
{
if (c1 == K_SNR)
{
ga_append(&line_ga, (char)K_SPECIAL);
ga_append(&line_ga, (char)KS_EXTRA);
ga_append(&line_ga, (char)KE_SNR);
}
else
{
semsg(e_cmd_maping_must_not_include_str_key,
get_special_key_name(c1, cmod));
aborted = TRUE;
}
}
else
ga_append(&line_ga, (char)c1);
cmod = 0;
}
no_mapping--;
if (aborted)
ga_clear(&line_ga);
return (char_u *)line_ga.ga_data;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
source shared.vim
source check.vim
source screendump.vim
source term_util.vim
func Test_abbreviation()
" abbreviation with 0x80 should work
@ -856,4 +857,471 @@ func Test_map_cpo_special_keycode()
mapclear!
endfunc
" Test for <Cmd> key in maps to execute commands
func Test_map_cmdkey()
new
" Error cases
let x = 0
noremap <F3> <Cmd><Cmd>let x = 1<CR>
call assert_fails('call feedkeys("\<F3>", "xt")', 'E1136:')
call assert_equal(0, x)
noremap <F3> <Cmd><F3>let x = 2<CR>
call assert_fails('call feedkeys("\<F3>", "xt")', 'E1137:')
call assert_equal(0, x)
noremap <F3> <Cmd>let x = 3
call assert_fails('call feedkeys("\<F3>", "xt!")', 'E1135:')
call assert_equal(0, x)
" works in various modes and sees the correct mode()
noremap <F3> <Cmd>let m = mode(1)<CR>
noremap! <F3> <Cmd>let m = mode(1)<CR>
" normal mode
call feedkeys("\<F3>", 'xt')
call assert_equal('n', m)
" visual mode
call feedkeys("v\<F3>", 'xt!')
call assert_equal('v', m)
" shouldn't leave the visual mode
call assert_equal('v', mode(1))
call feedkeys("\<Esc>", 'xt')
call assert_equal('n', mode(1))
" visual mapping in select mode
call feedkeys("gh\<F3>", 'xt!')
call assert_equal('v', m)
" shouldn't leave select mode
call assert_equal('s', mode(1))
call feedkeys("\<Esc>", 'xt')
call assert_equal('n', mode(1))
" select mode mapping
snoremap <F3> <Cmd>let m = mode(1)<cr>
call feedkeys("gh\<F3>", 'xt!')
call assert_equal('s', m)
" shouldn't leave select mode
call assert_equal('s', mode(1))
call feedkeys("\<Esc>", 'xt')
call assert_equal('n', mode(1))
" operator-pending mode
call feedkeys("d\<F3>", 'xt!')
call assert_equal('no', m)
" leaves the operator-pending mode
call assert_equal('n', mode(1))
" insert mode
call feedkeys("i\<F3>abc", 'xt')
call assert_equal('i', m)
call assert_equal('abc', getline('.'))
" replace mode
call feedkeys("0R\<F3>two", 'xt')
call assert_equal('R', m)
call assert_equal('two', getline('.'))
" virtual replace mode
call setline('.', "one\ttwo")
call feedkeys("4|gR\<F3>xxx", 'xt')
call assert_equal('Rv', m)
call assert_equal("onexxx\ttwo", getline('.'))
" cmdline mode
call feedkeys(":\<F3>\"xxx\<CR>", 'xt!')
call assert_equal('c', m)
call assert_equal('"xxx', @:)
" terminal mode
if CanRunVimInTerminal()
tnoremap <F3> <Cmd>let m = mode(1)<CR>
let buf = Run_shell_in_terminal({})
call feedkeys("\<F3>", 'xt')
call assert_equal('t', m)
call assert_equal('t', mode(1))
call StopShellInTerminal(buf)
call TermWait(buf)
close!
tunmap <F3>
endif
" invoke cmdline mode recursively
noremap! <F2> <Cmd>norm! :foo<CR>
%d
call setline(1, ['some short lines', 'of test text'])
call feedkeys(":bar\<F2>x\<C-B>\"\r", 'xt')
call assert_equal('"barx', @:)
unmap! <F2>
" test for calling a <SID> function
let lines =<< trim END
map <F2> <Cmd>call <SID>do_it()<CR>
func s:do_it()
let g:x = 32
endfunc
END
call writefile(lines, 'Xscript')
source Xscript
call feedkeys("\<F2>", 'xt')
call assert_equal(32, g:x)
call delete('Xscript')
unmap <F3>
unmap! <F3>
%bw!
endfunc
" text object enters visual mode
func TextObj()
if mode() !=# "v"
normal! v
end
call cursor(1, 3)
normal! o
call cursor(2, 4)
endfunc
func s:cmdmap(lhs, rhs)
exe 'noremap ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
exe 'noremap! ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
endfunc
func s:cmdunmap(lhs)
exe 'unmap ' .. a:lhs
exe 'unmap! ' .. a:lhs
endfunc
" Map various <Fx> keys used by the <Cmd> key tests
func s:setupMaps()
call s:cmdmap('<F3>', 'let m = mode(1)')
call s:cmdmap('<F4>', 'normal! ww')
call s:cmdmap('<F5>', 'normal! "ay')
call s:cmdmap('<F6>', 'throw "very error"')
call s:cmdmap('<F7>', 'call TextObj()')
call s:cmdmap('<F8>', 'startinsert')
call s:cmdmap('<F9>', 'stopinsert')
endfunc
" Remove the mappings setup by setupMaps()
func s:cleanupMaps()
call s:cmdunmap('<F3>')
call s:cmdunmap('<F4>')
call s:cmdunmap('<F5>')
call s:cmdunmap('<F6>')
call s:cmdunmap('<F7>')
call s:cmdunmap('<F8>')
call s:cmdunmap('<F9>')
endfunc
" Test for <Cmd> mapping in normal mode
func Test_map_cmdkey_normal_mode()
new
call s:setupMaps()
" check v:count and v:register works
call s:cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]')
call feedkeys("\<F2>", 'xt')
call assert_equal(['n', 0, '"'], s)
call feedkeys("7\<F2>", 'xt')
call assert_equal(['n', 7, '"'], s)
call feedkeys("\"e\<F2>", 'xt')
call assert_equal(['n', 0, 'e'], s)
call feedkeys("5\"k\<F2>", 'xt')
call assert_equal(['n', 5, 'k'], s)
call s:cmdunmap('<F2>')
call setline(1, ['some short lines', 'of test text'])
call feedkeys("\<F7>y", 'xt')
call assert_equal("me short lines\nof t", @")
call assert_equal('v', getregtype('"'))
call assert_equal([0, 1, 3, 0], getpos("'<"))
call assert_equal([0, 2, 4, 0], getpos("'>"))
" startinsert
%d
call feedkeys("\<F8>abc", 'xt')
call assert_equal('abc', getline(1))
" feedkeys are not executed immediately
noremap ,a <Cmd>call feedkeys("aalpha") \| let g:a = getline(2)<CR>
%d
call setline(1, ['some short lines', 'of test text'])
call cursor(2, 3)
call feedkeys(",a\<F3>", 'xt')
call assert_equal('of test text', g:a)
call assert_equal('n', m)
call assert_equal(['some short lines', 'of alphatest text'], getline(1, '$'))
nunmap ,a
" feedkeys(..., 'x') is executed immediately, but insert mode is aborted
noremap ,b <Cmd>call feedkeys("abeta", 'x') \| let g:b = getline(2)<CR>
call feedkeys(",b\<F3>", 'xt')
call assert_equal('n', m)
call assert_equal('of alphabetatest text', g:b)
nunmap ,b
call s:cleanupMaps()
%bw!
endfunc
" Test for <Cmd> mapping with the :normal command
func Test_map_cmdkey_normal_cmd()
new
noremap ,x <Cmd>call append(1, "xx") \| call append(1, "aa")<CR>
noremap ,f <Cmd>nosuchcommand<CR>
noremap ,e <Cmd>throw "very error" \| call append(1, "yy")<CR>
noremap ,m <Cmd>echoerr "The message." \| call append(1, "zz")<CR>
noremap ,w <Cmd>for i in range(5) \| if i==1 \| echoerr "Err" \| endif \| call append(1, i) \| endfor<CR>
call setline(1, ['some short lines', 'of test text'])
exe "norm ,x\r"
call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
call assert_fails('norm ,f', 'E492:')
call assert_fails('norm ,e', 'very error')
call assert_fails('norm ,m', 'The message.')
call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
%d
let caught_err = 0
try
exe "normal ,w"
catch /Vim(echoerr):Err/
let caught_err = 1
endtry
call assert_equal(1, caught_err)
call assert_equal(['', '0'], getline(1, '$'))
%d
call assert_fails('normal ,w', 'Err')
call assert_equal(['', '4', '3', '2' ,'1', '0'], getline(1, '$'))
call assert_equal(1, line('.'))
nunmap ,x
nunmap ,f
nunmap ,e
nunmap ,m
nunmap ,w
%bw!
endfunc
" Test for <Cmd> mapping in visual mode
func Test_map_cmdkey_visual_mode()
new
set showmode
call s:setupMaps()
call setline(1, ['some short lines', 'of test text'])
call feedkeys("v\<F4>", 'xt!')
call assert_equal(['v', 1, 12], [mode(1), col('v'), col('.')])
" can invoke an opeartor, ending the visual mode
let @a = ''
call feedkeys("\<F5>", 'xt!')
call assert_equal('n', mode(1))
call assert_equal('some short l', @a)
" error doesn't interrupt visual mode
call assert_fails('call feedkeys("ggvw\<F6>", "xt!")', 'E605:')
call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
call feedkeys("\<F7>", 'xt!')
call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
" startinsert gives "-- (insert) VISUAL --" mode
call feedkeys("\<F8>", 'xt!')
call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
redraw!
call assert_match('^-- (insert) VISUAL --', Screenline(&lines))
call feedkeys("\<Esc>new ", 'x')
call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
call s:cleanupMaps()
set showmode&
%bw!
endfunc
" Test for <Cmd> mapping in select mode
func Test_map_cmdkey_select_mode()
new
set showmode
call s:setupMaps()
snoremap <F1> <cmd>throw "very error"<CR>
snoremap <F2> <cmd>normal! <c-g>"by<CR>
call setline(1, ['some short lines', 'of test text'])
call feedkeys("gh\<F4>", "xt!")
call assert_equal(['s', 1, 12], [mode(1), col('v'), col('.')])
redraw!
call assert_match('^-- SELECT --', Screenline(&lines))
" visual mapping in select mode restarts select mode after operator
let @a = ''
call feedkeys("\<F5>", 'xt!')
call assert_equal('s', mode(1))
call assert_equal('some short l', @a)
" select mode mapping works, and does not restart select mode
let @b = ''
call feedkeys("\<F2>", 'xt!')
call assert_equal('n', mode(1))
call assert_equal('some short l', @b)
" error doesn't interrupt temporary visual mode
call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F6>", "xt!")', 'E605:')
redraw!
call assert_match('^-- VISUAL --', Screenline(&lines))
" quirk: restoration of select mode is not performed
call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
" error doesn't interrupt select mode
call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F1>", "xt!")', 'E605:')
redraw!
call assert_match('^-- SELECT --', Screenline(&lines))
call assert_equal(['s', 1, 6], [mode(1), col('v'), col('.')])
call feedkeys("\<F7>", 'xt!')
redraw!
call assert_match('^-- SELECT --', Screenline(&lines))
call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
" startinsert gives "-- SELECT (insert) --" mode
call feedkeys("\<F8>", 'xt!')
redraw!
call assert_match('^-- (insert) SELECT --', Screenline(&lines))
call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
call feedkeys("\<Esc>new ", 'x')
call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
sunmap <F1>
sunmap <F2>
call s:cleanupMaps()
set showmode&
%bw!
endfunc
" Test for <Cmd> mapping in operator-pending mode
func Test_map_cmdkey_op_pending_mode()
new
call s:setupMaps()
call setline(1, ['some short lines', 'of test text'])
call feedkeys("d\<F4>", 'xt')
call assert_equal(['lines', 'of test text'], getline(1, '$'))
call assert_equal(['some short '], getreg('"', 1, 1))
" create a new undo point
let &undolevels = &undolevels
call feedkeys(".", 'xt')
call assert_equal(['test text'], getline(1, '$'))
call assert_equal(['lines', 'of '], getreg('"', 1, 1))
" create a new undo point
let &undolevels = &undolevels
call feedkeys("uu", 'xt')
call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
" error aborts operator-pending, operator not performed
call assert_fails('call feedkeys("d\<F6>", "xt")', 'E605:')
call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
call feedkeys("\"bd\<F7>", 'xt')
call assert_equal(['soest text'], getline(1, '$'))
call assert_equal(['me short lines', 'of t'], getreg('b', 1, 1))
" startinsert aborts operator
call feedkeys("d\<F8>cc", 'xt')
call assert_equal(['soccest text'], getline(1, '$'))
call s:cleanupMaps()
%bw!
endfunc
" Test for <Cmd> mapping in insert mode
func Test_map_cmdkey_insert_mode()
new
call s:setupMaps()
call setline(1, ['some short lines', 'of test text'])
" works the same as <C-O>w<C-O>w
call feedkeys("iindeed \<F4>little ", 'xt')
call assert_equal(['indeed some short little lines', 'of test text'], getline(1, '$'))
call assert_fails('call feedkeys("i\<F6> 2", "xt")', 'E605:')
call assert_equal(['indeed some short little 2 lines', 'of test text'], getline(1, '$'))
" Note when entering visual mode from InsertEnter autocmd, an async event,
" or a <Cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode.
call feedkeys("i\<F7>stuff ", 'xt')
call assert_equal(['indeed some short little 2 lines', 'of stuff test text'], getline(1, '$'))
call assert_equal(['v', 1, 3, 2, 9], [mode(1), line('v'), col('v'), line('.'), col('.')])
call feedkeys("\<F5>", 'xt')
call assert_equal(['deed some short little 2 lines', 'of stuff '], getreg('a', 1, 1))
" also works as part of abbreviation
abbr foo <Cmd>let g:y = 17<CR>bar
exe "normal i\<space>foo "
call assert_equal(17, g:y)
call assert_equal('in bar deed some short little 2 lines', getline(1))
unabbr foo
" :startinsert does nothing
call setline(1, 'foo bar')
call feedkeys("ggi\<F8>vim", 'xt')
call assert_equal('vimfoo bar', getline(1))
" :stopinsert works
call feedkeys("ggi\<F9>Abc", 'xt')
call assert_equal('vimfoo barbc', getline(1))
call s:cleanupMaps()
%bw!
endfunc
" Test for <Cmd> mapping in insert-completion mode
func Test_map_cmdkey_insert_complete_mode()
new
call s:setupMaps()
call setline(1, 'some short lines')
call feedkeys("os\<C-X>\<C-N>\<F3>\<C-N> ", 'xt')
call assert_equal('ic', m)
call assert_equal(['some short lines', 'short '], getline(1, '$'))
call s:cleanupMaps()
%bw!
endfunc
" Test for <Cmd> mapping in cmdline mode
func Test_map_cmdkey_cmdline_mode()
new
call s:setupMaps()
call setline(1, ['some short lines', 'of test text'])
let x = 0
call feedkeys(":let x\<F3>= 10\r", 'xt')
call assert_equal('c', m)
call assert_equal(10, x)
" exception doesn't leave cmdline mode
call assert_fails('call feedkeys(":let x\<F6>= 20\r", "xt")', 'E605:')
call assert_equal(20, x)
" move cursor in the buffer from cmdline mode
call feedkeys(":let x\<F4>= 30\r", 'xt')
call assert_equal(30, x)
call assert_equal(12, col('.'))
" :startinsert takes effect after leaving cmdline mode
call feedkeys(":let x\<F8>= 40\rnew ", 'xt')
call assert_equal(40, x)
call assert_equal('some short new lines', getline(1))
call s:cleanupMaps()
%bw!
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

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