diff options
-rw-r--r-- | .github/scripts/close_unresponsive.js | 19 | ||||
-rw-r--r-- | .github/workflows/issue-open-check.yml | 34 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 8 | ||||
-rw-r--r-- | runtime/doc/map.txt | 21 | ||||
-rw-r--r-- | runtime/doc/repeat.txt | 1 | ||||
-rw-r--r-- | src/nvim/edit.c | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 3 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 12 | ||||
-rw-r--r-- | src/nvim/getchar.c | 40 | ||||
-rw-r--r-- | src/nvim/globals.h | 7 | ||||
-rw-r--r-- | src/nvim/menu.c | 4 | ||||
-rw-r--r-- | src/nvim/ops.c | 6 | ||||
-rw-r--r-- | src/nvim/popupmenu.c | 2 | ||||
-rw-r--r-- | src/nvim/runtime.c | 2 | ||||
-rw-r--r-- | test/functional/ex_cmds/cmd_map_spec.lua | 4 | ||||
-rw-r--r-- | test/old/testdir/test_mapping.vim | 482 | ||||
-rw-r--r-- | test/old/testdir/test_normal.vim | 23 |
17 files changed, 618 insertions, 52 deletions
diff --git a/.github/scripts/close_unresponsive.js b/.github/scripts/close_unresponsive.js index b7a92207ba..f0e8bbe93e 100644 --- a/.github/scripts/close_unresponsive.js +++ b/.github/scripts/close_unresponsive.js @@ -19,13 +19,18 @@ module.exports = async ({ github, context }) => { const numbers = issues.data.map((e) => e.number); for (const number of numbers) { - const timeline = await github.rest.issues.listEventsForTimeline({ - owner: owner, - repo: repo, - issue_number: number, - }); - const data = timeline.data.filter(labeledEvent); - const latest_response_label = data[data.length - 1]; + const events = await github.paginate( + github.rest.issues.listEventsForTimeline, + { + owner: owner, + repo: repo, + issue_number: number, + }, + (response) => response.data.filter(labeledEvent) + ); + + const latest_response_label = events[events.length - 1]; + const created_at = new Date(latest_response_label.created_at); const now = new Date(); const diff = now - created_at; diff --git a/.github/workflows/issue-open-check.yml b/.github/workflows/issue-open-check.yml new file mode 100644 index 0000000000..2471670dc6 --- /dev/null +++ b/.github/workflows/issue-open-check.yml @@ -0,0 +1,34 @@ +name: Issue Open Check + +on: + issues: + types: [opened] + +jobs: + issue-open-check: + permissions: + issues: write + runs-on: ubuntu-latest + steps: + - name: check issue title + id: check-issue + uses: actions/github-script@v6 + with: + script: | + const title = context.payload.issue.title; + const titleSplit = title.split(/\s+/).map(e => e.toLowerCase()); + const keywords = ['api', 'treesitter', 'ui', 'lsp', 'doc']; + var match = new Set(); + for(const keyword of keywords) { + if(titleSplit.includes(keyword)) { + match.add(keyword) + } + } + if(match.size !== 0){ + github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: Array.from(match) + }) + } diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 05fdf2f5bb..09c44a88af 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4270,10 +4270,10 @@ The input is in the variable "line", the results in the variables "file", getting the scriptnames in a Dictionary ~ *scriptnames-dictionary* -The |:scriptnames| command can be used to get a list of all script files that -have been sourced. There is no equivalent function or variable for this -(because it's rarely needed). In case you need to manipulate the list this -code can be used: > +The `:scriptnames` command can be used to get a list of all script files that +have been sourced. There is also the `getscriptinfo()` function, but the +information returned is not exactly the same. In case you need to manipulate +the output of `scriptnames` this code can be used: > " Get the output of ":scriptnames" in the scriptnames_output variable. let scriptnames_output = '' redir => scriptnames_output diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 164e2d4ec5..37158e2e76 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -290,7 +290,7 @@ Therefore the following is blocked for <expr> 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 |<Cmd>| mapping instead.) +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: > @@ -324,20 +324,22 @@ be seen as a special key. *<Cmd>* *:map-cmd* The <Cmd> pseudokey begins a "command mapping", which executes the command -directly (without changing modes). Where you might use ":...<CR>" in the +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> + 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 +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 cmdline-mode (which would otherwise require timer hacks). +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) |autocommand| was invoked -or an async event event was processed. +or an async event was processed. Note: - Because <Cmd> avoids mode-changes (unlike ":") it does not trigger @@ -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* + *E1255* *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. @@ -636,6 +638,7 @@ not to be matched with any key sequence. This is useful in plugins *<MouseMove>* The special key name "<MouseMove>" can be used to handle mouse movement. It needs to be enabled with 'mousemoveevent'. +The |getmousepos()| function can be used to obtain the mouse position. *<Char>* *<Char->* To map a character by its decimal, octal or hexadecimal number the <Char> diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 23030761dd..8644ce4b38 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -336,6 +336,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. :scr[iptnames] List all sourced script names, in the order they were first sourced. The number is used for the script ID |<SID>|. + Also see `getscriptinfo()`. :scr[iptnames][!] {scriptId} *:script* Edit script {scriptId}. Although ":scriptnames name" 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: // <Cmd>command<CR> do_cmdline(NULL, getcmdkeycmd, NULL, 0); goto check_pum; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 13299b0253..cb1f4d26fb 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3677,9 +3677,6 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv } else if (n2 >= len) { n2 = len; } - if (exclusive) { - n2--; - } if (n1 >= len || n2 < 0 || n1 > n2) { v = NULL; } else { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index fbb5e8d10c..7e20a298dd 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -467,12 +467,10 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b /// @param name function name void emsg_funcname(const char *errmsg, const char *name) { - char *p; + char *p = (char *)name; - if ((uint8_t)(*name) == K_SPECIAL) { + if ((uint8_t)name[0] == K_SPECIAL && name[1] != NUL && name[2] != NUL) { p = concat_str("<SNR>", name + 3); - } else { - p = (char *)name; } semsg(_(errmsg), p); @@ -1863,8 +1861,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part // Check for hard coded <SNR>: already translated function ID (from a user // command). - if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA - && (*pp)[2] == KE_SNR) { + if ((uint8_t)(*pp)[0] == K_SPECIAL && (uint8_t)(*pp)[1] == KS_EXTRA && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; return xmemdupz(start, (size_t)len); @@ -2140,7 +2137,6 @@ void ex_function(exarg_T *eap) char *theline; char *line_to_free = NULL; char c; - int saved_did_emsg; bool saved_wait_return = need_wait_return; char *name = NULL; char *p; @@ -2234,7 +2230,7 @@ void ex_function(exarg_T *eap) // An error in a function call during evaluation of an expression in magic // braces should not cause the function not to be defined. - saved_did_emsg = did_emsg; + const int saved_did_emsg = did_emsg; did_emsg = false; // diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index a0e8350287..ca555937ab 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -92,8 +92,8 @@ static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; static int typeahead_char = 0; // typeahead char that's not flushed -// when block_redo is true redo buffer will not be changed -// used by edit() to repeat insertions and 'V' command for redoing +/// When block_redo is true the redo buffer will not be changed. +/// Used by edit() to repeat insertions. static int block_redo = false; static int KeyNoremap = 0; // remapping flags @@ -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_("E1255: <Cmd> mapping must end with <CR>"); +static const char e_cmd_mapping_must_end_with_cr_before_second_cmd[] + = N_("E1136: <Cmd> mapping must end with <CR> before second <Cmd>"); // Free and clear a buffer. void free_buff(buffheader_T *buf) @@ -550,6 +554,25 @@ 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) +{ + if (block_redo) { + return; + } + + 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) @@ -2884,7 +2907,8 @@ int fix_input_buffer(uint8_t *buf, int len) return len; } -/// Get command argument for <Cmd> key +/// Function passed to do_cmdline() to get the command after a <Cmd> key from +/// typeahead. char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) { garray_T line_ga; @@ -2894,6 +2918,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 +2928,17 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) if (vgetorpeek(false) == NUL) { // incomplete <Cmd> is an error, because there is not much the user // could do in this state. - emsg(e_cmdmap_err); + 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 +2954,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, "<SNR>"); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 698f4a98c7..bedb529d04 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -993,7 +993,8 @@ EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set")); EXTERN const char e_invalidreg[] INIT(= N_("E850: Invalid register name")); EXTERN const char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\"")); EXTERN const char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior")); -EXTERN const char e_menuothermode[] INIT(= N_("E328: Menu only exists in another mode")); +EXTERN const char e_menu_only_exists_in_another_mode[] +INIT(= N_("E328: Menu only exists in another mode")); EXTERN const char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window")); EXTERN const char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); EXTERN const char e_unsupportedoption[] INIT(= N_("E519: Option not supported")); @@ -1003,10 +1004,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: <Cmd> mapping must end with <CR>")); -EXTERN const char e_cmdmap_repeated[] -INIT(= N_("E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); - 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/src/nvim/menu.c b/src/nvim/menu.c index 16fea2ccf1..898e3ddd27 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -559,7 +559,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent) } } else if (*name != NUL) { if (!silent) { - emsg(_(e_menuothermode)); + emsg(_(e_menu_only_exists_in_another_mode)); } return FAIL; } @@ -760,7 +760,7 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes) emsg(_(e_notsubmenu)); return NULL; } else if ((menu->modes & modes) == 0x0) { - emsg(_(e_menuothermode)); + emsg(_(e_menu_only_exists_in_another_mode)); return NULL; } else if (*p == NUL) { // found a full match return menu; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index bb66bb5731..c1511ab8da 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5858,7 +5858,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (repeat_cmdline == NULL) { ResetRedobuff(); } else { - AppendToRedobuffLit(repeat_cmdline, -1); + if (cap->cmdchar == ':') { + AppendToRedobuffLit(repeat_cmdline, -1); + } else { + AppendToRedobuffSpec(repeat_cmdline); + } AppendToRedobuff(NL_STR); XFREE_CLEAR(repeat_cmdline); } diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index d404aa9647..6d5d063a75 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -1083,7 +1083,7 @@ void pum_show_popupmenu(vimmenu_T *menu) // When there are only Terminal mode menus, using "popup Edit" results in // pum_size being zero. if (pum_size <= 0) { - emsg(e_menuothermode); + emsg(_(e_menu_only_exists_in_another_mode)); return; } diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index b4a23d544e..34e94d6021 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -2409,7 +2409,7 @@ void f_getscriptinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } if (sid <= 0) { - semsg(e_invargNval, "sid", tv_get_string(&sid_di->di_tv)); + semsg(_(e_invargNval), "sid", tv_get_string(&sid_di->di_tv)); return; } } else { diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 919d167712..12867179bd 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 <Cmd>', function() {1:~ }| {1:~ }| {1:~ }| - {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>} | + {2:E1136: <Cmd> mapping must end with <CR> before second <Cmd>} | ]]) command('noremap <F3> <Cmd>let x = 3') @@ -103,7 +103,7 @@ describe('mappings with <Cmd>', function() {1:~ }| {1:~ }| {1:~ }| - {2:E5520: <Cmd> mapping must end with <CR>} | + {2:E1255: <Cmd> mapping must end with <CR>} | ]]) eq(0, eval('x')) end) diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index e25c3c333e..8c957d4665 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,469 @@ 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>let x = 3 + call assert_fails('call feedkeys("\<F3>", "xt!")', 'E1255:') + 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 + func Test_map_cmdkey_redo() func SelectDash() call search('^---\n\zs', 'bcW') @@ -1002,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 <Cmd>let g:foo ..= '…'<Bar>normal! <C-Right><CR> + 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 <Cmd>eval 0<CR> + call feedkeys('dix.', 'xt') + + ounmap ix + bwipe! endfunc " Test for using <script> with a map to remap characters in rhs diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 330a16dffb..a854c9538f 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -3648,11 +3648,32 @@ func Test_horiz_motion() bwipe! endfunc -" Test for using a : command in operator pending mode +" Test for using a ":" command in operator pending mode func Test_normal_colon_op() new call setline(1, ['one', 'two']) call assert_beeps("normal! Gc:d\<CR>") + call assert_equal(['one'], getline(1, '$')) + + call setline(1, ['one…two…three!']) + normal! $ + " Using ":" as a movement is characterwise exclusive + call feedkeys("d:normal! F…\<CR>", 'xt') + call assert_equal(['one…two!'], getline(1, '$')) + " Check that redoing a command with 0x80 bytes works + call feedkeys('.', 'xt') + call assert_equal(['one!'], getline(1, '$')) + + call setline(1, ['one', 'two', 'three', 'four', 'five']) + " Add this to the command history + call feedkeys(":normal! G0\<CR>", 'xt') + " Use :normal! with control characters in operator pending mode + call feedkeys("d:normal! \<C-V>\<C-P>\<C-V>\<C-P>\<CR>", 'xt') + call assert_equal(['one', 'two', 'five'], getline(1, '$')) + " Check that redoing a command with control characters works + call feedkeys('.', 'xt') + call assert_equal(['five'], getline(1, '$')) + bwipe! endfunc |