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.
This commit is contained in:
glepnir
2025-10-13 07:36:06 +08:00
committed by GitHub
parent b0edab3631
commit 72b0bfa1fb
3 changed files with 34 additions and 5 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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()