mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
fix(api): count parameter in nvim_parse_cmd, nvim_cmd #34253
Problem: - nvim_parse_cmd('copen', {}) returns count: 0, causing nvim_cmd to override default behavior - nvim_cmd({cmd = 'copen', args = {10}}, {}) fails with "Wrong number of arguments" Solution: - Only include count field in parse result when explicitly provided or non-zero - Interpret single numeric argument as count for count-only commands like copen
This commit is contained in:
@@ -165,7 +165,12 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err
|
|||||||
|
|
||||||
if (ea.argt & EX_COUNT) {
|
if (ea.argt & EX_COUNT) {
|
||||||
Integer count = ea.addr_count > 0 ? ea.line2 : (cmd != NULL ? cmd->uc_def : 0);
|
Integer count = ea.addr_count > 0 ? ea.line2 : (cmd != NULL ? cmd->uc_def : 0);
|
||||||
PUT_KEY(result, cmd, count, count);
|
// For built-in commands, if count is not explicitly provided and the default value is 0,
|
||||||
|
// do not include the count field in the result, so the command uses its built-in default
|
||||||
|
// behavior.
|
||||||
|
if (ea.addr_count > 0 || (cmd != NULL && cmd->uc_def != 0) || count != 0) {
|
||||||
|
PUT_KEY(result, cmd, count, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ea.argt & EX_REGSTR) {
|
if (ea.argt & EX_REGSTR) {
|
||||||
@@ -377,68 +382,102 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
|
|||||||
ea.argt = get_cmd_argt(ea.cmdidx);
|
ea.argt = get_cmd_argt(ea.cmdidx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track whether the first argument was interpreted as count to avoid conflicts
|
||||||
|
bool count_from_first_arg = false;
|
||||||
// Parse command arguments since it's needed to get the command address type.
|
// Parse command arguments since it's needed to get the command address type.
|
||||||
if (HAS_KEY(cmd, cmd, args)) {
|
if (HAS_KEY(cmd, cmd, args)) {
|
||||||
// Process all arguments. Convert non-String arguments to String and check if String arguments
|
// Special handling: for commands that support count but not regular arguments,
|
||||||
// have non-whitespace characters.
|
// if a single numeric argument is provided, interpret it as count
|
||||||
args = arena_array(arena, cmd->args.size);
|
if (cmd->args.size == 1 && (ea.argt & EX_COUNT) && !(ea.argt & EX_EXTRA)) {
|
||||||
for (size_t i = 0; i < cmd->args.size; i++) {
|
Object first_arg = cmd->args.items[0];
|
||||||
Object elem = cmd->args.items[i];
|
bool is_numeric = false;
|
||||||
char *data_str;
|
int64_t count_value = 0;
|
||||||
|
|
||||||
switch (elem.type) {
|
if (first_arg.type == kObjectTypeInteger) {
|
||||||
case kObjectTypeBoolean:
|
is_numeric = true;
|
||||||
data_str = arena_alloc(arena, 2, false);
|
count_value = first_arg.data.integer;
|
||||||
data_str[0] = elem.data.boolean ? '1' : '0';
|
} else if (first_arg.type == kObjectTypeString) {
|
||||||
data_str[1] = NUL;
|
// Try to parse string as a number Example: vim.api.nvim_cmd({cmd = 'copen', args = {'10'}}, {})
|
||||||
ADD_C(args, CSTR_AS_OBJ(data_str));
|
char *endptr;
|
||||||
break;
|
long val = strtol(first_arg.data.string.data, &endptr, 10);
|
||||||
case kObjectTypeBuffer:
|
// Check if entire string was consumed (valid number) and string is not empty
|
||||||
case kObjectTypeWindow:
|
if (*endptr == '\0' && first_arg.data.string.size > 0) {
|
||||||
case kObjectTypeTabpage:
|
is_numeric = true;
|
||||||
case kObjectTypeInteger:
|
count_value = val;
|
||||||
data_str = arena_alloc(arena, NUMBUFLEN, false);
|
}
|
||||||
snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer);
|
}
|
||||||
ADD_C(args, CSTR_AS_OBJ(data_str));
|
|
||||||
break;
|
if (is_numeric && count_value >= 0) {
|
||||||
case kObjectTypeString:
|
// Interpret the argument as count
|
||||||
VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, {
|
count_from_first_arg = true;
|
||||||
goto end;
|
ea.addr_count = 1;
|
||||||
});
|
ea.line1 = ea.line2 = (linenr_T)count_value;
|
||||||
ADD_C(args, elem);
|
args = arena_array(arena, 0);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), {
|
|
||||||
goto end;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool argc_valid;
|
if (!count_from_first_arg) {
|
||||||
|
// Process all arguments. Convert non-String arguments to String and check if String arguments
|
||||||
|
// have non-whitespace characters.
|
||||||
|
args = arena_array(arena, cmd->args.size);
|
||||||
|
for (size_t i = 0; i < cmd->args.size; i++) {
|
||||||
|
Object elem = cmd->args.items[i];
|
||||||
|
char *data_str;
|
||||||
|
|
||||||
// Check if correct number of arguments is used.
|
switch (elem.type) {
|
||||||
switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
|
case kObjectTypeBoolean:
|
||||||
case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
|
data_str = arena_alloc(arena, 2, false);
|
||||||
argc_valid = args.size == 1;
|
data_str[0] = elem.data.boolean ? '1' : '0';
|
||||||
break;
|
data_str[1] = NUL;
|
||||||
case EX_EXTRA | EX_NOSPC:
|
ADD_C(args, CSTR_AS_OBJ(data_str));
|
||||||
argc_valid = args.size <= 1;
|
break;
|
||||||
break;
|
case kObjectTypeBuffer:
|
||||||
case EX_EXTRA | EX_NEEDARG:
|
case kObjectTypeWindow:
|
||||||
argc_valid = args.size >= 1;
|
case kObjectTypeTabpage:
|
||||||
break;
|
case kObjectTypeInteger:
|
||||||
case EX_EXTRA:
|
data_str = arena_alloc(arena, NUMBUFLEN, false);
|
||||||
argc_valid = true;
|
snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer);
|
||||||
break;
|
ADD_C(args, CSTR_AS_OBJ(data_str));
|
||||||
default:
|
break;
|
||||||
argc_valid = args.size == 0;
|
case kObjectTypeString:
|
||||||
break;
|
VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, {
|
||||||
|
goto end;
|
||||||
|
});
|
||||||
|
ADD_C(args, elem);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), {
|
||||||
|
goto end;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool argc_valid;
|
||||||
|
|
||||||
|
// Check if correct number of arguments is used.
|
||||||
|
switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
|
||||||
|
case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
|
||||||
|
argc_valid = args.size == 1;
|
||||||
|
break;
|
||||||
|
case EX_EXTRA | EX_NOSPC:
|
||||||
|
argc_valid = args.size <= 1;
|
||||||
|
break;
|
||||||
|
case EX_EXTRA | EX_NEEDARG:
|
||||||
|
argc_valid = args.size >= 1;
|
||||||
|
break;
|
||||||
|
case EX_EXTRA:
|
||||||
|
argc_valid = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
argc_valid = args.size == 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATE(argc_valid, "%s", "Wrong number of arguments", {
|
||||||
|
goto end;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
VALIDATE(argc_valid, "%s", "Wrong number of arguments", {
|
|
||||||
goto end;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
|
// Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
|
||||||
@@ -485,6 +524,9 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (HAS_KEY(cmd, cmd, count)) {
|
if (HAS_KEY(cmd, cmd, count)) {
|
||||||
|
VALIDATE(!count_from_first_arg, "%s", "Cannot specify both 'count' and numeric argument", {
|
||||||
|
goto end;
|
||||||
|
});
|
||||||
VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data);
|
VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data);
|
||||||
VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, {
|
VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, {
|
||||||
goto end;
|
goto end;
|
||||||
|
@@ -4783,6 +4783,14 @@ describe('API', function()
|
|||||||
)
|
)
|
||||||
eq('', eval('v:errmsg'))
|
eq('', eval('v:errmsg'))
|
||||||
end)
|
end)
|
||||||
|
it('does not include count field when no count provided for builtin commands', function()
|
||||||
|
local result = api.nvim_parse_cmd('copen', {})
|
||||||
|
eq(nil, result.count)
|
||||||
|
api.nvim_cmd(result, {})
|
||||||
|
eq(10, api.nvim_win_get_height(0))
|
||||||
|
result = api.nvim_parse_cmd('copen 5', {})
|
||||||
|
eq(5, result.count)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('nvim_cmd', function()
|
describe('nvim_cmd', function()
|
||||||
@@ -5396,6 +5404,57 @@ describe('API', function()
|
|||||||
-- Clean up
|
-- Clean up
|
||||||
os.remove('Xfile')
|
os.remove('Xfile')
|
||||||
end)
|
end)
|
||||||
|
it('interprets numeric args as count for count-only commands', function()
|
||||||
|
api.nvim_cmd({ cmd = 'copen', args = { 8 } }, {})
|
||||||
|
local height1 = api.nvim_win_get_height(0)
|
||||||
|
command('cclose')
|
||||||
|
api.nvim_cmd({ cmd = 'copen', count = 8 }, {})
|
||||||
|
local height2 = api.nvim_win_get_height(0)
|
||||||
|
command('cclose')
|
||||||
|
eq(height1, height2)
|
||||||
|
|
||||||
|
exec_lua 'vim.cmd.copen(5)'
|
||||||
|
height2 = api.nvim_win_get_height(0)
|
||||||
|
command('cclose')
|
||||||
|
eq(5, height2)
|
||||||
|
|
||||||
|
-- should reject both count and numeric arg
|
||||||
|
eq(
|
||||||
|
"Cannot specify both 'count' and numeric argument",
|
||||||
|
pcall_err(api.nvim_cmd, { cmd = 'copen', args = { 5 }, count = 10 }, {})
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
it('handles string numeric arguments correctly', function()
|
||||||
|
-- Valid string numbers should work
|
||||||
|
api.nvim_cmd({ cmd = 'copen', args = { '6' } }, {})
|
||||||
|
eq(6, api.nvim_win_get_height(0))
|
||||||
|
command('cclose')
|
||||||
|
-- Invalid strings should be rejected
|
||||||
|
eq(
|
||||||
|
'Wrong number of arguments',
|
||||||
|
pcall_err(api.nvim_cmd, { cmd = 'copen', args = { 'abc' } }, {})
|
||||||
|
)
|
||||||
|
-- Partial numbers should be rejected
|
||||||
|
eq(
|
||||||
|
'Wrong number of arguments',
|
||||||
|
pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '8abc' } }, {})
|
||||||
|
)
|
||||||
|
-- Empty string should be rejected
|
||||||
|
eq(
|
||||||
|
'Invalid command arg: expected non-whitespace',
|
||||||
|
pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '' } }, {})
|
||||||
|
)
|
||||||
|
-- Negative string numbers should be rejected
|
||||||
|
eq(
|
||||||
|
'Wrong number of arguments',
|
||||||
|
pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '-5' } }, {})
|
||||||
|
)
|
||||||
|
-- Leading/trailing spaces should be rejected
|
||||||
|
eq(
|
||||||
|
'Wrong number of arguments',
|
||||||
|
pcall_err(api.nvim_cmd, { cmd = 'copen', args = { ' 5 ' } }, {})
|
||||||
|
)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('nvim__redraw', function()
|
it('nvim__redraw', function()
|
||||||
|
Reference in New Issue
Block a user