diff options
| -rw-r--r-- | runtime/doc/if_lua.txt | 102 | ||||
| -rw-r--r-- | src/nvim/lua/executor.c | 47 | ||||
| -rw-r--r-- | test/functional/lua/utility_functions_spec.lua | 108 | 
3 files changed, 225 insertions, 32 deletions
| diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index d1b8cd4856..a123041866 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -1,7 +1,7 @@  *if_lua.txt*    Nvim -		  VIM REFERENCE MANUAL    by Luis Carvalho +			    NVIM REFERENCE MANUAL  Lua Interface to Nvim					*lua* *Lua* @@ -9,18 +9,18 @@ Lua Interface to Nvim					*lua* *Lua*                                        Type |gO| to see the table of contents.  ============================================================================== -1. Importing modules					*lua-require* +Importing modules					*lua-require* -Neovim lua interface automatically adjusts `package.path` and `package.cpath`  -according to effective &runtimepath value.  Adjustment happens after  -'runtimepath' is changed. `package.path` is adjusted by simply appending  -`/lua/?.lua` and `/lua/?/init.lua` to each directory from 'runtimepath' (`/`  -is actually the first character of `package.config`). +Nvim automatically adjusts `package.path` and `package.cpath` according to +effective 'runtimepath' value.  Adjustment happens whenever 'runtimepath' is +changed.  `package.path` is adjusted by simply appending `/lua/?.lua` and +`/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the +first character of `package.config`). -Similarly to `package.path`, modified directories from `runtimepath` are also  -added to `package.cpath`.  In this case, instead of appending `/lua/?.lua` and  -`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of  -the existing `package.cpath` are used.  Here is an example: +Similarly to `package.path`, modified directories from 'runtimepath' are also +added to `package.cpath`.  In this case, instead of appending `/lua/?.lua` and +`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of +the existing `package.cpath` are used.  Example:  1. Given that     - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`; @@ -61,7 +61,7 @@ paths when path is removed from 'runtimepath', adding paths when they are  added and reordering `package.path`/`package.cpath` content if 'runtimepath'   was reordered. -Note 2: even though adjustments happens automatically Neovim does not track  +Note 2: even though adjustments happens automatically Nvim does not track   current values of `package.path` or `package.cpath`.  If you happened to   delete some paths from there you need to reset 'runtimepath' to make them   readded.  Just running `let &runtimepath = &runtimepath` should work. @@ -72,7 +72,7 @@ badly written plugins using shell which will not work with paths containing  semicolons it is better to not have them in 'runtimepath' at all.  ------------------------------------------------------------------------------ -1.1. Example of the plugin which uses lua modules:	*lua-require-example* +Example of a plugin that uses lua modules		*lua-require-example*  The following example plugin adds a command `:MakeCharBlob` which transforms   current buffer into a long `unsigned char` array.  Lua contains transformation  @@ -149,7 +149,7 @@ lua/charblob.lua: >  	}  ============================================================================== -2. Commands						*lua-commands* +Commands						*lua-commands*  							*:lua*  :[range]lua {chunk} @@ -157,7 +157,7 @@ lua/charblob.lua: >  Examples:  > -	:lua vim.api.nvim_command('echo "Hello, Neovim!"') +	:lua vim.api.nvim_command('echo "Hello, Nvim!"')  <  :[range]lua << {endmarker} @@ -230,27 +230,80 @@ All these commands execute a Lua chunk from either the command line (:lua and  :luado) or a file (:luafile) with the given line [range]. Similarly to the Lua  interpreter, each chunk has its own scope and so only global variables are  shared between command calls. All Lua default libraries are available. In -addition, Lua "print" function has its output redirected to the Vim message +addition, Lua "print" function has its output redirected to the Nvim message  area, with arguments separated by a white space instead of a tab. -Lua uses the "vim" module (see |lua-vim|) to issue commands to Neovim +Lua uses the "vim" module (see |lua-vim|) to issue commands to Nvim  and manage buffers (|lua-buffer|) and windows (|lua-window|). However,  procedures that alter buffer content, open new buffers, and change cursor  position are restricted when the command is executed in the |sandbox|.  ============================================================================== -2. The vim module					*lua-vim* +The vim module						*lua-vim* -Lua interfaces Vim through the "vim" module. Currently it only has `api`  -submodule which is a table with all API functions. Descriptions of these  -functions may be found in |api.txt|. +Lua interfaces Nvim through the "vim" module. Currently it has the `api` +submodule and some Nvim-specific utilities. + +------------------------------------------------------------------------------ +vim.api.* functions + +`vim.api` exposes the Nvim |API| as a table of Lua functions. All functions +are available. + +For example, to use the "nvim_get_current_line()" API function, call +"vim.api.nvim_get_current_line()": > + +	print(tostring(vim.api.nvim_get_current_line())) + +------------------------------------------------------------------------------ +vim.* utility functions + +vim.stricmp(a, b)					*lua-vim.stricmp* +	Function used for case-insensitive string comparison.  Takes two  +	string arguments and returns 0, 1 or -1 if strings are equal, a is  +	greater then b or a is lesser then b respectively. + +vim.type_idx						*lua-vim.type_idx* +	Type index for use in |lua-special-tables|.  Specifying one of the  +	values from |lua-vim.types| allows typing the empty table (it is  +	unclear whether empty lua table represents empty list or empty array)  +	and forcing integral numbers to be |Float|.  See |lua-special-tbl| for  +	more details. + +vim.val_idx						*lua-vim.val_idx* +	Value index for tables representing |Float|s.  A table representing  +	floating-point value 1.0 looks like this: > +		{ +		  [vim.type_idx] = vim.types.float, +		  [vim.val_idx] = 1.0, +		} +<	See also |lua-vim.type_idx| and |lua-special-tbl|. + +vim.types						*lua-vim.types* +	Table with possible values for |lua-vim.type_idx|.  Contains two sets  +	of key-value pairs: first maps possible values for |lua-vim.type_idx|  +	to human-readable strings, second maps human-readable type names to  +	values for |lua-vim.type_idx|.  Currently contains pairs for `float`,  +	`array` and `dictionary` types. + +	Note: one must expect that values corresponding to `vim.types.float`,  +	`vim.types.array` and `vim.types.dictionary` fall under only two  +	following assumptions: +	1. Value may serve both as a key and as a value in a table.  Given the  +	   properties of lua tables this basically means “value is not `nil`”. +	2. For each value in `vim.types` table `vim.types[vim.types[value]]`  +	   is the same as `value`. +	No other restrictions are put on types, and it is not guaranteed that  +	values corresponding to `vim.types.float`, `vim.types.array` and  +	`vim.types.dictionary` will not change or that `vim.types` table will  +	only contain values for these three types.  ============================================================================== -3. The luaeval function					*lua-luaeval* *lua-eval* +The luaeval function					*lua-luaeval* *lua-eval*  							*luaeval()* -The (dual) equivalent of "vim.eval" for passing Lua values to Vim is +The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is  "luaeval". "luaeval" takes an expression string and an optional argument used   for _A inside expression and returns the result of the expression. It is   semantically equivalent in Lua to: @@ -262,7 +315,7 @@ semantically equivalent in Lua to:  	end  Note that "_A" receives the argument to "luaeval". Lua nils, numbers, strings,  -tables and booleans are converted to their Vim respective types. An error is  +tables and booleans are converted to their respective VimL types. An error is   thrown if conversion of any of the remaining Lua types is attempted.  Note 2: lua tables are used as both dictionaries and lists, thus making it  @@ -278,6 +331,7 @@ between these cases there is the following agreement:  3. Table with string keys, at least one of which contains NUL byte, is also      considered to be a dictionary, but this time it is converted to      a |msgpack-special-map|. +							*lua-special-tbl*  4. Table with `vim.type_idx` key may be a dictionary, a list or floating-point      value:     - `{[vim.type_idx]=vim.types.float, [vim.val_idx]=1}` is converted to  diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index eb821f7831..16bb4169c4 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -124,9 +124,40 @@ static void nlua_error(lua_State *const lstate, const char *const msg)  /// omitted.  static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL  { -  const char *s1 = luaL_checklstring(lstate, 1, NULL); -  const char *s2 = luaL_checklstring(lstate, 2, NULL); -  const int ret = STRICMP(s1, s2); +  size_t s1_len; +  size_t s2_len; +  const char *s1 = luaL_checklstring(lstate, 1, &s1_len); +  const char *s2 = luaL_checklstring(lstate, 2, &s2_len); +  char *nul1; +  char *nul2; +  int ret = 0; +  assert(s1[s1_len] == NUL); +  assert(s2[s2_len] == NUL); +  do { +    nul1 = memchr(s1, NUL, s1_len); +    nul2 = memchr(s2, NUL, s2_len); +    ret = STRICMP(s1, s2); +    if (ret == 0) { +      // Compare "a\0" greater then "a". +      if ((nul1 == NULL) != (nul2 == NULL)) { +        ret = ((nul1 != NULL) - (nul2 != NULL)); +        break; +      } +      if (nul1 != NULL) { +        assert(nul2 != NULL); +        // Can't shift both strings by the same amount of bytes: lowercase +        // letter may have different byte-length than uppercase. +        s1_len -= (size_t)(nul1 - s1) + 1; +        s2_len -= (size_t)(nul2 - s2) + 1; +        s1 = nul1 + 1; +        s2 = nul2 + 1; +      } else { +        break; +      } +    } else { +      break; +    } +  } while (true);    lua_pop(lstate, 2);    lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0)));    return 1; @@ -254,10 +285,6 @@ static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL  /// Called by lua interpreter itself to initialize state.  static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL  { -  // stricmp -  lua_pushcfunction(lstate, &nlua_stricmp); -  lua_setglobal(lstate, "stricmp"); -    // print    lua_pushcfunction(lstate, &nlua_print);    lua_setglobal(lstate, "print"); @@ -277,13 +304,17 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL    nlua_add_api_functions(lstate);    // vim.types, vim.type_idx, vim.val_idx    nlua_init_types(lstate); +  // stricmp +  lua_pushcfunction(lstate, &nlua_stricmp); +  lua_setfield(lstate, -2, "stricmp"); +    lua_setglobal(lstate, "vim");    return 0;  }  /// Initialize lua interpreter  /// -/// Crashes NeoVim if initialization fails. Should be called once per lua +/// Crashes Nvim if initialization fails. Should be called once per lua  /// interpreter instance.  ///  /// @return New lua interpreter instance. diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua new file mode 100644 index 0000000000..d5756e134d --- /dev/null +++ b/test/functional/lua/utility_functions_spec.lua @@ -0,0 +1,108 @@ +-- Test suite for testing interactions with API bindings +local helpers = require('test.functional.helpers')(after_each) + +local funcs = helpers.funcs +local clear = helpers.clear +local eq = helpers.eq + +before_each(clear) + +describe('vim.stricmp', function() +  -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has +  --    length 2 (in bytes). +  -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has +  --    length 3 (in bytes). +  -- +  -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems. +  -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works +  --       only on ASCII characters. +  it('works', function() +    eq(0, funcs.luaeval('vim.stricmp("a", "A")')) +    eq(0, funcs.luaeval('vim.stricmp("A", "a")')) +    eq(0, funcs.luaeval('vim.stricmp("a", "a")')) +    eq(0, funcs.luaeval('vim.stricmp("A", "A")')) + +    eq(0, funcs.luaeval('vim.stricmp("", "")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")')) + +    eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")')) + +    eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")')) + +    eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")')) +    eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")')) + +    eq(-1, funcs.luaeval('vim.stricmp("a", "B")')) +    eq(-1, funcs.luaeval('vim.stricmp("A", "b")')) +    eq(-1, funcs.luaeval('vim.stricmp("a", "b")')) +    eq(-1, funcs.luaeval('vim.stricmp("A", "B")')) + +    eq(-1, funcs.luaeval('vim.stricmp("", "\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")')) + +    eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")')) + +    eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")')) + +    eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")')) +    eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")')) + +    eq(1, funcs.luaeval('vim.stricmp("c", "B")')) +    eq(1, funcs.luaeval('vim.stricmp("C", "b")')) +    eq(1, funcs.luaeval('vim.stricmp("c", "b")')) +    eq(1, funcs.luaeval('vim.stricmp("C", "B")')) + +    eq(1, funcs.luaeval('vim.stricmp("\\0", "")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")')) + +    eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")')) + +    eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")')) +    eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")')) +    eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")')) +    eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")')) + +    eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")')) + +    eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")')) +    eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")')) +  end) +end) | 
