diff options
author | ZyX <kp-pav@yandex.ru> | 2017-05-23 00:46:57 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2017-05-25 16:27:40 +0300 |
commit | 97602371e6b00f280ede5e3f6b0e0c1144f46c72 (patch) | |
tree | 669833472c79750f8a590b766f0890b344a1f611 | |
parent | a5a5c83608e6d4455ac40e8786fd16eaf817c608 (diff) | |
download | rneovim-97602371e6b00f280ede5e3f6b0e0c1144f46c72.tar.gz rneovim-97602371e6b00f280ede5e3f6b0e0c1144f46c72.tar.bz2 rneovim-97602371e6b00f280ede5e3f6b0e0c1144f46c72.zip |
lua: Add paths from &runtimepath to package.path and package.cpath
-rw-r--r-- | runtime/doc/if_lua.txt | 27 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 2 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 71 | ||||
-rw-r--r-- | src/nvim/lua/vim.lua | 57 | ||||
-rw-r--r-- | test/functional/helpers.lua | 19 | ||||
-rw-r--r-- | test/functional/lua/overrides_spec.lua | 116 |
6 files changed, 265 insertions, 27 deletions
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 470f3bde7a..2e4d32027d 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -9,7 +9,32 @@ 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 each time +'runtimepath' is changed, `package.path` and `package.cpath` are adjusted by +prepending directories from 'runtimepath' each suffixed by `/lua` and +`?`-containing suffixes from `package.path` and `package.cpath`. I.e. when +'runtimepath' option contains `/foo` and `package.path` contains only +`./a?d/j/g.nlua;./?.lua;/bar/?.lua` the resulting `package.path` after +adjustments will look like this: > + + /foo/lua/?.lua;/foo/lua/a?d/j/g.nlua;./a?d/j/g.nlua;./?.lua;/bar/?.lua + +Note that code have taken everything starting from the last path component +from existing paths containing a question mark as a `?`-containing suffix, but +only applied unique suffixes. + +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. + +Note 3: paths from 'runtimepath' which contain semicolons cannot be put into +`package.[c]path` and thus are ignored. + +============================================================================== +2. Commands *lua-commands* *:lua* :[range]lua {chunk} diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 8851ef2d4b..24410ddaac 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -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 diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 6f9e381d8d..531cc42862 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -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); } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 8d1c5bdf4f..736182de11 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -1,2 +1,57 @@ -- 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 = {} + 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 + -- Find out path patterns. pathtrail should contain something like + -- /?.so, /?/init.lua, /?.lua. 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 + 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, +} diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index b03840b3fe..4bd9ae4bfb 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -579,6 +579,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, @@ -646,6 +664,7 @@ local module = { NIL = mpack.NIL, get_pathsep = get_pathsep, missing_provider = missing_provider, + alter_slashes = alter_slashes, } return function(after_each) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index c8aee130a7..927bddc373 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -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,116 @@ 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 = {} + for _, v in ipairs(runtimepaths) do + for _, suf in ipairs(sufs) do + new_paths[#new_paths + 1] = v .. suf:sub(1, 1) .. '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('path', './?/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.path'):sub(1, #new_paths_str)) + + set_path('path', './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.path'):sub(1, #new_paths_str)) + + set_path('path', './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.path'):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') + -- No suffixes known, so can’t add anything + eq(many_empty_path, eval_lua('package.path')) + local new_paths = get_new_paths({'/?.luaso'}, {'a'}) + local new_paths_str = table.concat(new_paths, ';') + eq(new_paths_str .. ';' .. many_empty_cpath, eval_lua('package.cpath')) + end) + it('preserves empty value', function() + set_path('path', '') + meths.set_option('runtimepath', 'a') + -- No suffixes known, so can’t add anything + eq('', 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) |