neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Msrc/nvim/ex_docmd.c | 52+++++++++++++++++++++++++++++++++++++++++++++++++++-
Mtest/old/testdir/test_cmdline.vim | 8++++++++
Mtest/old/testdir/test_ex_mode.vim | 12++++++++++++
Mtest/old/testdir/test_source.vim | 7+++++++
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