mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 19:38:20 +00:00
fix(runtime): don't use regexes inside lua require'mod'
Fixes #15147 and fixes #15497. Also sketch "subdir" caching. Currently this only caches whether an rtp entry has a "lua/" subdir but we could consider cache other subdirs potentially or even "lua/mybigplugin/" possibly. Note: the async_leftpad test doesn't actually fail on master, at least not deterministically (even when disabling the fast_breakcheck throttling). It's still useful as a regression test for further changes and included as such.
This commit is contained in:
@@ -48,5 +48,8 @@ return {
|
|||||||
"style";
|
"style";
|
||||||
"noautocmd";
|
"noautocmd";
|
||||||
};
|
};
|
||||||
|
runtime = {
|
||||||
|
"is_lua";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -756,6 +756,11 @@ ArrayOf(String) nvim_list_runtime_paths(Error *err)
|
|||||||
return nvim_get_runtime_file(NULL_STRING, true, err);
|
return nvim_get_runtime_file(NULL_STRING, true, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Array nvim__runtime_inspect(void)
|
||||||
|
{
|
||||||
|
return runtime_inspect();
|
||||||
|
}
|
||||||
|
|
||||||
/// Find files in runtime directories
|
/// Find files in runtime directories
|
||||||
///
|
///
|
||||||
/// 'name' can contain wildcards. For example
|
/// 'name' can contain wildcards. For example
|
||||||
@@ -794,6 +799,25 @@ String nvim__get_lib_dir(void)
|
|||||||
return cstr_as_string(get_lib_dir());
|
return cstr_as_string(get_lib_dir());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find files in runtime directories
|
||||||
|
///
|
||||||
|
/// @param pat pattern of files to search for
|
||||||
|
/// @param all whether to return all matches or only the first
|
||||||
|
/// @param options
|
||||||
|
/// is_lua: only search lua subdirs
|
||||||
|
/// @return list of absolute paths to the found files
|
||||||
|
ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err)
|
||||||
|
FUNC_API_SINCE(8)
|
||||||
|
FUNC_API_FAST
|
||||||
|
{
|
||||||
|
bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
|
||||||
|
if (ERROR_SET(err)) {
|
||||||
|
return (Array)ARRAY_DICT_INIT;
|
||||||
|
}
|
||||||
|
return runtime_get_named(is_lua, pat, all);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Changes the global working directory.
|
/// Changes the global working directory.
|
||||||
///
|
///
|
||||||
/// @param dir Directory path
|
/// @param dir Directory path
|
||||||
|
@@ -419,7 +419,7 @@ local function process_function(fn)
|
|||||||
|
|
||||||
if not fn.fast then
|
if not fn.fast then
|
||||||
write_shifted_output(output, string.format([[
|
write_shifted_output(output, string.format([[
|
||||||
if (!nlua_is_deferred_safe(lstate)) {
|
if (!nlua_is_deferred_safe()) {
|
||||||
return luaL_error(lstate, e_luv_api_disabled, "%s");
|
return luaL_error(lstate, e_luv_api_disabled, "%s");
|
||||||
}
|
}
|
||||||
]], fn.name))
|
]], fn.name))
|
||||||
|
@@ -790,7 +790,7 @@ int nlua_call(lua_State *lstate)
|
|||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
size_t name_len;
|
size_t name_len;
|
||||||
const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
|
const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
|
||||||
if (!nlua_is_deferred_safe(lstate)) {
|
if (!nlua_is_deferred_safe()) {
|
||||||
return luaL_error(lstate, e_luv_api_disabled, "vimL function");
|
return luaL_error(lstate, e_luv_api_disabled, "vimL function");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -846,7 +846,7 @@ free_vim_args:
|
|||||||
|
|
||||||
static int nlua_rpcrequest(lua_State *lstate)
|
static int nlua_rpcrequest(lua_State *lstate)
|
||||||
{
|
{
|
||||||
if (!nlua_is_deferred_safe(lstate)) {
|
if (!nlua_is_deferred_safe()) {
|
||||||
return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
|
return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
|
||||||
}
|
}
|
||||||
return nlua_rpc(lstate, true);
|
return nlua_rpc(lstate, true);
|
||||||
@@ -1316,7 +1316,7 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro
|
|||||||
|
|
||||||
/// check if the current execution context is safe for calling deferred API
|
/// check if the current execution context is safe for calling deferred API
|
||||||
/// methods. Luv callbacks are unsafe as they are called inside the uv loop.
|
/// methods. Luv callbacks are unsafe as they are called inside the uv loop.
|
||||||
bool nlua_is_deferred_safe(lua_State *lstate)
|
bool nlua_is_deferred_safe(void)
|
||||||
{
|
{
|
||||||
return in_fast_callback == 0;
|
return in_fast_callback == 0;
|
||||||
}
|
}
|
||||||
|
@@ -57,17 +57,19 @@ end
|
|||||||
function vim._load_package(name)
|
function vim._load_package(name)
|
||||||
local basename = name:gsub('%.', '/')
|
local basename = name:gsub('%.', '/')
|
||||||
local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
|
local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
|
||||||
for _,path in ipairs(paths) do
|
local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true})
|
||||||
local found = vim.api.nvim_get_runtime_file(path, false)
|
|
||||||
if #found > 0 then
|
if #found > 0 then
|
||||||
local f, err = loadfile(found[1])
|
local f, err = loadfile(found[1])
|
||||||
return f or error(err)
|
return f or error(err)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
|
local so_paths = {}
|
||||||
for _,trail in ipairs(vim._so_trails) do
|
for _,trail in ipairs(vim._so_trails) do
|
||||||
local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
|
local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
|
||||||
local found = vim.api.nvim_get_runtime_file(path, false)
|
table.insert(so_paths, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true})
|
||||||
if #found > 0 then
|
if #found > 0 then
|
||||||
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
|
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
|
||||||
-- a) strip prefix up to and including the first dash, if any
|
-- a) strip prefix up to and including the first dash, if any
|
||||||
@@ -79,7 +81,6 @@ function vim._load_package(name)
|
|||||||
local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
|
local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
|
||||||
return f or error(err)
|
return f or error(err)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
#include "nvim/ex_cmds.h"
|
#include "nvim/ex_cmds.h"
|
||||||
#include "nvim/ex_cmds2.h"
|
#include "nvim/ex_cmds2.h"
|
||||||
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/misc1.h"
|
#include "nvim/misc1.h"
|
||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
@@ -157,6 +158,34 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback,
|
|||||||
return did_one ? OK : FAIL;
|
return did_one ? OK : FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RuntimeSearchPath runtime_search_path_get_cached(int *ref)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
runtime_search_path_validate();
|
||||||
|
|
||||||
|
*ref = 0;
|
||||||
|
if (runtime_search_path_ref == NULL) {
|
||||||
|
// cached path was unreferenced. keep a ref to
|
||||||
|
// prevent runtime_search_path() to freeing it too early
|
||||||
|
(*ref)++;
|
||||||
|
runtime_search_path_ref = ref;
|
||||||
|
}
|
||||||
|
return runtime_search_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runtime_search_path_unref(RuntimeSearchPath path, int *ref)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
if (*ref) {
|
||||||
|
if (runtime_search_path_ref == ref) {
|
||||||
|
runtime_search_path_ref = NULL;
|
||||||
|
} else {
|
||||||
|
runtime_search_path_free(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Find the file "name" in all directories in "path" and invoke
|
/// Find the file "name" in all directories in "path" and invoke
|
||||||
/// "callback(fname, cookie)".
|
/// "callback(fname, cookie)".
|
||||||
/// "name" can contain wildcards.
|
/// "name" can contain wildcards.
|
||||||
@@ -167,7 +196,6 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback,
|
|||||||
/// return FAIL when no file could be sourced, OK otherwise.
|
/// return FAIL when no file could be sourced, OK otherwise.
|
||||||
int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void *cookie)
|
int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void *cookie)
|
||||||
{
|
{
|
||||||
runtime_search_path_validate();
|
|
||||||
char_u *tail;
|
char_u *tail;
|
||||||
int num_files;
|
int num_files;
|
||||||
char_u **files;
|
char_u **files;
|
||||||
@@ -182,14 +210,8 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void
|
|||||||
verbose_leave();
|
verbose_leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeSearchPath path = runtime_search_path;
|
int ref;
|
||||||
int ref = 0;
|
RuntimeSearchPath path = runtime_search_path_get_cached(&ref);
|
||||||
if (runtime_search_path_ref == NULL) {
|
|
||||||
// cached path was unreferenced. keep a ref to
|
|
||||||
// prevent runtime_search_path() to freeing it too early
|
|
||||||
ref++;
|
|
||||||
runtime_search_path_ref = &ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop over all entries in cached path
|
// Loop over all entries in cached path
|
||||||
for (size_t j = 0; j < kv_size(path); j++) {
|
for (size_t j = 0; j < kv_size(path); j++) {
|
||||||
@@ -206,7 +228,6 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void
|
|||||||
|
|
||||||
if (name == NULL) {
|
if (name == NULL) {
|
||||||
(*callback)((char_u *)item.path, cookie);
|
(*callback)((char_u *)item.path, cookie);
|
||||||
did_one = true;
|
|
||||||
} else if (buflen + STRLEN(name) + 2 < MAXPATHL) {
|
} else if (buflen + STRLEN(name) + 2 < MAXPATHL) {
|
||||||
STRCPY(buf, item.path);
|
STRCPY(buf, item.path);
|
||||||
add_pathsep((char *)buf);
|
add_pathsep((char *)buf);
|
||||||
@@ -255,17 +276,70 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ref) {
|
runtime_search_path_unref(path, &ref);
|
||||||
if (runtime_search_path_ref == &ref) {
|
|
||||||
runtime_search_path_ref = NULL;
|
|
||||||
} else {
|
|
||||||
runtime_search_path_free(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return did_one ? OK : FAIL;
|
return did_one ? OK : FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Array runtime_inspect(void)
|
||||||
|
{
|
||||||
|
RuntimeSearchPath path = runtime_search_path;
|
||||||
|
Array rv = ARRAY_DICT_INIT;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kv_size(path); i++) {
|
||||||
|
SearchPathItem *item = &kv_A(path, i);
|
||||||
|
Array entry = ARRAY_DICT_INIT;
|
||||||
|
ADD(entry, STRING_OBJ(cstr_to_string(item->path)));
|
||||||
|
ADD(entry, BOOLEAN_OBJ(item->after));
|
||||||
|
if (item->has_lua != kNone) {
|
||||||
|
ADD(entry, BOOLEAN_OBJ(item->has_lua == kTrue));
|
||||||
|
}
|
||||||
|
ADD(rv, ARRAY_OBJ(entry));
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
|
||||||
|
{
|
||||||
|
int ref;
|
||||||
|
RuntimeSearchPath path = runtime_search_path_get_cached(&ref);
|
||||||
|
ArrayOf(String) rv = ARRAY_DICT_INIT;
|
||||||
|
static char buf[MAXPATHL];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kv_size(path); i++) {
|
||||||
|
SearchPathItem *item = &kv_A(path, i);
|
||||||
|
if (lua) {
|
||||||
|
if (item->has_lua == kNone) {
|
||||||
|
size_t size = (size_t)snprintf(buf, sizeof buf, "%s/lua/", item->path);
|
||||||
|
item->has_lua = (size < sizeof buf && os_isdir((char_u *)buf)) ? kTrue : kFalse;
|
||||||
|
}
|
||||||
|
if (item->has_lua == kFalse) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = 0; j < pat.size; j++) {
|
||||||
|
Object pat_item = pat.items[j];
|
||||||
|
if (pat_item.type == kObjectTypeString) {
|
||||||
|
size_t size = (size_t)snprintf(buf, sizeof buf, "%s/%s",
|
||||||
|
item->path, pat_item.data.string.data);
|
||||||
|
if (size < sizeof buf) {
|
||||||
|
if (os_file_is_readable(buf)) {
|
||||||
|
ADD(rv, STRING_OBJ(cstr_to_string(buf)));
|
||||||
|
if (!all) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
runtime_search_path_unref(path, &ref);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
/// Find "name" in "path". When found, invoke the callback function for
|
/// Find "name" in "path". When found, invoke the callback function for
|
||||||
/// it: callback(fname, "cookie")
|
/// it: callback(fname, "cookie")
|
||||||
/// When "flags" has DIP_ALL repeat for all matches, otherwise only the first
|
/// When "flags" has DIP_ALL repeat for all matches, otherwise only the first
|
||||||
@@ -338,7 +412,7 @@ static void push_path(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp
|
|||||||
if (h == 0) {
|
if (h == 0) {
|
||||||
char *allocated = xstrdup(entry);
|
char *allocated = xstrdup(entry);
|
||||||
map_put(String, handle_T)(rtp_used, cstr_as_string(allocated), 1);
|
map_put(String, handle_T)(rtp_used, cstr_as_string(allocated), 1);
|
||||||
kv_push(*search_path, ((SearchPathItem){ allocated, after }));
|
kv_push(*search_path, ((SearchPathItem){ allocated, after, kNone }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +555,13 @@ void runtime_search_path_free(RuntimeSearchPath path)
|
|||||||
|
|
||||||
void runtime_search_path_validate(void)
|
void runtime_search_path_validate(void)
|
||||||
{
|
{
|
||||||
|
if (!nlua_is_deferred_safe()) {
|
||||||
|
// Cannot rebuild search path in an async context. As a plugin will invoke
|
||||||
|
// itself asynchronously from sync code in the same plugin, the sought
|
||||||
|
// after lua/autoload module will most likely already be in the cached path.
|
||||||
|
// Thus prefer using the stale cache over erroring out in this situation.
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!runtime_search_path_valid) {
|
if (!runtime_search_path_valid) {
|
||||||
if (!runtime_search_path_ref) {
|
if (!runtime_search_path_ref) {
|
||||||
runtime_search_path_free(runtime_search_path);
|
runtime_search_path_free(runtime_search_path);
|
||||||
|
@@ -10,6 +10,7 @@ typedef void (*DoInRuntimepathCB)(char_u *, void *);
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
char *path;
|
char *path;
|
||||||
bool after;
|
bool after;
|
||||||
|
TriState has_lua;
|
||||||
} SearchPathItem;
|
} SearchPathItem;
|
||||||
|
|
||||||
typedef kvec_t(SearchPathItem) RuntimeSearchPath;
|
typedef kvec_t(SearchPathItem) RuntimeSearchPath;
|
||||||
|
@@ -328,6 +328,15 @@ describe('startup', function()
|
|||||||
eq({9003, '\thowdy'}, exec_lua [[ return { _G.y, _G.z } ]])
|
eq({9003, '\thowdy'}, exec_lua [[ return { _G.y, _G.z } ]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("handles require from &packpath in an async handler", function()
|
||||||
|
-- NO! you cannot just speed things up by calling async functions during startup!
|
||||||
|
-- It doesn't make anything actually faster! NOOOO!
|
||||||
|
pack_clear [[ lua require'async_leftpad'('brrrr', 'async_res') ]]
|
||||||
|
|
||||||
|
-- haha, async leftpad go brrrrr
|
||||||
|
eq('\tbrrrr', exec_lua [[ return _G.async_res ]])
|
||||||
|
end)
|
||||||
|
|
||||||
it("handles :packadd during startup", function()
|
it("handles :packadd during startup", function()
|
||||||
-- control group: opt/bonus is not availabe by default
|
-- control group: opt/bonus is not availabe by default
|
||||||
pack_clear [[
|
pack_clear [[
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
return "I am fancy_y.lua"
|
@@ -0,0 +1 @@
|
|||||||
|
return "I am fancy_z.lua"
|
@@ -0,0 +1 @@
|
|||||||
|
return "I am fancy_x.lua"
|
@@ -0,0 +1 @@
|
|||||||
|
return "I am init.lua of fancy_x!"
|
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
return "I am init.lua of fancy_y!"
|
@@ -0,0 +1,3 @@
|
|||||||
|
return function (val, res)
|
||||||
|
vim.loop.new_async(function() _G[res] = require'leftpad'(val) end):send()
|
||||||
|
end
|
@@ -2255,7 +2255,7 @@ end)
|
|||||||
|
|
||||||
describe('lua: require("mod") from packages', function()
|
describe('lua: require("mod") from packages', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
command('set rtp+=test/functional/fixtures')
|
command('set rtp+=test/functional/fixtures pp+=test/functional/fixtures')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('propagates syntax error', function()
|
it('propagates syntax error', function()
|
||||||
@@ -2266,4 +2266,13 @@ describe('lua: require("mod") from packages', function()
|
|||||||
|
|
||||||
matches("unexpected symbol", syntax_error_msg)
|
matches("unexpected symbol", syntax_error_msg)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('uses the right order of mod.lua vs mod/init.lua', function()
|
||||||
|
-- lua/fancy_x.lua takes precedence over lua/fancy_x/init.lua
|
||||||
|
eq('I am fancy_x.lua', exec_lua [[ return require'fancy_x' ]])
|
||||||
|
-- but lua/fancy_y/init.lua takes precedence over after/lua/fancy_y.lua
|
||||||
|
eq('I am init.lua of fancy_y!', exec_lua [[ return require'fancy_y' ]])
|
||||||
|
-- safety check: after/lua/fancy_z.lua is still loaded
|
||||||
|
eq('I am fancy_z.lua', exec_lua [[ return require'fancy_z' ]])
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
Reference in New Issue
Block a user