commit 64a0441d204c756fcfac806d2820f05b31324d2a
parent a331d187ba7591f2bbf0841194e96ba0900c1106
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sun, 14 Dec 2025 10:36:23 +0800
Merge pull request #36934 from janlazo/vim-8.2.4633
vim-patch:8.2.{4633,4636,4637,4763,5092},9.0.0025
Diffstat:
4 files changed, 78 insertions(+), 1 deletion(-)
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
@@ -2511,8 +2511,23 @@ static char *ex_range_without_command(exarg_T *eap)
/// @return FAIL when the command is not to be executed.
int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod, bool skip_only)
{
+ char *orig_cmd = eap->cmd;
+ char *cmd_start = NULL;
+ bool use_plus_cmd = false;
+ bool has_visual_range = false;
CLEAR_POINTER(cmod);
+ if (strncmp(eap->cmd, "'<,'>", 5) == 0) {
+ // The automatically inserted Visual area range is skipped, so that
+ // typing ":cmdmod cmd" in Visual mode works without having to move the
+ // range to after the modififiers. The command will be
+ // "'<,'>cmdmod cmd", parse "cmdmod cmd" and then put back "'<,'>"
+ // before "cmd" below.
+ eap->cmd += 5;
+ cmd_start = eap->cmd;
+ has_visual_range = true;
+ }
+
// Repeat until no more command modifiers are found.
while (true) {
while (*eap->cmd == ' '
@@ -2521,14 +2536,16 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
eap->cmd++;
}
- // in ex mode, an empty line works like :+
+ // in ex mode, an empty command (after modifiers) works like :+
if (*eap->cmd == NUL && exmode_active
&& getline_equal(eap->ea_getline, eap->cookie, getexline)
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
eap->cmd = exmode_plus;
+ use_plus_cmd = true;
if (!skip_only) {
ex_pressedreturn = true;
}
+ break; // no modifiers following
}
// ignore comment and empty lines
@@ -2750,6 +2767,39 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
break;
}
+ if (has_visual_range) {
+ if (eap->cmd > cmd_start) {
+ // Move the '<,'> range to after the modifiers and insert a colon.
+ // Since the modifiers have been parsed put the colon on top of the
+ // space: "'<,'>mod cmd" -> "mod:'<,'>cmd
+ // Put eap->cmd after the colon.
+ if (use_plus_cmd) {
+ size_t len = strlen(cmd_start);
+
+ // Special case: empty command uses "+":
+ // "'<,'>mods" -> "mods *+
+ // Use "*" instead of "'<,'>" to avoid the command getting
+ // longer, in case is was allocated.
+ memmove(orig_cmd, cmd_start, len);
+ xmemcpyz(orig_cmd + len, S_LEN(" *+"));
+ } else {
+ memmove(cmd_start - 5, cmd_start, (size_t)(eap->cmd - cmd_start));
+ eap->cmd -= 5;
+ memmove(eap->cmd - 1, ":'<,'>", 6);
+ }
+ } else {
+ // No modifiers, move the pointer back.
+ // Special case: change empty command to "+".
+ if (use_plus_cmd) {
+ eap->cmd = "'<,'>+";
+ } else {
+ eap->cmd = orig_cmd;
+ }
+ }
+ } else if (use_plus_cmd) {
+ eap->cmd = exmode_plus;
+ }
+
return OK;
}
diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim
@@ -2561,6 +2561,14 @@ func Test_cmdwin_insert_mode_close()
call assert_equal(1, winnr('$'))
endfunc
+func Test_cmdwin_ex_mode_with_modifier()
+ " this was accessing memory after allocated text in Ex mode
+ new
+ call setline(1, ['some', 'text', 'lines'])
+ silent! call feedkeys("gQnormal vq:atopleft\<C-V>\<CR>\<CR>", 'xt')
+ bwipe!
+endfunc
+
" test that ";" works to find a match at the start of the first line
func Test_zero_line_search()
new
diff --git a/test/old/testdir/test_ex_mode.vim b/test/old/testdir/test_ex_mode.vim
@@ -287,6 +287,18 @@ func Test_ex_mode_large_indent()
bwipe!
endfunc
+" This was accessing illegal memory when using "+" for eap->cmd.
+func Test_empty_command_visual_mode()
+ let lines =<< trim END
+ r<sfile>
+ 0norm0V:
+ :qall!
+ END
+ call writefile(lines, 'Xexmodescript')
+ call assert_equal(1, RunVim([], [], '-u NONE -e -s -S Xexmodescript'))
+
+ call delete('Xexmodescript')
+endfunc
" Testing implicit print command
func Test_implicit_print()
diff --git a/test/old/testdir/test_source.vim b/test/old/testdir/test_source.vim
@@ -577,6 +577,13 @@ func Test_source_buffer_vim9()
call assert_equal(#{pi: 3.12, e: 2.71828}, g:Math)
call assert_equal(['vim', 'nano'], g:Editors)
+ " '<,'> range before the cmd modifier works
+ unlet g:Math
+ unlet g:Editors
+ exe "normal 6GV4j:vim9cmd source\<CR>"
+ call assert_equal(['vim', 'nano'], g:Editors)
+ unlet g:Editors
+
" test for using try/catch
%d _
let lines =<< trim END