feat(api): nvim_cmd supports plus ("+cmd", "++opt") flags #30103

Problem:
nvim_cmd does not handle plus flags.

Solution:
In nvim_cmd, parse the flags and set the relevant `ea` fields.
This commit is contained in:
glepnir
2025-05-05 20:58:36 +08:00
committed by GitHub
parent 912388f517
commit 1e7406fa38
3 changed files with 181 additions and 2 deletions

View File

@@ -634,6 +634,22 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
build_cmdline_str(&cmdline, &ea, &cmdinfo, args);
ea.cmdlinep = &cmdline;
// Check for "++opt=val" argument.
if (ea.argt & EX_ARGOPT) {
while (ea.arg[0] == '+' && ea.arg[1] == '+') {
char *orig_arg = ea.arg;
int result = getargopt(&ea);
VALIDATE_S(result != FAIL || is_cmd_ni(ea.cmdidx), "argument ", orig_arg, {
goto end;
});
}
}
// Check for "+command" argument.
if ((ea.argt & EX_CMDARG) && !ea.usefilter) {
ea.do_ecmd_cmd = getargcmd(&ea.arg);
}
garray_T capture_local;
const int save_msg_silent = msg_silent;
garray_T * const save_capture_ga = capture_ga;

View File

@@ -4147,7 +4147,7 @@ void separate_nextcmd(exarg_T *eap)
}
/// get + command from ex argument
static char *getargcmd(char **argp)
char *getargcmd(char **argp)
{
char *arg = *argp;
char *command = NULL;
@@ -4222,7 +4222,7 @@ static char *get_bad_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
/// Get "++opt=arg" argument.
///
/// @return FAIL or OK.
static int getargopt(exarg_T *eap)
int getargopt(exarg_T *eap)
{
char *arg = eap->arg + 2;
int *pp = NULL;

View File

@@ -5233,6 +5233,169 @@ describe('API', function()
assert_alive()
eq(false, exec_lua('return _G.success'))
end)
it('handles +flags correctly', function()
-- Write a file for testing +flags
t.write_file('testfile', 'Line 1\nLine 2\nLine 3', false, false)
-- Test + command (go to the last line)
local result = exec_lua([[
local parsed = vim.api.nvim_parse_cmd('edit + testfile', {})
vim.cmd(parsed)
return { vim.fn.line('.'), parsed.args, parsed.cmd }
]])
eq({ 3, { '+ testfile' }, 'edit' }, result)
-- Test +{num} command (go to line number)
result = exec_lua([[
vim.cmd(vim.api.nvim_parse_cmd('edit +1 testfile', {}))
return vim.fn.line('.')
]])
eq(1, result)
-- Test +/{pattern} command (go to line with pattern)
result = exec_lua([[
local parsed = vim.api.nvim_parse_cmd('edit +/Line\\ 2 testfile', {})
vim.cmd(parsed)
return {vim.fn.line('.'), parsed.args}
]])
eq({ 2, { '+/Line\\ 2 testfile' } }, result)
-- Test +{command} command (execute a command after opening the file)
result = exec_lua([[
vim.cmd(vim.api.nvim_parse_cmd('edit +set\\ nomodifiable testfile', {}))
return vim.bo.modifiable
]])
eq(false, result)
-- Test ++ flags structure in parsed command
result = exec_lua([[
local parsed = vim.api.nvim_parse_cmd('botright edit + testfile', {})
vim.cmd(parsed)
return { vim.fn.line('.'), parsed.cmd, parsed.args, parsed.mods.split }
]])
eq({ 3, 'edit', { '+ testfile' }, 'botright' }, result)
-- Clean up
os.remove('testfile')
end)
it('handles various ++ flags correctly', function()
-- Test ++ff flag
local result = exec_lua [[
local parsed = vim.api.nvim_parse_cmd('edit ++ff=mac test_ff_mac.txt', {})
vim.cmd(parsed)
return parsed.args
]]
eq({ '++ff=mac test_ff_mac.txt' }, result)
eq('mac', api.nvim_get_option_value('fileformat', {}))
eq('test_ff_mac.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
exec_lua [[
vim.cmd(vim.api.nvim_parse_cmd('edit ++fileformat=unix test_ff_unix.txt', {}))
]]
eq('unix', api.nvim_get_option_value('fileformat', {}))
eq('test_ff_unix.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
-- Test ++enc flag
exec_lua [[
vim.cmd(vim.api.nvim_parse_cmd('edit ++enc=utf-32 test_enc.txt', {}))
]]
eq('ucs-4', api.nvim_get_option_value('fileencoding', {}))
eq('test_enc.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
-- Test ++bin and ++nobin flags
exec_lua [[
vim.cmd(vim.api.nvim_parse_cmd('edit ++bin test_bin.txt', {}))
]]
eq(true, api.nvim_get_option_value('binary', {}))
eq('test_bin.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
exec_lua [[
vim.cmd(vim.api.nvim_parse_cmd('edit ++nobin test_nobin.txt', {}))
]]
eq(false, api.nvim_get_option_value('binary', {}))
eq('test_nobin.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
-- Test multiple flags together
exec_lua [[
vim.cmd(vim.api.nvim_parse_cmd('edit ++ff=mac ++enc=utf-32 ++bin test_multi.txt', {}))
]]
eq(true, api.nvim_get_option_value('binary', {}))
eq('mac', api.nvim_get_option_value('fileformat', {}))
eq('ucs-4', api.nvim_get_option_value('fileencoding', {}))
eq('test_multi.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t'))
end)
it('handles invalid and incorrect ++ flags gracefully', function()
-- Test invalid ++ff flag
local result = exec_lua [[
local cmd = vim.api.nvim_parse_cmd('edit ++ff=invalid test_invalid_ff.txt', {})
local _, err = pcall(vim.cmd, cmd)
return err
]]
matches("Invalid argument : '%+%+ff=invalid'$", result)
-- Test incorrect ++ syntax
result = exec_lua [[
local cmd = vim.api.nvim_parse_cmd('edit ++unknown=test_unknown.txt', {})
local _, err = pcall(vim.cmd, cmd)
return err
]]
matches("Invalid argument : '%+%+unknown=test_unknown.txt'$", result)
-- Test invalid ++bin flag
result = exec_lua [[
local cmd = vim.api.nvim_parse_cmd('edit ++binabc test_invalid_bin.txt', {})
local _, err = pcall(vim.cmd, cmd)
return err
]]
matches("Invalid argument : '%+%+binabc test_invalid_bin.txt'$", result)
end)
it('handles ++p for creating parent directory', function()
exec_lua [[
vim.cmd('edit flags_dir/test_create.txt')
vim.cmd(vim.api.nvim_parse_cmd('write! ++p', {}))
]]
eq(true, fn.isdirectory('flags_dir') == 1)
fn.delete('flags_dir', 'rf')
end)
it('tests editing files with bad utf8 sequences', function()
-- Write a file with bad utf8 sequences
local file = io.open('Xfile', 'wb')
file:write('[\255][\192][\226\137\240][\194\194]')
file:close()
exec_lua([[
vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 Xfile', {}))
]])
eq('[?][?][???][??]', api.nvim_get_current_line())
exec_lua([[
vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=_ Xfile', {}))
]])
eq('[_][_][___][__]', api.nvim_get_current_line())
exec_lua([[
vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=drop Xfile', {}))
]])
eq('[][][][]', api.nvim_get_current_line())
exec_lua([[
vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=keep Xfile', {}))
]])
eq('[\255][\192][\226\137\240][\194\194]', api.nvim_get_current_line())
local result = exec_lua([[
local _, err = pcall(vim.cmd, vim.api.nvim_parse_cmd('edit ++enc=utf8 ++bad=foo Xfile', {}))
return err
]])
matches("Invalid argument : '%+%+bad=foo'$", result)
-- Clean up
os.remove('Xfile')
end)
end)
it('nvim__redraw', function()