aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/api.txt15
-rw-r--r--src/nvim/api/vimscript.c30
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/getchar.c10
-rw-r--r--src/nvim/message.c36
-rw-r--r--src/nvim/testdir/test_help.vim24
-rw-r--r--src/nvim/testdir/test_ins_complete.vim9
-rw-r--r--src/nvim/testdir/test_mapping.vim34
-rw-r--r--src/nvim/window.c60
-rw-r--r--test/functional/api/vim_spec.lua76
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')