mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	Merge #6789 from ZyX-I/lua-path
lua: Add paths from &runtimepath to package.path and package.cpath
This commit is contained in:
		@@ -600,9 +600,26 @@ if(LUACHECK_PRG)
 | 
			
		||||
  add_custom_target(testlint
 | 
			
		||||
    COMMAND ${CMAKE_COMMAND}
 | 
			
		||||
      -DLUACHECK_PRG=${LUACHECK_PRG}
 | 
			
		||||
      -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
 | 
			
		||||
      -DLUAFILES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
 | 
			
		||||
      -DIGNORE_PATTERN="*/preload.lua"
 | 
			
		||||
      -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
 | 
			
		||||
      -P ${PROJECT_SOURCE_DIR}/cmake/RunTestsLint.cmake)
 | 
			
		||||
      -P ${PROJECT_SOURCE_DIR}/cmake/RunLuacheck.cmake)
 | 
			
		||||
 | 
			
		||||
  add_custom_target(
 | 
			
		||||
    blobcodelint
 | 
			
		||||
    COMMAND
 | 
			
		||||
      ${CMAKE_COMMAND}
 | 
			
		||||
        -DLUACHECK_PRG=${LUACHECK_PRG}
 | 
			
		||||
        -DLUAFILES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/src/nvim/lua
 | 
			
		||||
        -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
 | 
			
		||||
        -DREAD_GLOBALS=vim
 | 
			
		||||
        -P ${PROJECT_SOURCE_DIR}/cmake/RunLuacheck.cmake
 | 
			
		||||
  )
 | 
			
		||||
  # TODO(ZyX-I): Run linter for all lua code in src
 | 
			
		||||
  add_custom_target(
 | 
			
		||||
    lualint
 | 
			
		||||
    DEPENDS blobcodelint
 | 
			
		||||
  )
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(CPACK_PACKAGE_NAME "Neovim")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							@@ -107,6 +107,9 @@ functionaltest-lua: | nvim
 | 
			
		||||
testlint: | build/.ran-cmake deps
 | 
			
		||||
	$(BUILD_CMD) -C build testlint
 | 
			
		||||
 | 
			
		||||
lualint: | build/.ran-cmake deps
 | 
			
		||||
	$(BUILD_CMD) -C build lualint
 | 
			
		||||
 | 
			
		||||
unittest: | nvim
 | 
			
		||||
	+$(BUILD_CMD) -C build unittest
 | 
			
		||||
 | 
			
		||||
@@ -138,6 +141,6 @@ check-single-includes: build/.ran-cmake
 | 
			
		||||
appimage:
 | 
			
		||||
	bash scripts/genappimage.sh
 | 
			
		||||
 | 
			
		||||
lint: check-single-includes clint testlint
 | 
			
		||||
lint: check-single-includes clint testlint lualint
 | 
			
		||||
 | 
			
		||||
.PHONY: test testlint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage
 | 
			
		||||
.PHONY: test testlint lualint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,12 @@ run_test 'top_make testlint' testlint
 | 
			
		||||
 | 
			
		||||
exit_suite --continue
 | 
			
		||||
 | 
			
		||||
enter_suite 'lualint'
 | 
			
		||||
 | 
			
		||||
run_test 'top_make lualint' lualint
 | 
			
		||||
 | 
			
		||||
exit_suite --continue
 | 
			
		||||
 | 
			
		||||
enter_suite single-includes
 | 
			
		||||
 | 
			
		||||
CLICOLOR_FORCE=1 run_test_wd \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								cmake/RunLuacheck.cmake
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								cmake/RunLuacheck.cmake
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
set(LUACHECK_ARGS -q "${LUAFILES_DIR}")
 | 
			
		||||
if(DEFINED IGNORE_PATTERN)
 | 
			
		||||
  list(APPEND LUACHECK_ARGS --exclude-files "${LUAFILES_DIR}/${IGNORE_PATTERN}")
 | 
			
		||||
endif()
 | 
			
		||||
if(DEFINED CHECK_PATTERN)
 | 
			
		||||
  list(APPEND LUACHECK_ARGS --include-files "${LUAFILES_DIR}/${CHECK_PATTERN}")
 | 
			
		||||
endif()
 | 
			
		||||
if(DEFINED READ_GLOBALS)
 | 
			
		||||
  list(APPEND LUACHECK_ARGS --read-globals "${READ_GLOBALS}")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
execute_process(
 | 
			
		||||
  COMMAND "${LUACHECK_PRG}" ${LUACHECK_ARGS}
 | 
			
		||||
  WORKING_DIRECTORY "${LUAFILES_DIR}"
 | 
			
		||||
  ERROR_VARIABLE err
 | 
			
		||||
  RESULT_VARIABLE res
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if(NOT res EQUAL 0)
 | 
			
		||||
  message(STATUS "Output to stderr:\n${err}")
 | 
			
		||||
  message(FATAL_ERROR "Linting tests failed with error: ${res}.")
 | 
			
		||||
endif()
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
set(IGNORE_FILES "${TEST_DIR}/*/preload.lua")
 | 
			
		||||
 | 
			
		||||
execute_process(
 | 
			
		||||
  COMMAND ${LUACHECK_PRG} -q ${TEST_DIR} --exclude-files ${IGNORE_FILES}
 | 
			
		||||
  WORKING_DIRECTORY ${TEST_DIR}
 | 
			
		||||
  ERROR_VARIABLE err
 | 
			
		||||
  RESULT_VARIABLE res
 | 
			
		||||
  ${EXTRA_ARGS})
 | 
			
		||||
 | 
			
		||||
if(NOT res EQUAL 0)
 | 
			
		||||
  message(STATUS "Output to stderr:\n${err}")
 | 
			
		||||
  message(FATAL_ERROR "Linting tests failed with error: ${res}.")
 | 
			
		||||
endif()
 | 
			
		||||
@@ -9,7 +9,147 @@ Lua Interface to Nvim					*lua* *Lua*
 | 
			
		||||
                                      Type <M-]> to see the table of contents.
 | 
			
		||||
 | 
			
		||||
==============================================================================
 | 
			
		||||
1. Commands						*lua-commands*
 | 
			
		||||
1. 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`).
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
1. Given that
 | 
			
		||||
   - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`;
 | 
			
		||||
   - initial (defined at compile time or derived from 
 | 
			
		||||
     `$LUA_CPATH`/`$LUA_INIT`) `package.cpath` contains 
 | 
			
		||||
     `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`.
 | 
			
		||||
2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in 
 | 
			
		||||
   order: parts of the path starting from the first path component containing 
 | 
			
		||||
   question mark and preceding path separator.
 | 
			
		||||
3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as it’s the same 
 | 
			
		||||
   as the suffix of the first path from `package.path` (i.e. `./?.so`).  Which 
 | 
			
		||||
   leaves `/?.so` and `/a?d/j/g.elf`, in this order.
 | 
			
		||||
4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`.  The 
 | 
			
		||||
   second one contains semicolon which is a paths separator so it is out, 
 | 
			
		||||
   leaving only `/foo/bar` and `/abc`, in order.
 | 
			
		||||
5. The cartesian product of paths from 4. and suffixes from 3. is taken, 
 | 
			
		||||
   giving four variants. In each variant `/lua` path segment is inserted 
 | 
			
		||||
   between path and suffix, leaving
 | 
			
		||||
 | 
			
		||||
   - `/foo/bar/lua/?.so`
 | 
			
		||||
   - `/foo/bar/lua/a?d/j/g.elf`
 | 
			
		||||
   - `/abc/lua/?.so`
 | 
			
		||||
   - `/abc/lua/a?d/j/g.elf`
 | 
			
		||||
 | 
			
		||||
6. New paths are prepended to the original `package.cpath`.
 | 
			
		||||
 | 
			
		||||
The result will look like this:
 | 
			
		||||
 | 
			
		||||
    `/foo/bar,/xxx;yyy/baz,/abc` ('runtimepath')
 | 
			
		||||
    × `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` (`package.cpath`)
 | 
			
		||||
 | 
			
		||||
    = `/foo/bar/lua/?.so;/foo/bar/lua/a?d/j/g.elf;/abc/lua/?.so;/abc/lua/a?d/j/g.elf;./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`
 | 
			
		||||
 | 
			
		||||
Note: to keep up with 'runtimepath' updates paths added at previous update are 
 | 
			
		||||
remembered and removed at the next update, while all paths derived from the 
 | 
			
		||||
new 'runtimepath' are prepended as described above.  This allows removing 
 | 
			
		||||
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 
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
Note 3: skipping paths from 'runtimepath' which contain semicolons applies 
 | 
			
		||||
both to `package.path` and `package.cpath`.  Given that there is a number of 
 | 
			
		||||
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*
 | 
			
		||||
 | 
			
		||||
The following example plugin adds a command `:MakeCharBlob` which transforms 
 | 
			
		||||
current buffer into a long `unsigned char` array.  Lua contains transformation 
 | 
			
		||||
function in a module `lua/charblob.lua` which is imported in 
 | 
			
		||||
`autoload/charblob.vim` (`require("charblob")`).  Example plugin is supposed 
 | 
			
		||||
to be put into any directory from 'runtimepath', e.g. `~/.config/nvim` (in 
 | 
			
		||||
this case `lua/charblob.lua` means `~/.config/nvim/lua/charblob.lua`).
 | 
			
		||||
 | 
			
		||||
autoload/charblob.vim: >
 | 
			
		||||
 | 
			
		||||
	function charblob#encode_buffer()
 | 
			
		||||
	  call setline(1, luaeval(
 | 
			
		||||
	  \    'require("charblob").encode(unpack(_A))',
 | 
			
		||||
	  \    [getline(1, '$'), &textwidth, '  ']))
 | 
			
		||||
	endfunction
 | 
			
		||||
 | 
			
		||||
plugin/charblob.vim: >
 | 
			
		||||
 | 
			
		||||
	if exists('g:charblob_loaded')
 | 
			
		||||
	  finish
 | 
			
		||||
	endif
 | 
			
		||||
	let g:charblob_loaded = 1
 | 
			
		||||
 | 
			
		||||
	command MakeCharBlob :call charblob#encode_buffer()
 | 
			
		||||
 | 
			
		||||
lua/charblob.lua: >
 | 
			
		||||
 | 
			
		||||
	local function charblob_bytes_iter(lines)
 | 
			
		||||
	  local init_s = {
 | 
			
		||||
	    next_line_idx = 1,
 | 
			
		||||
	    next_byte_idx = 1,
 | 
			
		||||
	    lines = lines,
 | 
			
		||||
	  }
 | 
			
		||||
	  local function next(s, _)
 | 
			
		||||
	    if lines[s.next_line_idx] == nil then
 | 
			
		||||
	      return nil
 | 
			
		||||
	    end
 | 
			
		||||
	    if s.next_byte_idx > #(lines[s.next_line_idx]) then
 | 
			
		||||
	      s.next_line_idx = s.next_line_idx + 1
 | 
			
		||||
	      s.next_byte_idx = 1
 | 
			
		||||
	      return ('\n'):byte()
 | 
			
		||||
	    end
 | 
			
		||||
	    local ret = lines[s.next_line_idx]:byte(s.next_byte_idx)
 | 
			
		||||
	    if ret == ('\n'):byte() then
 | 
			
		||||
	      ret = 0  -- See :h NL-used-for-NUL.
 | 
			
		||||
	    end
 | 
			
		||||
	    s.next_byte_idx = s.next_byte_idx + 1
 | 
			
		||||
	    return ret
 | 
			
		||||
	  end
 | 
			
		||||
	  return next, init_s, nil
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local function charblob_encode(lines, textwidth, indent)
 | 
			
		||||
	  local ret = {
 | 
			
		||||
	    'const unsigned char blob[] = {',
 | 
			
		||||
	    indent,
 | 
			
		||||
	  }
 | 
			
		||||
	  for byte in charblob_bytes_iter(lines) do
 | 
			
		||||
	    --                .- space + number (width 3) + comma
 | 
			
		||||
	    if #(ret[#ret]) + 5 > textwidth then
 | 
			
		||||
	      ret[#ret + 1] = indent
 | 
			
		||||
	    else
 | 
			
		||||
	      ret[#ret] = ret[#ret] .. ' '
 | 
			
		||||
	    end
 | 
			
		||||
	    ret[#ret] = ret[#ret] .. (('%3u,'):format(byte))
 | 
			
		||||
	  end
 | 
			
		||||
	  ret[#ret + 1] = '};'
 | 
			
		||||
	  return ret
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
	  bytes_iter = charblob_bytes_iter,
 | 
			
		||||
	  encode = charblob_encode,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
==============================================================================
 | 
			
		||||
2. Commands						*lua-commands*
 | 
			
		||||
 | 
			
		||||
							*:lua*
 | 
			
		||||
:[range]lua {chunk}
 | 
			
		||||
 
 | 
			
		||||
@@ -244,6 +244,8 @@ Lua interface (|if_lua.txt|):
 | 
			
		||||
  while calling lua chunk: [string "<VimL compiled string>"]:1: TEST” in 
 | 
			
		||||
  Neovim.
 | 
			
		||||
- Lua has direct access to Nvim |API| via `vim.api`.
 | 
			
		||||
- Lua package.path and package.cpath are automatically updated according to
 | 
			
		||||
  'runtimepath': |lua-require|.
 | 
			
		||||
- Currently, most legacy Vim features are missing.
 | 
			
		||||
 | 
			
		||||
|input()| and |inputdialog()| gained support for each other’s features (return 
 | 
			
		||||
 
 | 
			
		||||
@@ -300,7 +300,7 @@ ArrayOf(String) nvim_list_runtime_paths(void)
 | 
			
		||||
  FUNC_API_SINCE(1)
 | 
			
		||||
{
 | 
			
		||||
  Array rv = ARRAY_DICT_INIT;
 | 
			
		||||
  uint8_t *rtp = p_rtp;
 | 
			
		||||
  char_u *rtp = p_rtp;
 | 
			
		||||
 | 
			
		||||
  if (*rtp == NUL) {
 | 
			
		||||
    // No paths
 | 
			
		||||
@@ -314,13 +314,14 @@ ArrayOf(String) nvim_list_runtime_paths(void)
 | 
			
		||||
    }
 | 
			
		||||
    rtp++;
 | 
			
		||||
  }
 | 
			
		||||
  rv.size++;
 | 
			
		||||
 | 
			
		||||
  // Allocate memory for the copies
 | 
			
		||||
  rv.items = xmalloc(sizeof(Object) * rv.size);
 | 
			
		||||
  rv.items = xmalloc(sizeof(*rv.items) * rv.size);
 | 
			
		||||
  // Reset the position
 | 
			
		||||
  rtp = p_rtp;
 | 
			
		||||
  // Start copying
 | 
			
		||||
  for (size_t i = 0; i < rv.size && *rtp != NUL; i++) {
 | 
			
		||||
  for (size_t i = 0; i < rv.size; i++) {
 | 
			
		||||
    rv.items[i].type = kObjectTypeString;
 | 
			
		||||
    rv.items[i].data.string.data = xmalloc(MAXPATHL);
 | 
			
		||||
    // Copy the path from 'runtimepath' to rv.items[i]
 | 
			
		||||
@@ -709,7 +710,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
 | 
			
		||||
Integer nvim_get_color_by_name(String name)
 | 
			
		||||
  FUNC_API_SINCE(1)
 | 
			
		||||
{
 | 
			
		||||
  return name_to_color((uint8_t *)name.data);
 | 
			
		||||
  return name_to_color((char_u *)name.data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Dictionary nvim_get_color_map(void)
 | 
			
		||||
@@ -871,7 +872,7 @@ static void write_msg(String message, bool to_err)
 | 
			
		||||
#define PUSH_CHAR(i, pos, line_buf, msg) \
 | 
			
		||||
  if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \
 | 
			
		||||
    line_buf[pos] = NUL; \
 | 
			
		||||
    msg((uint8_t *)line_buf); \
 | 
			
		||||
    msg((char_u *)line_buf); \
 | 
			
		||||
    pos = 0; \
 | 
			
		||||
    continue; \
 | 
			
		||||
  } \
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@
 | 
			
		||||
#include "nvim/api/vim.h"
 | 
			
		||||
#include "nvim/vim.h"
 | 
			
		||||
#include "nvim/ex_getln.h"
 | 
			
		||||
#include "nvim/ex_cmds2.h"
 | 
			
		||||
#include "nvim/message.h"
 | 
			
		||||
#include "nvim/memline.h"
 | 
			
		||||
#include "nvim/buffer_defs.h"
 | 
			
		||||
@@ -284,7 +285,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
 | 
			
		||||
///
 | 
			
		||||
/// Crashes NeoVim if initialization fails. Should be called once per lua
 | 
			
		||||
/// interpreter instance.
 | 
			
		||||
static lua_State *init_lua(void)
 | 
			
		||||
///
 | 
			
		||||
/// @return New lua interpreter instance.
 | 
			
		||||
static lua_State *nlua_init(void)
 | 
			
		||||
  FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
 | 
			
		||||
{
 | 
			
		||||
  lua_State *lstate = luaL_newstate();
 | 
			
		||||
@@ -297,7 +300,43 @@ static lua_State *init_lua(void)
 | 
			
		||||
  return lstate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static lua_State *global_lstate = NULL;
 | 
			
		||||
/// Enter lua interpreter
 | 
			
		||||
///
 | 
			
		||||
/// Calls nlua_init() if needed. Is responsible for pre-lua call initalization
 | 
			
		||||
/// like updating `package.[c]path` with directories derived from &runtimepath.
 | 
			
		||||
///
 | 
			
		||||
/// @return Interprter instance to use. Will either be initialized now or taken
 | 
			
		||||
///         from previous initalization.
 | 
			
		||||
static lua_State *nlua_enter(void)
 | 
			
		||||
  FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
 | 
			
		||||
{
 | 
			
		||||
  static lua_State *global_lstate = NULL;
 | 
			
		||||
  if (global_lstate == NULL) {
 | 
			
		||||
    global_lstate = nlua_init();
 | 
			
		||||
  }
 | 
			
		||||
  lua_State *const lstate = global_lstate;
 | 
			
		||||
  // Last used p_rtp value. Must not be dereferenced because value pointed to
 | 
			
		||||
  // may already be freed. Used to check whether &runtimepath option value
 | 
			
		||||
  // changed.
 | 
			
		||||
  static const void *last_p_rtp = NULL;
 | 
			
		||||
  if (last_p_rtp != (const void *)p_rtp) {
 | 
			
		||||
    // stack: (empty)
 | 
			
		||||
    lua_getglobal(lstate, "vim");
 | 
			
		||||
    // stack: vim
 | 
			
		||||
    lua_getfield(lstate, -1, "_update_package_paths");
 | 
			
		||||
    // stack: vim, vim._update_package_paths
 | 
			
		||||
    if (lua_pcall(lstate, 0, 0, 0)) {
 | 
			
		||||
      // stack: vim, error
 | 
			
		||||
      nlua_error(lstate, _("E5117: Error while updating package paths: %.*s"));
 | 
			
		||||
      // stack: vim
 | 
			
		||||
    }
 | 
			
		||||
    // stack: vim
 | 
			
		||||
    lua_pop(lstate, 1);
 | 
			
		||||
    // stack: (empty)
 | 
			
		||||
    last_p_rtp = (const void *)p_rtp;
 | 
			
		||||
  }
 | 
			
		||||
  return lstate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute lua string
 | 
			
		||||
///
 | 
			
		||||
@@ -308,11 +347,7 @@ static lua_State *global_lstate = NULL;
 | 
			
		||||
void executor_exec_lua(const String str, typval_T *const ret_tv)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL
 | 
			
		||||
{
 | 
			
		||||
  if (global_lstate == NULL) {
 | 
			
		||||
    global_lstate = init_lua();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0,
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_lua_string, 0,
 | 
			
		||||
                         (void *)&str, ret_tv);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -551,11 +586,7 @@ void executor_eval_lua(const String str, typval_T *const arg,
 | 
			
		||||
                       typval_T *const ret_tv)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL
 | 
			
		||||
{
 | 
			
		||||
  if (global_lstate == NULL) {
 | 
			
		||||
    global_lstate = init_lua();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0,
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_3(nlua_enter(), nlua_eval_lua_string, 0,
 | 
			
		||||
                         (void *)&str, arg, ret_tv);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -570,12 +601,8 @@ void executor_eval_lua(const String str, typval_T *const arg,
 | 
			
		||||
/// @return Return value of the execution.
 | 
			
		||||
Object executor_exec_lua_api(const String str, const Array args, Error *err)
 | 
			
		||||
{
 | 
			
		||||
  if (global_lstate == NULL) {
 | 
			
		||||
    global_lstate = init_lua();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Object retval = NIL;
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_4(global_lstate, nlua_exec_lua_string_api, 0,
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_4(nlua_enter(), nlua_exec_lua_string_api, 0,
 | 
			
		||||
                         (void *)&str, (void *)&args, &retval, err);
 | 
			
		||||
  return retval;
 | 
			
		||||
}
 | 
			
		||||
@@ -609,9 +636,6 @@ void ex_lua(exarg_T *const eap)
 | 
			
		||||
void ex_luado(exarg_T *const eap)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL
 | 
			
		||||
{
 | 
			
		||||
  if (global_lstate == NULL) {
 | 
			
		||||
    global_lstate = init_lua();
 | 
			
		||||
  }
 | 
			
		||||
  if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) {
 | 
			
		||||
    EMSG(_("cannot save undo information"));
 | 
			
		||||
    return;
 | 
			
		||||
@@ -621,7 +645,7 @@ void ex_luado(exarg_T *const eap)
 | 
			
		||||
    .data = (char *)eap->arg,
 | 
			
		||||
  };
 | 
			
		||||
  const linenr_T range[] = { eap->line1, eap->line2 };
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_luado_string, 0,
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_luado_string, 0,
 | 
			
		||||
                         (void *)&cmd, (void *)range);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -633,9 +657,6 @@ void ex_luado(exarg_T *const eap)
 | 
			
		||||
void ex_luafile(exarg_T *const eap)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL
 | 
			
		||||
{
 | 
			
		||||
  if (global_lstate == NULL) {
 | 
			
		||||
    global_lstate = init_lua();
 | 
			
		||||
  }
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_1(global_lstate, nlua_exec_lua_file, 0,
 | 
			
		||||
  NLUA_CALL_C_FUNCTION_1(nlua_enter(), nlua_exec_lua_file, 0,
 | 
			
		||||
                         (void *)eap->arg);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,64 @@
 | 
			
		||||
-- TODO(ZyX-I): Create compatibility layer.
 | 
			
		||||
return {}
 | 
			
		||||
--{{{1 package.path updater function
 | 
			
		||||
-- Last inserted paths. Used to clear out items from package.[c]path when they
 | 
			
		||||
-- are no longer in &runtimepath.
 | 
			
		||||
local last_nvim_paths = {}
 | 
			
		||||
local function _update_package_paths()
 | 
			
		||||
  local cur_nvim_paths = {}
 | 
			
		||||
  local rtps = vim.api.nvim_list_runtime_paths()
 | 
			
		||||
  local sep = package.config:sub(1, 1)
 | 
			
		||||
  for _, key in ipairs({'path', 'cpath'}) do
 | 
			
		||||
    local orig_str = package[key] .. ';'
 | 
			
		||||
    local pathtrails_ordered = {}
 | 
			
		||||
    local orig = {}
 | 
			
		||||
    -- Note: ignores trailing item without trailing `;`. Not using something
 | 
			
		||||
    -- simpler in order to preserve empty items (stand for default path).
 | 
			
		||||
    for s in orig_str:gmatch('[^;]*;') do
 | 
			
		||||
      s = s:sub(1, -2)  -- Strip trailing semicolon
 | 
			
		||||
      orig[#orig + 1] = s
 | 
			
		||||
    end
 | 
			
		||||
    if key == 'path' then
 | 
			
		||||
      -- /?.lua and /?/init.lua
 | 
			
		||||
      pathtrails_ordered = {sep .. '?.lua', sep .. '?' .. sep .. 'init.lua'}
 | 
			
		||||
    else
 | 
			
		||||
      local pathtrails = {}
 | 
			
		||||
      for _, s in ipairs(orig) do
 | 
			
		||||
        -- Find out path patterns. pathtrail should contain something like
 | 
			
		||||
        -- /?.so, \?.dll. This allows not to bother determining what correct
 | 
			
		||||
        -- suffixes are.
 | 
			
		||||
        local pathtrail = s:match('[/\\][^/\\]*%?.*$')
 | 
			
		||||
        if pathtrail and not pathtrails[pathtrail] then
 | 
			
		||||
          pathtrails[pathtrail] = true
 | 
			
		||||
          pathtrails_ordered[#pathtrails_ordered + 1] = pathtrail
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    local new = {}
 | 
			
		||||
    for _, rtp in ipairs(rtps) do
 | 
			
		||||
      if not rtp:match(';') then
 | 
			
		||||
        for _, pathtrail in pairs(pathtrails_ordered) do
 | 
			
		||||
          local new_path = rtp .. sep .. 'lua' .. pathtrail
 | 
			
		||||
          -- Always keep paths from &runtimepath at the start:
 | 
			
		||||
          -- append them here disregarding orig possibly containing one of them.
 | 
			
		||||
          new[#new + 1] = new_path
 | 
			
		||||
          cur_nvim_paths[new_path] = true
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    for _, orig_path in ipairs(orig) do
 | 
			
		||||
      -- Handle removing obsolete paths originating from &runtimepath: such
 | 
			
		||||
      -- paths either belong to cur_nvim_paths and were already added above or
 | 
			
		||||
      -- to last_nvim_paths and should not be added at all if corresponding
 | 
			
		||||
      -- entry was removed from &runtimepath list.
 | 
			
		||||
      if not (cur_nvim_paths[orig_path] or last_nvim_paths[orig_path]) then
 | 
			
		||||
        new[#new + 1] = orig_path
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    package[key] = table.concat(new, ';')
 | 
			
		||||
  end
 | 
			
		||||
  last_nvim_paths = cur_nvim_paths
 | 
			
		||||
end
 | 
			
		||||
--{{{1 Module definition
 | 
			
		||||
return {
 | 
			
		||||
  _update_package_paths = _update_package_paths,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -327,10 +327,10 @@ describe('api', function()
 | 
			
		||||
        {'nvim_get_mode', {}},
 | 
			
		||||
        {'nvim_eval',     {'1'}},
 | 
			
		||||
      }
 | 
			
		||||
      eq({{{mode='n', blocking=false},
 | 
			
		||||
      eq({ { {mode='n', blocking=false},
 | 
			
		||||
             13,
 | 
			
		||||
             {mode='n', blocking=false},  -- TODO: should be blocked=true
 | 
			
		||||
          1},
 | 
			
		||||
             1 },
 | 
			
		||||
           NIL}, meths.call_atomic(req))
 | 
			
		||||
      eq({mode='r', blocking=true}, nvim("get_mode"))
 | 
			
		||||
    end)
 | 
			
		||||
@@ -588,6 +588,36 @@ describe('api', function()
 | 
			
		||||
    end)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  describe('list_runtime_paths', function()
 | 
			
		||||
    it('returns nothing with empty &runtimepath', function()
 | 
			
		||||
      meths.set_option('runtimepath', '')
 | 
			
		||||
      eq({}, meths.list_runtime_paths())
 | 
			
		||||
    end)
 | 
			
		||||
    it('returns single runtimepath', function()
 | 
			
		||||
      meths.set_option('runtimepath', 'a')
 | 
			
		||||
      eq({'a'}, meths.list_runtime_paths())
 | 
			
		||||
    end)
 | 
			
		||||
    it('returns two runtimepaths', function()
 | 
			
		||||
      meths.set_option('runtimepath', 'a,b')
 | 
			
		||||
      eq({'a', 'b'}, meths.list_runtime_paths())
 | 
			
		||||
    end)
 | 
			
		||||
    it('returns empty strings when appropriate', function()
 | 
			
		||||
      meths.set_option('runtimepath', 'a,,b')
 | 
			
		||||
      eq({'a', '', 'b'}, meths.list_runtime_paths())
 | 
			
		||||
      meths.set_option('runtimepath', ',a,b')
 | 
			
		||||
      eq({'', 'a', 'b'}, meths.list_runtime_paths())
 | 
			
		||||
      meths.set_option('runtimepath', 'a,b,')
 | 
			
		||||
      eq({'a', 'b', ''}, meths.list_runtime_paths())
 | 
			
		||||
    end)
 | 
			
		||||
    it('truncates too long paths', function()
 | 
			
		||||
      local long_path = ('/a'):rep(8192)
 | 
			
		||||
      meths.set_option('runtimepath', long_path)
 | 
			
		||||
      local paths_list = meths.list_runtime_paths()
 | 
			
		||||
      neq({long_path}, paths_list)
 | 
			
		||||
      eq({long_path:sub(1, #(paths_list[1]))}, paths_list)
 | 
			
		||||
    end)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  it('can throw exceptions', function()
 | 
			
		||||
    local status, err = pcall(nvim, 'get_option', 'invalid-option')
 | 
			
		||||
    eq(false, status)
 | 
			
		||||
 
 | 
			
		||||
@@ -581,6 +581,24 @@ local function missing_provider(provider)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function alter_slashes(obj)
 | 
			
		||||
  if not iswin() then
 | 
			
		||||
    return obj
 | 
			
		||||
  end
 | 
			
		||||
  if type(obj) == 'string' then
 | 
			
		||||
    local ret = obj:gsub('/', '\\')
 | 
			
		||||
    return ret
 | 
			
		||||
  elseif type(obj) == 'table' then
 | 
			
		||||
    local ret = {}
 | 
			
		||||
    for k, v in pairs(obj) do
 | 
			
		||||
      ret[k] = alter_slashes(v)
 | 
			
		||||
    end
 | 
			
		||||
    return ret
 | 
			
		||||
  else
 | 
			
		||||
    assert(false, 'Could only alter slashes for tables of strings and strings')
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local module = {
 | 
			
		||||
  prepend_argv = prepend_argv,
 | 
			
		||||
  clear = clear,
 | 
			
		||||
@@ -649,6 +667,7 @@ local module = {
 | 
			
		||||
  NIL = mpack.NIL,
 | 
			
		||||
  get_pathsep = get_pathsep,
 | 
			
		||||
  missing_provider = missing_provider,
 | 
			
		||||
  alter_slashes = alter_slashes,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
return function(after_each)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,17 @@ local helpers = require('test.functional.helpers')(after_each)
 | 
			
		||||
local Screen = require('test.functional.ui.screen')
 | 
			
		||||
 | 
			
		||||
local eq = helpers.eq
 | 
			
		||||
local neq = helpers.neq
 | 
			
		||||
local NIL = helpers.NIL
 | 
			
		||||
local feed = helpers.feed
 | 
			
		||||
local clear = helpers.clear
 | 
			
		||||
local funcs = helpers.funcs
 | 
			
		||||
local meths = helpers.meths
 | 
			
		||||
local iswin = helpers.iswin
 | 
			
		||||
local command = helpers.command
 | 
			
		||||
local write_file = helpers.write_file
 | 
			
		||||
local redir_exec = helpers.redir_exec
 | 
			
		||||
local alter_slashes = helpers.alter_slashes
 | 
			
		||||
 | 
			
		||||
local screen
 | 
			
		||||
 | 
			
		||||
@@ -173,3 +176,119 @@ describe('debug.debug', function()
 | 
			
		||||
    ]])
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
describe('package.path/package.cpath', function()
 | 
			
		||||
  local sl = alter_slashes
 | 
			
		||||
 | 
			
		||||
  local function get_new_paths(sufs, runtimepaths)
 | 
			
		||||
    runtimepaths = runtimepaths or meths.list_runtime_paths()
 | 
			
		||||
    local new_paths = {}
 | 
			
		||||
    local sep = package.config:sub(1, 1)
 | 
			
		||||
    for _, v in ipairs(runtimepaths) do
 | 
			
		||||
      for _, suf in ipairs(sufs) do
 | 
			
		||||
        new_paths[#new_paths + 1] = v .. sep .. 'lua' .. suf
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    return new_paths
 | 
			
		||||
  end
 | 
			
		||||
  local function execute_lua(cmd, ...)
 | 
			
		||||
    return meths.execute_lua(cmd, {...})
 | 
			
		||||
  end
 | 
			
		||||
  local function eval_lua(expr, ...)
 | 
			
		||||
    return meths.execute_lua('return ' .. expr, {...})
 | 
			
		||||
  end
 | 
			
		||||
  local function set_path(which, value)
 | 
			
		||||
    return execute_lua('package[select(1, ...)] = select(2, ...)', which, value)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it('contains directories from &runtimepath on first invocation', function()
 | 
			
		||||
    local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
 | 
			
		||||
 | 
			
		||||
    local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'})
 | 
			
		||||
    local new_cpaths_str = table.concat(new_cpaths, ';')
 | 
			
		||||
    eq(new_cpaths_str, eval_lua('package.cpath'):sub(1, #new_cpaths_str))
 | 
			
		||||
  end)
 | 
			
		||||
  it('puts directories from &runtimepath always at the start', function()
 | 
			
		||||
    meths.set_option('runtimepath', 'a,b')
 | 
			
		||||
    local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b'})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
 | 
			
		||||
 | 
			
		||||
    set_path('path', sl'foo/?.lua;foo/?/init.lua;' .. new_paths_str)
 | 
			
		||||
 | 
			
		||||
    neq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
 | 
			
		||||
 | 
			
		||||
    command('set runtimepath+=c')
 | 
			
		||||
    new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b', 'c'})
 | 
			
		||||
    new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
 | 
			
		||||
  end)
 | 
			
		||||
  it('understands uncommon suffixes', function()
 | 
			
		||||
    set_path('cpath', './?/foo/bar/baz/x.nlua')
 | 
			
		||||
    meths.set_option('runtimepath', 'a')
 | 
			
		||||
    local new_paths = get_new_paths({'/?/foo/bar/baz/x.nlua'}, {'a'})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
 | 
			
		||||
 | 
			
		||||
    set_path('cpath', './yyy?zzz/x')
 | 
			
		||||
    meths.set_option('runtimepath', 'b')
 | 
			
		||||
    new_paths = get_new_paths({'/yyy?zzz/x'}, {'b'})
 | 
			
		||||
    new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
 | 
			
		||||
 | 
			
		||||
    set_path('cpath', './yyy?zzz/123?ghi/x')
 | 
			
		||||
    meths.set_option('runtimepath', 'b')
 | 
			
		||||
    new_paths = get_new_paths({'/yyy?zzz/123?ghi/x'}, {'b'})
 | 
			
		||||
    new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
 | 
			
		||||
  end)
 | 
			
		||||
  it('preserves empty items', function()
 | 
			
		||||
    local many_empty_path = ';;;;;;'
 | 
			
		||||
    local many_empty_cpath = ';;;;;;./?.luaso'
 | 
			
		||||
    set_path('path', many_empty_path)
 | 
			
		||||
    set_path('cpath', many_empty_cpath)
 | 
			
		||||
    meths.set_option('runtimepath', 'a')
 | 
			
		||||
    local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str .. ';' .. many_empty_path, eval_lua('package.path'))
 | 
			
		||||
    local new_cpaths = get_new_paths({'/?.luaso'}, {'a'})
 | 
			
		||||
    local new_cpaths_str = table.concat(new_cpaths, ';')
 | 
			
		||||
    eq(new_cpaths_str .. ';' .. many_empty_cpath, eval_lua('package.cpath'))
 | 
			
		||||
  end)
 | 
			
		||||
  it('preserves empty value', function()
 | 
			
		||||
    set_path('path', '')
 | 
			
		||||
    meths.set_option('runtimepath', 'a')
 | 
			
		||||
    local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str .. ';', eval_lua('package.path'))
 | 
			
		||||
  end)
 | 
			
		||||
  it('purges out all additions if runtimepath is set to empty', function()
 | 
			
		||||
    local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    local path = eval_lua('package.path')
 | 
			
		||||
    eq(new_paths_str, path:sub(1, #new_paths_str))
 | 
			
		||||
 | 
			
		||||
    local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'})
 | 
			
		||||
    local new_cpaths_str = table.concat(new_cpaths, ';')
 | 
			
		||||
    local cpath = eval_lua('package.cpath')
 | 
			
		||||
    eq(new_cpaths_str, cpath:sub(1, #new_cpaths_str))
 | 
			
		||||
 | 
			
		||||
    meths.set_option('runtimepath', '')
 | 
			
		||||
    eq(path:sub(#new_paths_str + 2, -1), eval_lua('package.path'))
 | 
			
		||||
    eq(cpath:sub(#new_cpaths_str + 2, -1), eval_lua('package.cpath'))
 | 
			
		||||
  end)
 | 
			
		||||
  it('works with paths with escaped commas', function()
 | 
			
		||||
    meths.set_option('runtimepath', '\\,')
 | 
			
		||||
    local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
 | 
			
		||||
  end)
 | 
			
		||||
  it('ignores paths with semicolons', function()
 | 
			
		||||
    meths.set_option('runtimepath', 'foo;bar,\\,')
 | 
			
		||||
    local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','})
 | 
			
		||||
    local new_paths_str = table.concat(new_paths, ';')
 | 
			
		||||
    eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user