From a2b9117ca8f8abe8d4c9e2d1bacb73b1902a4e1f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 7 May 2023 08:12:42 +0800 Subject: vim-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 special key. (Yegappan Lakshmanan, closes vim/vim#7282, closes 4784, based on patch by Bjorn Linse) https://github.com/vim/vim/commit/957cf67d50516ba98716f59c9e1cb6412ec1535d Change docs to match Vim if it's wording is better. Change error numbers to match Vim. Co-authored-by: Bram Moolenaar --- runtime/doc/map.txt | 20 +- src/nvim/edit.c | 2 +- src/nvim/getchar.c | 17 +- src/nvim/globals.h | 4 - test/functional/ex_cmds/cmd_map_spec.lua | 4 +- test/old/testdir/test_mapping.vim | 468 +++++++++++++++++++++++++++++++ 6 files changed, 494 insertions(+), 21 deletions(-) diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 164e2d4ec5..fccb6eeb75 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -290,7 +290,7 @@ Therefore the following is blocked for mappings: - 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. (Or use a || mapping instead.) +that, or use a || mapping instead. You can use getchar(), it consumes typeahead if there is any. E.g., if you have these mappings: > @@ -324,19 +324,21 @@ be seen as a special key. ** *:map-cmd* The pseudokey begins a "command mapping", which executes the command -directly (without changing modes). Where you might use ":..." in the +directly without changing modes. Where you might use ":..." in the {rhs} of a mapping, you can instead use "...". Example: > - noremap x echo mode(1) + 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 +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 cmdline-mode (which would otherwise require timer hacks). +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) |autocommand| was invoked +command: it is executed as if an (unrestricted) |autocmd| was invoked or an async event event was processed. Note: @@ -350,7 +352,7 @@ Note: - In Visual mode you can use `line('v')` and `col('v')` to get one end of the Visual area, the cursor is at the other end. - *E5520* + *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. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 2078fc4251..c544daee08 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -876,7 +876,7 @@ static int insert_handle_key(InsertState *s) state_handle_k_event(); goto check_pum; - case K_COMMAND: // some command + case K_COMMAND: // command do_cmdline(NULL, getcmdkeycmd, NULL, 0); goto check_pum; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index a0e8350287..31817676e1 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -134,6 +134,10 @@ static size_t last_recorded_len = 0; // number of last recorded chars #endif static const char e_recursive_mapping[] = N_("E223: Recursive mapping"); +static const char e_cmd_mapping_must_end_with_cr[] + = N_("E1135: mapping must end with "); +static const char e_cmd_mapping_must_end_with_cr_before_second_cmd[] + = N_("E1136: mapping must end with before second "); // Free and clear a buffer. void free_buff(buffheader_T *buf) @@ -2884,7 +2888,8 @@ int fix_input_buffer(uint8_t *buf, int len) return len; } -/// Get command argument for key +/// Function passed to do_cmdline() to get the command after a key from +/// typeahead. char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) { garray_T line_ga; @@ -2894,6 +2899,7 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) ga_init(&line_ga, 1, 32); + // no mapping for these characters no_mapping++; got_int = false; @@ -2903,16 +2909,17 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) if (vgetorpeek(false) == NUL) { // incomplete is an error, because there is not much the user // could do in this state. - emsg(e_cmdmap_err); + 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); // no mapping for these chars + c1 = vgetorpeek(true); c2 = vgetorpeek(true); if (c1 == KS_MODIFIER) { cmod = c2; @@ -2928,8 +2935,8 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) } else if (c1 == ESC) { aborted = true; } else if (c1 == K_COMMAND) { - // special case to give nicer error message - emsg(e_cmdmap_repeated); + // give a nicer error message for this special case + emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd)); aborted = true; } else if (c1 == K_SNR) { ga_concat(&line_ga, ""); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 698f4a98c7..6eae2f7589 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1003,10 +1003,6 @@ EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d")); EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s")); -EXTERN const char e_cmdmap_err[] INIT(= N_("E5520: mapping must end with ")); -EXTERN const char e_cmdmap_repeated[] -INIT(= N_("E5521: mapping must end with before second ")); - EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s")); EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback")); diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 919d167712..a0aec7fdd0 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -90,7 +90,7 @@ describe('mappings with ', function() {1:~ }| {1:~ }| {1:~ }| - {2:E5521: mapping must end with before second } | + {2:E1136: mapping must end with before second } | ]]) command('noremap let x = 3') @@ -103,7 +103,7 @@ describe('mappings with ', function() {1:~ }| {1:~ }| {1:~ }| - {2:E5520: mapping must end with } | + {2:E1135: mapping must end with } | ]]) eq(0, eval('x')) end) diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index e25c3c333e..b695c9e29b 100644 --- a/test/old/testdir/test_mapping.vim +++ b/test/old/testdir/test_mapping.vim @@ -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 @@ -968,6 +969,473 @@ func Test_map_cpo_special_keycode() 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 + func Test_map_cmdkey_redo() func SelectDash() call search('^---\n\zs', 'bcW') -- cgit From f7c1e460f8ae9e316a679a29945ce6a585336322 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 7 May 2023 08:31:06 +0800 Subject: vim-patch:8.2.2062: does not handle CTRL-V Problem: does not handle CTRL-V. Solution: Call get_literal() after encountering CTRL-V. (closes vim/vim#7387) https://github.com/vim/vim/commit/4a44120e3dc1d40dd7109658afd5e078360b1d8f Co-authored-by: Bram Moolenaar --- src/nvim/getchar.c | 5 +++++ test/old/testdir/test_mapping.vim | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 31817676e1..32fa517324 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2927,6 +2927,11 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) } c1 = TO_SPECIAL(c1, c2); } + if (c1 == Ctrl_V) { + // CTRL-V is followed by octal, hex or other characters, reverses + // what AppendToRedobuffLit() does. + c1 = get_literal(true); + } if (got_int) { aborted = true; diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index b695c9e29b..ec57bea59e 100644 --- a/test/old/testdir/test_mapping.vim +++ b/test/old/testdir/test_mapping.vim @@ -1084,6 +1084,11 @@ func Test_map_cmdkey() unmap unmap! %bw! + + " command line ending in "0" is handled without errors + onoremap ix eval 0 + call feedkeys('dix.', 'xt') + ounmap ix endfunc " text object enters visual mode -- cgit From 29c228dc1087676af5b72f4145ab146cff75156e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 7 May 2023 08:33:06 +0800 Subject: vim-patch:8.2.3887: E1135 is used for two different errors Problem: E1135 is used for two different errors. Solution: Renumber one error. https://github.com/vim/vim/commit/806da5176e9e9ab011d927c4ca33a8dde1769539 Co-authored-by: Bram Moolenaar --- runtime/doc/map.txt | 2 +- src/nvim/getchar.c | 2 +- test/functional/ex_cmds/cmd_map_spec.lua | 2 +- test/old/testdir/test_mapping.vim | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index fccb6eeb75..9ec592215e 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -352,7 +352,7 @@ Note: - In Visual mode you can use `line('v')` and `col('v')` to get one end of the Visual area, the cursor is at the other end. - *E1135* *E1136* + *E1255* *E1136* commands must terminate, that is, they must be followed by in the {rhs} of the mapping definition. |Command-line| mode is never entered. diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 32fa517324..07d7887fc6 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -135,7 +135,7 @@ static size_t last_recorded_len = 0; // number of last recorded chars static const char e_recursive_mapping[] = N_("E223: Recursive mapping"); static const char e_cmd_mapping_must_end_with_cr[] - = N_("E1135: mapping must end with "); + = N_("E1255: mapping must end with "); static const char e_cmd_mapping_must_end_with_cr_before_second_cmd[] = N_("E1136: mapping must end with before second "); diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index a0aec7fdd0..12867179bd 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -103,7 +103,7 @@ describe('mappings with ', function() {1:~ }| {1:~ }| {1:~ }| - {2:E1135: mapping must end with } | + {2:E1255: mapping must end with } | ]]) eq(0, eval('x')) end) diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index ec57bea59e..0cf357694f 100644 --- a/test/old/testdir/test_mapping.vim +++ b/test/old/testdir/test_mapping.vim @@ -984,7 +984,7 @@ func Test_map_cmdkey() call assert_equal(0, x) noremap let x = 3 - call assert_fails('call feedkeys("\", "xt!")', 'E1135:') + call assert_fails('call feedkeys("\", "xt!")', 'E1255:') call assert_equal(0, x) " works in various modes and sees the correct mode() -- cgit From 32331378134599ece34298f866889b4b311d7b79 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 7 May 2023 08:34:37 +0800 Subject: vim-patch:9.0.1516: cannot use special keys in mapping Problem: Cannot use special keys in mapping. Solution: Do allow for special keys in and mappings. (closes vim/vim#12326) https://github.com/vim/vim/commit/3ab3a864814f903da8a158c01820e4fbe1013c08 --- src/nvim/getchar.c | 20 +++++++++++++++----- src/nvim/ops.c | 2 +- test/old/testdir/test_mapping.vim | 27 ++++++++++++++++++--------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 07d7887fc6..c070e53089 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -554,6 +554,21 @@ void AppendToRedobuffLit(const char *str, int len) } } +/// Append "s" to the redo buffer, leaving 3-byte special key codes unmodified +/// and escaping other K_SPECIAL bytes. +void AppendToRedobuffSpec(const char *s) +{ + while (*s != NUL) { + if ((uint8_t)(*s) == K_SPECIAL && s[1] != NUL && s[2] != NUL) { + // Insert special key literally. + add_buff(&redobuff, s, 3L); + s += 3; + } else { + add_char_buff(&redobuff, mb_cptr2char_adv(&s)); + } + } +} + /// Append a character to the redo buffer. /// Translates special keys, NUL, K_SPECIAL and multibyte characters. void AppendCharToRedobuff(int c) @@ -2927,11 +2942,6 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) } c1 = TO_SPECIAL(c1, c2); } - if (c1 == Ctrl_V) { - // CTRL-V is followed by octal, hex or other characters, reverses - // what AppendToRedobuffLit() does. - c1 = get_literal(true); - } if (got_int) { aborted = true; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index bb66bb5731..ef26d5900d 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5858,7 +5858,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (repeat_cmdline == NULL) { ResetRedobuff(); } else { - AppendToRedobuffLit(repeat_cmdline, -1); + AppendToRedobuffSpec(repeat_cmdline); AppendToRedobuff(NL_STR); XFREE_CLEAR(repeat_cmdline); } diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index 0cf357694f..8c957d4665 100644 --- a/test/old/testdir/test_mapping.vim +++ b/test/old/testdir/test_mapping.vim @@ -979,10 +979,6 @@ func Test_map_cmdkey() 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!")', 'E1255:') call assert_equal(0, x) @@ -1084,11 +1080,6 @@ func Test_map_cmdkey() unmap unmap! %bw! - - " command line ending in "0" is handled without errors - onoremap ix eval 0 - call feedkeys('dix.', 'xt') - ounmap ix endfunc " text object enters visual mode @@ -1475,6 +1466,24 @@ func Test_map_cmdkey_redo() call delete('Xcmdtext') delfunc SelectDash ounmap i- + + new + call setline(1, 'aaa bbb ccc ddd') + + " command can contain special keys + onoremap ix let g:foo ..= '…'normal! + let g:foo = '' + call feedkeys('0dix.', 'xt') + call assert_equal('……', g:foo) + call assert_equal('ccc ddd', getline(1)) + unlet g:foo + + " command line ending in "0" is handled without errors + onoremap ix eval 0 + call feedkeys('dix.', 'xt') + + ounmap ix + bwipe! endfunc " Test for using