mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	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 }
			
			
This commit is contained in:
		@@ -651,6 +651,9 @@ nvim_add_user_command({name}, {command}, {*opts})
 | 
				
			|||||||
                               that contains the following keys:
 | 
					                               that contains the following keys:
 | 
				
			||||||
                               • args: (string) The args passed to the
 | 
					                               • args: (string) The args passed to the
 | 
				
			||||||
                                 command, if any |<args>|
 | 
					                                 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
 | 
					                               • bang: (boolean) "true" if the command was
 | 
				
			||||||
                                 executed with a ! modifier |<bang>|
 | 
					                                 executed with a ! modifier |<bang>|
 | 
				
			||||||
                               • line1: (number) The starting line of the
 | 
					                               • line1: (number) The starting line of the
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					///                 from Lua, the command can also be a Lua function. The function is called with a
 | 
				
			||||||
///                 single table argument that contains the following keys:
 | 
					///                 single table argument that contains the following keys:
 | 
				
			||||||
///                 - args: (string) The args passed to the command, if any |<args>|
 | 
					///                 - 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>|
 | 
					///                 - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
 | 
				
			||||||
///                 - line1: (number) The starting line of the command range |<line1>|
 | 
					///                 - line1: (number) The starting line of the command range |<line1>|
 | 
				
			||||||
///                 - line2: (number) The final line of the command range |<line2>|
 | 
					///                 - line2: (number) The final line of the command range |<line2>|
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
					 * split and quote args for <f-args>
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1814,8 +1814,31 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
 | 
				
			|||||||
  lua_pushinteger(lstate, eap->line2);
 | 
					  lua_pushinteger(lstate, eap->line2);
 | 
				
			||||||
  lua_setfield(lstate, -2, "line2");
 | 
					  lua_setfield(lstate, -2, "line2");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lua_newtable(lstate);  // f-args table
 | 
				
			||||||
  lua_pushstring(lstate, (const char *)eap->arg);
 | 
					  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_pushstring(lstate, (const char *)&eap->regname);
 | 
				
			||||||
  lua_setfield(lstate, -2, "reg");
 | 
					  lua_setfield(lstate, -2, "reg");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -107,7 +107,8 @@ describe('nvim_add_user_command', function()
 | 
				
			|||||||
    ]]
 | 
					    ]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    eq({
 | 
					    eq({
 | 
				
			||||||
      args = "hello",
 | 
					      args = [[hello my\ friend how\ are\ you?]],
 | 
				
			||||||
 | 
					      fargs = {[[hello]], [[my\ friend]], [[how\ are\ you?]]},
 | 
				
			||||||
      bang = false,
 | 
					      bang = false,
 | 
				
			||||||
      line1 = 1,
 | 
					      line1 = 1,
 | 
				
			||||||
      line2 = 1,
 | 
					      line2 = 1,
 | 
				
			||||||
@@ -115,13 +116,14 @@ describe('nvim_add_user_command', function()
 | 
				
			|||||||
      range = 0,
 | 
					      range = 0,
 | 
				
			||||||
      count = 2,
 | 
					      count = 2,
 | 
				
			||||||
      reg = "",
 | 
					      reg = "",
 | 
				
			||||||
    }, exec_lua [[
 | 
					    }, exec_lua [=[
 | 
				
			||||||
      vim.api.nvim_command('CommandWithLuaCallback hello')
 | 
					      vim.api.nvim_command([[CommandWithLuaCallback hello my\ friend how\ are\ you?]])
 | 
				
			||||||
      return result
 | 
					      return result
 | 
				
			||||||
    ]])
 | 
					    ]=])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    eq({
 | 
					    eq({
 | 
				
			||||||
      args = "",
 | 
					      args = 'h\tey',
 | 
				
			||||||
 | 
					      fargs = {[[h]], [[ey]]},
 | 
				
			||||||
      bang = true,
 | 
					      bang = true,
 | 
				
			||||||
      line1 = 10,
 | 
					      line1 = 10,
 | 
				
			||||||
      line2 = 10,
 | 
					      line2 = 10,
 | 
				
			||||||
@@ -129,13 +131,14 @@ describe('nvim_add_user_command', function()
 | 
				
			|||||||
      range = 1,
 | 
					      range = 1,
 | 
				
			||||||
      count = 10,
 | 
					      count = 10,
 | 
				
			||||||
      reg = "",
 | 
					      reg = "",
 | 
				
			||||||
    }, exec_lua [[
 | 
					    }, exec_lua [=[
 | 
				
			||||||
      vim.api.nvim_command('botright 10CommandWithLuaCallback!')
 | 
					      vim.api.nvim_command('botright 10CommandWithLuaCallback! h\tey')
 | 
				
			||||||
      return result
 | 
					      return result
 | 
				
			||||||
    ]])
 | 
					    ]=])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    eq({
 | 
					    eq({
 | 
				
			||||||
      args = "",
 | 
					      args = "h",
 | 
				
			||||||
 | 
					      fargs = {"h"},
 | 
				
			||||||
      bang = false,
 | 
					      bang = false,
 | 
				
			||||||
      line1 = 1,
 | 
					      line1 = 1,
 | 
				
			||||||
      line2 = 42,
 | 
					      line2 = 42,
 | 
				
			||||||
@@ -144,9 +147,52 @@ describe('nvim_add_user_command', function()
 | 
				
			|||||||
      count = 42,
 | 
					      count = 42,
 | 
				
			||||||
      reg = "",
 | 
					      reg = "",
 | 
				
			||||||
    }, exec_lua [[
 | 
					    }, exec_lua [[
 | 
				
			||||||
      vim.api.nvim_command('CommandWithLuaCallback 42')
 | 
					      vim.api.nvim_command('CommandWithLuaCallback 42 h')
 | 
				
			||||||
      return result
 | 
					      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)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('can define buffer-local commands', function()
 | 
					  it('can define buffer-local commands', function()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user