aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier Lopez <graulopezjavier@gmail.com>2022-02-27 14:35:06 -0500
committerGitHub <noreply@github.com>2022-02-27 12:35:06 -0700
commit1b5767aa3480c0cdc43f7a4b78f36a14e85a182f (patch)
tree2329eff263bf6893de4108ab6890515e5f1c3970
parentc65d93e60adcacded822f0ad5d539042e600f523 (diff)
downloadrneovim-1b5767aa3480c0cdc43f7a4b78f36a14e85a182f.tar.gz
rneovim-1b5767aa3480c0cdc43f7a4b78f36a14e85a182f.tar.bz2
rneovim-1b5767aa3480c0cdc43f7a4b78f36a14e85a182f.zip
feat(lua): add <f-args> to user commands callback (#17522)
Works similar to ex <f-args>. It only splits the arguments if the command has more than one posible argument. In cases were the command can only have 1 argument opts.fargs = { opts.args }
-rw-r--r--runtime/doc/api.txt3
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/ex_docmd.c24
-rw-r--r--src/nvim/lua/executor.c25
-rw-r--r--test/functional/api/command_spec.lua66
5 files changed, 109 insertions, 11 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index de7db3b0b7..cff616cf59 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -651,6 +651,9 @@ nvim_add_user_command({name}, {command}, {*opts})
that contains the following keys:
• args: (string) The args passed to the
command, if any |<args>|
+ • fargs: (table) The args split by unescaped
+ whitespace (when more than one argument is
+ allowed), if any |<f-args>|
• bang: (boolean) "true" if the command was
executed with a ! modifier |<bang>|
• line1: (number) The starting line of the
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index cd9d61ed24..302dccbde7 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2415,6 +2415,8 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
/// from Lua, the command can also be a Lua function. The function is called with a
/// single table argument that contains the following keys:
/// - args: (string) The args passed to the command, if any |<args>|
+/// - fargs: (table) The args split by unescaped whitespace (when more than one
+/// argument is allowed), if any |<f-args>|
/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
/// - line1: (number) The starting line of the command range |<line1>|
/// - line2: (number) The final line of the command range |<line2>|
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 48749afcb3..152a41c407 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5802,6 +5802,30 @@ static void ex_delcommand(exarg_T *eap)
}
}
+/// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback.
+/// Similar to uc_split_args(), but does not allocate, add quotes, add commas and is an iterator.
+///
+/// @note If no separator is found start = 0 and end = length - 1
+/// @param[in] arg String to split
+/// @param[in] iter Iteration counter
+/// @param[out] start Start of the split
+/// @param[out] end End of the split
+/// @param[in] length Length of the string
+/// @return false if it's the last split (don't call again), true otherwise (call again).
+bool uc_split_args_iter(const char_u *arg, int iter, int *start, int *end, int length)
+{
+ int pos;
+ *start = *end + (iter > 1 ? 2 : 0); // Skip whitespace after the first split
+ for (pos = *start; pos < length - 2; pos++) {
+ if (arg[pos] != '\\' && ascii_iswhite(arg[pos + 1])) {
+ *end = pos;
+ return true;
+ }
+ }
+ *end = length - 1;
+ return false;
+}
+
/*
* split and quote args for <f-args>
*/
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index d207f48435..3c1676581c 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1814,8 +1814,31 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
lua_pushinteger(lstate, eap->line2);
lua_setfield(lstate, -2, "line2");
+ lua_newtable(lstate); // f-args table
lua_pushstring(lstate, (const char *)eap->arg);
- lua_setfield(lstate, -2, "args");
+ lua_pushvalue(lstate, -1); // Reference for potential use on f-args
+ lua_setfield(lstate, -4, "args");
+
+ // Split args by unescaped whitespace |<f-args>| (nargs dependent)
+ if (cmd->uc_argt & EX_NOSPC) {
+ // Commands where nargs = 1 or "?" fargs is the same as args
+ lua_rawseti(lstate, -2, 1);
+ } else {
+ // Commands with more than one possible argument we split
+ lua_pop(lstate, 1); // Pop the reference of opts.args
+ int length = (int)STRLEN(eap->arg);
+ int start = 0;
+ int end = 0;
+ int i = 1;
+ bool res = true;
+ while (res) {
+ res = uc_split_args_iter(eap->arg, i, &start, &end, length);
+ lua_pushlstring(lstate, (const char *)eap->arg + start, (size_t)(end - start + 1));
+ lua_rawseti(lstate, -2, i);
+ i++;
+ }
+ }
+ lua_setfield(lstate, -2, "fargs");
lua_pushstring(lstate, (const char *)&eap->regname);
lua_setfield(lstate, -2, "reg");
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index de22c9078c..b80004f67c 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -107,7 +107,8 @@ describe('nvim_add_user_command', function()
]]
eq({
- args = "hello",
+ args = [[hello my\ friend how\ are\ you?]],
+ fargs = {[[hello]], [[my\ friend]], [[how\ are\ you?]]},
bang = false,
line1 = 1,
line2 = 1,
@@ -115,13 +116,14 @@ describe('nvim_add_user_command', function()
range = 0,
count = 2,
reg = "",
- }, exec_lua [[
- vim.api.nvim_command('CommandWithLuaCallback hello')
+ }, exec_lua [=[
+ vim.api.nvim_command([[CommandWithLuaCallback hello my\ friend how\ are\ you?]])
return result
- ]])
+ ]=])
eq({
- args = "",
+ args = 'h\tey',
+ fargs = {[[h]], [[ey]]},
bang = true,
line1 = 10,
line2 = 10,
@@ -129,13 +131,14 @@ describe('nvim_add_user_command', function()
range = 1,
count = 10,
reg = "",
- }, exec_lua [[
- vim.api.nvim_command('botright 10CommandWithLuaCallback!')
+ }, exec_lua [=[
+ vim.api.nvim_command('botright 10CommandWithLuaCallback! h\tey')
return result
- ]])
+ ]=])
eq({
- args = "",
+ args = "h",
+ fargs = {"h"},
bang = false,
line1 = 1,
line2 = 42,
@@ -144,9 +147,52 @@ describe('nvim_add_user_command', function()
count = 42,
reg = "",
}, exec_lua [[
- vim.api.nvim_command('CommandWithLuaCallback 42')
+ vim.api.nvim_command('CommandWithLuaCallback 42 h')
return result
]])
+
+ eq({
+ args = "",
+ fargs = {""}, -- fargs works without args
+ bang = false,
+ line1 = 1,
+ line2 = 1,
+ mods = "",
+ range = 0,
+ count = 2,
+ reg = "",
+ }, exec_lua [[
+ vim.api.nvim_command('CommandWithLuaCallback')
+ return result
+ ]])
+
+ -- f-args doesn't split when command nargs is 1 or "?"
+ exec_lua [[
+ result = {}
+ vim.api.nvim_add_user_command('CommandWithOneArg', function(opts)
+ result = opts
+ end, {
+ nargs = "?",
+ bang = true,
+ count = 2,
+ })
+ ]]
+
+ eq({
+ args = "hello I'm one argmuent",
+ fargs = {"hello I'm one argmuent"}, -- Doesn't split args
+ bang = false,
+ line1 = 1,
+ line2 = 1,
+ mods = "",
+ range = 0,
+ count = 2,
+ reg = "",
+ }, exec_lua [[
+ vim.api.nvim_command('CommandWithOneArg hello I\'m one argmuent')
+ return result
+ ]])
+
end)
it('can define buffer-local commands', function()