mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	fix(api): correctly pass f-args for nvim_create_user_command (#18098)
Skip runs of whitespace and do not include `\` characters when followed by another `\` or whitespace. This matches the behavior of <f-args> when used with `:command`.
This commit is contained in:
		| @@ -5774,27 +5774,45 @@ static void ex_delcommand(exarg_T *eap) | |||||||
| /// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback. | /// 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. | /// 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]  arg String to split | ||||||
| /// @param[in]  iter Iteration counter | /// @param[in]  arglen Length of {arg} | ||||||
| /// @param[out]  start Start of the split | /// @param[inout] end Index of last character of previous iteration | ||||||
| /// @param[out]  end End of the split | /// @param[out] buf Buffer to copy string into | ||||||
| /// @param[in]  length Length of the string | /// @param[out] len Length of string in {buf} | ||||||
| /// | /// | ||||||
| /// @return  false if it's the last split (don't call again), true otherwise (call again). | /// @return true if iteration is complete, else false | ||||||
| bool uc_split_args_iter(const char_u *arg, int iter, int *start, int *end, int length) | bool uc_split_args_iter(const char_u *arg, size_t arglen, size_t *end, char *buf, size_t *len) | ||||||
| { | { | ||||||
|   int pos; |   if (!arglen) { | ||||||
|   *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; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   size_t pos = *end; | ||||||
|  |   while (pos < arglen && ascii_iswhite(arg[pos])) { | ||||||
|  |     pos++; | ||||||
|   } |   } | ||||||
|   *end = length - 1; |  | ||||||
|  |   size_t l = 0; | ||||||
|  |   for (; pos < arglen - 1; pos++) { | ||||||
|  |     if (arg[pos] == '\\' && (arg[pos + 1] == '\\' || ascii_iswhite(arg[pos + 1]))) { | ||||||
|  |       buf[l++] = arg[++pos]; | ||||||
|  |     } else { | ||||||
|  |       buf[l++] = arg[pos]; | ||||||
|  |       if (ascii_iswhite(arg[pos + 1])) { | ||||||
|  |         *end = pos + 1; | ||||||
|  |         *len = l; | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (pos < arglen && !ascii_iswhite(arg[pos])) { | ||||||
|  |     buf[l++] = arg[pos]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   *len = l; | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| /// split and quote args for <f-args> | /// split and quote args for <f-args> | ||||||
| static char_u *uc_split_args(char_u *arg, size_t *lenp) | static char_u *uc_split_args(char_u *arg, size_t *lenp) | ||||||
|   | |||||||
| @@ -1869,18 +1869,22 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap) | |||||||
|   } else { |   } else { | ||||||
|     // Commands with more than one possible argument we split |     // Commands with more than one possible argument we split | ||||||
|     lua_pop(lstate, 1);  // Pop the reference of opts.args |     lua_pop(lstate, 1);  // Pop the reference of opts.args | ||||||
|     int length = (int)STRLEN(eap->arg); |     size_t length = STRLEN(eap->arg); | ||||||
|     int start = 0; |     size_t end = 0; | ||||||
|     int end = 0; |     size_t len = 0; | ||||||
|     int i = 1; |     int i = 1; | ||||||
|     bool res = true; |     char *buf = xcalloc(length, sizeof(char)); | ||||||
|     while (res) { |     bool done = false; | ||||||
|       res = uc_split_args_iter(eap->arg, i, &start, &end, length); |     while (!done) { | ||||||
|       lua_pushlstring(lstate, (const char *)eap->arg + start, (size_t)(end - start + 1)); |       done = uc_split_args_iter(eap->arg, length, &end, buf, &len); | ||||||
|  |       if (len > 0) { | ||||||
|  |         lua_pushlstring(lstate, buf, len); | ||||||
|         lua_rawseti(lstate, -2, i); |         lua_rawseti(lstate, -2, i); | ||||||
|         i++; |         i++; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     xfree(buf); | ||||||
|  |   } | ||||||
|   lua_setfield(lstate, -2, "fargs"); |   lua_setfield(lstate, -2, "fargs"); | ||||||
|  |  | ||||||
|   lua_pushstring(lstate, (const char *)&eap->regname); |   lua_pushstring(lstate, (const char *)&eap->regname); | ||||||
|   | |||||||
| @@ -114,8 +114,8 @@ describe('nvim_create_user_command', function() | |||||||
|     ]] |     ]] | ||||||
|  |  | ||||||
|     eq({ |     eq({ | ||||||
|       args = [[hello my\ friend how\ are\ you?]], |       args = [[this is    a\ test]], | ||||||
|       fargs = {[[hello]], [[my\ friend]], [[how\ are\ you?]]}, |       fargs = {"this", "is", "a test"}, | ||||||
|       bang = false, |       bang = false, | ||||||
|       line1 = 1, |       line1 = 1, | ||||||
|       line2 = 1, |       line2 = 1, | ||||||
| @@ -124,7 +124,37 @@ describe('nvim_create_user_command', function() | |||||||
|       count = 2, |       count = 2, | ||||||
|       reg = "", |       reg = "", | ||||||
|     }, exec_lua [=[ |     }, exec_lua [=[ | ||||||
|       vim.api.nvim_command([[CommandWithLuaCallback hello my\ friend how\ are\ you?]]) |       vim.api.nvim_command([[CommandWithLuaCallback this is    a\ test]]) | ||||||
|  |       return result | ||||||
|  |     ]=]) | ||||||
|  |  | ||||||
|  |     eq({ | ||||||
|  |       args = [[this   includes\ a backslash: \\]], | ||||||
|  |       fargs = {"this", "includes a", "backslash:", "\\"}, | ||||||
|  |       bang = false, | ||||||
|  |       line1 = 1, | ||||||
|  |       line2 = 1, | ||||||
|  |       mods = "", | ||||||
|  |       range = 0, | ||||||
|  |       count = 2, | ||||||
|  |       reg = "", | ||||||
|  |     }, exec_lua [=[ | ||||||
|  |       vim.api.nvim_command([[CommandWithLuaCallback this   includes\ a backslash: \\]]) | ||||||
|  |       return result | ||||||
|  |     ]=]) | ||||||
|  |  | ||||||
|  |     eq({ | ||||||
|  |       args = "a\\b", | ||||||
|  |       fargs = {"a\\b"}, | ||||||
|  |       bang = false, | ||||||
|  |       line1 = 1, | ||||||
|  |       line2 = 1, | ||||||
|  |       mods = "", | ||||||
|  |       range = 0, | ||||||
|  |       count = 2, | ||||||
|  |       reg = "", | ||||||
|  |     }, exec_lua [=[ | ||||||
|  |       vim.api.nvim_command('CommandWithLuaCallback a\\b') | ||||||
|       return result |       return result | ||||||
|     ]=]) |     ]=]) | ||||||
|  |  | ||||||
| @@ -160,7 +190,7 @@ describe('nvim_create_user_command', function() | |||||||
|  |  | ||||||
|     eq({ |     eq({ | ||||||
|       args = "", |       args = "", | ||||||
|       fargs = {""},  -- fargs works without args |       fargs = {},  -- fargs works without args | ||||||
|       bang = false, |       bang = false, | ||||||
|       line1 = 1, |       line1 = 1, | ||||||
|       line2 = 1, |       line2 = 1, | ||||||
| @@ -186,8 +216,8 @@ describe('nvim_create_user_command', function() | |||||||
|     ]] |     ]] | ||||||
|  |  | ||||||
|     eq({ |     eq({ | ||||||
|       args = "hello I'm one argmuent", |       args = "hello I'm one argument", | ||||||
|       fargs = {"hello I'm one argmuent"},  -- Doesn't split args |       fargs = {"hello I'm one argument"},  -- Doesn't split args | ||||||
|       bang = false, |       bang = false, | ||||||
|       line1 = 1, |       line1 = 1, | ||||||
|       line2 = 1, |       line2 = 1, | ||||||
| @@ -196,7 +226,7 @@ describe('nvim_create_user_command', function() | |||||||
|       count = 2, |       count = 2, | ||||||
|       reg = "", |       reg = "", | ||||||
|     }, exec_lua [[ |     }, exec_lua [[ | ||||||
|       vim.api.nvim_command('CommandWithOneArg hello I\'m one argmuent') |       vim.api.nvim_command('CommandWithOneArg hello I\'m one argument') | ||||||
|       return result |       return result | ||||||
|     ]]) |     ]]) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Gregory Anders
					Gregory Anders