mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00

Adds a `name` key to the opts dict passed to Lua command callbacks created using `nvim_create_user_command()`. This is useful for when multiple commands use the same callback. Note that this kind of behavior is not as strange as one might think, even some internal Neovim commands reuse the same internal C function, differing their behavior by checking the command name. `substitute`, `smagic` and `snomagic` are examples of that. This will also be useful for generalized Lua command preview functions that can preview a wide range of commands, in which case knowing the command name is necessary for the preview function to actually be able to execute the command that it's supposed to preview.
2183 lines
57 KiB
C
2183 lines
57 KiB
C
// This is an open source non-commercial project. Dear PVS-Studio, please check
|
|
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
|
|
#include <lauxlib.h>
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
#include <tree_sitter/api.h>
|
|
|
|
#include "luv/luv.h"
|
|
#include "nvim/api/extmark.h"
|
|
#include "nvim/api/private/defs.h"
|
|
#include "nvim/api/private/helpers.h"
|
|
#include "nvim/api/vim.h"
|
|
#include "nvim/ascii.h"
|
|
#include "nvim/assert.h"
|
|
#include "nvim/buffer_defs.h"
|
|
#include "nvim/change.h"
|
|
#include "nvim/cursor.h"
|
|
#include "nvim/drawscreen.h"
|
|
#include "nvim/eval.h"
|
|
#include "nvim/eval/funcs.h"
|
|
#include "nvim/eval/typval.h"
|
|
#include "nvim/eval/userfunc.h"
|
|
#include "nvim/event/loop.h"
|
|
#include "nvim/event/time.h"
|
|
#include "nvim/ex_cmds.h"
|
|
#include "nvim/ex_getln.h"
|
|
#include "nvim/extmark.h"
|
|
#include "nvim/func_attr.h"
|
|
#include "nvim/garray.h"
|
|
#include "nvim/getchar.h"
|
|
#include "nvim/lua/converter.h"
|
|
#include "nvim/lua/executor.h"
|
|
#include "nvim/lua/stdlib.h"
|
|
#include "nvim/lua/treesitter.h"
|
|
#include "nvim/macros.h"
|
|
#include "nvim/map.h"
|
|
#include "nvim/memline.h"
|
|
#include "nvim/message.h"
|
|
#include "nvim/msgpack_rpc/channel.h"
|
|
#include "nvim/os/os.h"
|
|
#include "nvim/profile.h"
|
|
#include "nvim/runtime.h"
|
|
#include "nvim/screen.h"
|
|
#include "nvim/ui.h"
|
|
#include "nvim/ui_compositor.h"
|
|
#include "nvim/undo.h"
|
|
#include "nvim/usercmd.h"
|
|
#include "nvim/version.h"
|
|
#include "nvim/vim.h"
|
|
#include "nvim/window.h"
|
|
|
|
static int in_fast_callback = 0;
|
|
|
|
// Initialized in nlua_init().
|
|
static lua_State *global_lstate = NULL;
|
|
|
|
static LuaRef require_ref = LUA_REFNIL;
|
|
|
|
static uv_thread_t main_thread;
|
|
|
|
typedef struct {
|
|
Error err;
|
|
String lua_err_str;
|
|
} LuaError;
|
|
|
|
typedef struct {
|
|
char *name;
|
|
const uint8_t *data;
|
|
size_t size;
|
|
} ModuleDef;
|
|
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "lua/executor.c.generated.h"
|
|
# include "lua/vim_module.generated.h"
|
|
#endif
|
|
|
|
#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \
|
|
for (int i = 0; i < argcount; i++) { \
|
|
if (args[i].v_type == VAR_UNKNOWN) { \
|
|
lua_pushnil(lstate); \
|
|
} else { \
|
|
nlua_push_typval(lstate, &args[i], special); \
|
|
} \
|
|
}
|
|
|
|
#if __has_feature(address_sanitizer)
|
|
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.
|
|
/// @param[in] msg Message base, must contain one `%s`.
|
|
static void nlua_error(lua_State *const lstate, const char *const msg)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
size_t len;
|
|
const char *str = NULL;
|
|
|
|
if (luaL_getmetafield(lstate, -1, "__tostring")) {
|
|
if (lua_isfunction(lstate, -1) && luaL_callmeta(lstate, -2, "__tostring")) {
|
|
// call __tostring, convert the result and pop result.
|
|
str = lua_tolstring(lstate, -1, &len);
|
|
lua_pop(lstate, 1);
|
|
}
|
|
// pop __tostring.
|
|
lua_pop(lstate, 1);
|
|
}
|
|
|
|
if (!str) {
|
|
// defer to lua default conversion, this will render tables as [NULL].
|
|
str = lua_tolstring(lstate, -1, &len);
|
|
}
|
|
|
|
msg_ext_set_kind("lua_error");
|
|
semsg_multiline(msg, (int)len, str);
|
|
|
|
lua_pop(lstate, 1);
|
|
}
|
|
|
|
/// Like lua_pcall, but use debug.traceback as errfunc.
|
|
///
|
|
/// @param lstate Lua interpreter state
|
|
/// @param[in] nargs Number of arguments expected by the function being called.
|
|
/// @param[in] nresults Number of results the function returns.
|
|
static int nlua_pcall(lua_State *lstate, int nargs, int nresults)
|
|
{
|
|
lua_getglobal(lstate, "debug");
|
|
lua_getfield(lstate, -1, "traceback");
|
|
lua_remove(lstate, -2);
|
|
lua_insert(lstate, -2 - nargs);
|
|
int status = lua_pcall(lstate, nargs, nresults, -2 - nargs);
|
|
if (status) {
|
|
lua_remove(lstate, -2);
|
|
} else {
|
|
lua_remove(lstate, -1 - nresults);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/// Gets the version of the current Nvim build.
|
|
///
|
|
/// @param lstate Lua interpreter state.
|
|
static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
Dictionary version = version_dict();
|
|
nlua_push_Dictionary(lstate, version, true);
|
|
api_free_dictionary(version);
|
|
return 1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
int retval;
|
|
|
|
// luv callbacks might be executed at any os_breakcheck/line_breakcheck
|
|
// call, so using the API directly here is not safe.
|
|
in_fast_callback++;
|
|
|
|
int top = lua_gettop(lstate);
|
|
int status = nlua_pcall(lstate, nargs, nresult);
|
|
if (status) {
|
|
if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
|
|
// consider out of memory errors unrecoverable, just like xmalloc()
|
|
mch_errmsg(e_outofmem);
|
|
mch_errmsg("\n");
|
|
preserve_exit();
|
|
}
|
|
const char *error = lua_tostring(lstate, -1);
|
|
|
|
multiqueue_put(main_loop.events, nlua_luv_error_event,
|
|
2, xstrdup(error), (intptr_t)kCallback);
|
|
lua_pop(lstate, 1); // error message
|
|
retval = -status;
|
|
} else { // LUA_OK
|
|
if (nresult == LUA_MULTRET) {
|
|
nresult = lua_gettop(lstate) - top + nargs + 1;
|
|
}
|
|
retval = nresult;
|
|
}
|
|
|
|
in_fast_callback--;
|
|
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 MSWIN
|
|
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];
|
|
lua_State *const lstate = global_lstate;
|
|
nlua_pushref(lstate, cb);
|
|
nlua_unref_global(lstate, cb);
|
|
if (nlua_pcall(lstate, 0, 0)) {
|
|
nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
|
|
}
|
|
}
|
|
|
|
/// Schedule Lua callback on main loop's event queue
|
|
///
|
|
/// @param lstate Lua interpreter state.
|
|
static int nlua_schedule(lua_State *const lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
if (lua_type(lstate, 1) != LUA_TFUNCTION) {
|
|
lua_pushliteral(lstate, "vim.schedule: expected function");
|
|
return lua_error(lstate);
|
|
}
|
|
|
|
LuaRef cb = nlua_ref_global(lstate, 1);
|
|
|
|
multiqueue_put(main_loop.events, nlua_schedule_event,
|
|
1, (void *)(ptrdiff_t)cb);
|
|
return 0;
|
|
}
|
|
|
|
// Dummy timer callback. Used by f_wait().
|
|
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
|
|
{}
|
|
|
|
// Dummy timer close callback. Used by f_wait().
|
|
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
|
|
{
|
|
xfree(tw);
|
|
}
|
|
|
|
static bool nlua_wait_condition(lua_State *lstate, int *status, bool *callback_result)
|
|
{
|
|
lua_pushvalue(lstate, 2);
|
|
*status = nlua_pcall(lstate, 0, 1);
|
|
if (*status) {
|
|
return true; // break on error, but keep error on stack
|
|
}
|
|
*callback_result = lua_toboolean(lstate, -1);
|
|
lua_pop(lstate, 1);
|
|
return *callback_result; // break if true
|
|
}
|
|
|
|
/// "vim.wait(timeout, condition[, interval])" function
|
|
static int nlua_wait(lua_State *lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
intptr_t timeout = luaL_checkinteger(lstate, 1);
|
|
if (timeout < 0) {
|
|
return luaL_error(lstate, "timeout must be >= 0");
|
|
}
|
|
|
|
int lua_top = lua_gettop(lstate);
|
|
|
|
// Check if condition can be called.
|
|
bool is_function = false;
|
|
if (lua_top >= 2 && !lua_isnil(lstate, 2)) {
|
|
is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
|
|
|
|
// Check if condition is callable table
|
|
if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
|
|
is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
|
|
lua_pop(lstate, 1);
|
|
}
|
|
|
|
if (!is_function) {
|
|
lua_pushliteral(lstate,
|
|
"vim.wait: if passed, condition must be a function");
|
|
return lua_error(lstate);
|
|
}
|
|
}
|
|
|
|
intptr_t interval = 200;
|
|
if (lua_top >= 3 && !lua_isnil(lstate, 3)) {
|
|
interval = luaL_checkinteger(lstate, 3);
|
|
if (interval < 0) {
|
|
return luaL_error(lstate, "interval must be >= 0");
|
|
}
|
|
}
|
|
|
|
bool fast_only = false;
|
|
if (lua_top >= 4) {
|
|
fast_only = lua_toboolean(lstate, 4);
|
|
}
|
|
|
|
MultiQueue *loop_events = fast_only || in_fast_callback > 0
|
|
? main_loop.fast_events : main_loop.events;
|
|
|
|
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
|
|
|
|
// Start dummy timer.
|
|
time_watcher_init(&main_loop, tw, NULL);
|
|
tw->events = loop_events;
|
|
tw->blockable = true;
|
|
time_watcher_start(tw,
|
|
dummy_timer_due_cb,
|
|
(uint64_t)interval,
|
|
(uint64_t)interval);
|
|
|
|
int pcall_status = 0;
|
|
bool callback_result = false;
|
|
|
|
LOOP_PROCESS_EVENTS_UNTIL(&main_loop,
|
|
loop_events,
|
|
(int)timeout,
|
|
got_int || (is_function ? nlua_wait_condition(lstate,
|
|
&pcall_status,
|
|
&callback_result) : false));
|
|
|
|
// Stop dummy timer
|
|
time_watcher_stop(tw);
|
|
time_watcher_close(tw, dummy_timer_close_cb);
|
|
|
|
if (pcall_status) {
|
|
return lua_error(lstate);
|
|
} else if (callback_result) {
|
|
lua_pushboolean(lstate, 1);
|
|
lua_pushnil(lstate);
|
|
} else if (got_int) {
|
|
got_int = false;
|
|
vgetc();
|
|
lua_pushboolean(lstate, 0);
|
|
lua_pushinteger(lstate, -2);
|
|
} else {
|
|
lua_pushboolean(lstate, 0);
|
|
lua_pushinteger(lstate, -1);
|
|
}
|
|
|
|
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));
|
|
CLEAR_POINTER(ref_state);
|
|
ref_state->nil_ref = LUA_NOREF;
|
|
ref_state->empty_dict_ref = LUA_NOREF;
|
|
if (!is_thread) {
|
|
nlua_global_refs = ref_state;
|
|
}
|
|
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;
|
|
}
|
|
|
|
int nlua_get_global_ref_count(void)
|
|
{
|
|
return nlua_global_refs->ref_count;
|
|
}
|
|
|
|
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, ref_state, -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, ref_state, -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 int nlua_module_preloader(lua_State *lstate)
|
|
{
|
|
size_t i = (size_t)lua_tointeger(lstate, lua_upvalueindex(1));
|
|
ModuleDef def = builtin_modules[i];
|
|
char name[256];
|
|
name[0] = '@';
|
|
size_t off = xstrlcpy(name + 1, def.name, (sizeof name) - 2);
|
|
strchrsub(name + 1, '.', '/');
|
|
xstrlcpy(name + 1 + off, ".lua", (sizeof name) - 2 - off);
|
|
|
|
if (luaL_loadbuffer(lstate, (const char *)def.data, def.size - 1, name)) {
|
|
return lua_error(lstate);
|
|
}
|
|
|
|
lua_call(lstate, 0, 1); // propagates error to caller
|
|
return 1;
|
|
}
|
|
|
|
static bool nlua_init_packages(lua_State *lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
// put builtin packages in preload
|
|
lua_getglobal(lstate, "package"); // [package]
|
|
lua_getfield(lstate, -1, "preload"); // [package, preload]
|
|
for (size_t i = 0; i < ARRAY_SIZE(builtin_modules); i++) {
|
|
ModuleDef def = builtin_modules[i];
|
|
lua_pushinteger(lstate, (long)i); // [package, preload, i]
|
|
lua_pushcclosure(lstate, nlua_module_preloader, 1); // [package, preload, cclosure]
|
|
lua_setfield(lstate, -2, def.name); // [package, preload]
|
|
|
|
if (nlua_disable_preload && strequal(def.name, "vim.inspect")) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
lua_pop(lstate, 2); // []
|
|
|
|
lua_getglobal(lstate, "require");
|
|
lua_pushstring(lstate, "vim._init_packages");
|
|
if (nlua_pcall(lstate, 1, 0)) {
|
|
mch_errmsg(lua_tostring(lstate, -1));
|
|
mch_errmsg("\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// "vim.ui_attach(ns_id, {ext_foo=true}, cb)" function
|
|
static int nlua_ui_attach(lua_State *lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
uint32_t ns_id = (uint32_t)luaL_checkinteger(lstate, 1);
|
|
|
|
if (!ns_initialized(ns_id)) {
|
|
return luaL_error(lstate, "invalid ns_id");
|
|
}
|
|
if (!lua_istable(lstate, 2)) {
|
|
return luaL_error(lstate, "ext_widgets must be a table");
|
|
}
|
|
if (!lua_isfunction(lstate, 3)) {
|
|
return luaL_error(lstate, "callback must be a Lua function");
|
|
}
|
|
|
|
bool ext_widgets[kUIGlobalCount] = { false };
|
|
bool tbl_has_true_val = false;
|
|
|
|
lua_pushvalue(lstate, 2);
|
|
lua_pushnil(lstate);
|
|
while (lua_next(lstate, -2)) {
|
|
// [dict, key, val]
|
|
size_t len;
|
|
const char *s = lua_tolstring(lstate, -2, &len);
|
|
bool val = lua_toboolean(lstate, -1);
|
|
|
|
for (size_t i = 0; i < kUIGlobalCount; i++) {
|
|
if (strequal(s, ui_ext_names[i])) {
|
|
if (val) {
|
|
tbl_has_true_val = true;
|
|
}
|
|
ext_widgets[i] = val;
|
|
goto ok;
|
|
}
|
|
}
|
|
|
|
return luaL_error(lstate, "Unexpected key: %s", s);
|
|
ok:
|
|
lua_pop(lstate, 1);
|
|
}
|
|
|
|
if (!tbl_has_true_val) {
|
|
return luaL_error(lstate, "ext_widgets table must contain at least one 'true' value");
|
|
}
|
|
|
|
LuaRef ui_event_cb = nlua_ref_global(lstate, 3);
|
|
ui_comp_add_cb(ns_id, ui_event_cb, ext_widgets);
|
|
return 0;
|
|
}
|
|
|
|
/// "vim.ui_detach(ns_id)" function
|
|
static int nlua_ui_detach(lua_State *lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
uint32_t ns_id = (uint32_t)luaL_checkinteger(lstate, 1);
|
|
|
|
if (!ns_initialized(ns_id)) {
|
|
return luaL_error(lstate, "invalid ns_id");
|
|
}
|
|
|
|
ui_comp_remove_cb(ns_id);
|
|
return 0;
|
|
}
|
|
|
|
/// Initialize lua interpreter state
|
|
///
|
|
/// Called by lua interpreter itself to initialize state.
|
|
static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
// print
|
|
lua_pushcfunction(lstate, &nlua_print);
|
|
lua_setglobal(lstate, "print");
|
|
|
|
// debug.debug
|
|
lua_getglobal(lstate, "debug");
|
|
lua_pushcfunction(lstate, &nlua_debug);
|
|
lua_setfield(lstate, -2, "debug");
|
|
lua_pop(lstate, 1);
|
|
|
|
#ifdef MSWIN
|
|
// os.getenv
|
|
lua_getglobal(lstate, "os");
|
|
lua_pushcfunction(lstate, &nlua_getenv);
|
|
lua_setfield(lstate, -2, "getenv");
|
|
lua_pop(lstate, 1);
|
|
#endif
|
|
|
|
// vim
|
|
lua_newtable(lstate);
|
|
|
|
// vim.api
|
|
nlua_add_api_functions(lstate);
|
|
|
|
// vim.types, vim.type_idx, vim.val_idx
|
|
nlua_init_types(lstate);
|
|
|
|
// neovim version
|
|
lua_pushcfunction(lstate, &nlua_nvim_version);
|
|
lua_setfield(lstate, -2, "version");
|
|
|
|
// schedule
|
|
lua_pushcfunction(lstate, &nlua_schedule);
|
|
lua_setfield(lstate, -2, "schedule");
|
|
|
|
// in_fast_event
|
|
lua_pushcfunction(lstate, &nlua_in_fast_event);
|
|
lua_setfield(lstate, -2, "in_fast_event");
|
|
|
|
// call
|
|
lua_pushcfunction(lstate, &nlua_call);
|
|
lua_setfield(lstate, -2, "call");
|
|
|
|
// rpcrequest
|
|
lua_pushcfunction(lstate, &nlua_rpcrequest);
|
|
lua_setfield(lstate, -2, "rpcrequest");
|
|
|
|
// rpcnotify
|
|
lua_pushcfunction(lstate, &nlua_rpcnotify);
|
|
lua_setfield(lstate, -2, "rpcnotify");
|
|
|
|
// wait
|
|
lua_pushcfunction(lstate, &nlua_wait);
|
|
lua_setfield(lstate, -2, "wait");
|
|
|
|
// ui_attach
|
|
lua_pushcfunction(lstate, &nlua_ui_attach);
|
|
lua_setfield(lstate, -2, "ui_attach");
|
|
|
|
// ui_detach
|
|
lua_pushcfunction(lstate, &nlua_ui_detach);
|
|
lua_setfield(lstate, -2, "ui_detach");
|
|
|
|
nlua_common_vim_init(lstate, false);
|
|
|
|
// patch require() (only for --startuptime)
|
|
if (time_fd != NULL) {
|
|
lua_getglobal(lstate, "require");
|
|
// Must do this after nlua_common_vim_init where nlua_global_refs is initialized.
|
|
require_ref = nlua_ref_global(lstate, -1);
|
|
lua_pop(lstate, 1);
|
|
lua_pushcfunction(lstate, &nlua_require);
|
|
lua_setglobal(lstate, "require");
|
|
}
|
|
|
|
// internal vim._treesitter... API
|
|
nlua_add_treesitter(lstate);
|
|
|
|
nlua_state_add_stdlib(lstate, false);
|
|
|
|
lua_setglobal(lstate, "vim");
|
|
|
|
if (!nlua_init_packages(lstate)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Initialize global lua interpreter
|
|
///
|
|
/// Crashes Nvim if initialization fails.
|
|
void nlua_init(void)
|
|
{
|
|
#ifdef NLUA_TRACK_REFS
|
|
const char *env = os_getenv("NVIM_LUA_NOTRACK");
|
|
if (!env || !*env) {
|
|
nlua_track_refs = true;
|
|
}
|
|
#endif
|
|
|
|
lua_State *lstate = luaL_newstate();
|
|
if (lstate == NULL) {
|
|
mch_errmsg(_("E970: Failed to initialize lua interpreter\n"));
|
|
os_exit(1);
|
|
}
|
|
luaL_openlibs(lstate);
|
|
if (!nlua_state_init(lstate)) {
|
|
mch_errmsg(_("E970: Failed to initialize builtin lua modules\n"));
|
|
os_exit(1);
|
|
}
|
|
|
|
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_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_setglobal(lstate, "vim");
|
|
|
|
nlua_init_packages(lstate);
|
|
|
|
lua_getglobal(lstate, "package");
|
|
lua_getfield(lstate, -1, "loaded");
|
|
lua_getglobal(lstate, "vim");
|
|
lua_setfield(lstate, -2, "vim");
|
|
lua_pop(lstate, 2);
|
|
|
|
return lstate;
|
|
}
|
|
|
|
void nlua_free_all_mem(void)
|
|
{
|
|
if (!global_lstate) {
|
|
return;
|
|
}
|
|
lua_State *lstate = global_lstate;
|
|
nlua_unref_global(lstate, require_ref);
|
|
nlua_common_free_all_mem(lstate);
|
|
}
|
|
|
|
static void nlua_common_free_all_mem(lua_State *lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
|
|
nlua_unref(lstate, ref_state, ref_state->nil_ref);
|
|
nlua_unref(lstate, ref_state, ref_state->empty_dict_ref);
|
|
|
|
#ifdef NLUA_TRACK_REFS
|
|
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)(&ref_state->ref_markers);
|
|
}
|
|
#endif
|
|
|
|
lua_close(lstate);
|
|
}
|
|
static void nlua_print_event(void **argv)
|
|
{
|
|
char *str = argv[0];
|
|
const size_t len = (size_t)(intptr_t)argv[1] - 1; // exclude final NUL
|
|
|
|
for (size_t i = 0; i < len;) {
|
|
if (got_int) {
|
|
break;
|
|
}
|
|
const size_t start = i;
|
|
while (i < len) {
|
|
switch (str[i]) {
|
|
case NUL:
|
|
str[i] = NL;
|
|
i++;
|
|
continue;
|
|
case NL:
|
|
// TODO(bfredl): use proper multiline msg? Probably should implement
|
|
// print() in lua in terms of nvim_message(), when it is available.
|
|
str[i] = NUL;
|
|
i++;
|
|
break;
|
|
default:
|
|
i++;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
msg(str + start);
|
|
}
|
|
if (len && str[len - 1] == NUL) { // Last was newline
|
|
msg("");
|
|
}
|
|
xfree(str);
|
|
}
|
|
|
|
/// Print as a Vim message
|
|
///
|
|
/// @param lstate Lua interpreter state.
|
|
static int nlua_print(lua_State *const lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
#define PRINT_ERROR(msg) \
|
|
do { \
|
|
errmsg = msg; \
|
|
errmsg_len = sizeof(msg) - 1; \
|
|
goto nlua_print_error; \
|
|
} while (0)
|
|
const int nargs = lua_gettop(lstate);
|
|
lua_getglobal(lstate, "tostring");
|
|
const char *errmsg = NULL;
|
|
size_t errmsg_len = 0;
|
|
garray_T msg_ga;
|
|
ga_init(&msg_ga, 1, 80);
|
|
int curargidx = 1;
|
|
for (; curargidx <= nargs; curargidx++) {
|
|
lua_pushvalue(lstate, -1); // tostring
|
|
lua_pushvalue(lstate, curargidx); // arg
|
|
// Do not use nlua_pcall here to avoid duplicate stack trace information
|
|
if (lua_pcall(lstate, 1, 1, 0)) {
|
|
errmsg = lua_tolstring(lstate, -1, &errmsg_len);
|
|
goto nlua_print_error;
|
|
}
|
|
size_t len;
|
|
const char *const s = lua_tolstring(lstate, -1, &len);
|
|
if (s == NULL) {
|
|
PRINT_ERROR("<Unknown error: lua_tolstring returned NULL for tostring result>");
|
|
}
|
|
ga_concat_len(&msg_ga, s, len);
|
|
if (curargidx < nargs) {
|
|
ga_append(&msg_ga, ' ');
|
|
}
|
|
lua_pop(lstate, 1);
|
|
}
|
|
#undef PRINT_ERROR
|
|
ga_append(&msg_ga, NUL);
|
|
|
|
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, (intptr_t)msg_ga.ga_len);
|
|
} else {
|
|
nlua_print_event((void *[]){ msg_ga.ga_data,
|
|
(void *)(intptr_t)msg_ga.ga_len });
|
|
}
|
|
return 0;
|
|
|
|
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(buff, IOSIZE, fmt, curargidx,
|
|
(int)errmsg_len, errmsg);
|
|
lua_pushlstring(lstate, buff, len);
|
|
xfree(buff);
|
|
return lua_error(lstate);
|
|
}
|
|
|
|
/// require() for --startuptime
|
|
///
|
|
/// @param lstate Lua interpreter state.
|
|
static int nlua_require(lua_State *const lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
const char *name = luaL_checkstring(lstate, 1);
|
|
lua_settop(lstate, 1);
|
|
// [ name ]
|
|
|
|
// try cached module from package.loaded first
|
|
lua_getfield(lstate, LUA_REGISTRYINDEX, "_LOADED");
|
|
lua_getfield(lstate, 2, name);
|
|
// [ name package.loaded module ]
|
|
if (lua_toboolean(lstate, -1)) {
|
|
return 1;
|
|
}
|
|
lua_pop(lstate, 2);
|
|
// [ name ]
|
|
|
|
// push original require below the module name
|
|
nlua_pushref(lstate, require_ref);
|
|
lua_insert(lstate, 1);
|
|
// [ require name ]
|
|
|
|
if (time_fd == NULL) {
|
|
// after log file was closed, try to restore
|
|
// global require to the original function...
|
|
lua_getglobal(lstate, "require");
|
|
// ...only if it's still referencing this wrapper,
|
|
// to not overwrite it in case someone happened to
|
|
// patch it in the meantime...
|
|
if (lua_iscfunction(lstate, -1) && lua_tocfunction(lstate, -1) == nlua_require) {
|
|
lua_pushvalue(lstate, 1);
|
|
lua_setglobal(lstate, "require");
|
|
}
|
|
lua_pop(lstate, 1);
|
|
|
|
// ...and then call require directly.
|
|
lua_call(lstate, 1, 1);
|
|
return 1;
|
|
}
|
|
|
|
proftime_T rel_time;
|
|
proftime_T start_time;
|
|
time_push(&rel_time, &start_time);
|
|
int status = lua_pcall(lstate, 1, 1, 0);
|
|
if (status == 0) {
|
|
vim_snprintf((char *)IObuff, IOSIZE, "require('%s')", name);
|
|
time_msg((char *)IObuff, &start_time);
|
|
}
|
|
time_pop(rel_time);
|
|
|
|
return status == 0 ? 1 : lua_error(lstate);
|
|
}
|
|
|
|
/// debug.debug: interaction with user while debugging.
|
|
///
|
|
/// @param lstate Lua interpreter state.
|
|
static int nlua_debug(lua_State *lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
const typval_T input_args[] = {
|
|
{
|
|
.v_lock = VAR_FIXED,
|
|
.v_type = VAR_STRING,
|
|
.vval.v_string = "lua_debug> ",
|
|
},
|
|
{
|
|
.v_type = VAR_UNKNOWN,
|
|
},
|
|
};
|
|
for (;;) {
|
|
lua_settop(lstate, 0);
|
|
typval_T input;
|
|
get_user_input(input_args, &input, false, false);
|
|
msg_putchar('\n'); // Avoid outputting on input line.
|
|
if (input.v_type != VAR_STRING
|
|
|| input.vval.v_string == NULL
|
|
|| *input.vval.v_string == NUL
|
|
|| strcmp(input.vval.v_string, "cont") == 0) {
|
|
tv_clear(&input);
|
|
return 0;
|
|
}
|
|
if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string,
|
|
strlen(input.vval.v_string), "=(debug command)")) {
|
|
nlua_error(lstate, _("E5115: Error while loading debug string: %.*s"));
|
|
} else if (nlua_pcall(lstate, 0, 0)) {
|
|
nlua_error(lstate, _("E5116: Error while calling debug string: %.*s"));
|
|
}
|
|
tv_clear(&input);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int nlua_in_fast_event(lua_State *lstate)
|
|
{
|
|
lua_pushboolean(lstate, in_fast_callback > 0);
|
|
return 1;
|
|
}
|
|
|
|
static bool viml_func_is_fast(const char *name)
|
|
{
|
|
const EvalFuncDef *const fdef = find_internal_func(name);
|
|
if (fdef) {
|
|
return fdef->fast;
|
|
}
|
|
// Not a vimL function
|
|
return false;
|
|
}
|
|
|
|
int nlua_call(lua_State *lstate)
|
|
{
|
|
Error err = ERROR_INIT;
|
|
size_t name_len;
|
|
const char *name = luaL_checklstring(lstate, 1, &name_len);
|
|
if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) {
|
|
return luaL_error(lstate, e_luv_api_disabled, "vimL function");
|
|
}
|
|
|
|
int nargs = lua_gettop(lstate) - 1;
|
|
if (nargs > MAX_FUNC_ARGS) {
|
|
return luaL_error(lstate, "Function called with too many arguments");
|
|
}
|
|
|
|
typval_T vim_args[MAX_FUNC_ARGS + 1];
|
|
int i = 0; // also used for freeing the variables
|
|
for (; i < nargs; i++) {
|
|
lua_pushvalue(lstate, i + 2);
|
|
if (!nlua_pop_typval(lstate, &vim_args[i])) {
|
|
api_set_error(&err, kErrorTypeException,
|
|
"error converting argument %d", i + 1);
|
|
goto free_vim_args;
|
|
}
|
|
}
|
|
|
|
TRY_WRAP({
|
|
// TODO(bfredl): this should be simplified in error handling refactor
|
|
force_abort = false;
|
|
suppress_errthrow = false;
|
|
did_throw = false;
|
|
did_emsg = false;
|
|
|
|
try_start();
|
|
typval_T rettv;
|
|
funcexe_T funcexe = FUNCEXE_INIT;
|
|
funcexe.fe_firstline = curwin->w_cursor.lnum;
|
|
funcexe.fe_lastline = curwin->w_cursor.lnum;
|
|
funcexe.fe_evaluate = true;
|
|
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
|
|
// (TRY_WRAP) to capture abort-causing non-exception errors.
|
|
(void)call_func((char *)name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
|
|
if (!try_end(&err)) {
|
|
nlua_push_typval(lstate, &rettv, false);
|
|
}
|
|
tv_clear(&rettv);
|
|
});
|
|
|
|
free_vim_args:
|
|
while (i > 0) {
|
|
tv_clear(&vim_args[--i]);
|
|
}
|
|
if (ERROR_SET(&err)) {
|
|
lua_pushstring(lstate, err.msg);
|
|
api_clear_error(&err);
|
|
return lua_error(lstate);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int nlua_rpcrequest(lua_State *lstate)
|
|
{
|
|
if (!nlua_is_deferred_safe()) {
|
|
return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
|
|
}
|
|
return nlua_rpc(lstate, true);
|
|
}
|
|
|
|
static int nlua_rpcnotify(lua_State *lstate)
|
|
{
|
|
return nlua_rpc(lstate, false);
|
|
}
|
|
|
|
static int nlua_rpc(lua_State *lstate, bool request)
|
|
{
|
|
size_t name_len;
|
|
uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1);
|
|
const char *name = luaL_checklstring(lstate, 2, &name_len);
|
|
int nargs = lua_gettop(lstate) - 2;
|
|
Error err = ERROR_INIT;
|
|
Array args = ARRAY_DICT_INIT;
|
|
|
|
for (int i = 0; i < nargs; i++) {
|
|
lua_pushvalue(lstate, i + 3);
|
|
ADD(args, nlua_pop_Object(lstate, false, &err));
|
|
if (ERROR_SET(&err)) {
|
|
api_free_array(args);
|
|
goto check_err;
|
|
}
|
|
}
|
|
|
|
if (request) {
|
|
ArenaMem res_mem = NULL;
|
|
Object result = rpc_send_call(chan_id, name, args, &res_mem, &err);
|
|
if (!ERROR_SET(&err)) {
|
|
nlua_push_Object(lstate, result, false);
|
|
arena_mem_free(res_mem);
|
|
}
|
|
} else {
|
|
if (!rpc_send_event(chan_id, name, args)) {
|
|
api_set_error(&err, kErrorTypeValidation,
|
|
"Invalid channel: %" PRIu64, chan_id);
|
|
}
|
|
}
|
|
|
|
check_err:
|
|
if (ERROR_SET(&err)) {
|
|
lua_pushstring(lstate, err.msg);
|
|
api_clear_error(&err);
|
|
return lua_error(lstate);
|
|
}
|
|
|
|
return request ? 1 : 0;
|
|
}
|
|
|
|
static int nlua_nil_tostring(lua_State *lstate)
|
|
{
|
|
lua_pushstring(lstate, "vim.NIL");
|
|
return 1;
|
|
}
|
|
|
|
static int nlua_empty_dict_tostring(lua_State *lstate)
|
|
{
|
|
lua_pushstring(lstate, "vim.empty_dict()");
|
|
return 1;
|
|
}
|
|
|
|
#ifdef MSWIN
|
|
/// os.getenv: override os.getenv to maintain coherency. #9681
|
|
///
|
|
/// uv_os_setenv uses SetEnvironmentVariableW which does not update _environ.
|
|
///
|
|
/// @param lstate Lua interpreter state.
|
|
static int nlua_getenv(lua_State *lstate)
|
|
{
|
|
lua_pushstring(lstate, os_getenv(luaL_checkstring(lstate, 1)));
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/// add the value to the registry
|
|
/// The current implementation does not support calls from threads.
|
|
LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index)
|
|
{
|
|
lua_pushvalue(lstate, index);
|
|
LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
|
|
if (ref > 0) {
|
|
ref_state->ref_count++;
|
|
#ifdef NLUA_TRACK_REFS
|
|
if (nlua_track_refs) {
|
|
// dummy allocation to make LeakSanitizer track our luarefs
|
|
pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3));
|
|
}
|
|
#endif
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
LuaRef nlua_ref_global(lua_State *lstate, int index)
|
|
{
|
|
return nlua_ref(lstate, nlua_global_refs, index);
|
|
}
|
|
|
|
/// remove the value from the registry
|
|
void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref)
|
|
{
|
|
if (ref > 0) {
|
|
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)(&ref_state->ref_markers, ref));
|
|
}
|
|
#endif
|
|
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
|
|
}
|
|
}
|
|
|
|
void nlua_unref_global(lua_State *lstate, LuaRef ref)
|
|
{
|
|
nlua_unref(lstate, nlua_global_refs, ref);
|
|
}
|
|
|
|
void api_free_luaref(LuaRef ref)
|
|
{
|
|
nlua_unref_global(global_lstate, ref);
|
|
}
|
|
|
|
/// push a value referenced in the registry
|
|
void nlua_pushref(lua_State *lstate, LuaRef ref)
|
|
{
|
|
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
|
|
}
|
|
|
|
/// Gets a new reference to an object stored at original_ref
|
|
///
|
|
/// NOTE: It does not copy the value, it creates a new ref to the lua object.
|
|
/// Leaves the stack unchanged.
|
|
LuaRef api_new_luaref(LuaRef original_ref)
|
|
{
|
|
if (original_ref == LUA_NOREF) {
|
|
return LUA_NOREF;
|
|
}
|
|
|
|
lua_State *const lstate = global_lstate;
|
|
nlua_pushref(lstate, original_ref);
|
|
LuaRef new_ref = nlua_ref_global(lstate, -1);
|
|
lua_pop(lstate, 1);
|
|
return new_ref;
|
|
}
|
|
|
|
/// Evaluate lua string
|
|
///
|
|
/// Used for luaeval().
|
|
///
|
|
/// @param[in] str String to execute.
|
|
/// @param[in] arg Second argument to `luaeval()`.
|
|
/// @param[out] ret_tv Location where result will be saved.
|
|
///
|
|
/// @return Result of the execution.
|
|
void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret_tv)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
#define EVALHEADER "local _A=select(1,...) return ("
|
|
const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1;
|
|
char *lcmd;
|
|
if (lcmd_len < IOSIZE) {
|
|
lcmd = (char *)IObuff;
|
|
} else {
|
|
lcmd = xmalloc(lcmd_len);
|
|
}
|
|
memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1);
|
|
memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size);
|
|
lcmd[lcmd_len - 1] = ')';
|
|
#undef EVALHEADER
|
|
nlua_typval_exec(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv);
|
|
|
|
if (lcmd != (char *)IObuff) {
|
|
xfree(lcmd);
|
|
}
|
|
}
|
|
|
|
void nlua_typval_call(const char *str, size_t len, typval_T *const args, int argcount,
|
|
typval_T *ret_tv)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
#define CALLHEADER "return "
|
|
#define CALLSUFFIX "(...)"
|
|
const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1;
|
|
char *lcmd;
|
|
if (lcmd_len < IOSIZE) {
|
|
lcmd = (char *)IObuff;
|
|
} else {
|
|
lcmd = xmalloc(lcmd_len);
|
|
}
|
|
memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1);
|
|
memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len);
|
|
memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX,
|
|
sizeof(CALLSUFFIX) - 1);
|
|
#undef CALLHEADER
|
|
#undef CALLSUFFIX
|
|
|
|
nlua_typval_exec(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv);
|
|
|
|
if (lcmd != (char *)IObuff) {
|
|
xfree(lcmd);
|
|
}
|
|
}
|
|
|
|
void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
|
|
nlua_pushref(lstate, xp->xp_luaref);
|
|
lua_pushstring(lstate, xp->xp_pattern);
|
|
lua_pushstring(lstate, xp->xp_line);
|
|
lua_pushinteger(lstate, xp->xp_col);
|
|
|
|
if (nlua_pcall(lstate, 3, 1)) {
|
|
nlua_error(lstate, _("E5108: Error executing Lua function: %.*s"));
|
|
return;
|
|
}
|
|
|
|
nlua_pop_typval(lstate, ret_tv);
|
|
}
|
|
|
|
static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name,
|
|
typval_T *const args, int argcount, bool special, typval_T *ret_tv)
|
|
{
|
|
if (check_secure()) {
|
|
if (ret_tv) {
|
|
ret_tv->v_type = VAR_NUMBER;
|
|
ret_tv->vval.v_number = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
lua_State *const lstate = global_lstate;
|
|
if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) {
|
|
nlua_error(lstate, _("E5107: Error loading lua %.*s"));
|
|
return;
|
|
}
|
|
|
|
PUSH_ALL_TYPVALS(lstate, args, argcount, special);
|
|
|
|
if (nlua_pcall(lstate, argcount, ret_tv ? 1 : 0)) {
|
|
nlua_error(lstate, _("E5108: Error executing lua %.*s"));
|
|
return;
|
|
}
|
|
|
|
if (ret_tv) {
|
|
nlua_pop_typval(lstate, ret_tv);
|
|
}
|
|
}
|
|
|
|
int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name)
|
|
{
|
|
const sctx_T save_current_sctx = current_sctx;
|
|
current_sctx.sc_sid = SID_STR;
|
|
current_sctx.sc_seq = 0;
|
|
current_sctx.sc_lnum = 0;
|
|
estack_push(ETYPE_SCRIPT, name, 0);
|
|
|
|
garray_T ga;
|
|
char_u *line = NULL;
|
|
|
|
ga_init(&ga, (int)sizeof(char_u *), 10);
|
|
while ((line = (char_u *)fgetline(0, cookie, 0, false)) != NULL) {
|
|
GA_APPEND(char_u *, &ga, line);
|
|
}
|
|
char *code = ga_concat_strings_sep(&ga, "\n");
|
|
size_t len = strlen(code);
|
|
nlua_typval_exec(code, len, name, NULL, 0, false, NULL);
|
|
|
|
estack_pop();
|
|
current_sctx = save_current_sctx;
|
|
ga_clear_strings(&ga);
|
|
xfree(code);
|
|
return OK;
|
|
}
|
|
|
|
/// Call a LuaCallable given some typvals
|
|
///
|
|
/// Used to call any lua callable passed from Lua into VimL
|
|
///
|
|
/// @param[in] lstate Lua State
|
|
/// @param[in] lua_cb Lua Callable
|
|
/// @param[in] argcount Count of typval arguments
|
|
/// @param[in] argvars Typval Arguments
|
|
/// @param[out] rettv The return value from the called function.
|
|
int typval_exec_lua_callable(LuaRef lua_cb, int argcount, typval_T *argvars, typval_T *rettv)
|
|
{
|
|
lua_State *lstate = global_lstate;
|
|
|
|
nlua_pushref(lstate, lua_cb);
|
|
|
|
PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
|
|
|
|
if (nlua_pcall(lstate, argcount, 1)) {
|
|
nlua_print(lstate);
|
|
return FCERR_OTHER;
|
|
}
|
|
|
|
nlua_pop_typval(lstate, rettv);
|
|
|
|
return FCERR_NONE;
|
|
}
|
|
|
|
/// Execute Lua string
|
|
///
|
|
/// Used for nvim_exec_lua() and internally to execute a lua string.
|
|
///
|
|
/// @param[in] str String to execute.
|
|
/// @param[in] args array of ... args
|
|
/// @param[out] err Location where error will be saved.
|
|
///
|
|
/// @return Return value of the execution.
|
|
Object nlua_exec(const String str, const Array args, Error *err)
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
|
|
if (luaL_loadbuffer(lstate, str.data, str.size, "<nvim>")) {
|
|
size_t len;
|
|
const char *errstr = lua_tolstring(lstate, -1, &len);
|
|
api_set_error(err, kErrorTypeValidation,
|
|
"Error loading lua: %.*s", (int)len, errstr);
|
|
return NIL;
|
|
}
|
|
|
|
for (size_t i = 0; i < args.size; i++) {
|
|
nlua_push_Object(lstate, args.items[i], false);
|
|
}
|
|
|
|
if (nlua_pcall(lstate, (int)args.size, 1)) {
|
|
size_t len;
|
|
const char *errstr = lua_tolstring(lstate, -1, &len);
|
|
api_set_error(err, kErrorTypeException,
|
|
"Error executing lua: %.*s", (int)len, errstr);
|
|
return NIL;
|
|
}
|
|
|
|
return nlua_pop_Object(lstate, false, err);
|
|
}
|
|
|
|
bool nlua_ref_is_function(LuaRef ref)
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
nlua_pushref(lstate, ref);
|
|
|
|
// TODO(tjdevries): This should probably check for callable tables as well.
|
|
// We should put some work maybe into simplifying how all of that works
|
|
bool is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
|
|
lua_pop(lstate, 1);
|
|
|
|
return is_function;
|
|
}
|
|
|
|
/// call a LuaRef as a function (or table with __call metamethod)
|
|
///
|
|
/// @param ref the reference to call (not consumed)
|
|
/// @param name if non-NULL, sent to callback as first arg
|
|
/// if NULL, only args are used
|
|
/// @param retval if true, convert return value to Object
|
|
/// if false, only check if return value is truthy
|
|
/// @param err Error details, if any (if NULL, errors are echoed)
|
|
/// @return Return value of function, if retval was set. Otherwise
|
|
/// BOOLEAN_OBJ(true) or NIL.
|
|
Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Error *err)
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
nlua_pushref(lstate, ref);
|
|
int nargs = (int)args.size;
|
|
if (name != NULL) {
|
|
lua_pushstring(lstate, name);
|
|
nargs++;
|
|
}
|
|
for (size_t i = 0; i < args.size; i++) {
|
|
nlua_push_Object(lstate, args.items[i], false);
|
|
}
|
|
|
|
if (nlua_pcall(lstate, nargs, 1)) {
|
|
// if err is passed, the caller will deal with the error.
|
|
if (err) {
|
|
size_t len;
|
|
const char *errstr = lua_tolstring(lstate, -1, &len);
|
|
api_set_error(err, kErrorTypeException,
|
|
"Error executing lua: %.*s", (int)len, errstr);
|
|
} else {
|
|
nlua_error(lstate, _("Error executing lua callback: %.*s"));
|
|
}
|
|
return NIL;
|
|
}
|
|
|
|
if (retval) {
|
|
Error dummy = ERROR_INIT;
|
|
if (err == NULL) {
|
|
err = &dummy;
|
|
}
|
|
return nlua_pop_Object(lstate, false, err);
|
|
} else {
|
|
bool value = lua_toboolean(lstate, -1);
|
|
lua_pop(lstate, 1);
|
|
|
|
return value ? BOOLEAN_OBJ(true) : NIL;
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
bool nlua_is_deferred_safe(void)
|
|
{
|
|
return in_fast_callback == 0;
|
|
}
|
|
|
|
/// Run lua string
|
|
///
|
|
/// Used for :lua.
|
|
///
|
|
/// @param eap VimL command being run.
|
|
void ex_lua(exarg_T *const eap)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
size_t len;
|
|
char *code = script_get(eap, &len);
|
|
if (eap->skip) {
|
|
xfree(code);
|
|
return;
|
|
}
|
|
// When =expr is used transform it to print(vim.inspect(expr))
|
|
if (code[0] == '=') {
|
|
len += sizeof("vim.pretty_print()") - sizeof("=");
|
|
// code_buf needs to be 1 char larger then len for null byte in the end.
|
|
// lua nlua_typval_exec doesn't expect null terminated string so len
|
|
// needs to end before null byte.
|
|
char *code_buf = xmallocz(len);
|
|
vim_snprintf(code_buf, len + 1, "vim.pretty_print(%s)", code + 1);
|
|
xfree(code);
|
|
code = code_buf;
|
|
}
|
|
|
|
nlua_typval_exec(code, len, ":lua", NULL, 0, false, NULL);
|
|
|
|
xfree(code);
|
|
}
|
|
|
|
/// Run lua string for each line in range
|
|
///
|
|
/// Used for :luado.
|
|
///
|
|
/// @param eap VimL command being run.
|
|
void ex_luado(exarg_T *const eap)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) {
|
|
emsg(_("cannot save undo information"));
|
|
return;
|
|
}
|
|
const char *const cmd = (const char *)eap->arg;
|
|
const size_t cmd_len = strlen(cmd);
|
|
|
|
lua_State *const lstate = global_lstate;
|
|
|
|
#define DOSTART "return function(line, linenr) "
|
|
#define DOEND " end"
|
|
const size_t lcmd_len = (cmd_len
|
|
+ (sizeof(DOSTART) - 1)
|
|
+ (sizeof(DOEND) - 1));
|
|
char *lcmd;
|
|
if (lcmd_len < IOSIZE) {
|
|
lcmd = (char *)IObuff;
|
|
} else {
|
|
lcmd = xmalloc(lcmd_len + 1);
|
|
}
|
|
memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1);
|
|
memcpy(lcmd + sizeof(DOSTART) - 1, cmd, cmd_len);
|
|
memcpy(lcmd + sizeof(DOSTART) - 1 + cmd_len, DOEND, sizeof(DOEND) - 1);
|
|
#undef DOSTART
|
|
#undef DOEND
|
|
|
|
if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) {
|
|
nlua_error(lstate, _("E5109: Error loading lua: %.*s"));
|
|
if (lcmd_len >= IOSIZE) {
|
|
xfree(lcmd);
|
|
}
|
|
return;
|
|
}
|
|
if (lcmd_len >= IOSIZE) {
|
|
xfree(lcmd);
|
|
}
|
|
if (nlua_pcall(lstate, 0, 1)) {
|
|
nlua_error(lstate, _("E5110: Error executing lua: %.*s"));
|
|
return;
|
|
}
|
|
for (linenr_T l = eap->line1; l <= eap->line2; l++) {
|
|
if (l > curbuf->b_ml.ml_line_count) {
|
|
break;
|
|
}
|
|
lua_pushvalue(lstate, -1);
|
|
const char *old_line = (const char *)ml_get_buf(curbuf, l, false);
|
|
lua_pushstring(lstate, old_line);
|
|
lua_pushnumber(lstate, (lua_Number)l);
|
|
if (nlua_pcall(lstate, 2, 1)) {
|
|
nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
|
|
break;
|
|
}
|
|
if (lua_isstring(lstate, -1)) {
|
|
size_t old_line_len = strlen(old_line);
|
|
|
|
size_t new_line_len;
|
|
const char *const new_line = lua_tolstring(lstate, -1, &new_line_len);
|
|
char *const new_line_transformed = xmemdupz(new_line, new_line_len);
|
|
for (size_t i = 0; i < new_line_len; i++) {
|
|
if (new_line_transformed[i] == NUL) {
|
|
new_line_transformed[i] = '\n';
|
|
}
|
|
}
|
|
ml_replace(l, new_line_transformed, false);
|
|
inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len);
|
|
}
|
|
lua_pop(lstate, 1);
|
|
}
|
|
lua_pop(lstate, 1);
|
|
check_cursor();
|
|
redraw_curbuf_later(UPD_NOT_VALID);
|
|
}
|
|
|
|
/// Run lua file
|
|
///
|
|
/// Used for :luafile.
|
|
///
|
|
/// @param eap VimL command being run.
|
|
void ex_luafile(exarg_T *const eap)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
nlua_exec_file((const char *)eap->arg);
|
|
}
|
|
|
|
/// execute lua code from a file.
|
|
///
|
|
/// Note: we call the lua global loadfile as opposed to calling luaL_loadfile
|
|
/// in case loadfile has been overridden in the users environment.
|
|
///
|
|
/// @param path path of the file
|
|
///
|
|
/// @return true if everything ok, false if there was an error (echoed)
|
|
bool nlua_exec_file(const char *path)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
|
|
lua_getglobal(lstate, "loadfile");
|
|
lua_pushstring(lstate, path);
|
|
|
|
if (nlua_pcall(lstate, 1, 2)) {
|
|
nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
|
|
return false;
|
|
}
|
|
|
|
// loadstring() returns either:
|
|
// 1. nil, error
|
|
// 2. chunk, nil
|
|
|
|
if (lua_isnil(lstate, -2)) {
|
|
// 1
|
|
nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s"));
|
|
assert(lua_isnil(lstate, -1));
|
|
lua_pop(lstate, 1);
|
|
return false;
|
|
}
|
|
|
|
// 2
|
|
assert(lua_isnil(lstate, -1));
|
|
lua_pop(lstate, 1);
|
|
|
|
if (nlua_pcall(lstate, 0, 0)) {
|
|
nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int tslua_get_language_version(lua_State *L)
|
|
{
|
|
lua_pushnumber(L, TREE_SITTER_LANGUAGE_VERSION);
|
|
return 1;
|
|
}
|
|
|
|
int tslua_get_minimum_language_version(lua_State *L)
|
|
{
|
|
lua_pushnumber(L, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION);
|
|
return 1;
|
|
}
|
|
|
|
static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
tslua_init(lstate);
|
|
|
|
lua_pushcfunction(lstate, tslua_push_parser);
|
|
lua_setfield(lstate, -2, "_create_ts_parser");
|
|
|
|
lua_pushcfunction(lstate, tslua_add_language);
|
|
lua_setfield(lstate, -2, "_ts_add_language");
|
|
|
|
lua_pushcfunction(lstate, tslua_has_language);
|
|
lua_setfield(lstate, -2, "_ts_has_language");
|
|
|
|
lua_pushcfunction(lstate, tslua_remove_lang);
|
|
lua_setfield(lstate, -2, "_ts_remove_language");
|
|
|
|
lua_pushcfunction(lstate, tslua_inspect_lang);
|
|
lua_setfield(lstate, -2, "_ts_inspect_language");
|
|
|
|
lua_pushcfunction(lstate, tslua_parse_query);
|
|
lua_setfield(lstate, -2, "_ts_parse_query");
|
|
|
|
lua_pushcfunction(lstate, tslua_get_language_version);
|
|
lua_setfield(lstate, -2, "_ts_get_language_version");
|
|
|
|
lua_pushcfunction(lstate, tslua_get_minimum_language_version);
|
|
lua_setfield(lstate, -2, "_ts_get_minimum_language_version");
|
|
}
|
|
|
|
int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results)
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
int ret = OK;
|
|
|
|
// [ vim ]
|
|
lua_getglobal(lstate, "vim");
|
|
|
|
// [ vim, vim._expand_pat ]
|
|
lua_getfield(lstate, -1, "_expand_pat");
|
|
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
|
|
|
// [ vim, vim._expand_pat, buf ]
|
|
lua_pushlstring(lstate, (const char *)pat, strlen(pat));
|
|
|
|
if (nlua_pcall(lstate, 1, 2) != 0) {
|
|
nlua_error(lstate,
|
|
_("Error executing vim._expand_pat: %.*s"));
|
|
return FAIL;
|
|
}
|
|
|
|
Error err = ERROR_INIT;
|
|
|
|
*num_results = 0;
|
|
*results = NULL;
|
|
|
|
int prefix_len = (int)nlua_pop_Integer(lstate, &err);
|
|
if (ERROR_SET(&err)) {
|
|
ret = FAIL;
|
|
goto cleanup;
|
|
}
|
|
|
|
Array completions = nlua_pop_Array(lstate, &err);
|
|
if (ERROR_SET(&err)) {
|
|
ret = FAIL;
|
|
goto cleanup_array;
|
|
}
|
|
|
|
garray_T result_array;
|
|
ga_init(&result_array, (int)sizeof(char *), 80);
|
|
for (size_t i = 0; i < completions.size; i++) {
|
|
Object v = completions.items[i];
|
|
|
|
if (v.type != kObjectTypeString) {
|
|
ret = FAIL;
|
|
goto cleanup_array;
|
|
}
|
|
|
|
GA_APPEND(char_u *, &result_array, (char_u *)string_to_cstr(v.data.string));
|
|
}
|
|
|
|
xp->xp_pattern += prefix_len;
|
|
*results = result_array.ga_data;
|
|
*num_results = result_array.ga_len;
|
|
|
|
cleanup_array:
|
|
api_free_array(completions);
|
|
|
|
cleanup:
|
|
|
|
if (ret == FAIL) {
|
|
ga_clear(&result_array);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int nlua_is_thread(lua_State *lstate)
|
|
{
|
|
lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
|
|
|
|
return 1;
|
|
}
|
|
|
|
bool nlua_is_table_from_lua(typval_T *const arg)
|
|
{
|
|
if (arg->v_type == VAR_DICT) {
|
|
return arg->vval.v_dict->lua_table_ref != LUA_NOREF;
|
|
} else if (arg->v_type == VAR_LIST) {
|
|
return arg->vval.v_list->lua_table_ref != LUA_NOREF;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
char_u *nlua_register_table_as_callable(typval_T *const arg)
|
|
{
|
|
LuaRef table_ref = LUA_NOREF;
|
|
if (arg->v_type == VAR_DICT) {
|
|
table_ref = arg->vval.v_dict->lua_table_ref;
|
|
} else if (arg->v_type == VAR_LIST) {
|
|
table_ref = arg->vval.v_list->lua_table_ref;
|
|
}
|
|
|
|
if (table_ref == LUA_NOREF) {
|
|
return NULL;
|
|
}
|
|
|
|
lua_State *const lstate = global_lstate;
|
|
|
|
#ifndef NDEBUG
|
|
int top = lua_gettop(lstate);
|
|
#endif
|
|
|
|
nlua_pushref(lstate, table_ref); // [table]
|
|
if (!lua_getmetatable(lstate, -1)) {
|
|
lua_pop(lstate, 1);
|
|
assert(top == lua_gettop(lstate));
|
|
return NULL;
|
|
} // [table, mt]
|
|
|
|
lua_getfield(lstate, -1, "__call"); // [table, mt, mt.__call]
|
|
if (!lua_isfunction(lstate, -1)) {
|
|
lua_pop(lstate, 3);
|
|
assert(top == lua_gettop(lstate));
|
|
return NULL;
|
|
}
|
|
lua_pop(lstate, 2); // [table]
|
|
|
|
LuaRef func = nlua_ref_global(lstate, -1);
|
|
|
|
char_u *name = register_luafunc(func);
|
|
|
|
lua_pop(lstate, 1); // []
|
|
assert(top == lua_gettop(lstate));
|
|
|
|
return name;
|
|
}
|
|
|
|
void nlua_execute_on_key(int c)
|
|
{
|
|
char_u buf[NUMBUFLEN];
|
|
size_t buf_len = special_to_buf(c, mod_mask, false, buf);
|
|
|
|
lua_State *const lstate = global_lstate;
|
|
|
|
#ifndef NDEBUG
|
|
int top = lua_gettop(lstate);
|
|
#endif
|
|
|
|
// [ vim ]
|
|
lua_getglobal(lstate, "vim");
|
|
|
|
// [ vim, vim._on_key ]
|
|
lua_getfield(lstate, -1, "_on_key");
|
|
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
|
|
|
// [ vim, vim._on_key, buf ]
|
|
lua_pushlstring(lstate, (const char *)buf, buf_len);
|
|
|
|
int save_got_int = got_int;
|
|
got_int = false; // avoid interrupts when the key typed is Ctrl-C
|
|
if (nlua_pcall(lstate, 1, 0)) {
|
|
nlua_error(lstate,
|
|
_("Error executing vim.on_key Lua callback: %.*s"));
|
|
}
|
|
got_int |= save_got_int;
|
|
|
|
// [ vim ]
|
|
lua_pop(lstate, 1);
|
|
|
|
#ifndef NDEBUG
|
|
// [ ]
|
|
assert(top == lua_gettop(lstate));
|
|
#endif
|
|
}
|
|
|
|
// Sets the editor "script context" during Lua execution. Used by :verbose.
|
|
// @param[out] current
|
|
void nlua_set_sctx(sctx_T *current)
|
|
{
|
|
if (p_verbose <= 0 || current->sc_sid != SID_LUA) {
|
|
return;
|
|
}
|
|
lua_State *const lstate = global_lstate;
|
|
lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug));
|
|
|
|
// Files where internal wrappers are defined so we can ignore them
|
|
// like vim.o/opt etc are defined in _meta.lua
|
|
char *ignorelist[] = {
|
|
"vim/_meta.lua",
|
|
"vim/keymap.lua",
|
|
};
|
|
int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]);
|
|
|
|
for (int level = 1; true; level++) {
|
|
if (lua_getstack(lstate, level, info) != 1) {
|
|
goto cleanup;
|
|
}
|
|
if (lua_getinfo(lstate, "nSl", info) == 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
bool is_ignored = false;
|
|
if (info->what[0] == 'C' || info->source[0] != '@') {
|
|
is_ignored = true;
|
|
} else {
|
|
for (int i = 0; i < ignorelist_size; i++) {
|
|
if (strncmp(ignorelist[i], info->source + 1, strlen(ignorelist[i])) == 0) {
|
|
is_ignored = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (is_ignored) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
char *source_path = fix_fname(info->source + 1);
|
|
get_current_script_id(&source_path, current);
|
|
xfree(source_path);
|
|
current->sc_lnum = info->currentline;
|
|
current->sc_seq = -1;
|
|
|
|
cleanup:
|
|
xfree(info);
|
|
}
|
|
|
|
/// @param preview Invoke the callback as a |:command-preview| handler.
|
|
int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
|
|
nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref);
|
|
|
|
lua_newtable(lstate);
|
|
lua_pushstring(lstate, cmd->uc_name);
|
|
lua_setfield(lstate, -2, "name");
|
|
|
|
lua_pushboolean(lstate, eap->forceit == 1);
|
|
lua_setfield(lstate, -2, "bang");
|
|
|
|
lua_pushinteger(lstate, eap->line1);
|
|
lua_setfield(lstate, -2, "line1");
|
|
|
|
lua_pushinteger(lstate, eap->line2);
|
|
lua_setfield(lstate, -2, "line2");
|
|
|
|
lua_newtable(lstate); // f-args table
|
|
lua_pushstring(lstate, (const char *)eap->arg);
|
|
lua_pushvalue(lstate, -1); // Reference for potential use on f-args
|
|
lua_setfield(lstate, -4, "args");
|
|
|
|
// Split args by unescaped whitespace |<f-args>| (nargs dependent)
|
|
if (cmd->uc_argt & EX_NOSPC) {
|
|
if ((cmd->uc_argt & EX_NEEDARG) || strlen(eap->arg)) {
|
|
// For commands where nargs is 1 or "?" and argument is passed, fargs = { args }
|
|
lua_rawseti(lstate, -2, 1);
|
|
} else {
|
|
// if nargs = "?" and no argument is passed, fargs = {}
|
|
lua_pop(lstate, 1); // Pop the reference of opts.args
|
|
}
|
|
} else if (eap->args == NULL) {
|
|
// For commands with more than one possible argument, split if argument list isn't available.
|
|
lua_pop(lstate, 1); // Pop the reference of opts.args
|
|
size_t length = strlen(eap->arg);
|
|
size_t end = 0;
|
|
size_t len = 0;
|
|
int i = 1;
|
|
char *buf = xcalloc(length, sizeof(char));
|
|
bool done = false;
|
|
while (!done) {
|
|
done = uc_split_args_iter(eap->arg, length, &end, buf, &len);
|
|
if (len > 0) {
|
|
lua_pushlstring(lstate, buf, len);
|
|
lua_rawseti(lstate, -2, i);
|
|
i++;
|
|
}
|
|
}
|
|
xfree(buf);
|
|
} else {
|
|
// If argument list is available, just use it.
|
|
lua_pop(lstate, 1);
|
|
for (size_t i = 0; i < eap->argc; i++) {
|
|
lua_pushlstring(lstate, eap->args[i], eap->arglens[i]);
|
|
lua_rawseti(lstate, -2, (int)i + 1);
|
|
}
|
|
}
|
|
lua_setfield(lstate, -2, "fargs");
|
|
|
|
char reg[2] = { (char)eap->regname, NUL };
|
|
lua_pushstring(lstate, reg);
|
|
lua_setfield(lstate, -2, "reg");
|
|
|
|
lua_pushinteger(lstate, eap->addr_count);
|
|
lua_setfield(lstate, -2, "range");
|
|
|
|
if (eap->addr_count > 0) {
|
|
lua_pushinteger(lstate, eap->line2);
|
|
} else {
|
|
lua_pushinteger(lstate, cmd->uc_def);
|
|
}
|
|
lua_setfield(lstate, -2, "count");
|
|
|
|
// The size of this buffer is chosen empirically to be large enough to hold
|
|
// every possible modifier (with room to spare). If the list of possible
|
|
// modifiers grows this may need to be updated.
|
|
char buf[200] = { 0 };
|
|
(void)uc_mods(buf, &cmdmod, false);
|
|
lua_pushstring(lstate, buf);
|
|
lua_setfield(lstate, -2, "mods");
|
|
|
|
lua_newtable(lstate); // smods table
|
|
|
|
lua_pushinteger(lstate, cmdmod.cmod_tab - 1);
|
|
lua_setfield(lstate, -2, "tab");
|
|
|
|
lua_pushinteger(lstate, cmdmod.cmod_verbose - 1);
|
|
lua_setfield(lstate, -2, "verbose");
|
|
|
|
if (cmdmod.cmod_split & WSP_ABOVE) {
|
|
lua_pushstring(lstate, "aboveleft");
|
|
} else if (cmdmod.cmod_split & WSP_BELOW) {
|
|
lua_pushstring(lstate, "belowright");
|
|
} else if (cmdmod.cmod_split & WSP_TOP) {
|
|
lua_pushstring(lstate, "topleft");
|
|
} else if (cmdmod.cmod_split & WSP_BOT) {
|
|
lua_pushstring(lstate, "botright");
|
|
} else {
|
|
lua_pushstring(lstate, "");
|
|
}
|
|
lua_setfield(lstate, -2, "split");
|
|
|
|
lua_pushboolean(lstate, cmdmod.cmod_split & WSP_VERT);
|
|
lua_setfield(lstate, -2, "vertical");
|
|
lua_pushboolean(lstate, cmdmod.cmod_split & WSP_HOR);
|
|
lua_setfield(lstate, -2, "horizontal");
|
|
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SILENT);
|
|
lua_setfield(lstate, -2, "silent");
|
|
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_ERRSILENT);
|
|
lua_setfield(lstate, -2, "emsg_silent");
|
|
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_UNSILENT);
|
|
lua_setfield(lstate, -2, "unsilent");
|
|
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SANDBOX);
|
|
lua_setfield(lstate, -2, "sandbox");
|
|
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_NOAUTOCMD);
|
|
lua_setfield(lstate, -2, "noautocmd");
|
|
|
|
typedef struct {
|
|
int flag;
|
|
char *name;
|
|
} mod_entry_T;
|
|
static mod_entry_T mod_entries[] = {
|
|
{ CMOD_BROWSE, "browse" },
|
|
{ CMOD_CONFIRM, "confirm" },
|
|
{ CMOD_HIDE, "hide" },
|
|
{ CMOD_KEEPALT, "keepalt" },
|
|
{ CMOD_KEEPJUMPS, "keepjumps" },
|
|
{ CMOD_KEEPMARKS, "keepmarks" },
|
|
{ CMOD_KEEPPATTERNS, "keeppatterns" },
|
|
{ CMOD_LOCKMARKS, "lockmarks" },
|
|
{ CMOD_NOSWAPFILE, "noswapfile" }
|
|
};
|
|
|
|
// The modifiers that are simple flags
|
|
for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
|
|
lua_pushboolean(lstate, cmdmod.cmod_flags & mod_entries[i].flag);
|
|
lua_setfield(lstate, -2, mod_entries[i].name);
|
|
}
|
|
|
|
lua_setfield(lstate, -2, "smods");
|
|
|
|
if (preview) {
|
|
lua_pushinteger(lstate, cmdpreview_get_ns());
|
|
|
|
handle_T cmdpreview_bufnr = cmdpreview_get_bufnr();
|
|
if (cmdpreview_bufnr != 0) {
|
|
lua_pushinteger(lstate, cmdpreview_bufnr);
|
|
} else {
|
|
lua_pushnil(lstate);
|
|
}
|
|
}
|
|
|
|
if (nlua_pcall(lstate, preview ? 3 : 1, preview ? 1 : 0)) {
|
|
nlua_error(lstate, _("Error executing Lua callback: %.*s"));
|
|
return 0;
|
|
}
|
|
|
|
int retv = 0;
|
|
|
|
if (preview) {
|
|
if (lua_isnumber(lstate, -1) && (retv = (int)lua_tointeger(lstate, -1)) >= 0 && retv <= 2) {
|
|
lua_pop(lstate, 1);
|
|
} else {
|
|
retv = 0;
|
|
}
|
|
}
|
|
|
|
return retv;
|
|
}
|
|
|
|
/// String representation of a Lua function reference
|
|
///
|
|
/// @return Allocated string
|
|
char *nlua_funcref_str(LuaRef ref)
|
|
{
|
|
lua_State *const lstate = global_lstate;
|
|
StringBuilder str = KV_INITIAL_VALUE;
|
|
kv_resize(str, 16);
|
|
|
|
if (!lua_checkstack(lstate, 1)) {
|
|
goto plain;
|
|
}
|
|
nlua_pushref(lstate, ref);
|
|
if (!lua_isfunction(lstate, -1)) {
|
|
lua_pop(lstate, 1);
|
|
goto plain;
|
|
}
|
|
|
|
lua_Debug ar;
|
|
if (lua_getinfo(lstate, ">S", &ar) && *ar.source == '@' && ar.linedefined >= 0) {
|
|
char *src = home_replace_save(NULL, ar.source + 1);
|
|
kv_printf(str, "<Lua %d: %s:%d>", ref, src, ar.linedefined);
|
|
xfree(src);
|
|
return str.items;
|
|
}
|
|
|
|
plain:
|
|
kv_printf(str, "<Lua %d>", ref);
|
|
return str.items;
|
|
}
|