mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +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 |   add_custom_target(testlint | ||||||
|     COMMAND ${CMAKE_COMMAND} |     COMMAND ${CMAKE_COMMAND} | ||||||
|       -DLUACHECK_PRG=${LUACHECK_PRG} |       -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} |       -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() | endif() | ||||||
|  |  | ||||||
| set(CPACK_PACKAGE_NAME "Neovim") | set(CPACK_PACKAGE_NAME "Neovim") | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							| @@ -107,6 +107,9 @@ functionaltest-lua: | nvim | |||||||
| testlint: | build/.ran-cmake deps | testlint: | build/.ran-cmake deps | ||||||
| 	$(BUILD_CMD) -C build testlint | 	$(BUILD_CMD) -C build testlint | ||||||
|  |  | ||||||
|  | lualint: | build/.ran-cmake deps | ||||||
|  | 	$(BUILD_CMD) -C build lualint | ||||||
|  |  | ||||||
| unittest: | nvim | unittest: | nvim | ||||||
| 	+$(BUILD_CMD) -C build unittest | 	+$(BUILD_CMD) -C build unittest | ||||||
|  |  | ||||||
| @@ -138,6 +141,6 @@ check-single-includes: build/.ran-cmake | |||||||
| appimage: | appimage: | ||||||
| 	bash scripts/genappimage.sh | 	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 | exit_suite --continue | ||||||
|  |  | ||||||
|  | enter_suite 'lualint' | ||||||
|  |  | ||||||
|  | run_test 'top_make lualint' lualint | ||||||
|  |  | ||||||
|  | exit_suite --continue | ||||||
|  |  | ||||||
| enter_suite single-includes | enter_suite single-includes | ||||||
|  |  | ||||||
| CLICOLOR_FORCE=1 run_test_wd \ | 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. |                                       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* | 							*:lua* | ||||||
| :[range]lua {chunk} | :[range]lua {chunk} | ||||||
|   | |||||||
| @@ -244,6 +244,8 @@ Lua interface (|if_lua.txt|): | |||||||
|   while calling lua chunk: [string "<VimL compiled string>"]:1: TEST” in  |   while calling lua chunk: [string "<VimL compiled string>"]:1: TEST” in  | ||||||
|   Neovim. |   Neovim. | ||||||
| - Lua has direct access to Nvim |API| via `vim.api`. | - 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. | - Currently, most legacy Vim features are missing. | ||||||
|  |  | ||||||
| |input()| and |inputdialog()| gained support for each other’s features (return  | |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) |   FUNC_API_SINCE(1) | ||||||
| { | { | ||||||
|   Array rv = ARRAY_DICT_INIT; |   Array rv = ARRAY_DICT_INIT; | ||||||
|   uint8_t *rtp = p_rtp; |   char_u *rtp = p_rtp; | ||||||
|  |  | ||||||
|   if (*rtp == NUL) { |   if (*rtp == NUL) { | ||||||
|     // No paths |     // No paths | ||||||
| @@ -314,13 +314,14 @@ ArrayOf(String) nvim_list_runtime_paths(void) | |||||||
|     } |     } | ||||||
|     rtp++; |     rtp++; | ||||||
|   } |   } | ||||||
|  |   rv.size++; | ||||||
|  |  | ||||||
|   // Allocate memory for the copies |   // Allocate memory for the copies | ||||||
|   rv.items = xmalloc(sizeof(Object) * rv.size); |   rv.items = xmalloc(sizeof(*rv.items) * rv.size); | ||||||
|   // Reset the position |   // Reset the position | ||||||
|   rtp = p_rtp; |   rtp = p_rtp; | ||||||
|   // Start copying |   // 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].type = kObjectTypeString; | ||||||
|     rv.items[i].data.string.data = xmalloc(MAXPATHL); |     rv.items[i].data.string.data = xmalloc(MAXPATHL); | ||||||
|     // Copy the path from 'runtimepath' to rv.items[i] |     // 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) | Integer nvim_get_color_by_name(String name) | ||||||
|   FUNC_API_SINCE(1) |   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) | 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) \ | #define PUSH_CHAR(i, pos, line_buf, msg) \ | ||||||
|   if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \ |   if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \ | ||||||
|     line_buf[pos] = NUL; \ |     line_buf[pos] = NUL; \ | ||||||
|     msg((uint8_t *)line_buf); \ |     msg((char_u *)line_buf); \ | ||||||
|     pos = 0; \ |     pos = 0; \ | ||||||
|     continue; \ |     continue; \ | ||||||
|   } \ |   } \ | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ | |||||||
| #include "nvim/api/vim.h" | #include "nvim/api/vim.h" | ||||||
| #include "nvim/vim.h" | #include "nvim/vim.h" | ||||||
| #include "nvim/ex_getln.h" | #include "nvim/ex_getln.h" | ||||||
|  | #include "nvim/ex_cmds2.h" | ||||||
| #include "nvim/message.h" | #include "nvim/message.h" | ||||||
| #include "nvim/memline.h" | #include "nvim/memline.h" | ||||||
| #include "nvim/buffer_defs.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 | /// Crashes NeoVim if initialization fails. Should be called once per lua | ||||||
| /// interpreter instance. | /// 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 |   FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT | ||||||
| { | { | ||||||
|   lua_State *lstate = luaL_newstate(); |   lua_State *lstate = luaL_newstate(); | ||||||
| @@ -297,7 +300,43 @@ static lua_State *init_lua(void) | |||||||
|   return lstate; |   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 | /// 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) | void executor_exec_lua(const String str, typval_T *const ret_tv) | ||||||
|   FUNC_ATTR_NONNULL_ALL |   FUNC_ATTR_NONNULL_ALL | ||||||
| { | { | ||||||
|   if (global_lstate == NULL) { |   NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_lua_string, 0, | ||||||
|     global_lstate = init_lua(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0, |  | ||||||
|                          (void *)&str, ret_tv); |                          (void *)&str, ret_tv); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -551,11 +586,7 @@ void executor_eval_lua(const String str, typval_T *const arg, | |||||||
|                        typval_T *const ret_tv) |                        typval_T *const ret_tv) | ||||||
|   FUNC_ATTR_NONNULL_ALL |   FUNC_ATTR_NONNULL_ALL | ||||||
| { | { | ||||||
|   if (global_lstate == NULL) { |   NLUA_CALL_C_FUNCTION_3(nlua_enter(), nlua_eval_lua_string, 0, | ||||||
|     global_lstate = init_lua(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0, |  | ||||||
|                          (void *)&str, arg, ret_tv); |                          (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. | /// @return Return value of the execution. | ||||||
| Object executor_exec_lua_api(const String str, const Array args, Error *err) | Object executor_exec_lua_api(const String str, const Array args, Error *err) | ||||||
| { | { | ||||||
|   if (global_lstate == NULL) { |  | ||||||
|     global_lstate = init_lua(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Object retval = NIL; |   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); |                          (void *)&str, (void *)&args, &retval, err); | ||||||
|   return retval; |   return retval; | ||||||
| } | } | ||||||
| @@ -609,9 +636,6 @@ void ex_lua(exarg_T *const eap) | |||||||
| void ex_luado(exarg_T *const eap) | void ex_luado(exarg_T *const eap) | ||||||
|   FUNC_ATTR_NONNULL_ALL |   FUNC_ATTR_NONNULL_ALL | ||||||
| { | { | ||||||
|   if (global_lstate == NULL) { |  | ||||||
|     global_lstate = init_lua(); |  | ||||||
|   } |  | ||||||
|   if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) { |   if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) { | ||||||
|     EMSG(_("cannot save undo information")); |     EMSG(_("cannot save undo information")); | ||||||
|     return; |     return; | ||||||
| @@ -621,7 +645,7 @@ void ex_luado(exarg_T *const eap) | |||||||
|     .data = (char *)eap->arg, |     .data = (char *)eap->arg, | ||||||
|   }; |   }; | ||||||
|   const linenr_T range[] = { eap->line1, eap->line2 }; |   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); |                          (void *)&cmd, (void *)range); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -633,9 +657,6 @@ void ex_luado(exarg_T *const eap) | |||||||
| void ex_luafile(exarg_T *const eap) | void ex_luafile(exarg_T *const eap) | ||||||
|   FUNC_ATTR_NONNULL_ALL |   FUNC_ATTR_NONNULL_ALL | ||||||
| { | { | ||||||
|   if (global_lstate == NULL) { |   NLUA_CALL_C_FUNCTION_1(nlua_enter(), nlua_exec_lua_file, 0, | ||||||
|     global_lstate = init_lua(); |  | ||||||
|   } |  | ||||||
|   NLUA_CALL_C_FUNCTION_1(global_lstate, nlua_exec_lua_file, 0, |  | ||||||
|                          (void *)eap->arg); |                          (void *)eap->arg); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,2 +1,64 @@ | |||||||
| -- TODO(ZyX-I): Create compatibility layer. | -- 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,11 +327,11 @@ describe('api', function() | |||||||
|         {'nvim_get_mode', {}}, |         {'nvim_get_mode', {}}, | ||||||
|         {'nvim_eval',     {'1'}}, |         {'nvim_eval',     {'1'}}, | ||||||
|       } |       } | ||||||
|       eq({{{mode='n', blocking=false}, |       eq({ { {mode='n', blocking=false}, | ||||||
|           13, |              13, | ||||||
|           {mode='n', blocking=false},  -- TODO: should be blocked=true |              {mode='n', blocking=false},  -- TODO: should be blocked=true | ||||||
|           1}, |              1 }, | ||||||
|         NIL}, meths.call_atomic(req)) |            NIL}, meths.call_atomic(req)) | ||||||
|       eq({mode='r', blocking=true}, nvim("get_mode")) |       eq({mode='r', blocking=true}, nvim("get_mode")) | ||||||
|     end) |     end) | ||||||
|     -- TODO: bug #6166 |     -- TODO: bug #6166 | ||||||
| @@ -588,6 +588,36 @@ describe('api', function() | |||||||
|     end) |     end) | ||||||
|   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() |   it('can throw exceptions', function() | ||||||
|     local status, err = pcall(nvim, 'get_option', 'invalid-option') |     local status, err = pcall(nvim, 'get_option', 'invalid-option') | ||||||
|     eq(false, status) |     eq(false, status) | ||||||
|   | |||||||
| @@ -581,6 +581,24 @@ local function missing_provider(provider) | |||||||
|   end |   end | ||||||
| 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 = { | local module = { | ||||||
|   prepend_argv = prepend_argv, |   prepend_argv = prepend_argv, | ||||||
|   clear = clear, |   clear = clear, | ||||||
| @@ -649,6 +667,7 @@ local module = { | |||||||
|   NIL = mpack.NIL, |   NIL = mpack.NIL, | ||||||
|   get_pathsep = get_pathsep, |   get_pathsep = get_pathsep, | ||||||
|   missing_provider = missing_provider, |   missing_provider = missing_provider, | ||||||
|  |   alter_slashes = alter_slashes, | ||||||
| } | } | ||||||
|  |  | ||||||
| return function(after_each) | return function(after_each) | ||||||
|   | |||||||
| @@ -3,14 +3,17 @@ local helpers = require('test.functional.helpers')(after_each) | |||||||
| local Screen = require('test.functional.ui.screen') | local Screen = require('test.functional.ui.screen') | ||||||
|  |  | ||||||
| local eq = helpers.eq | local eq = helpers.eq | ||||||
|  | local neq = helpers.neq | ||||||
| local NIL = helpers.NIL | local NIL = helpers.NIL | ||||||
| local feed = helpers.feed | local feed = helpers.feed | ||||||
| local clear = helpers.clear | local clear = helpers.clear | ||||||
| local funcs = helpers.funcs | local funcs = helpers.funcs | ||||||
| local meths = helpers.meths | local meths = helpers.meths | ||||||
|  | local iswin = helpers.iswin | ||||||
| local command = helpers.command | local command = helpers.command | ||||||
| local write_file = helpers.write_file | local write_file = helpers.write_file | ||||||
| local redir_exec = helpers.redir_exec | local redir_exec = helpers.redir_exec | ||||||
|  | local alter_slashes = helpers.alter_slashes | ||||||
|  |  | ||||||
| local screen | local screen | ||||||
|  |  | ||||||
| @@ -173,3 +176,119 @@ describe('debug.debug', function() | |||||||
|     ]]) |     ]]) | ||||||
|   end) |   end) | ||||||
| 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
	 Justin M. Keyes
					Justin M. Keyes