commit 72b0bfa1fb7e897e5126aabae718a5480f466b9e
parent b0edab363113cce7ff7cb7b8884a599b98f44fdc
Author: glepnir <glephunter@gmail.com>
Date: Mon, 13 Oct 2025 07:36:06 +0800
fix(api): nvim_parse_cmd handle nextcmd for commands without EX_TRLBAR (#36055)
Problem: nvim_parse_cmd('exe "ls"|edit foo', {}) fails to separate
nextcmd, returning args as { '"ls"|edit', 'foo' } instead of { '"ls"' }
with nextcmd='edit foo'.
Solution: Skip expressions before checking for '|' separator.
Diffstat:
3 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -1986,11 +1986,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
}
// ":exe one two" completes "two"
- if ((cmdidx == CMD_execute
- || cmdidx == CMD_echo
- || cmdidx == CMD_echon
- || cmdidx == CMD_echomsg)
- && xp->xp_context == EXPAND_EXPRESSION) {
+ if (cmd_has_expr_args(cmdidx) && xp->xp_context == EXPAND_EXPRESSION) {
while (true) {
char *const n = skiptowhite(arg);
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
@@ -13,6 +13,7 @@
#include "auto/config.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
#include "nvim/api/vimscript.h"
@@ -1507,6 +1508,16 @@ static bool parse_bang(const exarg_T *eap, char **p)
return false;
}
+/// Check if command expects expression arguments that need special parsing
+bool cmd_has_expr_args(cmdidx_T cmdidx)
+{
+ return cmdidx == CMD_execute
+ || cmdidx == CMD_echo
+ || cmdidx == CMD_echon
+ || cmdidx == CMD_echomsg
+ || cmdidx == CMD_echoerr;
+}
+
/// Parse command line and return information about the first command.
/// If parsing is done successfully, need to free cmod_filter_pat and cmod_filter_regmatch.regprog
/// after calling, usually done using undo_cmdmod() or execute_cmd().
@@ -1596,6 +1607,22 @@ bool parse_cmdline(char **cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const ch
// Don't do this for ":read !cmd" and ":write !cmd".
if ((eap->argt & EX_TRLBAR)) {
separate_nextcmd(eap);
+ } else if (cmd_has_expr_args(eap->cmdidx)) {
+ // For commands without EX_TRLBAR, check for '|' separator
+ // by skipping over expressions (including string literals)
+ char *arg = eap->arg;
+ while (*arg != NUL && *arg != '|' && *arg != '\n') {
+ char *start = arg;
+ skip_expr(&arg, NULL);
+ // If skip_expr didn't advance, move forward to avoid infinite loop
+ if (arg == start) {
+ arg++;
+ }
+ }
+ if (*arg == '|' || *arg == '\n') {
+ eap->nextcmd = check_nextcmd(arg);
+ *arg = NUL;
+ }
}
// Fail if command doesn't support bang but is used with a bang
if (!(eap->argt & EX_BANG) && eap->forceit) {
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
@@ -4849,6 +4849,12 @@ describe('API', function()
result = api.nvim_parse_cmd('copen 5', {})
eq(5, result.count)
end)
+ it('parses nextcmd for commands #36029', function()
+ local result = api.nvim_parse_cmd('exe "ls"|edit foo', {})
+ eq({ '"ls"' }, result.args)
+ eq('execute', result.cmd)
+ eq('edit foo', result.nextcmd)
+ end)
end)
describe('nvim_cmd', function()