diff options
-rw-r--r-- | runtime/doc/api.txt | 15 | ||||
-rw-r--r-- | src/nvim/api/vimscript.c | 30 | ||||
-rw-r--r-- | src/nvim/edit.c | 2 | ||||
-rw-r--r-- | src/nvim/getchar.c | 10 | ||||
-rw-r--r-- | src/nvim/message.c | 36 | ||||
-rw-r--r-- | src/nvim/testdir/test_help.vim | 24 | ||||
-rw-r--r-- | src/nvim/testdir/test_ins_complete.vim | 9 | ||||
-rw-r--r-- | src/nvim/testdir/test_mapping.vim | 34 | ||||
-rw-r--r-- | src/nvim/window.c | 60 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 76 |
10 files changed, 163 insertions, 133 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index d4477df803..bb7a238468 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1775,14 +1775,15 @@ nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()* Dictionary containing command information, with these keys: • cmd: (string) Command name. - • range: (number) Number of items in the command - |<range>|. Can be 0, 1 or 2. - • line1: (number) Starting line of command |<range>|. -1 - if command cannot take a range. |<line1>| - • line2: (number) Final line of command |<range>|. -1 if - command cannot take a range. |<line2>| + • range: (array) Command <range>. Can have 0-2 elements + depending on how many items the range contains. Has no + elements if command doesn't accept a range or if no + range was specified, one element if only a single range + item was specified and two elements if both range items + were specified. • count: (number) Any |<count>| that was supplied to the - command. -1 if command cannot take a count. + command. -1 if command cannot take a count. Mutually + exclusive with "range". • reg: (number) The optional command |<register>|, if specified. Empty string if not specified or if command cannot take a register. diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index acd89119f9..698b2d06fb 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -747,13 +747,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E /// @param[out] err Error details, if any. /// @return Dictionary containing command information, with these keys: /// - cmd: (string) Command name. -/// - range: (number) Number of items in the command |<range>|. Can be 0, 1 or 2. -/// - line1: (number) Starting line of command |<range>|. -1 if command cannot take a range. -/// |<line1>| -/// - line2: (number) Final line of command |<range>|. -1 if command cannot take a range. -/// |<line2>| +/// - range: (array) Command <range>. Can have 0-2 elements depending on how many items the +/// range contains. Has no elements if command doesn't accept a range or if +/// no range was specified, one element if only a single range item was +/// specified and two elements if both range items were specified. /// - count: (number) Any |<count>| that was supplied to the command. -1 if command cannot -/// take a count. +/// take a count. Mutually exclusive with "range". /// - reg: (number) The optional command |<register>|, if specified. Empty string if not /// specified or if command cannot take a register. /// - bang: (boolean) Whether command contains a |<bang>| (!) modifier. @@ -849,15 +848,24 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); } - PUT(result, "range", INTEGER_OBJ(ea.addr_count)); - PUT(result, "line1", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line1 : -1)); - PUT(result, "line2", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line2 : -1)); + if ((ea.argt & EX_RANGE) && !(ea.argt & EX_COUNT) && ea.addr_count > 0) { + Array range = ARRAY_DICT_INIT; + if (ea.addr_count > 1) { + ADD(range, INTEGER_OBJ(ea.line1)); + } + ADD(range, INTEGER_OBJ(ea.line2)); + PUT(result, "range", ARRAY_OBJ(range));; + } else { + PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT)); + } if (ea.argt & EX_COUNT) { - if (ea.addr_count > 0 || cmd == NULL) { + if (ea.addr_count > 0) { PUT(result, "count", INTEGER_OBJ(ea.line2)); - } else { + } else if (cmd != NULL) { PUT(result, "count", INTEGER_OBJ(cmd->uc_def)); + } else { + PUT(result, "count", INTEGER_OBJ(0)); } } else { PUT(result, "count", INTEGER_OBJ(-1)); diff --git a/src/nvim/edit.c b/src/nvim/edit.c index f17ac52f15..f2c3f64790 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -553,7 +553,7 @@ static int insert_check(VimState *state) Insstart_orig = Insstart; } - if (stop_insert_mode && !pum_visible()) { + if (stop_insert_mode && !compl_started) { // ":stopinsert" used or 'insertmode' reset s->count = 0; return 0; // exit insert mode diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index df9297dc0b..374fa11b23 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2902,7 +2902,8 @@ void set_maparg_lhs_rhs(const char_u *const orig_lhs, const size_t orig_lhs_len, replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT | REPTERM_NO_SIMPLIFY, NULL, cpo_flags); mapargs->rhs_len = STRLEN(replaced); - mapargs->rhs_is_noop = false; + // XXX: even when orig_rhs is non-empty, replace_termcodes may produce an empty string. + mapargs->rhs_is_noop = orig_rhs[0] != NUL && mapargs->rhs_len == 0; mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1); } @@ -3765,12 +3766,7 @@ static void showmap(mapblock_T *mp, bool local) } else if (mp->m_str[0] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { - // Remove escaping of K_SPECIAL, because "m_str" is in a format to be used - // as typeahead. - char_u *s = vim_strsave(mp->m_str); - vim_unescape_ks(s); - msg_outtrans_special(s, false, 0); - xfree(s); + msg_outtrans_special(mp->m_str, false, 0); } if (mp->m_desc != NULL) { diff --git a/src/nvim/message.c b/src/nvim/message.c index f0ef4e1d4f..cdcbcc6700 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1671,11 +1671,13 @@ const char *str2special(const char **const sp, const bool replace_spaces, const { static char buf[7]; - // Try to un-escape a multi-byte character. Return the un-escaped - // string if it is a multi-byte character. - const char *const p = mb_unescape(sp); - if (p != NULL) { - return p; + { + // Try to un-escape a multi-byte character. Return the un-escaped + // string if it is a multi-byte character. + const char *const p = mb_unescape(sp); + if (p != NULL) { + return p; + } } const char *str = *sp; @@ -1698,18 +1700,24 @@ const char *str2special(const char **const sp, const bool replace_spaces, const } if (!IS_SPECIAL(c)) { - const int len = utf_ptr2len((const char_u *)str); + *sp = str; + // Try to un-escape a multi-byte character after modifiers. + const char *p = mb_unescape(sp); - // Check for an illegal byte. - if (MB_BYTE2LEN((uint8_t)(*str)) > len) { - transchar_nonprint(curbuf, (char_u *)buf, c); - *sp = str + 1; - return buf; + if (p == NULL) { + const int len = utf_ptr2len((const char_u *)str); + // Check for an illegal byte. + if (MB_BYTE2LEN((uint8_t)(*str)) > len) { + transchar_nonprint(curbuf, (char_u *)buf, c); + *sp = str + 1; + return buf; + } + *sp = str + len; + p = str; } - // Since 'special' is TRUE the multi-byte character 'c' will be + // Since 'special' is true the multi-byte character 'c' will be // processed by get_special_key_name(). - c = utf_ptr2char((const char_u *)str); - *sp = str + len; + c = utf_ptr2char((const char_u *)p); } else { *sp = str + 1; } diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim index b2d943be00..9569cfa4e5 100644 --- a/src/nvim/testdir/test_help.vim +++ b/src/nvim/testdir/test_help.vim @@ -9,6 +9,30 @@ func Test_help_restore_snapshot() helpclose endfunc +func Test_help_restore_snapshot_split() + " Squeeze the unnamed buffer, Xfoo and the help one side-by-side and focus + " the first one before calling :help. + let bnr = bufnr() + botright vsp Xfoo + wincmd h + help + wincmd L + let g:did_bufenter = v:false + augroup T + au! + au BufEnter Xfoo let g:did_bufenter = v:true + augroup END + helpclose + augroup! T + " We're back to the unnamed buffer. + call assert_equal(bnr, bufnr()) + " No BufEnter was triggered for Xfoo. + call assert_equal(v:false, g:did_bufenter) + + close! + bwipe! +endfunc + func Test_help_errors() call assert_fails('help doesnotexist', 'E149:') call assert_fails('help!', 'E478:') diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 24eaf9e8b1..90b57323af 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -516,6 +516,15 @@ func Test_pum_stopped_by_timer() call delete('Xpumscript') endfunc +func Test_complete_stopinsert_startinsert() + nnoremap <F2> <Cmd>startinsert<CR> + inoremap <F2> <Cmd>stopinsert<CR> + " This just checks if this causes an error + call feedkeys("i\<C-X>\<C-N>\<F2>\<F2>", 'x') + nunmap <F2> + iunmap <F2> +endfunc + func Test_pum_with_folds_two_tabs() CheckScreendump diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index 752b1700d0..b5158295b4 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -464,6 +464,40 @@ func Test_list_mappings() call assert_equal(['n ,n <Nop>'], \ execute('nmap ,n')->trim()->split("\n")) + " verbose map + call assert_match("\tLast set from .*/test_mapping.vim line \\d\\+$", + \ execute('verbose map ,n')->trim()->split("\n")[1]) + + " character with K_SPECIAL byte in rhs + nmap foo … + call assert_equal(['n foo …'], + \ execute('nmap foo')->trim()->split("\n")) + + " modified character with K_SPECIAL byte in rhs + nmap foo <M-…> + call assert_equal(['n foo <M-…>'], + \ execute('nmap foo')->trim()->split("\n")) + + " character with K_SPECIAL byte in lhs + nmap … foo + call assert_equal(['n … foo'], + \ execute('nmap …')->trim()->split("\n")) + + " modified character with K_SPECIAL byte in lhs + nmap <M-…> foo + call assert_equal(['n <M-…> foo'], + \ execute('nmap <M-…>')->trim()->split("\n")) + + " map to CTRL-V + exe "nmap ,k \<C-V>" + call assert_equal(['n ,k <Nop>'], + \ execute('nmap ,k')->trim()->split("\n")) + + " map with space at the beginning + exe "nmap \<C-V> w <Nop>" + call assert_equal(['n <Space>w <Nop>'], + \ execute("nmap \<C-V> w")->trim()->split("\n")) + nmapclear endfunc diff --git a/src/nvim/window.c b/src/nvim/window.c index 110c5644d9..887df31650 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2813,10 +2813,11 @@ int win_close(win_T *win, bool free_buf, bool force) wp = win_free_mem(win, &dir, NULL); if (help_window) { - // Closing the help window moves the cursor back to the original window. - win_T *tmpwp = get_snapshot_focus(SNAP_HELP_IDX); - if (tmpwp != NULL) { - wp = tmpwp; + // Closing the help window moves the cursor back to the current window + // of the snapshot. + win_T *prev_win = get_snapshot_curwin(SNAP_HELP_IDX); + if (win_valid(prev_win)) { + wp = prev_win; } } @@ -6824,6 +6825,35 @@ static void clear_snapshot_rec(frame_T *fr) } } +/// Traverse a snapshot to find the previous curwin. +static win_T *get_snapshot_curwin_rec(frame_T *ft) +{ + win_T *wp; + + if (ft->fr_next != NULL) { + if ((wp = get_snapshot_curwin_rec(ft->fr_next)) != NULL) { + return wp; + } + } + if (ft->fr_child != NULL) { + if ((wp = get_snapshot_curwin_rec(ft->fr_child)) != NULL) { + return wp; + } + } + + return ft->fr_win; +} + +/// @return the current window stored in the snapshot or NULL. +static win_T *get_snapshot_curwin(int idx) +{ + if (curtab->tp_snapshot[idx] == NULL) { + return NULL; + } + + return get_snapshot_curwin_rec(curtab->tp_snapshot[idx]); +} + /// Restore a previously created snapshot, if there is any. /// This is only done if the screen size didn't change and the window layout is /// still the same. @@ -6896,28 +6926,6 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } -/// Gets the focused window (the one holding the cursor) of the snapshot. -static win_T *get_snapshot_focus(int idx) -{ - if (curtab->tp_snapshot[idx] == NULL) { - return NULL; - } - - frame_T *sn = curtab->tp_snapshot[idx]; - // This should be equivalent to the recursive algorithm found in - // restore_snapshot as far as traveling nodes go. - while (sn->fr_child != NULL || sn->fr_next != NULL) { - while (sn->fr_child != NULL) { - sn = sn->fr_child; - } - if (sn->fr_next != NULL) { - sn = sn->fr_next; - } - } - - return win_valid(sn->fr_win) ? sn->fr_win : NULL; -} - /// Set "win" to be the curwin and "tp" to be the current tab page. /// restore_win() MUST be called to undo, also when FAIL is returned. /// No autocommands will be executed until restore_win() is called. diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 7e54ae0248..610036f484 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -3104,9 +3104,7 @@ describe('API', function() cmd = 'echo', args = { 'foo' }, bang = false, - line1 = -1, - line2 = -1, - range = 0, + range = {}, count = -1, reg = '', addr = 'none', @@ -3142,9 +3140,7 @@ describe('API', function() cmd = 'substitute', args = { '/math.random/math.max/' }, bang = false, - line1 = 4, - line2 = 6, - range = 2, + range = { 4, 6 }, count = -1, reg = '', addr = 'line', @@ -3180,9 +3176,7 @@ describe('API', function() cmd = 'buffer', args = {}, bang = false, - line1 = 1, - line2 = 1, - range = 1, + range = {}, count = 1, reg = '', addr = 'buf', @@ -3218,9 +3212,7 @@ describe('API', function() cmd = 'put', args = {}, bang = false, - line1 = 1, - line2 = 1, - range = 0, + range = {}, count = -1, reg = '+', addr = 'line', @@ -3256,9 +3248,7 @@ describe('API', function() cmd = 'write', args = {}, bang = true, - line1 = 1, - line2 = 1, - range = 0, + range = {}, count = -1, reg = '', addr = 'line', @@ -3294,9 +3284,7 @@ describe('API', function() cmd = 'split', args = { 'foo.txt' }, bang = false, - line1 = 1, - line2 = 1, - range = 0, + range = {}, count = -1, reg = '', addr = '?', @@ -3333,9 +3321,7 @@ describe('API', function() cmd = 'MyCommand', args = { 'test', 'it' }, bang = true, - line1 = 4, - line2 = 6, - range = 2, + range = { 4, 6 }, count = -1, reg = '', addr = 'line', @@ -3371,9 +3357,7 @@ describe('API', function() cmd = 'argadd', args = { 'a.txt' }, bang = false, - line1 = 0, - line2 = 0, - range = 0, + range = {}, count = -1, reg = '', addr = 'arg', @@ -3410,9 +3394,7 @@ describe('API', function() cmd = 'MyCommand', args = { 'test it' }, bang = false, - line1 = -1, - line2 = -1, - range = 0, + range = {}, count = -1, reg = '', addr = 'none', @@ -3443,46 +3425,6 @@ describe('API', function() } }, meths.parse_cmd('MyCommand test it', {})) end) - it('sets correct default range', function() - command('command -range=% -addr=buffers MyCommand echo foo') - command('new') - eq({ - cmd = 'MyCommand', - args = {}, - bang = false, - line1 = 1, - line2 = 2, - range = 0, - count = -1, - reg = '', - addr = 'buf', - magic = { - file = false, - bar = false - }, - nargs = '0', - nextcmd = '', - mods = { - browse = false, - confirm = false, - emsg_silent = false, - hide = false, - keepalt = false, - keepjumps = false, - keepmarks = false, - keeppatterns = false, - lockmarks = false, - noautocmd = false, - noswapfile = false, - sandbox = false, - silent = false, - vertical = false, - split = "", - tab = 0, - verbose = -1 - } - }, meths.parse_cmd('MyCommand', {})) - end) it('errors for invalid command', function() eq('Error while parsing command line', pcall_err(meths.parse_cmd, 'Fubar', {})) command('command! Fubar echo foo') |