mirror of
https://github.com/neovim/neovim.git
synced 2025-09-10 21:38:19 +00:00
fix(api): nvim_parse_cmd parses :map incorrectly #34068
Problem: nvim_parse_cmd() incorrectly splits mapping commands like into three arguments instead of preserving whitespace in the RHS. Solution: Add special handling for mapping commands to parse them as exactly two arguments - the LHS and the RHS with all whitespace preserved.
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
#include "nvim/autocmd.h"
|
#include "nvim/autocmd.h"
|
||||||
#include "nvim/autocmd_defs.h"
|
#include "nvim/autocmd_defs.h"
|
||||||
#include "nvim/buffer_defs.h"
|
#include "nvim/buffer_defs.h"
|
||||||
|
#include "nvim/charset.h"
|
||||||
#include "nvim/cmdexpand_defs.h"
|
#include "nvim/cmdexpand_defs.h"
|
||||||
#include "nvim/ex_cmds_defs.h"
|
#include "nvim/ex_cmds_defs.h"
|
||||||
#include "nvim/ex_docmd.h"
|
#include "nvim/ex_docmd.h"
|
||||||
@@ -40,6 +41,31 @@
|
|||||||
# include "api/command.c.generated.h"
|
# include "api/command.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Parse arguments for :map/:abbrev commands, preserving whitespace in RHS.
|
||||||
|
/// @param arg_str The argument string to parse
|
||||||
|
/// @param arena Arena allocator
|
||||||
|
/// @return Array with at most 2 elements: [lhs, rhs]
|
||||||
|
static Array parse_map_cmd(const char *arg_str, Arena *arena)
|
||||||
|
{
|
||||||
|
Array args = arena_array(arena, 2);
|
||||||
|
|
||||||
|
char *lhs_start = (char *)arg_str;
|
||||||
|
char *lhs_end = skiptowhite(lhs_start);
|
||||||
|
size_t lhs_len = (size_t)(lhs_end - lhs_start);
|
||||||
|
|
||||||
|
// Add the LHS (first argument)
|
||||||
|
ADD_C(args, STRING_OBJ(cstrn_as_string(lhs_start, lhs_len)));
|
||||||
|
|
||||||
|
// Add the RHS (second argument) if it exists, preserving all whitespace
|
||||||
|
char *rhs_start = skipwhite(lhs_end);
|
||||||
|
if (*rhs_start != NUL) {
|
||||||
|
size_t rhs_len = strlen(rhs_start);
|
||||||
|
ADD_C(args, STRING_OBJ(cstrn_as_string(rhs_start, rhs_len)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse command line.
|
/// Parse command line.
|
||||||
///
|
///
|
||||||
/// Doesn't check the validity of command arguments.
|
/// Doesn't check the validity of command arguments.
|
||||||
@@ -121,9 +147,15 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err
|
|||||||
Array args = ARRAY_DICT_INIT;
|
Array args = ARRAY_DICT_INIT;
|
||||||
size_t length = strlen(ea.arg);
|
size_t length = strlen(ea.arg);
|
||||||
|
|
||||||
|
// Check if this is a mapping command that needs special handling
|
||||||
|
// like mapping commands need special argument parsing to preserve whitespace in RHS:
|
||||||
|
// "map a b c" => { args=["a", "b c"], ... }
|
||||||
|
if (is_map_cmd(ea.cmdidx) && *ea.arg != NUL) {
|
||||||
|
// For mapping commands, split differently to preserve whitespace
|
||||||
|
args = parse_map_cmd(ea.arg, arena);
|
||||||
|
} else if (ea.argt & EX_NOSPC) {
|
||||||
// For nargs = 1 or '?', pass the entire argument list as a single argument,
|
// For nargs = 1 or '?', pass the entire argument list as a single argument,
|
||||||
// otherwise split arguments by whitespace.
|
// otherwise split arguments by whitespace.
|
||||||
if (ea.argt & EX_NOSPC) {
|
|
||||||
if (*ea.arg != NUL) {
|
if (*ea.arg != NUL) {
|
||||||
args = arena_array(arena, 1);
|
args = arena_array(arena, 1);
|
||||||
ADD_C(args, STRING_OBJ(cstrn_as_string(ea.arg, length)));
|
ADD_C(args, STRING_OBJ(cstrn_as_string(ea.arg, length)));
|
||||||
|
@@ -8187,3 +8187,18 @@ uint32_t get_cmd_argt(cmdidx_T cmdidx)
|
|||||||
{
|
{
|
||||||
return cmdnames[(int)cmdidx].cmd_argt;
|
return cmdnames[(int)cmdidx].cmd_argt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a command is a :map/:abbrev command.
|
||||||
|
bool is_map_cmd(cmdidx_T cmdidx)
|
||||||
|
{
|
||||||
|
if (IS_USER_CMDIDX(cmdidx)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ex_func_T func = cmdnames[cmdidx].cmd_func;
|
||||||
|
return func == ex_map // :map, :nmap, :noremap, etc.
|
||||||
|
|| func == ex_unmap // :unmap, :nunmap, etc.
|
||||||
|
|| func == ex_mapclear // :mapclear, :nmapclear, etc.
|
||||||
|
|| func == ex_abbreviate // :abbreviate, :iabbrev, etc.
|
||||||
|
|| func == ex_abclear; // :abclear, :iabclear, etc.
|
||||||
|
}
|
||||||
|
@@ -4641,6 +4641,45 @@ describe('API', function()
|
|||||||
},
|
},
|
||||||
}, api.nvim_parse_cmd('argadd a.txt | argadd b.txt', {}))
|
}, api.nvim_parse_cmd('argadd a.txt | argadd b.txt', {}))
|
||||||
end)
|
end)
|
||||||
|
it('parses :map commands with space in RHS', function()
|
||||||
|
eq({
|
||||||
|
addr = 'none',
|
||||||
|
args = { 'a', 'b c' },
|
||||||
|
bang = false,
|
||||||
|
cmd = 'map',
|
||||||
|
magic = {
|
||||||
|
bar = true,
|
||||||
|
file = false,
|
||||||
|
},
|
||||||
|
mods = {
|
||||||
|
browse = false,
|
||||||
|
confirm = false,
|
||||||
|
emsg_silent = false,
|
||||||
|
filter = {
|
||||||
|
force = false,
|
||||||
|
pattern = '',
|
||||||
|
},
|
||||||
|
hide = false,
|
||||||
|
horizontal = false,
|
||||||
|
keepalt = false,
|
||||||
|
keepjumps = false,
|
||||||
|
keepmarks = false,
|
||||||
|
keeppatterns = false,
|
||||||
|
lockmarks = false,
|
||||||
|
noautocmd = false,
|
||||||
|
noswapfile = false,
|
||||||
|
sandbox = false,
|
||||||
|
silent = false,
|
||||||
|
split = '',
|
||||||
|
tab = -1,
|
||||||
|
unsilent = false,
|
||||||
|
verbose = -1,
|
||||||
|
vertical = false,
|
||||||
|
},
|
||||||
|
nargs = '*',
|
||||||
|
nextcmd = '',
|
||||||
|
}, api.nvim_parse_cmd('map a b c', {}))
|
||||||
|
end)
|
||||||
it('works for nargs=1', function()
|
it('works for nargs=1', function()
|
||||||
command('command -nargs=1 MyCommand echo <q-args>')
|
command('command -nargs=1 MyCommand echo <q-args>')
|
||||||
eq({
|
eq({
|
||||||
|
Reference in New Issue
Block a user