mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 20:08:17 +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