To: vim_dev@googlegroups.com Subject: Patch 8.2.1978 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1978 Problem: Making a mapping work in all modes is complicated. Solution: Add the special key. (Yegappan Lakshmanan, closes #7282, closes 4784, based on patch by Bjorn Linse) Files: runtime/doc/autocmd.txt, runtime/doc/eval.txt, runtime/doc/map.txt, src/edit.c, src/errors.h, src/ex_docmd.c, src/ex_getln.c, src/getchar.c, src/insexpand.c, src/keymap.h, src/map.c, src/misc2.c, src/normal.c, src/ops.c, src/proto/getchar.pro, src/screen.c, src/terminal.c, src/testdir/test_mapping.vim *** ../vim-8.2.1977/runtime/doc/autocmd.txt 2020-10-21 12:19:50.080854732 +0200 --- runtime/doc/autocmd.txt 2020-11-12 13:28:53.302992440 +0100 *************** *** 551,562 **** *CmdlineEnter* CmdlineEnter After moving the cursor to the command line, where the user can type a command or search ! string. is set to a single character, indicating the type of command-line. |cmdwin-char| *CmdlineLeave* ! CmdlineLeave Before leaving the command line. Also when abandoning the command line, after typing CTRL-C or . When the commands result in an error the --- 551,565 ---- *CmdlineEnter* CmdlineEnter After moving the cursor to the command line, where the user can type a command or search ! string; including non-interactive use of ":" ! in a mapping, but not when using ||. is set to a single character, indicating the type of command-line. |cmdwin-char| *CmdlineLeave* ! CmdlineLeave Before leaving the command line; including ! non-interactive use of ":" in a mapping, but ! not when using ||. Also when abandoning the command line, after typing CTRL-C or . When the commands result in an error the *** ../vim-8.2.1977/runtime/doc/eval.txt 2020-11-09 18:31:30.544791868 +0100 --- runtime/doc/eval.txt 2020-11-12 13:23:14.947599257 +0100 *************** *** 8592,8597 **** --- 8662,8668 ---- the following mappings: > nnoremap GG ":echom ".screencol()."\n" nnoremap GG :echom screencol() + nnoremap GG echom screencol() < screenpos({winid}, {lnum}, {col}) *screenpos()* The result is a Dict with the screen position of the text *** ../vim-8.2.1977/runtime/doc/map.txt 2020-10-07 17:28:47.473370302 +0200 --- runtime/doc/map.txt 2020-11-12 13:56:22.455089311 +0100 *************** *** 254,260 **** - The |:normal| command. - Moving the cursor is allowed, but it is restored afterwards. If you want the mapping to do any of these let the returned characters do ! that. You can use getchar(), it consumes typeahead if there is any. E.g., if you have these mappings: > --- 271,277 ---- - The |:normal| command. - Moving the cursor is allowed, but it is restored afterwards. If you want the mapping to do any of these let the returned characters do ! that, or use a || mapping instead. You can use getchar(), it consumes typeahead if there is any. E.g., if you have these mappings: > *************** *** 283,297 **** CTRL-L inserts the next number, CTRL-R resets the count. CTRL-R returns an empty string, so that nothing is inserted. ! Note that there are some tricks to make special keys work and escape CSI bytes ! in the text. The |:map| command also does this, thus you must avoid that it ! is done twice. This does not work: > ! :imap "" ! Because the . This does work: > ! :imap "\u611B" ! Using 0x80 as a single byte before other text does not work, it will be seen ! as a special key. 1.3 MAPPING AND MODES *:map-modes* --- 300,341 ---- CTRL-L inserts the next number, CTRL-R resets the count. CTRL-R returns an empty string, so that nothing is inserted. ! Note that using 0x80 as a single byte before other text does not work, it will ! be seen as a special key. ! ! ** *:map-cmd* ! The special text begins a "command mapping", it executes the command ! directly without changing modes. Where you might use ":..." in the ! {rhs} of a mapping, you can instead use "...". ! Example: > ! noremap x echo mode(1) ! < ! This is more flexible than `:` in Visual and Operator-pending mode, or ! `:` 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 halfway Insert mode: > ! nnoremap aText echo mode(1) Added ! ! Unlike mappings, there are no special restrictions on the ! command: it is executed as if an (unrestricted) |autocmd| was invoked. ! ! Note: ! - Because avoids mode-changes it does not trigger |CmdlineEnter| and ! |CmdlineLeave| events, because no user interaction is expected. ! - For the same reason, |keycodes| like 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* ! commands must terminate, that is, they must be followed by in the ! {rhs} of the mapping definition. |Command-line| mode is never entered. ! ! *E1137* ! commands can have only normal characters and cannot contain special ! characters like function keys. 1.3 MAPPING AND MODES *:map-modes* *** ../vim-8.2.1977/src/edit.c 2020-11-11 20:52:36.970181354 +0100 --- src/edit.c 2020-11-12 13:56:56.759026220 +0100 *************** *** 1031,1036 **** --- 1031,1040 ---- case K_IGNORE: // Something mapped to nothing break; + case K_COMMAND: // command + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case K_CURSORHOLD: // Didn't type something for a while. ins_apply_autocmds(EVENT_CURSORHOLDI); did_cursorhold = TRUE; *** ../vim-8.2.1977/src/errors.h 2020-11-12 12:08:47.982254065 +0100 --- src/errors.h 2020-11-12 13:59:27.146751553 +0100 *************** *** 295,297 **** --- 295,303 ---- EXTERN char e_using_string_as_bool_str[] INIT(= N_("E1135: Using a String as a Bool: \"%s\"")); #endif + EXTERN char e_cmd_mapping_must_end_with_cr[] + INIT(=N_("E1135: mapping must end with ")); + EXTERN char e_cmd_mapping_must_end_with_cr_before_second_cmd[] + INIT(=N_("E1136: mapping must end with before second ")); + EXTERN char e_cmd_maping_must_not_include_str_key[] + INIT(= N_("E1137: mapping must not include %s key")); *** ../vim-8.2.1977/src/ex_docmd.c 2020-11-07 18:40:47.132725219 +0100 --- src/ex_docmd.c 2020-11-12 13:23:14.955599247 +0100 *************** *** 8148,8153 **** --- 8148,8156 ---- restart_edit = 'i'; curwin->w_curswant = 0; // avoid MAXCOL } + + if (VIsual_active) + showmode(); } /* *** ../vim-8.2.1977/src/ex_getln.c 2020-10-24 20:49:37.494683051 +0200 --- src/ex_getln.c 2020-11-12 14:00:20.286655120 +0100 *************** *** 1711,1716 **** --- 1711,1720 ---- c = safe_vgetc(); while (c == K_IGNORE || c == K_NOP); + if (c == K_COMMAND + && do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT) == OK) + goto cmdline_changed; + if (KeyTyped) { some_key_typed = TRUE; *** ../vim-8.2.1977/src/getchar.c 2020-11-04 11:03:08.376891850 +0100 --- src/getchar.c 2020-11-12 14:04:36.753604783 +0100 *************** *** 3619,3621 **** --- 3619,3714 ---- ); } #endif + + /* + * Function passed to do_cmdline() to get the command after a 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 is an error, because there is not much the user + // could do in this state. + emsg(_(e_cmd_mapping_must_end_with_cr)); + aborted = TRUE; + break; + } + + // Get one character at a time. + c1 = vgetorpeek(TRUE); + + // Get two extra bytes for special keys + if (c1 == K_SPECIAL) + { + c1 = vgetorpeek(TRUE); + c2 = vgetorpeek(TRUE); + if (c1 == KS_MODIFIER) + { + cmod = c2; + continue; + } + c1 = TO_SPECIAL(c1, c2); + } + + if (got_int) + aborted = TRUE; + else if (c1 == '\r' || c1 == '\n') + c1 = NUL; // end the line + else if (c1 == ESC) + aborted = TRUE; + else if (c1 == K_COMMAND) + { + // give a nicer error message for this special case + emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd)); + aborted = TRUE; + } + else if (IS_SPECIAL(c1)) + { + if (c1 == K_SNR) + { + ga_append(&line_ga, (char)K_SPECIAL); + ga_append(&line_ga, (char)KS_EXTRA); + ga_append(&line_ga, (char)KE_SNR); + } + else + { + semsg(e_cmd_maping_must_not_include_str_key, + get_special_key_name(c1, cmod)); + aborted = TRUE; + } + } + else + ga_append(&line_ga, (char)c1); + + cmod = 0; + } + + no_mapping--; + + if (aborted) + ga_clear(&line_ga); + + return (char_u *)line_ga.ga_data; + } *** ../vim-8.2.1977/src/insexpand.c 2020-10-28 20:19:56.372057081 +0100 --- src/insexpand.c 2020-11-12 13:23:14.959599239 +0100 *************** *** 1822,1828 **** // Ignore end of Select mode mapping and mouse scroll buttons. if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP ! || c == K_MOUSELEFT || c == K_MOUSERIGHT) return retval; #ifdef FEAT_PROP_POPUP --- 1822,1828 ---- // Ignore end of Select mode mapping and mouse scroll buttons. if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP ! || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_COMMAND) return retval; #ifdef FEAT_PROP_POPUP *** ../vim-8.2.1977/src/keymap.h 2019-11-30 18:40:33.000000000 +0100 --- src/keymap.h 2020-11-12 14:05:18.421372489 +0100 *************** *** 274,279 **** --- 274,280 ---- , KE_FOCUSLOST = 99 // focus lost , KE_MOUSEMOVE = 100 // mouse moved with no button down , KE_CANCEL = 101 // return from vgetc() + , KE_COMMAND = 102 // special key }; /* *************** *** 449,459 **** #define K_RIGHTMOUSE TERMCAP2KEY(KS_EXTRA, KE_RIGHTMOUSE) #define K_RIGHTDRAG TERMCAP2KEY(KS_EXTRA, KE_RIGHTDRAG) #define K_RIGHTRELEASE TERMCAP2KEY(KS_EXTRA, KE_RIGHTRELEASE) ! #define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE) ! #define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE) #define K_X1DRAG TERMCAP2KEY(KS_EXTRA, KE_X1DRAG) #define K_X1RELEASE TERMCAP2KEY(KS_EXTRA, KE_X1RELEASE) ! #define K_X2MOUSE TERMCAP2KEY(KS_EXTRA, KE_X2MOUSE) #define K_X2DRAG TERMCAP2KEY(KS_EXTRA, KE_X2DRAG) #define K_X2RELEASE TERMCAP2KEY(KS_EXTRA, KE_X2RELEASE) --- 450,460 ---- #define K_RIGHTMOUSE TERMCAP2KEY(KS_EXTRA, KE_RIGHTMOUSE) #define K_RIGHTDRAG TERMCAP2KEY(KS_EXTRA, KE_RIGHTDRAG) #define K_RIGHTRELEASE TERMCAP2KEY(KS_EXTRA, KE_RIGHTRELEASE) ! #define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE) ! #define K_X1MOUSE TERMCAP2KEY(KS_EXTRA, KE_X1MOUSE) #define K_X1DRAG TERMCAP2KEY(KS_EXTRA, KE_X1DRAG) #define K_X1RELEASE TERMCAP2KEY(KS_EXTRA, KE_X1RELEASE) ! #define K_X2MOUSE TERMCAP2KEY(KS_EXTRA, KE_X2MOUSE) #define K_X2DRAG TERMCAP2KEY(KS_EXTRA, KE_X2DRAG) #define K_X2RELEASE TERMCAP2KEY(KS_EXTRA, KE_X2RELEASE) *************** *** 477,482 **** --- 478,485 ---- #define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD) + #define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) + // Bits for modifier mask // 0x01 cannot be used, because the modifier must be 0x02 or higher #define MOD_MASK_SHIFT 0x02 *** ../vim-8.2.1977/src/map.c 2020-10-01 21:37:17.798009505 +0200 --- src/map.c 2020-11-12 13:23:14.963599234 +0100 *************** *** 1639,1646 **** * Returns NULL when out of memory. */ char_u * ! vim_strsave_escape_csi( ! char_u *p) { char_u *res; char_u *s, *d; --- 1639,1645 ---- * Returns NULL when out of memory. */ char_u * ! vim_strsave_escape_csi(char_u *p) { char_u *res; char_u *s, *d; *** ../vim-8.2.1977/src/misc2.c 2020-10-28 13:53:46.549128959 +0100 --- src/misc2.c 2020-11-12 13:23:14.963599234 +0100 *************** *** 2530,2535 **** --- 2530,2536 ---- {K_PLUG, (char_u *)"Plug"}, {K_CURSORHOLD, (char_u *)"CursorHold"}, {K_IGNORE, (char_u *)"Ignore"}, + {K_COMMAND, (char_u *)"Cmd"}, {0, NULL} // NOTE: When adding a long name update MAX_KEY_NAME_LEN. }; *** ../vim-8.2.1977/src/normal.c 2020-10-28 20:19:56.376057067 +0100 --- src/normal.c 2020-11-12 13:23:14.967599227 +0100 *************** *** 375,380 **** --- 375,381 ---- #endif {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0}, {K_PS, nv_edit, 0, 0}, + {K_COMMAND, nv_colon, 0, 0}, }; // Number of commands in nv_cmds[]. *************** *** 3312,3321 **** static void nv_colon(cmdarg_T *cap) { ! int old_p_im; ! int cmd_result; ! if (VIsual_active) nv_operator(cap); else { --- 3313,3323 ---- static void nv_colon(cmdarg_T *cap) { ! int old_p_im; ! int cmd_result; ! int is_cmdkey = cap->cmdchar == K_COMMAND; ! if (VIsual_active && !is_cmdkey) nv_operator(cap); else { *************** *** 3325,3331 **** cap->oap->motion_type = MCHAR; cap->oap->inclusive = FALSE; } ! else if (cap->count0) { // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); --- 3327,3333 ---- cap->oap->motion_type = MCHAR; cap->oap->inclusive = FALSE; } ! else if (cap->count0 && !is_cmdkey) { // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); *************** *** 3343,3349 **** old_p_im = p_im; // get a command line and execute it ! cmd_result = do_cmdline(NULL, getexline, NULL, cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); // If 'insertmode' changed, enter or exit Insert mode --- 3345,3351 ---- old_p_im = p_im; // get a command line and execute it ! cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); // If 'insertmode' changed, enter or exit Insert mode *** ../vim-8.2.1977/src/ops.c 2020-10-24 20:49:37.498683038 +0200 --- src/ops.c 2020-11-12 13:23:14.967599227 +0100 *************** *** 3490,3496 **** AppendToRedobuffLit(cap->searchbuf, -1); AppendToRedobuff(NL_STR); } ! else if (cap->cmdchar == ':') { // do_cmdline() has stored the first typed line in // "repeat_cmdline". When several lines are typed repeating --- 3490,3496 ---- AppendToRedobuffLit(cap->searchbuf, -1); AppendToRedobuff(NL_STR); } ! else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { // do_cmdline() has stored the first typed line in // "repeat_cmdline". When several lines are typed repeating *** ../vim-8.2.1977/src/proto/getchar.pro 2020-06-06 22:36:20.464116743 +0200 --- src/proto/getchar.pro 2020-11-12 14:05:48.405211967 +0100 *************** *** 51,54 **** --- 51,55 ---- void vungetc(int c); int fix_input_buffer(char_u *buf, int len); int input_available(void); + char_u *getcmdkeycmd(int promptc, void *cookie, int indent, getline_opt_T do_concat); /* vim: set ft=c : */ *** ../vim-8.2.1977/src/screen.c 2020-11-06 17:58:31.727138982 +0100 --- src/screen.c 2020-11-12 13:23:14.971599222 +0100 *************** *** 4192,4198 **** #endif msg_puts_attr(_(" INSERT"), attr); } ! else if (restart_edit == 'I' || restart_edit == 'A') msg_puts_attr(_(" (insert)"), attr); else if (restart_edit == 'R') msg_puts_attr(_(" (replace)"), attr); --- 4192,4199 ---- #endif msg_puts_attr(_(" INSERT"), attr); } ! else if (restart_edit == 'I' || restart_edit == 'i' || ! restart_edit == 'a' || restart_edit == 'A') msg_puts_attr(_(" (insert)"), attr); else if (restart_edit == 'R') msg_puts_attr(_(" (replace)"), attr); *** ../vim-8.2.1977/src/terminal.c 2020-11-06 17:58:31.727138982 +0100 --- src/terminal.c 2020-11-12 13:23:14.975599214 +0100 *************** *** 2180,2185 **** --- 2180,2189 ---- return FAIL; } } + break; + + case K_COMMAND: + return do_cmdline(NULL, getcmdkeycmd, NULL, 0); } if (typed) mouse_was_outside = FALSE; *** ../vim-8.2.1977/src/testdir/test_mapping.vim 2020-10-28 20:19:56.376057067 +0100 --- src/testdir/test_mapping.vim 2020-11-12 13:23:14.975599214 +0100 *************** *** 3,8 **** --- 3,9 ---- source shared.vim source check.vim source screendump.vim + source term_util.vim func Test_abbreviation() " abbreviation with 0x80 should work *************** *** 856,859 **** --- 857,1327 ---- mapclear! endfunc + " Test for key in maps to execute commands + func Test_map_cmdkey() + new + + " Error cases + let x = 0 + noremap let x = 1 + call assert_fails('call feedkeys("\", "xt")', 'E1136:') + call assert_equal(0, x) + + noremap let x = 2 + call assert_fails('call feedkeys("\", "xt")', 'E1137:') + call assert_equal(0, x) + + noremap let x = 3 + call assert_fails('call feedkeys("\", "xt!")', 'E1135:') + call assert_equal(0, x) + + " works in various modes and sees the correct mode() + noremap let m = mode(1) + noremap! let m = mode(1) + + " normal mode + call feedkeys("\", 'xt') + call assert_equal('n', m) + + " visual mode + call feedkeys("v\", 'xt!') + call assert_equal('v', m) + " shouldn't leave the visual mode + call assert_equal('v', mode(1)) + call feedkeys("\", 'xt') + call assert_equal('n', mode(1)) + + " visual mapping in select mode + call feedkeys("gh\", 'xt!') + call assert_equal('v', m) + " shouldn't leave select mode + call assert_equal('s', mode(1)) + call feedkeys("\", 'xt') + call assert_equal('n', mode(1)) + + " select mode mapping + snoremap let m = mode(1) + call feedkeys("gh\", 'xt!') + call assert_equal('s', m) + " shouldn't leave select mode + call assert_equal('s', mode(1)) + call feedkeys("\", 'xt') + call assert_equal('n', mode(1)) + + " operator-pending mode + call feedkeys("d\", 'xt!') + call assert_equal('no', m) + " leaves the operator-pending mode + call assert_equal('n', mode(1)) + + " insert mode + call feedkeys("i\abc", 'xt') + call assert_equal('i', m) + call assert_equal('abc', getline('.')) + + " replace mode + call feedkeys("0R\two", 'xt') + call assert_equal('R', m) + call assert_equal('two', getline('.')) + + " virtual replace mode + call setline('.', "one\ttwo") + call feedkeys("4|gR\xxx", 'xt') + call assert_equal('Rv', m) + call assert_equal("onexxx\ttwo", getline('.')) + + " cmdline mode + call feedkeys(":\\"xxx\", 'xt!') + call assert_equal('c', m) + call assert_equal('"xxx', @:) + + " terminal mode + if CanRunVimInTerminal() + tnoremap let m = mode(1) + let buf = Run_shell_in_terminal({}) + call feedkeys("\", 'xt') + call assert_equal('t', m) + call assert_equal('t', mode(1)) + call StopShellInTerminal(buf) + call TermWait(buf) + close! + tunmap + endif + + " invoke cmdline mode recursively + noremap! norm! :foo + %d + call setline(1, ['some short lines', 'of test text']) + call feedkeys(":bar\x\\"\r", 'xt') + call assert_equal('"barx', @:) + unmap! + + " test for calling a function + let lines =<< trim END + map call do_it() + func s:do_it() + let g:x = 32 + endfunc + END + call writefile(lines, 'Xscript') + source Xscript + call feedkeys("\", 'xt') + call assert_equal(32, g:x) + call delete('Xscript') + + unmap + unmap! + %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 .. ' ' .. a:rhs .. '' + exe 'noremap! ' .. a:lhs .. ' ' .. a:rhs .. '' + endfunc + + func s:cmdunmap(lhs) + exe 'unmap ' .. a:lhs + exe 'unmap! ' .. a:lhs + endfunc + + " Map various keys used by the key tests + func s:setupMaps() + call s:cmdmap('', 'let m = mode(1)') + call s:cmdmap('', 'normal! ww') + call s:cmdmap('', 'normal! "ay') + call s:cmdmap('', 'throw "very error"') + call s:cmdmap('', 'call TextObj()') + call s:cmdmap('', 'startinsert') + call s:cmdmap('', 'stopinsert') + endfunc + + " Remove the mappings setup by setupMaps() + func s:cleanupMaps() + call s:cmdunmap('') + call s:cmdunmap('') + call s:cmdunmap('') + call s:cmdunmap('') + call s:cmdunmap('') + call s:cmdunmap('') + call s:cmdunmap('') + endfunc + + " Test for mapping in normal mode + func Test_map_cmdkey_normal_mode() + new + call s:setupMaps() + + " check v:count and v:register works + call s:cmdmap('', 'let s = [mode(1), v:count, v:register]') + call feedkeys("\", 'xt') + call assert_equal(['n', 0, '"'], s) + call feedkeys("7\", 'xt') + call assert_equal(['n', 7, '"'], s) + call feedkeys("\"e\", 'xt') + call assert_equal(['n', 0, 'e'], s) + call feedkeys("5\"k\", 'xt') + call assert_equal(['n', 5, 'k'], s) + call s:cmdunmap('') + + call setline(1, ['some short lines', 'of test text']) + call feedkeys("\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("\abc", 'xt') + call assert_equal('abc', getline(1)) + + " feedkeys are not executed immediately + noremap ,a call feedkeys("aalpha") \| let g:a = getline(2) + %d + call setline(1, ['some short lines', 'of test text']) + call cursor(2, 3) + call feedkeys(",a\", '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 call feedkeys("abeta", 'x') \| let g:b = getline(2) + call feedkeys(",b\", 'xt') + call assert_equal('n', m) + call assert_equal('of alphabetatest text', g:b) + nunmap ,b + + call s:cleanupMaps() + %bw! + endfunc + + " Test for mapping with the :normal command + func Test_map_cmdkey_normal_cmd() + new + noremap ,x call append(1, "xx") \| call append(1, "aa") + noremap ,f nosuchcommand + noremap ,e throw "very error" \| call append(1, "yy") + noremap ,m echoerr "The message." \| call append(1, "zz") + noremap ,w for i in range(5) \| if i==1 \| echoerr "Err" \| endif \| call append(1, i) \| endfor + + 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 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\", 'xt!') + call assert_equal(['v', 1, 12], [mode(1), col('v'), col('.')]) + + " can invoke an opeartor, ending the visual mode + let @a = '' + call feedkeys("\", '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\", "xt!")', 'E605:') + call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')]) + call feedkeys("\", 'xt!') + call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')]) + + " startinsert gives "-- (insert) VISUAL --" mode + call feedkeys("\", '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("\new ", 'x') + call assert_equal(['some short lines', 'of new test text'], getline(1, '$')) + + call s:cleanupMaps() + set showmode& + %bw! + endfunc + + " Test for mapping in select mode + func Test_map_cmdkey_select_mode() + new + set showmode + call s:setupMaps() + + snoremap throw "very error" + snoremap normal! "by + call setline(1, ['some short lines', 'of test text']) + + call feedkeys("gh\", "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("\", '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("\", '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("\ggvw\\", "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("\ggvw\\", "xt!")', 'E605:') + redraw! + call assert_match('^-- SELECT --', Screenline(&lines)) + call assert_equal(['s', 1, 6], [mode(1), col('v'), col('.')]) + + call feedkeys("\", '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("\", '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("\new ", 'x') + call assert_equal(['some short lines', 'of new test text'], getline(1, '$')) + + sunmap + sunmap + call s:cleanupMaps() + set showmode& + %bw! + endfunc + + " Test for 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\", '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\", "xt")', 'E605:') + call assert_equal(['some short lines', 'of test text'], getline(1, '$')) + + call feedkeys("\"bd\", '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\cc", 'xt') + call assert_equal(['soccest text'], getline(1, '$')) + + call s:cleanupMaps() + %bw! + endfunc + + " Test for 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 ww + call feedkeys("iindeed \little ", 'xt') + call assert_equal(['indeed some short little lines', 'of test text'], getline(1, '$')) + call assert_fails('call feedkeys("i\ 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 mapping, vim ends up in undocumented "INSERT VISUAL" mode. + call feedkeys("i\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("\", 'xt') + call assert_equal(['deed some short little 2 lines', 'of stuff '], getreg('a', 1, 1)) + + " also works as part of abbreviation + abbr foo let g:y = 17bar + exe "normal i\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\vim", 'xt') + call assert_equal('vimfoo bar', getline(1)) + + " :stopinsert works + call feedkeys("ggi\Abc", 'xt') + call assert_equal('vimfoo barbc', getline(1)) + + call s:cleanupMaps() + %bw! + endfunc + + " Test for 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\\\\ ", 'xt') + call assert_equal('ic', m) + call assert_equal(['some short lines', 'short '], getline(1, '$')) + + call s:cleanupMaps() + %bw! + endfunc + + " Test for 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\= 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\= 20\r", "xt")', 'E605:') + call assert_equal(20, x) + + " move cursor in the buffer from cmdline mode + call feedkeys(":let x\= 30\r", 'xt') + call assert_equal(30, x) + call assert_equal(12, col('.')) + + " :startinsert takes effect after leaving cmdline mode + call feedkeys(":let x\= 40\rnew ", 'xt') + call assert_equal(40, x) + call assert_equal('some short new lines', getline(1)) + + call s:cleanupMaps() + %bw! + endfunc + " vim: shiftwidth=2 sts=2 expandtab *** ../vim-8.2.1977/src/version.c 2020-11-12 12:08:47.986254110 +0100 --- src/version.c 2020-11-12 13:26:21.555313427 +0100 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 1978, /**/ -- hundred-and-one symptoms of being an internet addict: 238. You think faxes are old-fashioned. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///