From 2e94406ba0ee707ecb17c901e1e8590734d6cc06 Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 19 Aug 2024 19:51:00 +0800 Subject: [PATCH] feat(api): nvim_cmd support plus flags Problem: currently nvim_cmd can not handle plus flags correct. Solution: add plus flags support. --- src/nvim/api/command.c | 20 +++- src/nvim/ex_docmd.c | 4 +- test/functional/api/vim_spec.lua | 152 +++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 3 deletions(-) diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 78be0a2cf7..5e97d37746 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -27,6 +27,7 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/ops.h" +#include "nvim/optionstr.h" #include "nvim/pos_defs.h" #include "nvim/regexp.h" #include "nvim/strings.h" @@ -317,7 +318,6 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena char *cmdline = NULL; char *cmdname = NULL; ArrayOf(String) args = ARRAY_DICT_INIT; - String retv = (String)STRING_INIT; #define OBJ_TO_BOOL(var, value, default, varname) \ @@ -633,6 +633,24 @@ 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; + // set when Not Implemented + const int ni = is_cmd_ni(ea.cmdidx); + + // Check for "++opt=val" argument. + if (ea.argt & EX_ARGOPT) { + while (ea.arg[0] == '+' && ea.arg[1] == '+') { + if (getargopt(&ea) == FAIL && !ni) { + api_set_error(err, kErrorTypeValidation, "Invalid argument"); + 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; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index b5a5a0f466..af76ff732f 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4128,7 +4128,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; @@ -4203,7 +4203,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; diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 4210b7ecf0..338074f4bf 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -5205,4 +5205,156 @@ describe('API', function() api.nvim__redraw({ win = 0, range = { 0, -1 } }) n.assert_alive() end) + + describe('nvim_cmd with plus #flags', function() + it('handles +flags correctly', function() + -- Write a file for testing +flags + command(':call writefile(["Line 1", "Line 2", "Line 3"], "testfile")') + + -- Test + command (go to the last line) + local result = exec_lua([[ + vim.cmd(vim.api.nvim_parse_cmd('edit + testfile', {})) + return vim.fn.line('.') + ]]) + eq(3, 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([[ + vim.cmd(vim.api.nvim_parse_cmd('edit +/Line\\ 2 testfile', {})) + return vim.fn.line('.') + ]]) + eq(2, 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) + + -- Clean up + os.remove('testfile') + end) + + it('handles various ++ flags correctly', function() + -- Test ++ff flag + exec_lua [[ + vim.cmd(vim.api.nvim_parse_cmd('edit ++ff=mac test_ff_mac.txt', {})) + ]] + 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 + ]] + eq('Invalid argument', 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 + ]] + eq('Invalid argument', 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 + ]] + eq('Invalid argument', 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 + ]]) + eq('Invalid argument', result) + -- Clean up + os.remove('Xfile') + end) + end) end)