mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
feat(lua): add proper support of luv threads
This commit is contained in:
parent
d0f8f76224
commit
b87867e69e
48
runtime/lua/vim/_load_package.lua
Normal file
48
runtime/lua/vim/_load_package.lua
Normal file
@ -0,0 +1,48 @@
|
||||
-- prevents luacheck from making lints for setting things on vim
|
||||
local vim = assert(vim)
|
||||
|
||||
local pathtrails = {}
|
||||
vim._so_trails = {}
|
||||
for s in (package.cpath..';'):gmatch('[^;]*;') do
|
||||
s = s:sub(1, -2) -- Strip trailing semicolon
|
||||
-- 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
|
||||
table.insert(vim._so_trails, pathtrail)
|
||||
end
|
||||
end
|
||||
|
||||
function vim._load_package(name)
|
||||
local basename = name:gsub('%.', '/')
|
||||
local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
|
||||
local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true})
|
||||
if #found > 0 then
|
||||
local f, err = loadfile(found[1])
|
||||
return f or error(err)
|
||||
end
|
||||
|
||||
local so_paths = {}
|
||||
for _,trail in ipairs(vim._so_trails) do
|
||||
local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
|
||||
table.insert(so_paths, path)
|
||||
end
|
||||
|
||||
found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true})
|
||||
if #found > 0 then
|
||||
-- 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
|
||||
-- b) replace all dots by underscores
|
||||
-- c) prepend "luaopen_"
|
||||
-- So "foo-bar.baz" should result in "luaopen_bar_baz"
|
||||
local dash = name:find("-", 1, true)
|
||||
local modname = dash and name:sub(dash + 1) or name
|
||||
local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
|
||||
return f or error(err)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
table.insert(package.loaders, 1, vim._load_package)
|
@ -776,7 +776,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
|
||||
|
||||
if (has_metatable) {
|
||||
|
||||
nlua_pushref(l, nlua_empty_dict_ref);
|
||||
nlua_pushref(l, nlua_get_empty_dict_ref(l));
|
||||
if (lua_rawequal(l, -2, -1)) {
|
||||
as_empty_dict = true;
|
||||
} else {
|
||||
@ -822,7 +822,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
|
||||
}
|
||||
break;
|
||||
case LUA_TUSERDATA:
|
||||
nlua_pushref(l, nlua_nil_ref);
|
||||
nlua_pushref(l, nlua_get_nil_ref(l));
|
||||
bool is_nil = lua_rawequal(l, -2, -1);
|
||||
lua_pop(l, 1);
|
||||
if (is_nil) {
|
||||
@ -1285,7 +1285,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json)
|
||||
|
||||
/* Handle empty objects */
|
||||
if (token.type == T_OBJ_END) {
|
||||
nlua_pushref(l, nlua_empty_dict_ref); \
|
||||
nlua_pushref(l, nlua_get_empty_dict_ref(l)); \
|
||||
lua_setmetatable(l, -2); \
|
||||
json_decode_ascend(json);
|
||||
return;
|
||||
@ -1392,7 +1392,7 @@ static void json_process_value(lua_State *l, json_parse_t *json,
|
||||
if (use_luanil) {
|
||||
lua_pushnil(l);
|
||||
} else {
|
||||
nlua_pushref(l, nlua_nil_ref);
|
||||
nlua_pushref(l, nlua_get_nil_ref(l));
|
||||
}
|
||||
break;;
|
||||
default:
|
||||
@ -1549,7 +1549,15 @@ int lua_cjson_new(lua_State *l)
|
||||
};
|
||||
|
||||
/* Initialise number conversions */
|
||||
lua_getfield(l, LUA_REGISTRYINDEX, "nvim.thread");
|
||||
bool is_thread = lua_toboolean(l, -1);
|
||||
lua_pop(l, 1);
|
||||
|
||||
// Since fpconv_init does not need to be called multiple times and is not
|
||||
// thread safe, it should only be called in the main thread.
|
||||
if (!is_thread) {
|
||||
fpconv_init();
|
||||
}
|
||||
|
||||
/* Test if array metatables are in registry */
|
||||
lua_pushlightuserdata(l, json_lightudata_mask(&json_empty_array));
|
||||
@ -1582,7 +1590,7 @@ int lua_cjson_new(lua_State *l)
|
||||
compat_luaL_setfuncs(l, reg, 1);
|
||||
|
||||
/* Set cjson.null */
|
||||
nlua_pushref(l, nlua_nil_ref);
|
||||
nlua_pushref(l, nlua_get_nil_ref(l));
|
||||
lua_setfield(l, -2, "null");
|
||||
|
||||
/* Set cjson.empty_array_mt */
|
||||
|
@ -63,6 +63,7 @@ set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua)
|
||||
set(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua)
|
||||
set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua)
|
||||
set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lua)
|
||||
set(LUA_LOAD_PACKAGE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_load_package.lua)
|
||||
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
|
||||
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
|
||||
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
|
||||
@ -336,6 +337,7 @@ add_custom_command(
|
||||
${LUA_F_MODULE_SOURCE} lua_F_module
|
||||
${LUA_META_MODULE_SOURCE} lua_meta_module
|
||||
${LUA_FILETYPE_MODULE_SOURCE} lua_filetype_module
|
||||
${LUA_LOAD_PACKAGE_MODULE_SOURCE} lua_load_package_module
|
||||
DEPENDS
|
||||
${CHAR_BLOB_GENERATOR}
|
||||
${LUA_VIM_MODULE_SOURCE}
|
||||
@ -344,6 +346,7 @@ add_custom_command(
|
||||
${LUA_F_MODULE_SOURCE}
|
||||
${LUA_META_MODULE_SOURCE}
|
||||
${LUA_FILETYPE_MODULE_SOURCE}
|
||||
${LUA_LOAD_PACKAGE_MODULE_SOURCE}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
|
@ -1955,7 +1955,7 @@ Dictionary nvim__stats(void)
|
||||
Dictionary rv = ARRAY_DICT_INIT;
|
||||
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
|
||||
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
|
||||
PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
|
||||
PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
|
||||
&& ret.string_keys_num == 0)) {
|
||||
ret.type = kObjectTypeArray;
|
||||
if (tsize == 0 && lua_getmetatable(lstate, -1)) {
|
||||
nlua_pushref(lstate, nlua_empty_dict_ref);
|
||||
nlua_pushref(lstate, nlua_get_empty_dict_ref(lstate));
|
||||
if (lua_rawequal(lstate, -2, -1)) {
|
||||
ret.type = kObjectTypeDictionary;
|
||||
}
|
||||
@ -401,7 +401,7 @@ nlua_pop_typval_table_processing_end:
|
||||
}
|
||||
case LUA_TUSERDATA: {
|
||||
// TODO(bfredl): check mt.__call and convert to function?
|
||||
nlua_pushref(lstate, nlua_nil_ref);
|
||||
nlua_pushref(lstate, nlua_get_nil_ref(lstate));
|
||||
bool is_nil = lua_rawequal(lstate, -2, -1);
|
||||
lua_pop(lstate, 1);
|
||||
if (is_nil) {
|
||||
@ -445,7 +445,7 @@ static bool typval_conv_special = false;
|
||||
if (typval_conv_special) { \
|
||||
lua_pushnil(lstate); \
|
||||
} else { \
|
||||
nlua_pushref(lstate, nlua_nil_ref); \
|
||||
nlua_pushref(lstate, nlua_get_nil_ref(lstate)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
@ -495,7 +495,7 @@ static bool typval_conv_special = false;
|
||||
nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
|
||||
} else { \
|
||||
lua_createtable(lstate, 0, 0); \
|
||||
nlua_pushref(lstate, nlua_empty_dict_ref); \
|
||||
nlua_pushref(lstate, nlua_get_empty_dict_ref(lstate)); \
|
||||
lua_setmetatable(lstate, -2); \
|
||||
} \
|
||||
} while (0)
|
||||
@ -734,7 +734,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, bool special
|
||||
} else {
|
||||
lua_createtable(lstate, 0, (int)dict.size);
|
||||
if (dict.size == 0 && !special) {
|
||||
nlua_pushref(lstate, nlua_empty_dict_ref);
|
||||
nlua_pushref(lstate, nlua_get_empty_dict_ref(lstate));
|
||||
lua_setmetatable(lstate, -2);
|
||||
}
|
||||
}
|
||||
@ -782,7 +782,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
|
||||
if (special) {
|
||||
lua_pushnil(lstate);
|
||||
} else {
|
||||
nlua_pushref(lstate, nlua_nil_ref);
|
||||
nlua_pushref(lstate, nlua_get_nil_ref(lstate));
|
||||
}
|
||||
break;
|
||||
case kObjectTypeLuaRef: {
|
||||
@ -1206,7 +1206,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
|
||||
break;
|
||||
|
||||
case LUA_TUSERDATA: {
|
||||
nlua_pushref(lstate, nlua_nil_ref);
|
||||
nlua_pushref(lstate, nlua_get_nil_ref(lstate));
|
||||
bool is_nil = lua_rawequal(lstate, -2, -1);
|
||||
lua_pop(lstate, 1);
|
||||
if (is_nil) {
|
||||
|
@ -45,11 +45,22 @@ static int in_fast_callback = 0;
|
||||
// Initialized in nlua_init().
|
||||
static lua_State *global_lstate = NULL;
|
||||
|
||||
static uv_thread_t main_thread;
|
||||
|
||||
typedef struct {
|
||||
Error err;
|
||||
String lua_err_str;
|
||||
} LuaError;
|
||||
|
||||
typedef struct {
|
||||
LuaRef nil_ref;
|
||||
LuaRef empty_dict_ref;
|
||||
int ref_count;
|
||||
#if __has_feature(address_sanitizer)
|
||||
PMap(handle_T) ref_markers;
|
||||
#endif
|
||||
} nlua_ref_state_t;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "lua/executor.c.generated.h"
|
||||
# include "lua/vim_module.generated.h"
|
||||
@ -65,11 +76,16 @@ typedef struct {
|
||||
}
|
||||
|
||||
#if __has_feature(address_sanitizer)
|
||||
static PMap(handle_T) nlua_ref_markers = MAP_INIT;
|
||||
static bool nlua_track_refs = false;
|
||||
# define NLUA_TRACK_REFS
|
||||
#endif
|
||||
|
||||
typedef enum luv_err_type {
|
||||
kCallback,
|
||||
kThread,
|
||||
kThreadCallback,
|
||||
} luv_err_t;
|
||||
|
||||
/// Convert lua error into a Vim error message
|
||||
///
|
||||
/// @param lstate Lua interpreter state.
|
||||
@ -122,8 +138,21 @@ static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
||||
static void nlua_luv_error_event(void **argv)
|
||||
{
|
||||
char *error = (char *)argv[0];
|
||||
luv_err_t type = (luv_err_t)(intptr_t)argv[1];
|
||||
msg_ext_set_kind("lua_error");
|
||||
switch (type) {
|
||||
case kCallback:
|
||||
semsg_multiline("Error executing luv callback:\n%s", error);
|
||||
break;
|
||||
case kThread:
|
||||
semsg_multiline("Error in luv thread:\n%s", error);
|
||||
break;
|
||||
case kThreadCallback:
|
||||
semsg_multiline("Error in luv callback, thread:\n%s", error);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
xfree(error);
|
||||
}
|
||||
|
||||
@ -148,7 +177,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
|
||||
const char *error = lua_tostring(lstate, -1);
|
||||
|
||||
multiqueue_put(main_loop.events, nlua_luv_error_event,
|
||||
1, xstrdup(error));
|
||||
2, xstrdup(error), (intptr_t)kCallback);
|
||||
lua_pop(lstate, 1); // error message
|
||||
retval = -status;
|
||||
} else { // LUA_OK
|
||||
@ -162,6 +191,106 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int nlua_luv_thread_cb_cfpcall(lua_State *lstate, int nargs, int nresult,
|
||||
int flags)
|
||||
{
|
||||
return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, true);
|
||||
}
|
||||
|
||||
static int nlua_luv_thread_cfpcall(lua_State *lstate, int nargs, int nresult,
|
||||
int flags)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, false);
|
||||
}
|
||||
|
||||
static int nlua_luv_thread_cfcpcall(lua_State *lstate, lua_CFunction func,
|
||||
void *ud, int flags)
|
||||
FUNC_ATTR_NONNULL_ARG(1, 2)
|
||||
{
|
||||
lua_pushcfunction(lstate, func);
|
||||
lua_pushlightuserdata(lstate, ud);
|
||||
int retval = nlua_luv_thread_cfpcall(lstate, 1, 0, flags);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nresult,
|
||||
int flags, bool is_callback)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
int retval;
|
||||
|
||||
int top = lua_gettop(lstate);
|
||||
int status = lua_pcall(lstate, nargs, nresult, 0);
|
||||
if (status) {
|
||||
if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
|
||||
// Terminate this thread, as the main thread may be able to continue
|
||||
// execution.
|
||||
mch_errmsg(e_outofmem);
|
||||
mch_errmsg("\n");
|
||||
lua_close(lstate);
|
||||
#ifdef WIN32
|
||||
ExitThread(0);
|
||||
#else
|
||||
pthread_exit(0);
|
||||
#endif
|
||||
}
|
||||
const char *error = lua_tostring(lstate, -1);
|
||||
|
||||
loop_schedule_deferred(&main_loop,
|
||||
event_create(nlua_luv_error_event, 2,
|
||||
xstrdup(error),
|
||||
is_callback
|
||||
? (intptr_t)kThreadCallback
|
||||
: (intptr_t)kThread));
|
||||
lua_pop(lstate, 1); // error message
|
||||
retval = -status;
|
||||
} else { // LUA_OK
|
||||
if (nresult == LUA_MULTRET) {
|
||||
nresult = lua_gettop(lstate) - top + nargs + 1;
|
||||
}
|
||||
retval = nresult;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int nlua_thr_api_nvim__get_runtime(lua_State *lstate)
|
||||
{
|
||||
if (lua_gettop(lstate) != 3) {
|
||||
return luaL_error(lstate, "Expected 3 arguments");
|
||||
}
|
||||
|
||||
luaL_checktype(lstate, -1, LUA_TTABLE);
|
||||
lua_getfield(lstate, -1, "is_lua");
|
||||
if (!lua_isboolean(lstate, -1)) {
|
||||
return luaL_error(lstate, "is_lua is not a boolean");
|
||||
}
|
||||
bool is_lua = lua_toboolean(lstate, -1);
|
||||
lua_pop(lstate, 2);
|
||||
|
||||
luaL_checktype(lstate, -1, LUA_TBOOLEAN);
|
||||
bool all = lua_toboolean(lstate, -1);
|
||||
lua_pop(lstate, 1);
|
||||
|
||||
Error err = ERROR_INIT;
|
||||
const Array pat = nlua_pop_Array(lstate, &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
luaL_where(lstate, 1);
|
||||
lua_pushstring(lstate, err.msg);
|
||||
api_clear_error(&err);
|
||||
lua_concat(lstate, 2);
|
||||
return lua_error(lstate);
|
||||
}
|
||||
|
||||
ArrayOf(String) ret = runtime_get_named_thread(is_lua, pat, all);
|
||||
nlua_push_Array(lstate, ret, true);
|
||||
api_free_array(ret);
|
||||
api_free_array(pat);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void nlua_schedule_event(void **argv)
|
||||
{
|
||||
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
|
||||
@ -302,6 +431,157 @@ static int nlua_wait(lua_State *lstate)
|
||||
return 2;
|
||||
}
|
||||
|
||||
static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state));
|
||||
memset(ref_state, 0, sizeof(*ref_state));
|
||||
ref_state->nil_ref = LUA_NOREF;
|
||||
ref_state->empty_dict_ref = LUA_NOREF;
|
||||
return ref_state;
|
||||
}
|
||||
|
||||
static nlua_ref_state_t *nlua_get_ref_state(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
lua_getfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state");
|
||||
nlua_ref_state_t *ref_state = lua_touserdata(lstate, -1);
|
||||
lua_pop(lstate, 1);
|
||||
|
||||
return ref_state;
|
||||
}
|
||||
|
||||
LuaRef nlua_get_nil_ref(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
|
||||
return ref_state->nil_ref;
|
||||
}
|
||||
|
||||
LuaRef nlua_get_empty_dict_ref(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
|
||||
return ref_state->empty_dict_ref;
|
||||
}
|
||||
|
||||
static int nlua_get_ref_count(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
|
||||
return ref_state->ref_count;
|
||||
}
|
||||
|
||||
int nlua_get_global_ref_count(void)
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
return nlua_get_ref_count(lstate);
|
||||
}
|
||||
|
||||
static void nlua_common_vim_init(lua_State *lstate, bool is_thread)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
nlua_ref_state_t *ref_state = nlua_new_ref_state(lstate, is_thread);
|
||||
lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state");
|
||||
|
||||
// vim.is_thread
|
||||
lua_pushboolean(lstate, is_thread);
|
||||
lua_setfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
|
||||
lua_pushcfunction(lstate, &nlua_is_thread);
|
||||
lua_setfield(lstate, -2, "is_thread");
|
||||
|
||||
// vim.NIL
|
||||
lua_newuserdata(lstate, 0);
|
||||
lua_createtable(lstate, 0, 0);
|
||||
lua_pushcfunction(lstate, &nlua_nil_tostring);
|
||||
lua_setfield(lstate, -2, "__tostring");
|
||||
lua_setmetatable(lstate, -2);
|
||||
ref_state->nil_ref = nlua_ref(lstate, -1);
|
||||
lua_pushvalue(lstate, -1);
|
||||
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL");
|
||||
lua_setfield(lstate, -2, "NIL");
|
||||
|
||||
// vim._empty_dict_mt
|
||||
lua_createtable(lstate, 0, 0);
|
||||
lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
|
||||
lua_setfield(lstate, -2, "__tostring");
|
||||
ref_state->empty_dict_ref = nlua_ref(lstate, -1);
|
||||
lua_pushvalue(lstate, -1);
|
||||
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
|
||||
lua_setfield(lstate, -2, "_empty_dict_mt");
|
||||
|
||||
// vim.loop
|
||||
if (is_thread) {
|
||||
luv_set_callback(lstate, nlua_luv_thread_cb_cfpcall);
|
||||
luv_set_thread(lstate, nlua_luv_thread_cfpcall);
|
||||
luv_set_cthread(lstate, nlua_luv_thread_cfcpcall);
|
||||
} else {
|
||||
luv_set_loop(lstate, &main_loop.uv);
|
||||
luv_set_callback(lstate, nlua_luv_cfpcall);
|
||||
}
|
||||
luaopen_luv(lstate);
|
||||
lua_pushvalue(lstate, -1);
|
||||
lua_setfield(lstate, -3, "loop");
|
||||
|
||||
// package.loaded.luv = vim.loop
|
||||
// otherwise luv will be reinitialized when require'luv'
|
||||
lua_getglobal(lstate, "package");
|
||||
lua_getfield(lstate, -1, "loaded");
|
||||
lua_pushvalue(lstate, -3);
|
||||
lua_setfield(lstate, -2, "luv");
|
||||
lua_pop(lstate, 3);
|
||||
}
|
||||
|
||||
static void nlua_common_package_init(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
{
|
||||
const char *code = (char *)&shared_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua")
|
||||
|| nlua_pcall(lstate, 0, 0)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const char *code = (char *)&lua_load_package_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(lua_load_package_module) - 1, "@vim/_load_package.lua")
|
||||
|| lua_pcall(lstate, 0, 0, 0)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating _load_package module: %.*s"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
lua_getglobal(lstate, "package"); // [package]
|
||||
lua_getfield(lstate, -1, "loaded"); // [package, loaded]
|
||||
|
||||
const char *code = (char *)&inspect_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua")
|
||||
|| nlua_pcall(lstate, 0, 1)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// [package, loaded, inspect]
|
||||
lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
|
||||
}
|
||||
|
||||
{
|
||||
const char *code = (char *)&lua_F_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua")
|
||||
|| nlua_pcall(lstate, 0, 1)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n"));
|
||||
return;
|
||||
}
|
||||
// [package, loaded, module]
|
||||
lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
|
||||
|
||||
lua_pop(lstate, 2); // []
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize lua interpreter state
|
||||
///
|
||||
/// Called by lua interpreter itself to initialize state.
|
||||
@ -362,80 +642,22 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
||||
lua_pushcfunction(lstate, &nlua_wait);
|
||||
lua_setfield(lstate, -2, "wait");
|
||||
|
||||
// vim.NIL
|
||||
lua_newuserdata(lstate, 0);
|
||||
lua_createtable(lstate, 0, 0);
|
||||
lua_pushcfunction(lstate, &nlua_nil_tostring);
|
||||
lua_setfield(lstate, -2, "__tostring");
|
||||
lua_setmetatable(lstate, -2);
|
||||
nlua_nil_ref = nlua_ref(lstate, -1);
|
||||
lua_pushvalue(lstate, -1);
|
||||
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL");
|
||||
lua_setfield(lstate, -2, "NIL");
|
||||
|
||||
// vim._empty_dict_mt
|
||||
lua_createtable(lstate, 0, 0);
|
||||
lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
|
||||
lua_setfield(lstate, -2, "__tostring");
|
||||
nlua_empty_dict_ref = nlua_ref(lstate, -1);
|
||||
lua_pushvalue(lstate, -1);
|
||||
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
|
||||
lua_setfield(lstate, -2, "_empty_dict_mt");
|
||||
nlua_common_vim_init(lstate, false);
|
||||
|
||||
// internal vim._treesitter... API
|
||||
nlua_add_treesitter(lstate);
|
||||
|
||||
// vim.loop
|
||||
luv_set_loop(lstate, &main_loop.uv);
|
||||
luv_set_callback(lstate, nlua_luv_cfpcall);
|
||||
luaopen_luv(lstate);
|
||||
lua_pushvalue(lstate, -1);
|
||||
lua_setfield(lstate, -3, "loop");
|
||||
|
||||
// package.loaded.luv = vim.loop
|
||||
// otherwise luv will be reinitialized when require'luv'
|
||||
lua_getglobal(lstate, "package");
|
||||
lua_getfield(lstate, -1, "loaded");
|
||||
lua_pushvalue(lstate, -3);
|
||||
lua_setfield(lstate, -2, "luv");
|
||||
lua_pop(lstate, 3);
|
||||
|
||||
nlua_state_add_stdlib(lstate);
|
||||
nlua_state_add_stdlib(lstate, false);
|
||||
|
||||
lua_setglobal(lstate, "vim");
|
||||
|
||||
{
|
||||
const char *code = (char *)&shared_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua")
|
||||
|| nlua_pcall(lstate, 0, 0)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n"));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
nlua_common_package_init(lstate);
|
||||
|
||||
{
|
||||
lua_getglobal(lstate, "package"); // [package]
|
||||
lua_getfield(lstate, -1, "loaded"); // [package, loaded]
|
||||
|
||||
const char *code = (char *)&inspect_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua")
|
||||
|| nlua_pcall(lstate, 0, 1)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n"));
|
||||
return 1;
|
||||
}
|
||||
// [package, loaded, inspect]
|
||||
lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
|
||||
|
||||
code = (char *)&lua_F_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua")
|
||||
|| nlua_pcall(lstate, 0, 1)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n"));
|
||||
return 1;
|
||||
}
|
||||
// [package, loaded, module]
|
||||
lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
|
||||
|
||||
code = (char *)&lua_filetype_module[0];
|
||||
char *code = (char *)&lua_filetype_module[0];
|
||||
if (luaL_loadbuffer(lstate, code, sizeof(lua_filetype_module) - 1, "@vim/filetype.lua")
|
||||
|| nlua_pcall(lstate, 0, 1)) {
|
||||
nlua_error(lstate, _("E5106: Error while creating vim.filetype module: %.*s"));
|
||||
@ -495,9 +717,60 @@ void nlua_init(void)
|
||||
luaL_openlibs(lstate);
|
||||
nlua_state_init(lstate);
|
||||
|
||||
luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem);
|
||||
|
||||
global_lstate = lstate;
|
||||
|
||||
main_thread = uv_thread_self();
|
||||
}
|
||||
|
||||
static lua_State *nlua_thread_acquire_vm(void)
|
||||
{
|
||||
// If it is called from the main thread, it will attempt to rebuild the cache.
|
||||
const uv_thread_t self = uv_thread_self();
|
||||
if (uv_thread_equal(&main_thread, &self)) {
|
||||
runtime_search_path_validate();
|
||||
}
|
||||
|
||||
lua_State *lstate = luaL_newstate();
|
||||
|
||||
// Add in the lua standard libraries
|
||||
luaL_openlibs(lstate);
|
||||
|
||||
// print
|
||||
lua_pushcfunction(lstate, &nlua_print);
|
||||
lua_setglobal(lstate, "print");
|
||||
|
||||
lua_pushinteger(lstate, 0);
|
||||
lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount");
|
||||
|
||||
// vim
|
||||
lua_newtable(lstate);
|
||||
|
||||
nlua_common_vim_init(lstate, true);
|
||||
|
||||
nlua_state_add_stdlib(lstate, true);
|
||||
|
||||
lua_setglobal(lstate, "vim");
|
||||
|
||||
nlua_common_package_init(lstate);
|
||||
|
||||
lua_getglobal(lstate, "vim");
|
||||
lua_getglobal(lstate, "package");
|
||||
lua_getfield(lstate, -1, "loaded");
|
||||
lua_getfield(lstate, -1, "vim.inspect");
|
||||
lua_setfield(lstate, -4, "inspect");
|
||||
lua_pop(lstate, 3);
|
||||
|
||||
lua_getglobal(lstate, "vim");
|
||||
lua_createtable(lstate, 0, 0);
|
||||
lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime);
|
||||
lua_setfield(lstate, -2, "nvim__get_runtime");
|
||||
lua_setfield(lstate, -2, "api");
|
||||
lua_pop(lstate, 1);
|
||||
|
||||
return lstate;
|
||||
}
|
||||
|
||||
void nlua_free_all_mem(void)
|
||||
{
|
||||
@ -505,26 +778,30 @@ void nlua_free_all_mem(void)
|
||||
return;
|
||||
}
|
||||
lua_State *lstate = global_lstate;
|
||||
nlua_common_free_all_mem(lstate);
|
||||
}
|
||||
|
||||
nlua_unref(lstate, nlua_nil_ref);
|
||||
nlua_unref(lstate, nlua_empty_dict_ref);
|
||||
static void nlua_common_free_all_mem(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
nlua_unref(lstate, nlua_get_nil_ref(lstate));
|
||||
nlua_unref(lstate, nlua_get_empty_dict_ref(lstate));
|
||||
|
||||
#ifdef NLUA_TRACK_REFS
|
||||
if (nlua_refcount) {
|
||||
fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
|
||||
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
|
||||
if (ref_state->ref_count) {
|
||||
fprintf(stderr, "%d lua references were leaked!", ref_state->ref_count);
|
||||
}
|
||||
|
||||
if (nlua_track_refs) {
|
||||
// in case there are leaked luarefs, leak the associated memory
|
||||
// to get LeakSanitizer stacktraces on exit
|
||||
pmap_destroy(handle_T)(&nlua_ref_markers);
|
||||
pmap_destroy(handle_T)(&ref_state->ref_markers);
|
||||
}
|
||||
#endif
|
||||
|
||||
nlua_refcount = 0;
|
||||
lua_close(lstate);
|
||||
}
|
||||
|
||||
static void nlua_print_event(void **argv)
|
||||
{
|
||||
char *str = argv[0];
|
||||
@ -602,9 +879,18 @@ static int nlua_print(lua_State *const lstate)
|
||||
#undef PRINT_ERROR
|
||||
ga_append(&msg_ga, NUL);
|
||||
|
||||
if (in_fast_callback) {
|
||||
lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
|
||||
bool is_thread = lua_toboolean(lstate, -1);
|
||||
lua_pop(lstate, 1);
|
||||
|
||||
if (is_thread) {
|
||||
loop_schedule_deferred(&main_loop,
|
||||
event_create(nlua_print_event, 2,
|
||||
msg_ga.ga_data,
|
||||
(intptr_t)msg_ga.ga_len));
|
||||
} else if (in_fast_callback) {
|
||||
multiqueue_put(main_loop.events, nlua_print_event,
|
||||
2, msg_ga.ga_data, msg_ga.ga_len);
|
||||
2, msg_ga.ga_data, (intptr_t)msg_ga.ga_len);
|
||||
} else {
|
||||
nlua_print_event((void *[]){ msg_ga.ga_data,
|
||||
(void *)(intptr_t)msg_ga.ga_len });
|
||||
@ -613,10 +899,12 @@ static int nlua_print(lua_State *const lstate)
|
||||
|
||||
nlua_print_error:
|
||||
ga_clear(&msg_ga);
|
||||
char *buff = xmalloc(IOSIZE);
|
||||
const char *fmt = _("E5114: Error while converting print argument #%i: %.*s");
|
||||
size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx,
|
||||
size_t len = (size_t)vim_snprintf(buff, IOSIZE, fmt, curargidx,
|
||||
(int)errmsg_len, errmsg);
|
||||
lua_pushlstring(lstate, (char *)IObuff, len);
|
||||
lua_pushlstring(lstate, buff, len);
|
||||
xfree(buff);
|
||||
return lua_error(lstate);
|
||||
}
|
||||
|
||||
@ -806,16 +1094,19 @@ static int nlua_getenv(lua_State *lstate)
|
||||
|
||||
|
||||
/// add the value to the registry
|
||||
/// The current implementation does not support calls from threads.
|
||||
LuaRef nlua_ref(lua_State *lstate, int index)
|
||||
{
|
||||
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
|
||||
|
||||
lua_pushvalue(lstate, index);
|
||||
LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
|
||||
if (ref > 0) {
|
||||
nlua_refcount++;
|
||||
ref_state->ref_count++;
|
||||
#ifdef NLUA_TRACK_REFS
|
||||
if (nlua_track_refs) {
|
||||
// dummy allocation to make LeakSanitizer track our luarefs
|
||||
pmap_put(handle_T)(&nlua_ref_markers, ref, xmalloc(3));
|
||||
pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -825,12 +1116,14 @@ LuaRef nlua_ref(lua_State *lstate, int index)
|
||||
/// remove the value from the registry
|
||||
void nlua_unref(lua_State *lstate, LuaRef ref)
|
||||
{
|
||||
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
|
||||
|
||||
if (ref > 0) {
|
||||
nlua_refcount--;
|
||||
ref_state->ref_count--;
|
||||
#ifdef NLUA_TRACK_REFS
|
||||
// NB: don't remove entry from map to track double-unref
|
||||
if (nlua_track_refs) {
|
||||
xfree(pmap_get(handle_T)(&nlua_ref_markers, ref));
|
||||
xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref));
|
||||
}
|
||||
#endif
|
||||
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
|
||||
@ -1391,6 +1684,13 @@ cleanup:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nlua_is_thread(lua_State *lstate)
|
||||
{
|
||||
lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Required functions for lua c functions as VimL callbacks
|
||||
|
||||
int nlua_CFunction_func_call(int argcount, typval_T *argvars, typval_T *rettv, void *state)
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
|
||||
#include "nvim/assert.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/ex_cmds_defs.h"
|
||||
@ -14,11 +15,6 @@
|
||||
// Generated by msgpack-gen.lua
|
||||
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
|
||||
|
||||
EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
|
||||
EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
|
||||
|
||||
EXTERN int nlua_refcount INIT(= 0);
|
||||
|
||||
#define set_api_error(s, err) \
|
||||
do { \
|
||||
Error *err_ = (err); \
|
||||
|
@ -471,8 +471,9 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
||||
}
|
||||
|
||||
|
||||
void nlua_state_add_stdlib(lua_State *const lstate)
|
||||
void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
|
||||
{
|
||||
if (!is_thread) {
|
||||
// stricmp
|
||||
lua_pushcfunction(lstate, &nlua_stricmp);
|
||||
lua_setfield(lstate, -2, "stricmp");
|
||||
@ -509,6 +510,11 @@ void nlua_state_add_stdlib(lua_State *const lstate)
|
||||
lua_pushcfunction(lstate, &nlua_setvar);
|
||||
lua_setfield(lstate, -2, "_setvar");
|
||||
|
||||
// vim.spell
|
||||
luaopen_spell(lstate);
|
||||
lua_setfield(lstate, -2, "spell");
|
||||
}
|
||||
|
||||
// vim.mpack
|
||||
luaopen_mpack(lstate);
|
||||
lua_pushvalue(lstate, -1);
|
||||
@ -526,10 +532,7 @@ void nlua_state_add_stdlib(lua_State *const lstate)
|
||||
lua_pushcfunction(lstate, &nlua_xdl_diff);
|
||||
lua_setfield(lstate, -2, "diff");
|
||||
|
||||
// vim.spell
|
||||
luaopen_spell(lstate);
|
||||
lua_setfield(lstate, -2, "spell");
|
||||
|
||||
// vim.json
|
||||
lua_cjson_new(lstate);
|
||||
lua_setfield(lstate, -2, "json");
|
||||
}
|
||||
|
@ -43,53 +43,6 @@ assert(vim.inspect)
|
||||
vim.filetype = package.loaded['vim.filetype']
|
||||
assert(vim.filetype)
|
||||
|
||||
local pathtrails = {}
|
||||
vim._so_trails = {}
|
||||
for s in (package.cpath..';'):gmatch('[^;]*;') do
|
||||
s = s:sub(1, -2) -- Strip trailing semicolon
|
||||
-- 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
|
||||
table.insert(vim._so_trails, pathtrail)
|
||||
end
|
||||
end
|
||||
|
||||
function vim._load_package(name)
|
||||
local basename = name:gsub('%.', '/')
|
||||
local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
|
||||
local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true})
|
||||
if #found > 0 then
|
||||
local f, err = loadfile(found[1])
|
||||
return f or error(err)
|
||||
end
|
||||
|
||||
local so_paths = {}
|
||||
for _,trail in ipairs(vim._so_trails) do
|
||||
local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
|
||||
table.insert(so_paths, path)
|
||||
end
|
||||
|
||||
found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true})
|
||||
if #found > 0 then
|
||||
-- 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
|
||||
-- b) replace all dots by underscores
|
||||
-- c) prepend "luaopen_"
|
||||
-- So "foo-bar.baz" should result in "luaopen_bar_baz"
|
||||
local dash = name:find("-", 1, true)
|
||||
local modname = dash and name:sub(dash + 1) or name
|
||||
local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
|
||||
return f or error(err)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Insert vim._load_package after the preloader at position 2
|
||||
table.insert(package.loaders, 2, vim._load_package)
|
||||
|
||||
-- These are for loading runtime modules lazily since they aren't available in
|
||||
-- the nvim binary as specified in executor.c
|
||||
setmetatable(vim, {
|
||||
|
@ -154,10 +154,10 @@ bool event_teardown(void)
|
||||
void early_init(mparm_T *paramp)
|
||||
{
|
||||
env_init();
|
||||
fs_init();
|
||||
eval_init(); // init global variables
|
||||
init_path(argv0 ? argv0 : "nvim");
|
||||
init_normal_cmds(); // Init the table of Normal mode commands.
|
||||
runtime_search_path_init();
|
||||
highlight_init();
|
||||
|
||||
#ifdef WIN32
|
||||
@ -230,6 +230,10 @@ int main(int argc, char **argv)
|
||||
// `argc` and `argv` are also copied, so that they can be changed.
|
||||
init_params(¶ms, argc, argv);
|
||||
|
||||
// Since os_open is called during the init_startuptime, we need to call
|
||||
// fs_init before it.
|
||||
fs_init();
|
||||
|
||||
init_startuptime(¶ms);
|
||||
|
||||
// Need to find "--clean" before actually parsing arguments.
|
||||
|
@ -40,8 +40,10 @@
|
||||
bool did_try_to_free = false; \
|
||||
uv_call_start: {} \
|
||||
uv_fs_t req; \
|
||||
fs_loop_lock(); \
|
||||
ret = func(&fs_loop, &req, __VA_ARGS__); \
|
||||
uv_fs_req_cleanup(&req); \
|
||||
fs_loop_unlock(); \
|
||||
if (ret == UV_ENOMEM && !did_try_to_free) { \
|
||||
try_to_free_memory(); \
|
||||
did_try_to_free = true; \
|
||||
@ -52,12 +54,24 @@ uv_call_start: {} \
|
||||
// Many fs functions from libuv return that value on success.
|
||||
static const int kLibuvSuccess = 0;
|
||||
static uv_loop_t fs_loop;
|
||||
static uv_mutex_t fs_loop_mutex;
|
||||
|
||||
|
||||
// Initialize the fs module
|
||||
void fs_init(void)
|
||||
{
|
||||
uv_loop_init(&fs_loop);
|
||||
uv_mutex_init_recursive(&fs_loop_mutex);
|
||||
}
|
||||
|
||||
void fs_loop_lock(void)
|
||||
{
|
||||
uv_mutex_lock(&fs_loop_mutex);
|
||||
}
|
||||
|
||||
void fs_loop_unlock(void)
|
||||
{
|
||||
uv_mutex_unlock(&fs_loop_mutex);
|
||||
}
|
||||
|
||||
|
||||
@ -98,9 +112,12 @@ bool os_isrealdir(const char *name)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
uv_fs_t request;
|
||||
fs_loop_lock();
|
||||
if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) {
|
||||
fs_loop_unlock();
|
||||
return false;
|
||||
}
|
||||
fs_loop_unlock();
|
||||
if (S_ISLNK(request.statbuf.st_mode)) {
|
||||
return false;
|
||||
} else {
|
||||
@ -738,7 +755,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
|
||||
return UV_EINVAL;
|
||||
}
|
||||
uv_fs_t request;
|
||||
fs_loop_lock();
|
||||
int result = uv_fs_stat(&fs_loop, &request, name, NULL);
|
||||
fs_loop_unlock();
|
||||
if (result == kLibuvSuccess) {
|
||||
*statbuf = request.statbuf;
|
||||
}
|
||||
@ -935,9 +954,11 @@ int os_mkdtemp(const char *template, char *path)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
uv_fs_t request;
|
||||
fs_loop_lock();
|
||||
int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL);
|
||||
fs_loop_unlock();
|
||||
if (result == kLibuvSuccess) {
|
||||
STRNCPY(path, request.path, TEMP_FILE_PATH_MAXLEN);
|
||||
xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN);
|
||||
}
|
||||
uv_fs_req_cleanup(&request);
|
||||
return result;
|
||||
@ -962,7 +983,9 @@ int os_rmdir(const char *path)
|
||||
bool os_scandir(Directory *dir, const char *path)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
fs_loop_lock();
|
||||
int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL);
|
||||
fs_loop_unlock();
|
||||
if (r < 0) {
|
||||
os_closedir(dir);
|
||||
}
|
||||
@ -1023,7 +1046,9 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info)
|
||||
return false;
|
||||
}
|
||||
uv_fs_t request;
|
||||
fs_loop_lock();
|
||||
bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess;
|
||||
fs_loop_unlock();
|
||||
if (ok) {
|
||||
file_info->stat = request.statbuf;
|
||||
}
|
||||
@ -1041,6 +1066,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
|
||||
{
|
||||
uv_fs_t request;
|
||||
memset(file_info, 0, sizeof(*file_info));
|
||||
fs_loop_lock();
|
||||
bool ok = uv_fs_fstat(&fs_loop,
|
||||
&request,
|
||||
file_descriptor,
|
||||
@ -1049,6 +1075,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
|
||||
file_info->stat = request.statbuf;
|
||||
}
|
||||
uv_fs_req_cleanup(&request);
|
||||
fs_loop_unlock();
|
||||
return ok;
|
||||
}
|
||||
|
||||
@ -1165,6 +1192,7 @@ char *os_realpath(const char *name, char *buf)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
uv_fs_t request;
|
||||
fs_loop_lock();
|
||||
int result = uv_fs_realpath(&fs_loop, &request, name, NULL);
|
||||
if (result == kLibuvSuccess) {
|
||||
if (buf == NULL) {
|
||||
@ -1173,6 +1201,7 @@ char *os_realpath(const char *name, char *buf)
|
||||
xstrlcpy(buf, request.ptr, MAXPATHL + 1);
|
||||
}
|
||||
uv_fs_req_cleanup(&request);
|
||||
fs_loop_unlock();
|
||||
return result == kLibuvSuccess ? buf : NULL;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,23 @@
|
||||
static bool runtime_search_path_valid = false;
|
||||
static int *runtime_search_path_ref = NULL;
|
||||
static RuntimeSearchPath runtime_search_path;
|
||||
static RuntimeSearchPath runtime_search_path_thread;
|
||||
static uv_mutex_t runtime_search_path_mutex;
|
||||
|
||||
void runtime_search_path_init(void)
|
||||
{
|
||||
uv_mutex_init(&runtime_search_path_mutex);
|
||||
}
|
||||
|
||||
void runtime_search_path_lock(void)
|
||||
{
|
||||
uv_mutex_lock(&runtime_search_path_mutex);
|
||||
}
|
||||
|
||||
void runtime_search_path_unlock(void)
|
||||
{
|
||||
uv_mutex_unlock(&runtime_search_path_mutex);
|
||||
}
|
||||
|
||||
/// ":runtime [what] {name}"
|
||||
void ex_runtime(exarg_T *eap)
|
||||
@ -172,6 +189,17 @@ RuntimeSearchPath runtime_search_path_get_cached(int *ref)
|
||||
return runtime_search_path;
|
||||
}
|
||||
|
||||
RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
|
||||
{
|
||||
RuntimeSearchPath dst = KV_INITIAL_VALUE;
|
||||
for (size_t j = 0; j < kv_size(src); j++) {
|
||||
SearchPathItem src_item = kv_A(src, j);
|
||||
kv_push(dst, ((SearchPathItem){ xstrdup(src_item.path), src_item.after, src_item.has_lua }));
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void runtime_search_path_unref(RuntimeSearchPath path, int *ref)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
@ -302,15 +330,33 @@ 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];
|
||||
|
||||
ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path);
|
||||
|
||||
runtime_search_path_unref(path, &ref);
|
||||
return rv;
|
||||
}
|
||||
|
||||
ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all)
|
||||
{
|
||||
runtime_search_path_lock();
|
||||
ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, runtime_search_path_thread);
|
||||
runtime_search_path_unlock();
|
||||
return rv;
|
||||
}
|
||||
|
||||
ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
|
||||
RuntimeSearchPath path)
|
||||
{
|
||||
ArrayOf(String) rv = ARRAY_DICT_INIT;
|
||||
size_t buf_len = MAXPATHL;
|
||||
char *buf = xmalloc(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;
|
||||
size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path);
|
||||
item->has_lua = (size < buf_len && os_isdir((char_u *)buf));
|
||||
}
|
||||
if (item->has_lua == kFalse) {
|
||||
continue;
|
||||
@ -320,9 +366,9 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
|
||||
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",
|
||||
size_t size = (size_t)snprintf(buf, buf_len, "%s/%s",
|
||||
item->path, pat_item.data.string.data);
|
||||
if (size < sizeof buf) {
|
||||
if (size < buf_len) {
|
||||
if (os_file_is_readable(buf)) {
|
||||
ADD(rv, STRING_OBJ(cstr_to_string(buf)));
|
||||
if (!all) {
|
||||
@ -333,9 +379,8 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
runtime_search_path_unref(path, &ref);
|
||||
xfree(buf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -569,6 +614,10 @@ void runtime_search_path_validate(void)
|
||||
runtime_search_path = runtime_search_path_build();
|
||||
runtime_search_path_valid = true;
|
||||
runtime_search_path_ref = NULL; // initially unowned
|
||||
runtime_search_path_lock();
|
||||
runtime_search_path_free(runtime_search_path_thread);
|
||||
runtime_search_path_thread = copy_runtime_search_path(runtime_search_path);
|
||||
runtime_search_path_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
403
test/functional/lua/thread_spec.lua
Normal file
403
test/functional/lua/thread_spec.lua
Normal file
@ -0,0 +1,403 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local assert_alive = helpers.assert_alive
|
||||
local clear = helpers.clear
|
||||
local feed = helpers.feed
|
||||
local eq = helpers.eq
|
||||
local exec_lua = helpers.exec_lua
|
||||
local next_msg = helpers.next_msg
|
||||
local NIL = helpers.NIL
|
||||
local pcall_err = helpers.pcall_err
|
||||
|
||||
describe('thread', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new(50, 10)
|
||||
screen:attach()
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {bold = true, foreground = Screen.colors.Blue1},
|
||||
[2] = {bold = true, reverse = true},
|
||||
[3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
|
||||
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
|
||||
[5] = {bold = true},
|
||||
})
|
||||
end)
|
||||
|
||||
it('entry func is executed in protected mode', function()
|
||||
local code = [[
|
||||
local thread = vim.loop.new_thread(function()
|
||||
error('Error in thread entry func')
|
||||
end)
|
||||
vim.loop.thread_join(thread)
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
screen:expect([[
|
||||
|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
{3:Error in luv thread:} |
|
||||
{3:[string "<nvim>"]:2: Error in thread entry func} |
|
||||
{4:Press ENTER or type command to continue}^ |
|
||||
]])
|
||||
feed('<cr>')
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('callback is executed in protected mode', function()
|
||||
local code = [[
|
||||
local thread = vim.loop.new_thread(function()
|
||||
local timer = vim.loop.new_timer()
|
||||
local function ontimeout()
|
||||
timer:stop()
|
||||
timer:close()
|
||||
error('Error in thread callback')
|
||||
end
|
||||
timer:start(10, 0, ontimeout)
|
||||
vim.loop.run()
|
||||
end)
|
||||
vim.loop.thread_join(thread)
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
screen:expect([[
|
||||
|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
{3:Error in luv callback, thread:} |
|
||||
{3:[string "<nvim>"]:6: Error in thread callback} |
|
||||
{4:Press ENTER or type command to continue}^ |
|
||||
]])
|
||||
feed('<cr>')
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
describe('print', function()
|
||||
it('work', function()
|
||||
local code = [[
|
||||
local thread = vim.loop.new_thread(function()
|
||||
print('print in thread')
|
||||
end)
|
||||
vim.loop.thread_join(thread)
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
print in thread |
|
||||
]])
|
||||
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('vim.*', function()
|
||||
before_each(function()
|
||||
clear()
|
||||
local code = [[
|
||||
Thread_Test = {}
|
||||
|
||||
Thread_Test.entry_func = function(async, entry_str, args)
|
||||
local decoded_args = vim.mpack.decode(args)
|
||||
assert(loadstring(entry_str))(async, decoded_args)
|
||||
end
|
||||
|
||||
function Thread_Test:do_test()
|
||||
local async
|
||||
local on_async = self.on_async
|
||||
async = vim.loop.new_async(function(ret)
|
||||
on_async(ret)
|
||||
async:close()
|
||||
end)
|
||||
local thread =
|
||||
vim.loop.new_thread(self.entry_func, async, self.entry_str, self.args)
|
||||
vim.loop.thread_join(thread)
|
||||
end
|
||||
|
||||
Thread_Test.new = function(entry, on_async, ...)
|
||||
self = {}
|
||||
setmetatable(self, {__index = Thread_Test})
|
||||
self.args = vim.mpack.encode({...})
|
||||
self.entry_str = string.dump(entry)
|
||||
self.on_async = on_async
|
||||
return self
|
||||
end
|
||||
]]
|
||||
exec_lua(code)
|
||||
end)
|
||||
|
||||
it('is_thread', function()
|
||||
local code = [[
|
||||
local entry = function(async)
|
||||
async:send(vim.is_thread())
|
||||
end
|
||||
local on_async = function(ret)
|
||||
vim.rpcnotify(1, 'result', ret)
|
||||
end
|
||||
local thread_test = Thread_Test.new(entry, on_async)
|
||||
thread_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result', {true}}, next_msg())
|
||||
end)
|
||||
|
||||
it('loop', function()
|
||||
local code = [[
|
||||
local entry = function(async)
|
||||
async:send(vim.loop.version())
|
||||
end
|
||||
local on_async = function(ret)
|
||||
vim.rpcnotify(1, ret)
|
||||
end
|
||||
local thread_test = Thread_Test.new(entry, on_async)
|
||||
thread_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
local msg = next_msg()
|
||||
eq(msg[1], 'notification')
|
||||
assert(tonumber(msg[2]) >= 72961)
|
||||
end)
|
||||
|
||||
it('mpack', function()
|
||||
local code = [[
|
||||
local entry = function(async)
|
||||
async:send(vim.mpack.encode({33, vim.NIL, 'text'}))
|
||||
end
|
||||
local on_async = function(ret)
|
||||
vim.rpcnotify(1, 'result', vim.mpack.decode(ret))
|
||||
end
|
||||
local thread_test = Thread_Test.new(entry, on_async)
|
||||
thread_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
|
||||
end)
|
||||
|
||||
it('json', function()
|
||||
local code = [[
|
||||
local entry = function(async)
|
||||
async:send(vim.json.encode({33, vim.NIL, 'text'}))
|
||||
end
|
||||
local on_async = function(ret)
|
||||
vim.rpcnotify(1, 'result', vim.json.decode(ret))
|
||||
end
|
||||
local thread_test = Thread_Test.new(entry, on_async)
|
||||
thread_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
|
||||
end)
|
||||
|
||||
it('diff', function()
|
||||
local code = [[
|
||||
local entry = function(async)
|
||||
async:send(vim.diff('Hello\n', 'Helli\n'))
|
||||
end
|
||||
local on_async = function(ret)
|
||||
vim.rpcnotify(1, 'result', ret)
|
||||
end
|
||||
local thread_test = Thread_Test.new(entry, on_async)
|
||||
thread_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result',
|
||||
{table.concat({
|
||||
'@@ -1 +1 @@',
|
||||
'-Hello',
|
||||
'+Helli',
|
||||
''
|
||||
}, '\n')}},
|
||||
next_msg())
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('threadpool', function()
|
||||
before_each(clear)
|
||||
|
||||
it('is_thread', function()
|
||||
eq(false, exec_lua('return vim.is_thread()'))
|
||||
|
||||
local code = [[
|
||||
local work_fn = function()
|
||||
return vim.is_thread()
|
||||
end
|
||||
local after_work_fn = function(ret)
|
||||
vim.rpcnotify(1, 'result', ret)
|
||||
end
|
||||
local work = vim.loop.new_work(work_fn, after_work_fn)
|
||||
work:queue()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result', {true}}, next_msg())
|
||||
end)
|
||||
|
||||
it('with invalid argument', function()
|
||||
local code = [[
|
||||
local work = vim.loop.new_thread(function() end, function() end)
|
||||
work:queue({})
|
||||
]]
|
||||
|
||||
eq([[Error executing lua: [string "<nvim>"]:0: Error: thread arg not support type 'function' at 1]],
|
||||
pcall_err(exec_lua, code))
|
||||
end)
|
||||
|
||||
it('with invalid return value', function()
|
||||
local screen = Screen.new(50, 10)
|
||||
screen:attach()
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {bold = true, foreground = Screen.colors.Blue1},
|
||||
[2] = {bold = true, reverse = true},
|
||||
[3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
|
||||
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
|
||||
[5] = {bold = true},
|
||||
})
|
||||
|
||||
local code = [[
|
||||
local work = vim.loop.new_work(function() return {} end, function() end)
|
||||
work:queue()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
screen:expect([[
|
||||
|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
{3:Error in luv thread:} |
|
||||
{3:Error: thread arg not support type 'table' at 1} |
|
||||
{4:Press ENTER or type command to continue}^ |
|
||||
]])
|
||||
end)
|
||||
|
||||
describe('vim.*', function()
|
||||
before_each(function()
|
||||
clear()
|
||||
local code = [[
|
||||
Threadpool_Test = {}
|
||||
|
||||
Threadpool_Test.work_fn = function(work_fn_str, args)
|
||||
local decoded_args = vim.mpack.decode(args)
|
||||
return assert(loadstring(work_fn_str))(decoded_args)
|
||||
end
|
||||
|
||||
function Threadpool_Test:do_test()
|
||||
local work =
|
||||
vim.loop.new_work(self.work_fn, self.after_work)
|
||||
work:queue(self.work_fn_str, self.args)
|
||||
end
|
||||
|
||||
Threadpool_Test.new = function(work_fn, after_work, ...)
|
||||
self = {}
|
||||
setmetatable(self, {__index = Threadpool_Test})
|
||||
self.args = vim.mpack.encode({...})
|
||||
self.work_fn_str = string.dump(work_fn)
|
||||
self.after_work = after_work
|
||||
return self
|
||||
end
|
||||
]]
|
||||
exec_lua(code)
|
||||
end)
|
||||
|
||||
it('loop', function()
|
||||
local code = [[
|
||||
local work_fn = function()
|
||||
return vim.loop.version()
|
||||
end
|
||||
local after_work_fn = function(ret)
|
||||
vim.rpcnotify(1, ret)
|
||||
end
|
||||
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
|
||||
threadpool_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
local msg = next_msg()
|
||||
eq(msg[1], 'notification')
|
||||
assert(tonumber(msg[2]) >= 72961)
|
||||
end)
|
||||
|
||||
it('mpack', function()
|
||||
local code = [[
|
||||
local work_fn = function()
|
||||
local var = vim.mpack.encode({33, vim.NIL, 'text'})
|
||||
return var
|
||||
end
|
||||
local after_work_fn = function(ret)
|
||||
vim.rpcnotify(1, 'result', vim.mpack.decode(ret))
|
||||
end
|
||||
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
|
||||
threadpool_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
|
||||
end)
|
||||
|
||||
it('json', function()
|
||||
local code = [[
|
||||
local work_fn = function()
|
||||
local var = vim.json.encode({33, vim.NIL, 'text'})
|
||||
return var
|
||||
end
|
||||
local after_work_fn = function(ret)
|
||||
vim.rpcnotify(1, 'result', vim.json.decode(ret))
|
||||
end
|
||||
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
|
||||
threadpool_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg())
|
||||
end)
|
||||
|
||||
it('work', function()
|
||||
local code = [[
|
||||
local work_fn = function()
|
||||
return vim.diff('Hello\n', 'Helli\n')
|
||||
end
|
||||
local after_work_fn = function(ret)
|
||||
vim.rpcnotify(1, 'result', ret)
|
||||
end
|
||||
local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn)
|
||||
threadpool_test:do_test()
|
||||
]]
|
||||
exec_lua(code)
|
||||
|
||||
eq({'notification', 'result',
|
||||
{table.concat({
|
||||
'@@ -1 +1 @@',
|
||||
'-Hello',
|
||||
'+Helli',
|
||||
''
|
||||
}, '\n')}},
|
||||
next_msg())
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user