aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/lua
diff options
context:
space:
mode:
authorJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
committerJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
commit308e1940dcd64aa6c344c403d4f9e0dda58d9c5c (patch)
tree35fe43e01755e0f312650667004487a44d6b7941 /src/nvim/lua
parent96a00c7c588b2f38a2424aeeb4ea3581d370bf2d (diff)
parente8c94697bcbe23a5c7b07c292b90a6b70aadfa87 (diff)
downloadrneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.gz
rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.bz2
rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.zip
Merge remote-tracking branch 'upstream/master' into rahm
Diffstat (limited to 'src/nvim/lua')
-rw-r--r--src/nvim/lua/converter.c62
-rw-r--r--src/nvim/lua/executor.c898
-rw-r--r--src/nvim/lua/executor.h25
-rw-r--r--src/nvim/lua/spell.c8
-rw-r--r--src/nvim/lua/stdlib.c136
-rw-r--r--src/nvim/lua/treesitter.c48
-rw-r--r--src/nvim/lua/vim.lua711
-rw-r--r--src/nvim/lua/xdiff.c9
8 files changed, 881 insertions, 1016 deletions
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 8a702ddd60..4d6e6090b8 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -49,7 +49,6 @@ typedef struct {
#define LUA_PUSH_STATIC_STRING(lstate, s) \
lua_pushlstring(lstate, s, sizeof(s) - 1)
-
static LuaTableProps nlua_traverse_table(lua_State *const lstate)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -156,7 +155,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_global_refs->empty_dict_ref);
if (lua_rawequal(lstate, -2, -1)) {
ret.type = kObjectTypeDictionary;
}
@@ -286,9 +285,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
break;
case LUA_TBOOLEAN:
cur.tv->v_type = VAR_BOOL;
- cur.tv->vval.v_bool = (lua_toboolean(lstate, -1)
- ? kBoolVarTrue
- : kBoolVarFalse);
+ cur.tv->vval.v_bool = (lua_toboolean(lstate, -1) ? kBoolVarTrue : kBoolVarFalse);
break;
case LUA_TSTRING: {
size_t len;
@@ -316,7 +313,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
LuaRef table_ref = LUA_NOREF;
if (lua_getmetatable(lstate, -1)) {
lua_pop(lstate, 1);
- table_ref = nlua_ref(lstate, -1);
+ table_ref = nlua_ref_global(lstate, -1);
}
const LuaTableProps table_props = nlua_traverse_table(lstate);
@@ -389,19 +386,19 @@ nlua_pop_typval_table_processing_end:
}
case LUA_TFUNCTION: {
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
- state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.func_ref = nlua_ref_global(lstate, -1);
char_u *name = register_cfunc(&nlua_CFunction_func_call,
&nlua_CFunction_func_free,
state);
cur.tv->v_type = VAR_FUNC;
- cur.tv->vval.v_string = vim_strsave(name);
+ cur.tv->vval.v_string = (char *)vim_strsave(name);
break;
}
case LUA_TUSERDATA: {
// TODO(bfredl): check mt.__call and convert to function?
- nlua_pushref(lstate, nlua_nil_ref);
+ nlua_pushref(lstate, nlua_global_refs->nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
if (is_nil) {
@@ -445,7 +442,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_global_refs->nil_ref); \
} \
} while (0)
@@ -478,7 +475,12 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
- TYPVAL_ENCODE_CONV_NIL(tv); \
+ ufunc_T *fp = find_func(fun); \
+ if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \
+ nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \
+ } else { \
+ TYPVAL_ENCODE_CONV_NIL(tv); \
+ } \
goto typval_encode_stop_converting_one_item; \
} while (0)
@@ -495,7 +497,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_global_refs->empty_dict_ref); \
lua_setmetatable(lstate, -2); \
} \
} while (0)
@@ -551,8 +553,8 @@ static bool typval_conv_special = false;
const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \
if (mpval.type == conv_type) { \
if (conv_type == kMPConvDict \
- ? (void *)mpval.data.d.dict == (void *)(val) \
- : (void *)mpval.data.l.list == (void *)(val)) { \
+ ? (void *)mpval.data.d.dict == (void *)(val) \
+ : (void *)mpval.data.l.list == (void *)(val)) { \
lua_pushvalue(lstate, \
-((int)((kv_size(*mpstack) - backref + 1) * 2))); \
break; \
@@ -671,7 +673,6 @@ static inline void nlua_create_typed_table(lua_State *lstate, const size_t narr,
lua_rawset(lstate, -3);
}
-
/// Convert given String to lua string
///
/// Leaves converted string on top of the stack.
@@ -726,7 +727,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_global_refs->empty_dict_ref);
lua_setmetatable(lstate, -2);
}
}
@@ -774,7 +775,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_global_refs->nil_ref);
}
break;
case kObjectTypeLuaRef: {
@@ -805,7 +806,6 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
}
}
-
/// Convert lua value to string
///
/// Always pops one value from the stack.
@@ -1125,10 +1125,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
case LUA_TSTRING: {
size_t len;
const char *s = lua_tolstring(lstate, -1, &len);
- *cur.obj = STRING_OBJ(((String) {
- .data = xmemdupz(s, len),
- .size = len,
- }));
+ *cur.obj = STRING_OBJ(((String) { .data = xmemdupz(s, len), .size = len }));
break;
}
case LUA_TNUMBER: {
@@ -1146,11 +1143,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
switch (table_props.type) {
case kObjectTypeArray:
- *cur.obj = ARRAY_OBJ(((Array) {
- .items = NULL,
- .size = 0,
- .capacity = 0,
- }));
+ *cur.obj = ARRAY_OBJ(((Array) { .items = NULL, .size = 0, .capacity = 0 }));
if (table_props.maxidx != 0) {
cur.obj->data.array.items =
xcalloc(table_props.maxidx,
@@ -1161,11 +1154,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
}
break;
case kObjectTypeDictionary:
- *cur.obj = DICTIONARY_OBJ(((Dictionary) {
- .items = NULL,
- .size = 0,
- .capacity = 0,
- }));
+ *cur.obj = DICTIONARY_OBJ(((Dictionary) { .items = NULL, .size = 0, .capacity = 0 }));
if (table_props.string_keys_num != 0) {
cur.obj->data.dictionary.items =
xcalloc(table_props.string_keys_num,
@@ -1191,14 +1180,14 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
case LUA_TFUNCTION:
if (ref) {
- *cur.obj = LUAREF_OBJ(nlua_ref(lstate, -1));
+ *cur.obj = LUAREF_OBJ(nlua_ref_global(lstate, -1));
} else {
goto type_error;
}
break;
case LUA_TUSERDATA: {
- nlua_pushref(lstate, nlua_nil_ref);
+ nlua_pushref(lstate, nlua_global_refs->nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
if (is_nil) {
@@ -1232,7 +1221,7 @@ type_error:
LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
{
- LuaRef rv = nlua_ref(lstate, -1);
+ LuaRef rv = nlua_ref_global(lstate, -1);
lua_pop(lstate, 1);
return rv;
}
@@ -1244,7 +1233,7 @@ LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
type ret; \
if (lua_type(lstate, -1) != LUA_TNUMBER) { \
api_set_error(err, kErrorTypeValidation, "Expected Lua number"); \
- ret = (type)-1; \
+ ret = (type) - 1; \
} else { \
ret = (type)lua_tonumber(lstate, -1); \
} \
@@ -1305,7 +1294,6 @@ void nlua_init_types(lua_State *const lstate)
lua_rawset(lstate, -3);
}
-
void nlua_pop_keydict(lua_State *L, void *retval, field_hash hashy, Error *err)
{
if (!lua_istable(L, -1)) {
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index cfdbe7b344..ad03ebd1ed 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -4,6 +4,7 @@
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
+#include <tree_sitter/api.h>
#include "luv/luv.h"
#include "nvim/api/private/defs.h"
@@ -14,6 +15,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/cursor.h"
+#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
@@ -34,21 +36,33 @@
#include "nvim/message.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/os.h"
+#include "nvim/profile.h"
#include "nvim/screen.h"
#include "nvim/undo.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"
@@ -64,11 +78,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.
@@ -77,7 +96,22 @@ static void nlua_error(lua_State *const lstate, const char *const msg)
FUNC_ATTR_NONNULL_ALL
{
size_t len;
- const char *const str = lua_tolstring(lstate, -1, &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);
@@ -105,7 +139,6 @@ static int nlua_pcall(lua_State *lstate, int nargs, int nresults)
return status;
}
-
/// Gets the version of the current Nvim build.
///
/// @param lstate Lua interpreter state.
@@ -117,12 +150,24 @@ static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
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");
- semsg_multiline("Error executing luv callback:\n%s", 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);
}
@@ -147,7 +192,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
@@ -161,12 +206,109 @@ 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];
lua_State *const lstate = global_lstate;
nlua_pushref(lstate, cb);
- nlua_unref(lstate, cb);
+ nlua_unref_global(lstate, cb);
if (nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
}
@@ -183,7 +325,7 @@ static int nlua_schedule(lua_State *const lstate)
return lua_error(lstate);
}
- LuaRef cb = nlua_ref(lstate, 1);
+ LuaRef cb = nlua_ref_global(lstate, 1);
multiqueue_put(main_loop.events, nlua_schedule_event,
1, (void *)(ptrdiff_t)cb);
@@ -192,8 +334,7 @@ static int nlua_schedule(lua_State *const lstate)
// 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)
@@ -219,7 +360,7 @@ static int nlua_wait(lua_State *lstate)
{
intptr_t timeout = luaL_checkinteger(lstate, 1);
if (timeout < 0) {
- return luaL_error(lstate, "timeout must be > 0");
+ return luaL_error(lstate, "timeout must be >= 0");
}
int lua_top = lua_gettop(lstate);
@@ -246,7 +387,7 @@ static int nlua_wait(lua_State *lstate)
if (lua_top >= 3 && !lua_isnil(lstate, 3)) {
interval = luaL_checkinteger(lstate, 3);
if (interval < 0) {
- return luaL_error(lstate, "interval must be > 0");
+ return luaL_error(lstate, "interval must be >= 0");
}
}
@@ -301,10 +442,154 @@ 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;
+ 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;
+}
+
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
-static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
// print
lua_pushcfunction(lstate, &nlua_print);
@@ -361,117 +646,30 @@ 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");
+ nlua_common_vim_init(lstate, false);
- // 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");
+ // 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);
- // 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;
- }
- }
-
- {
- 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];
- 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"));
- return 1;
- }
- // [package, loaded, module]
- lua_setfield(lstate, -2, "vim.filetype"); // [package, loaded]
-
- lua_pop(lstate, 2); // []
- }
-
- {
- const char *code = (char *)&vim_module[0];
- if (luaL_loadbuffer(lstate, code, sizeof(vim_module) - 1, "@vim.lua")
- || nlua_pcall(lstate, 0, 0)) {
- nlua_error(lstate, _("E5106: Error while creating vim module: %.*s\n"));
- return 1;
- }
- }
-
- {
- lua_getglobal(lstate, "package"); // [package]
- lua_getfield(lstate, -1, "loaded"); // [package, loaded]
-
- const char *code = (char *)&lua_meta_module[0];
- if (luaL_loadbuffer(lstate, code, sizeof(lua_meta_module) - 1, "@vim/_meta.lua")
- || nlua_pcall(lstate, 0, 1)) {
- nlua_error(lstate, _("E5106: Error while creating vim._meta module: %.*s\n"));
- return 1;
- }
- // [package, loaded, module]
- lua_setfield(lstate, -2, "vim._meta"); // [package, loaded]
-
- lua_pop(lstate, 2); // []
+ if (!nlua_init_packages(lstate)) {
+ return false;
}
- return 0;
+ return true;
}
/// Initialize global lua interpreter
@@ -488,15 +686,66 @@ void nlua_init(void)
lua_State *lstate = luaL_newstate();
if (lstate == NULL) {
- emsg(_("E970: Failed to initialize lua interpreter"));
- preserve_exit();
+ mch_errmsg(_("E970: Failed to initialize lua interpreter\n"));
+ os_exit(1);
}
luaL_openlibs(lstate);
- nlua_state_init(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)
{
@@ -504,30 +753,35 @@ void nlua_free_all_mem(void)
return;
}
lua_State *lstate = global_lstate;
+ nlua_unref_global(lstate, require_ref);
+ 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_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 (nlua_refcount) {
- fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
+ 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];
- const size_t len = (size_t)(intptr_t)argv[1]-1; // exclude final NUL
+ const size_t len = (size_t)(intptr_t)argv[1] - 1; // exclude final NUL
for (size_t i = 0; i < len;) {
if (got_int) {
@@ -601,9 +855,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 });
@@ -612,13 +875,71 @@ 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);
}
+/// 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.
@@ -629,7 +950,7 @@ static int nlua_debug(lua_State *lstate)
{
.v_lock = VAR_FIXED,
.v_type = VAR_STRING,
- .vval.v_string = (char_u *)"lua_debug> ",
+ .vval.v_string = "lua_debug> ",
},
{
.v_type = VAR_UNKNOWN,
@@ -664,16 +985,26 @@ int nlua_in_fast_event(lua_State *lstate)
return 1;
}
+static bool viml_func_is_fast(const char *name)
+{
+ const EvalFuncDef *const fdef = find_internal_func((const char *)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_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
- if (!nlua_is_deferred_safe()) {
+ 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;
+ int nargs = lua_gettop(lstate) - 1;
if (nargs > MAX_FUNC_ARGS) {
return luaL_error(lstate, "Function called with too many arguments");
}
@@ -681,10 +1012,10 @@ int nlua_call(lua_State *lstate)
typval_T vim_args[MAX_FUNC_ARGS + 1];
int i = 0; // also used for freeing the variables
for (; i < nargs; i++) {
- lua_pushvalue(lstate, (int)i+2);
+ lua_pushvalue(lstate, i + 2);
if (!nlua_pop_typval(lstate, &vim_args[i])) {
api_set_error(&err, kErrorTypeException,
- "error converting argument %d", i+1);
+ "error converting argument %d", i + 1);
goto free_vim_args;
}
}
@@ -704,7 +1035,7 @@ int nlua_call(lua_State *lstate)
funcexe.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(name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
+ (void)call_func((char *)name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
if (!try_end(&err)) {
nlua_push_typval(lstate, &rettv, false);
}
@@ -741,12 +1072,12 @@ 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;
+ 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, (int)i+3);
+ lua_pushvalue(lstate, i + 3);
ADD(args, nlua_pop_Object(lstate, false, &err));
if (ERROR_SET(&err)) {
api_free_array(args);
@@ -755,10 +1086,11 @@ static int nlua_rpc(lua_State *lstate, bool request)
}
if (request) {
- Object result = rpc_send_call(chan_id, name, args, &err);
+ 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);
- api_free_object(result);
+ arena_mem_free(res_mem, NULL);
}
} else {
if (!rpc_send_event(chan_id, name, args)) {
@@ -789,7 +1121,6 @@ static int nlua_empty_dict_tostring(lua_State *lstate)
return 1;
}
-
#ifdef WIN32
/// os.getenv: override os.getenv to maintain coherency. #9681
///
@@ -803,42 +1134,52 @@ static int nlua_getenv(lua_State *lstate)
}
#endif
-
/// add the value to the registry
-LuaRef nlua_ref(lua_State *lstate, int index)
+/// 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) {
- 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
}
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, LuaRef ref)
+void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref)
{
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);
}
}
+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_lstate, ref);
+ nlua_unref_global(global_lstate, ref);
}
/// push a value referenced in the registry
@@ -847,7 +1188,6 @@ 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.
@@ -860,12 +1200,11 @@ LuaRef api_new_luaref(LuaRef original_ref)
lua_State *const lstate = global_lstate;
nlua_pushref(lstate, original_ref);
- LuaRef new_ref = nlua_ref(lstate, -1);
+ LuaRef new_ref = nlua_ref_global(lstate, -1);
lua_pop(lstate, 1);
return new_ref;
}
-
/// Evaluate lua string
///
/// Used for luaeval().
@@ -930,8 +1269,8 @@ void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv)
lua_State *const lstate = global_lstate;
nlua_pushref(lstate, xp->xp_luaref);
- lua_pushstring(lstate, (char *)xp->xp_pattern);
- lua_pushstring(lstate, (char *)xp->xp_line);
+ lua_pushstring(lstate, xp->xp_pattern);
+ lua_pushstring(lstate, xp->xp_line);
lua_pushinteger(lstate, xp->xp_col);
if (nlua_pcall(lstate, 3, 1)) {
@@ -984,7 +1323,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name)
char_u *line = NULL;
ga_init(&ga, (int)sizeof(char_u *), 10);
- while ((line = fgetline(0, cookie, 0, false)) != NULL) {
+ while ((line = (char_u *)fgetline(0, cookie, 0, false)) != NULL) {
GA_APPEND(char_u *, &ga, line);
}
char *code = ga_concat_strings_sep(&ga, "\n");
@@ -1062,6 +1401,19 @@ Object nlua_exec(const String str, const Array args, Error *err)
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)
@@ -1136,7 +1488,7 @@ void ex_lua(exarg_T *const eap)
// 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);
+ vim_snprintf(code_buf, len + 1, "vim.pretty_print(%s)", code + 1);
xfree(code);
code = code_buf;
}
@@ -1217,7 +1569,7 @@ void ex_luado(exarg_T *const eap)
new_line_transformed[i] = '\n';
}
}
- ml_replace(l, (char_u *)new_line_transformed, false);
+ ml_replace(l, new_line_transformed, false);
inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len);
}
lua_pop(lstate, 1);
@@ -1240,6 +1592,9 @@ void ex_luafile(exarg_T *const eap)
/// 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)
@@ -1248,11 +1603,30 @@ bool nlua_exec_file(const char *path)
{
lua_State *const lstate = global_lstate;
- if (luaL_loadfile(lstate, path)) {
+ 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;
@@ -1267,6 +1641,12 @@ int tslua_get_language_version(lua_State *L)
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);
@@ -1288,6 +1668,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
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_u *pat, int *num_results, char_u ***results)
@@ -1338,9 +1721,7 @@ int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char_u ***resul
goto cleanup_array;
}
- GA_APPEND(char_u *,
- &result_array,
- vim_strsave((char_u *)v.data.string.data));
+ GA_APPEND(char_u *, &result_array, (char_u *)string_to_cstr(v.data.string));
}
xp->xp_pattern += prefix_len;
@@ -1359,6 +1740,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)
@@ -1375,7 +1763,7 @@ void nlua_CFunction_func_free(void *state)
lua_State *const lstate = global_lstate;
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
- nlua_unref(lstate, funcstate->lua_callable.func_ref);
+ nlua_unref_global(lstate, funcstate->lua_callable.func_ref);
xfree(funcstate);
}
@@ -1425,12 +1813,11 @@ char_u *nlua_register_table_as_callable(typval_T *const arg)
lua_pop(lstate, 2); // [table]
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
- state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.func_ref = nlua_ref_global(lstate, -1);
char_u *name = register_cfunc(&nlua_CFunction_func_call,
&nlua_CFunction_func_free, state);
-
lua_pop(lstate, 1); // []
assert(top == lua_gettop(lstate));
@@ -1458,10 +1845,13 @@ void nlua_execute_on_key(int c)
// [ 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);
@@ -1472,11 +1862,64 @@ void nlua_execute_on_key(int c)
#endif
}
-void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
+// 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]);
- nlua_pushref(lstate, cmd->uc_luaref);
+ 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((char_u *)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_pushboolean(lstate, eap->forceit == 1);
@@ -1488,8 +1931,42 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
lua_pushinteger(lstate, eap->line2);
lua_setfield(lstate, -2, "line2");
+ lua_newtable(lstate); // f-args table
lua_pushstring(lstate, (const char *)eap->arg);
- lua_setfield(lstate, -2, "args");
+ 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) {
+ // Commands where nargs = 1 or "?" fargs is the same as args
+ lua_rawseti(lstate, -2, 1);
+ } 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");
lua_pushstring(lstate, (const char *)&eap->regname);
lua_setfield(lstate, -2, "reg");
@@ -1508,12 +1985,93 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
// 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);
+ (void)uc_mods(buf, &cmdmod, false);
lua_pushstring(lstate, buf);
lua_setfield(lstate, -2, "mods");
- if (nlua_pcall(lstate, 1, 0)) {
+ lua_newtable(lstate); // smods table
+
+ lua_pushinteger(lstate, cmdmod.cmod_tab);
+ 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_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;
+}
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index bf78f7ec5e..e96494ec5a 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -5,6 +5,7 @@
#include <lua.h>
#include "nvim/api/private/defs.h"
+#include "nvim/assert.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
@@ -14,18 +15,14 @@
// 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); \
- err_->type = kErrorTypeException; \
- err_->set = true; \
- memcpy(&err_->msg[0], s, sizeof(s)); \
- } while (0)
+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;
#define NLUA_CLEAR_REF(x) \
do { \
@@ -39,4 +36,8 @@ EXTERN int nlua_refcount INIT(= 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/executor.h.generated.h"
#endif
+
+EXTERN nlua_ref_state_t *nlua_global_refs INIT(= NULL);
+EXTERN bool nlua_disable_preload INIT(= false);
+
#endif // NVIM_LUA_EXECUTOR_H
diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c
index 3a63f61200..31a2b2d19f 100644
--- a/src/nvim/lua/spell.c
+++ b/src/nvim/lua/spell.c
@@ -1,12 +1,12 @@
// 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 <lua.h>
#include <lauxlib.h>
+#include <lua.h>
+#include "nvim/lua/spell.h"
#include "nvim/spell.h"
#include "nvim/vim.h"
-#include "nvim/lua/spell.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/spell.c.generated.h"
@@ -45,7 +45,7 @@ int nlua_spell_check(lua_State *lstate)
size_t pos = 0;
int capcol = -1;
int no_res = 0;
- const char * result;
+ const char *result;
lua_createtable(lstate, 0, 0);
@@ -90,7 +90,7 @@ int nlua_spell_check(lua_State *lstate)
static const luaL_Reg spell_functions[] = {
{ "check", nlua_spell_check },
- { NULL , NULL }
+ { NULL, NULL }
};
int luaopen_spell(lua_State *L)
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 18a579ed0f..8fde85b163 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -25,12 +25,13 @@
#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
+#include "nvim/lua/spell.h"
#include "nvim/lua/stdlib.h"
#include "nvim/lua/treesitter.h"
#include "nvim/lua/xdiff.h"
-#include "nvim/lua/spell.h"
#include "nvim/macros.h"
#include "nvim/map.h"
#include "nvim/memline.h"
@@ -54,12 +55,12 @@ static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str)
regmatch_T rm;
rm.regprog = *prog;
rm.rm_ic = false;
- bool match = vim_regexec(&rm, str, 0);
+ bool match = vim_regexec(&rm, (char *)str, 0);
*prog = rm.regprog;
if (match) {
- lua_pushinteger(lstate, (lua_Integer)(rm.startp[0]-str));
- lua_pushinteger(lstate, (lua_Integer)(rm.endp[0]-str));
+ lua_pushinteger(lstate, (lua_Integer)(rm.startp[0] - str));
+ lua_pushinteger(lstate, (lua_Integer)(rm.endp[0] - str));
return 2;
}
return 0;
@@ -88,7 +89,7 @@ static int regex_match_line(lua_State *lstate)
}
long bufnr = luaL_checkinteger(lstate, 2);
- long rownr = luaL_checkinteger(lstate, 3);
+ linenr_T rownr = (linenr_T)luaL_checkinteger(lstate, 3);
long start = 0, end = -1;
if (narg >= 4) {
start = luaL_checkinteger(lstate, 4);
@@ -109,7 +110,7 @@ static int regex_match_line(lua_State *lstate)
return luaL_error(lstate, "invalid row");
}
- char_u *line = ml_get_buf(buf, rownr+1, false);
+ char_u *line = ml_get_buf(buf, rownr + 1, false);
size_t len = STRLEN(line);
if (start < 0 || (size_t)start > len) {
@@ -125,7 +126,7 @@ static int regex_match_line(lua_State *lstate)
line[end] = NUL;
}
- int nret = regex_match(lstate, prog, line+start);
+ int nret = regex_match(lstate, prog, line + start);
if (end >= 0) {
line[end] = save;
@@ -207,7 +208,7 @@ static int nlua_str_utf_pos(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
size_t idx = 1;
size_t clen;
for (size_t i = 0; i < s1_len && s1[i] != NUL; i += clen) {
- clen = (size_t)utf_ptr2len_len((const char_u *)(s1)+i, (int)(s1_len-i));
+ clen = (size_t)utf_ptr2len_len((const char_u *)(s1) + i, (int)(s1_len - i));
lua_pushinteger(lstate, (long)i + 1);
lua_rawseti(lstate, -2, (int)idx);
idx++;
@@ -251,7 +252,7 @@ static int nlua_str_utf_end(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
if (offset < 0 || offset > (intptr_t)s1_len) {
return luaL_error(lstate, "index out of range");
}
- int tail_offset = mb_tail_off((char_u *)s1, (char_u *)s1 + offset - 1);
+ int tail_offset = mb_tail_off(s1, s1 + offset - 1);
lua_pushinteger(lstate, tail_offset);
return 1;
}
@@ -294,12 +295,14 @@ int nlua_regex(lua_State *lstate)
TRY_WRAP({
try_start();
- prog = vim_regcomp((char_u *)text, RE_AUTO | RE_MAGIC | RE_STRICT);
+ prog = vim_regcomp((char *)text, RE_AUTO | RE_MAGIC | RE_STRICT);
try_end(&err);
});
if (ERROR_SET(&err)) {
- return luaL_error(lstate, "couldn't parse regex: %s", err.msg);
+ nlua_push_errstr(lstate, "couldn't parse regex: %s", err.msg);
+ api_clear_error(&err);
+ return lua_error(lstate);
}
assert(prog);
@@ -337,18 +340,19 @@ static dict_T *nlua_get_var_scope(lua_State *lstate)
dict = tabpage->tp_vars;
}
} else {
- luaL_error(lstate, "invalid scope", err.msg);
+ luaL_error(lstate, "invalid scope");
return NULL;
}
if (ERROR_SET(&err)) {
- luaL_error(lstate, "FAIL: %s", err.msg);
+ nlua_push_errstr(lstate, "scoped variable: %s", err.msg);
+ api_clear_error(&err);
+ lua_error(lstate);
return NULL;
}
return dict;
}
-
int nlua_setvar(lua_State *lstate)
{
// non-local return if not found
@@ -408,6 +412,12 @@ int nlua_getvar(lua_State *lstate)
const char *name = luaL_checklstring(lstate, 3, &len);
dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ if (di == NULL && dict == &globvardict) { // try to autoload script
+ if (!script_autoload(name, len, false) || aborting()) {
+ return 0; // nil
+ }
+ di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ }
if (di == NULL) {
return 0; // nil
}
@@ -463,44 +473,52 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
return 1;
}
-
-void nlua_state_add_stdlib(lua_State *const lstate)
+void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
{
- // stricmp
- lua_pushcfunction(lstate, &nlua_stricmp);
- lua_setfield(lstate, -2, "stricmp");
- // str_utfindex
- lua_pushcfunction(lstate, &nlua_str_utfindex);
- lua_setfield(lstate, -2, "str_utfindex");
- // str_byteindex
- lua_pushcfunction(lstate, &nlua_str_byteindex);
- lua_setfield(lstate, -2, "str_byteindex");
- // str_utf_pos
- lua_pushcfunction(lstate, &nlua_str_utf_pos);
- lua_setfield(lstate, -2, "str_utf_pos");
- // str_utf_start
- lua_pushcfunction(lstate, &nlua_str_utf_start);
- lua_setfield(lstate, -2, "str_utf_start");
- // str_utf_end
- lua_pushcfunction(lstate, &nlua_str_utf_end);
- lua_setfield(lstate, -2, "str_utf_end");
- // regex
- lua_pushcfunction(lstate, &nlua_regex);
- lua_setfield(lstate, -2, "regex");
- luaL_newmetatable(lstate, "nvim_regex");
- luaL_register(lstate, NULL, regex_meta);
-
- lua_pushvalue(lstate, -1); // [meta, meta]
- lua_setfield(lstate, -2, "__index"); // [meta]
- lua_pop(lstate, 1); // don't use metatable now
-
- // _getvar
- lua_pushcfunction(lstate, &nlua_getvar);
- lua_setfield(lstate, -2, "_getvar");
-
- // _setvar
- lua_pushcfunction(lstate, &nlua_setvar);
- lua_setfield(lstate, -2, "_setvar");
+ if (!is_thread) {
+ // TODO(bfredl): some of basic string functions should already be
+ // (or be easy to make) threadsafe
+
+ // stricmp
+ lua_pushcfunction(lstate, &nlua_stricmp);
+ lua_setfield(lstate, -2, "stricmp");
+ // str_utfindex
+ lua_pushcfunction(lstate, &nlua_str_utfindex);
+ lua_setfield(lstate, -2, "str_utfindex");
+ // str_byteindex
+ lua_pushcfunction(lstate, &nlua_str_byteindex);
+ lua_setfield(lstate, -2, "str_byteindex");
+ // str_utf_pos
+ lua_pushcfunction(lstate, &nlua_str_utf_pos);
+ lua_setfield(lstate, -2, "str_utf_pos");
+ // str_utf_start
+ lua_pushcfunction(lstate, &nlua_str_utf_start);
+ lua_setfield(lstate, -2, "str_utf_start");
+ // str_utf_end
+ lua_pushcfunction(lstate, &nlua_str_utf_end);
+ lua_setfield(lstate, -2, "str_utf_end");
+ // regex
+ lua_pushcfunction(lstate, &nlua_regex);
+ lua_setfield(lstate, -2, "regex");
+ luaL_newmetatable(lstate, "nvim_regex");
+ luaL_register(lstate, NULL, regex_meta);
+
+ lua_pushvalue(lstate, -1); // [meta, meta]
+ lua_setfield(lstate, -2, "__index"); // [meta]
+ lua_pop(lstate, 1); // don't use metatable now
+
+ // _getvar
+ lua_pushcfunction(lstate, &nlua_getvar);
+ lua_setfield(lstate, -2, "_getvar");
+
+ // _setvar
+ 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);
@@ -519,10 +537,18 @@ 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");
}
+
+/// like luaL_error, but allow cleanup
+void nlua_push_errstr(lua_State *L, const char *fmt, ...)
+{
+ va_list argp;
+ va_start(argp, fmt);
+ luaL_where(L, 1);
+ lua_pushvfstring(L, fmt, argp);
+ va_end(argp);
+ lua_concat(L, 2);
+}
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index f4067ad02f..b96193d199 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -16,6 +16,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/buffer.h"
+#include "nvim/lib/kvec.h"
#include "nvim/lua/treesitter.h"
#include "nvim/memline.h"
#include "tree_sitter/api.h"
@@ -104,6 +105,7 @@ static struct luaL_Reg treecursor_meta[] = {
{ NULL, NULL }
};
+static kvec_t(TSQueryCursor *) cursors = KV_INITIAL_VALUE;
static PMap(cstr_t) langs = MAP_INIT;
static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
@@ -208,7 +210,7 @@ int tslua_inspect_lang(lua_State *L)
size_t nsymbols = (size_t)ts_language_symbol_count(lang);
- lua_createtable(L, nsymbols-1, 1); // [retval, symbols]
+ lua_createtable(L, nsymbols - 1, 1); // [retval, symbols]
for (size_t i = 0; i < nsymbols; i++) {
TSSymbolType t = ts_language_symbol_type(lang, i);
if (t == TSSymbolTypeAuxiliary) {
@@ -298,15 +300,15 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position
*bytes_read = 0;
return "";
}
- char_u *line = ml_get_buf(bp, position.row+1, false);
+ char_u *line = ml_get_buf(bp, position.row + 1, false);
size_t len = STRLEN(line);
if (position.column > len) {
*bytes_read = 0;
return "";
}
- size_t tocopy = MIN(len-position.column, BUFSIZE);
+ size_t tocopy = MIN(len - position.column, BUFSIZE);
- memcpy(buf, line+position.column, tocopy);
+ memcpy(buf, line + position.column, tocopy);
// Translate embedded \n to NUL
memchrsub(buf, '\n', '\0', tocopy);
*bytes_read = (uint32_t)tocopy;
@@ -334,7 +336,7 @@ static void push_ranges(lua_State *L, const TSRange *ranges, const unsigned int
lua_pushinteger(L, ranges[i].end_point.column);
lua_rawseti(L, -2, 4);
- lua_rawseti(L, -2, i+1);
+ lua_rawseti(L, -2, i + 1);
}
}
@@ -389,7 +391,7 @@ static int parser_parse(lua_State *L)
return luaL_error(L, "An error occurred when parsing.");
}
- // The new tree will be pushed to the stack, without copy, owwership is now to
+ // The new tree will be pushed to the stack, without copy, ownership is now to
// the lua GC.
// Old tree is still owned by the lua GC.
uint32_t n_ranges = 0;
@@ -527,7 +529,6 @@ static int parser_set_ranges(lua_State *L)
size_t tbl_len = lua_objlen(L, 2);
TSRange *ranges = xmalloc(sizeof(TSRange) * tbl_len);
-
// [ parser, ranges ]
for (size_t index = 0; index < tbl_len; index++) {
lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ]
@@ -556,7 +557,6 @@ static int parser_get_ranges(lua_State *L)
return 1;
}
-
// Tree methods
/// push tree interface on lua stack.
@@ -654,7 +654,6 @@ static bool node_check(lua_State *L, int index, TSNode *res)
return false;
}
-
static int node_tostring(lua_State *L)
{
TSNode node;
@@ -1037,7 +1036,7 @@ static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx)
{
for (int i = 0; i < match->capture_count; i++) {
push_node(L, match->captures[i].node, nodeidx);
- lua_rawseti(L, -2, match->captures[i].index+1);
+ lua_rawseti(L, -2, match->captures[i].index + 1);
}
}
@@ -1049,7 +1048,7 @@ static int query_next_match(lua_State *L)
TSQuery *query = query_check(L, lua_upvalueindex(3));
TSQueryMatch match;
if (ts_query_cursor_next_match(cursor, &match)) {
- lua_pushinteger(L, match.pattern_index+1); // [index]
+ lua_pushinteger(L, match.pattern_index + 1); // [index]
lua_createtable(L, ts_query_capture_count(query), 2); // [index, match]
set_match(L, &match, lua_upvalueindex(2));
return 2;
@@ -1057,7 +1056,6 @@ static int query_next_match(lua_State *L)
return 0;
}
-
static int query_next_capture(lua_State *L)
{
// Upvalues are:
@@ -1082,7 +1080,7 @@ static int query_next_capture(lua_State *L)
if (ts_query_cursor_next_capture(cursor, &match, &capture_index)) {
TSQueryCapture capture = match.captures[capture_index];
- lua_pushinteger(L, capture.index+1); // [index]
+ lua_pushinteger(L, capture.index + 1); // [index]
push_node(L, capture.node, lua_upvalueindex(2)); // [index, node]
// Now check if we need to run the predicates
@@ -1094,7 +1092,7 @@ static int query_next_capture(lua_State *L)
lua_pushvalue(L, lua_upvalueindex(4)); // [index, node, match]
set_match(L, &match, lua_upvalueindex(2));
- lua_pushinteger(L, match.pattern_index+1);
+ lua_pushinteger(L, match.pattern_index + 1);
lua_setfield(L, -2, "pattern");
if (match.capture_count > 1) {
@@ -1116,13 +1114,17 @@ static int node_rawquery(lua_State *L)
return 0;
}
TSQuery *query = query_check(L, 2);
- // TODO(bfredl): these are expensive allegedly,
- // use a reuse list later on?
- TSQueryCursor *cursor = ts_query_cursor_new();
+
+ TSQueryCursor *cursor;
+ if (kv_size(cursors) > 0) {
+ cursor = kv_pop(cursors);
+ } else {
+ cursor = ts_query_cursor_new();
+ }
// TODO(clason): API introduced after tree-sitter release 0.19.5
// remove guard when minimum ts version is bumped to 0.19.6+
#ifdef NVIM_TS_HAS_SET_MATCH_LIMIT
- ts_query_cursor_set_match_limit(cursor, 32);
+ ts_query_cursor_set_match_limit(cursor, 64);
#endif
ts_query_cursor_exec(cursor, query, node);
@@ -1161,7 +1163,8 @@ static int node_rawquery(lua_State *L)
static int querycursor_gc(lua_State *L)
{
TSLua_cursor *ud = luaL_checkudata(L, 1, TS_META_QUERYCURSOR);
- ts_query_cursor_delete(ud->cursor);
+ kv_push(cursors, ud->cursor);
+ ud->cursor = NULL;
return 0;
}
@@ -1198,7 +1201,6 @@ int tslua_parse_query(lua_State *L)
return 1;
}
-
static const char *query_err_string(TSQueryError err)
{
switch (err) {
@@ -1273,7 +1275,7 @@ static int query_inspect(lua_State *L)
&strlen);
lua_pushlstring(L, str, strlen); // [retval, patterns, pat, pred, item]
} else if (step[k].type == TSQueryPredicateStepTypeCapture) {
- lua_pushnumber(L, step[k].value_id+1); // [..., pat, pred, item]
+ lua_pushnumber(L, step[k].value_id + 1); // [..., pat, pred, item]
} else {
abort();
}
@@ -1281,7 +1283,7 @@ static int query_inspect(lua_State *L)
}
// last predicate should have ended with TypeDone
lua_pop(L, 1); // [retval, patters, pat]
- lua_rawseti(L, -2, i+1); // [retval, patterns]
+ lua_rawseti(L, -2, i + 1); // [retval, patterns]
}
lua_setfield(L, -2, "patterns"); // [retval]
@@ -1291,7 +1293,7 @@ static int query_inspect(lua_State *L)
uint32_t strlen;
const char *str = ts_query_capture_name_for_id(query, i, &strlen);
lua_pushlstring(L, str, strlen); // [retval, captures, capture]
- lua_rawseti(L, -2, i+1);
+ lua_rawseti(L, -2, i + 1);
}
lua_setfield(L, -2, "captures"); // [retval]
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
deleted file mode 100644
index 731e7d8d36..0000000000
--- a/src/nvim/lua/vim.lua
+++ /dev/null
@@ -1,711 +0,0 @@
--- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib)
---
--- Lua code lives in one of three places:
--- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the
--- `inspect` and `lpeg` modules.
--- 2. runtime/lua/vim/shared.lua: Code shared between Nvim and tests.
--- (This will go away if we migrate to nvim as the test-runner.)
--- 3. src/nvim/lua/: Compiled-into Nvim itself.
---
--- Guideline: "If in doubt, put it in the runtime".
---
--- Most functions should live directly in `vim.`, not in submodules.
--- The only "forbidden" names are those claimed by legacy `if_lua`:
--- $ vim
--- :lua for k,v in pairs(vim) do print(k) end
--- buffer
--- open
--- window
--- lastline
--- firstline
--- type
--- line
--- eval
--- dict
--- beep
--- list
--- command
---
--- Reference (#6580):
--- - https://github.com/luafun/luafun
--- - https://github.com/rxi/lume
--- - http://leafo.net/lapis/reference/utilities.html
--- - https://github.com/torch/paths
--- - https://github.com/bakpakin/Fennel (pretty print, repl)
--- - https://github.com/howl-editor/howl/tree/master/lib/howl/util
-
-local vim = vim
-assert(vim)
-
-vim.inspect = package.loaded['vim.inspect']
-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
-
-table.insert(package.loaders, 1, 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, {
- __index = function(t, key)
- if key == 'treesitter' then
- t.treesitter = require('vim.treesitter')
- return t.treesitter
- elseif key == 'F' then
- t.F = require('vim.F')
- return t.F
- elseif require('vim.uri')[key] ~= nil then
- -- Expose all `vim.uri` functions on the `vim` module.
- t[key] = require('vim.uri')[key]
- return t[key]
- elseif key == 'lsp' then
- t.lsp = require('vim.lsp')
- return t.lsp
- elseif key == 'highlight' then
- t.highlight = require('vim.highlight')
- return t.highlight
- elseif key == 'diagnostic' then
- t.diagnostic = require('vim.diagnostic')
- return t.diagnostic
- elseif key == 'ui' then
- t.ui = require('vim.ui')
- return t.ui
- elseif key == 'keymap' then
- t.keymap = require('vim.keymap')
- return t.keymap
- end
- end
-})
-
-vim.log = {
- levels = {
- TRACE = 0;
- DEBUG = 1;
- INFO = 2;
- WARN = 3;
- ERROR = 4;
- }
-}
-
--- Internal-only until comments in #8107 are addressed.
--- Returns:
--- {errcode}, {output}
-function vim._system(cmd)
- local out = vim.fn.system(cmd)
- local err = vim.v.shell_error
- return err, out
-end
-
--- Gets process info from the `ps` command.
--- Used by nvim_get_proc() as a fallback.
-function vim._os_proc_info(pid)
- if pid == nil or pid <= 0 or type(pid) ~= 'number' then
- error('invalid pid')
- end
- local cmd = { 'ps', '-p', pid, '-o', 'comm=', }
- local err, name = vim._system(cmd)
- if 1 == err and vim.trim(name) == '' then
- return {} -- Process not found.
- elseif 0 ~= err then
- error('command failed: '..vim.fn.string(cmd))
- end
- local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', })
- -- Remove trailing whitespace.
- name = vim.trim(name):gsub('^.*/', '')
- ppid = tonumber(ppid) or -1
- return {
- name = name,
- pid = pid,
- ppid = ppid,
- }
-end
-
--- Gets process children from the `pgrep` command.
--- Used by nvim_get_proc_children() as a fallback.
-function vim._os_proc_children(ppid)
- if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
- error('invalid ppid')
- end
- local cmd = { 'pgrep', '-P', ppid, }
- local err, rv = vim._system(cmd)
- if 1 == err and vim.trim(rv) == '' then
- return {} -- Process not found.
- elseif 0 ~= err then
- error('command failed: '..vim.fn.string(cmd))
- end
- local children = {}
- for s in rv:gmatch('%S+') do
- local i = tonumber(s)
- if i ~= nil then
- table.insert(children, i)
- end
- end
- return children
-end
-
--- TODO(ZyX-I): Create compatibility layer.
-
---- Return a human-readable representation of the given object.
----
----@see https://github.com/kikito/inspect.lua
----@see https://github.com/mpeterv/vinspect
-local function inspect(object, options) -- luacheck: no unused
- error(object, options) -- Stub for gen_vimdoc.py
-end
-
-do
- local tdots, tick, got_line1 = 0, 0, false
-
- --- Paste handler, invoked by |nvim_paste()| when a conforming UI
- --- (such as the |TUI|) pastes text into the editor.
- ---
- --- Example: To remove ANSI color codes when pasting:
- --- <pre>
- --- vim.paste = (function(overridden)
- --- return function(lines, phase)
- --- for i,line in ipairs(lines) do
- --- -- Scrub ANSI color codes from paste input.
- --- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
- --- end
- --- overridden(lines, phase)
- --- end
- --- end)(vim.paste)
- --- </pre>
- ---
- ---@see |paste|
- ---
- ---@param lines |readfile()|-style list of lines to paste. |channel-lines|
- ---@param phase -1: "non-streaming" paste: the call contains all lines.
- --- If paste is "streamed", `phase` indicates the stream state:
- --- - 1: starts the paste (exactly once)
- --- - 2: continues the paste (zero or more times)
- --- - 3: ends the paste (exactly once)
- ---@returns false if client should cancel the paste.
- function vim.paste(lines, phase)
- local call = vim.api.nvim_call_function
- local now = vim.loop.now()
- local mode = call('mode', {}):sub(1,1)
- if phase < 2 then -- Reset flags.
- tdots, tick, got_line1 = now, 0, false
- elseif mode ~= 'c' then
- vim.api.nvim_command('undojoin')
- end
- if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line.
- got_line1 = (#lines > 1)
- vim.api.nvim_set_option('paste', true) -- For nvim_input().
- local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub.
- vim.api.nvim_input(line1)
- vim.api.nvim_set_option('paste', false)
- elseif mode ~= 'c' then
- if phase < 2 and mode:find('^[vV\22sS\19]') then
- vim.api.nvim_command([[exe "normal! \<Del>"]])
- vim.api.nvim_put(lines, 'c', false, true)
- elseif phase < 2 and not mode:find('^[iRt]') then
- vim.api.nvim_put(lines, 'c', true, true)
- -- XXX: Normal-mode: workaround bad cursor-placement after first chunk.
- vim.api.nvim_command('normal! a')
- elseif phase < 2 and mode == 'R' then
- local nchars = 0
- for _, line in ipairs(lines) do
- nchars = nchars + line:len()
- end
- local row, col = unpack(vim.api.nvim_win_get_cursor(0))
- local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1]
- local firstline = lines[1]
- firstline = bufline:sub(1, col)..firstline
- lines[1] = firstline
- lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len())
- vim.api.nvim_buf_set_lines(0, row-1, row, false, lines)
- else
- vim.api.nvim_put(lines, 'c', false, true)
- end
- end
- if phase ~= -1 and (now - tdots >= 100) then
- local dots = ('.'):rep(tick % 4)
- tdots = now
- tick = tick + 1
- -- Use :echo because Lua print('') is a no-op, and we want to clear the
- -- message when there are zero dots.
- vim.api.nvim_command(('echo "%s"'):format(dots))
- end
- if phase == -1 or phase == 3 then
- vim.api.nvim_command('redraw'..(tick > 1 and '|echo ""' or ''))
- end
- return true -- Paste will not continue if not returning `true`.
- end
-end
-
---- Defers callback `cb` until the Nvim API is safe to call.
----
----@see |lua-loop-callbacks|
----@see |vim.schedule()|
----@see |vim.in_fast_event()|
-function vim.schedule_wrap(cb)
- return (function (...)
- local args = vim.F.pack_len(...)
- vim.schedule(function() cb(vim.F.unpack_len(args)) end)
- end)
-end
-
---- <Docs described in |vim.empty_dict()| >
----@private
-function vim.empty_dict()
- return setmetatable({}, vim._empty_dict_mt)
-end
-
--- vim.fn.{func}(...)
-vim.fn = setmetatable({}, {
- __index = function(t, key)
- local _fn
- if vim.api[key] ~= nil then
- _fn = function()
- error(string.format("Tried to call API function with vim.fn: use vim.api.%s instead", key))
- end
- else
- _fn = function(...)
- return vim.call(key, ...)
- end
- end
- t[key] = _fn
- return _fn
- end
-})
-
-vim.funcref = function(viml_func_name)
- return vim.fn[viml_func_name]
-end
-
--- An easier alias for commands.
-vim.cmd = function(command)
- return vim.api.nvim_exec(command, false)
-end
-
--- These are the vim.env/v/g/o/bo/wo variable magic accessors.
-do
- local validate = vim.validate
-
- --@private
- local function make_dict_accessor(scope, handle)
- validate {
- scope = {scope, 's'};
- }
- local mt = {}
- function mt:__newindex(k, v)
- return vim._setvar(scope, handle or 0, k, v)
- end
- function mt:__index(k)
- if handle == nil and type(k) == 'number' then
- return make_dict_accessor(scope, k)
- end
- return vim._getvar(scope, handle or 0, k)
- end
- return setmetatable({}, mt)
- end
-
- vim.g = make_dict_accessor('g', false)
- vim.v = make_dict_accessor('v', false)
- vim.b = make_dict_accessor('b')
- vim.w = make_dict_accessor('w')
- vim.t = make_dict_accessor('t')
-end
-
---- Get a table of lines with start, end columns for a region marked by two points
----
----@param bufnr number of buffer
----@param pos1 (line, column) tuple marking beginning of region
----@param pos2 (line, column) tuple marking end of region
----@param regtype type of selection (:help setreg)
----@param inclusive boolean indicating whether the selection is end-inclusive
----@return region lua table of the form {linenr = {startcol,endcol}}
-function vim.region(bufnr, pos1, pos2, regtype, inclusive)
- if not vim.api.nvim_buf_is_loaded(bufnr) then
- vim.fn.bufload(bufnr)
- end
-
- -- check that region falls within current buffer
- local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
- pos1[1] = math.min(pos1[1], buf_line_count - 1)
- pos2[1] = math.min(pos2[1], buf_line_count - 1)
-
- -- in case of block selection, columns need to be adjusted for non-ASCII characters
- -- TODO: handle double-width characters
- local bufline
- if regtype:byte() == 22 then
- bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1]
- pos1[2] = vim.str_utfindex(bufline, pos1[2])
- end
-
- local region = {}
- for l = pos1[1], pos2[1] do
- local c1, c2
- if regtype:byte() == 22 then -- block selection: take width from regtype
- c1 = pos1[2]
- c2 = c1 + regtype:sub(2)
- -- and adjust for non-ASCII characters
- bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
- if c1 < #bufline then
- c1 = vim.str_byteindex(bufline, c1)
- end
- if c2 < #bufline then
- c2 = vim.str_byteindex(bufline, c2)
- end
- else
- c1 = (l == pos1[1]) and (pos1[2]) or 0
- c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1
- end
- table.insert(region, l, {c1, c2})
- end
- return region
-end
-
---- Defers calling `fn` until `timeout` ms passes.
----
---- Use to do a one-shot timer that calls `fn`
---- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are
---- safe to call.
----@param fn Callback to call once `timeout` expires
----@param timeout Number of milliseconds to wait before calling `fn`
----@return timer luv timer object
-function vim.defer_fn(fn, timeout)
- vim.validate { fn = { fn, 'c', true}; }
- local timer = vim.loop.new_timer()
- timer:start(timeout, 0, vim.schedule_wrap(function()
- timer:stop()
- timer:close()
-
- fn()
- end))
-
- return timer
-end
-
-
---- Display a notification to the user.
----
---- This function can be overridden by plugins to display notifications using a
---- custom provider (such as the system notification provider). By default,
---- writes to |:messages|.
----
----@param msg string Content of the notification to show to the user.
----@param level number|nil One of the values from |vim.log.levels|.
----@param opts table|nil Optional parameters. Unused by default.
-function vim.notify(msg, level, opts) -- luacheck: no unused args
- if level == vim.log.levels.ERROR then
- vim.api.nvim_err_writeln(msg)
- elseif level == vim.log.levels.WARN then
- vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {})
- else
- vim.api.nvim_echo({{msg}}, true, {})
- end
-end
-
-do
- local notified = {}
-
- --- Display a notification only one time.
- ---
- --- Like |vim.notify()|, but subsequent calls with the same message will not
- --- display a notification.
- ---
- ---@param msg string Content of the notification to show to the user.
- ---@param level number|nil One of the values from |vim.log.levels|.
- ---@param opts table|nil Optional parameters. Unused by default.
- function vim.notify_once(msg, level, opts) -- luacheck: no unused args
- if not notified[msg] then
- vim.notify(msg, level, opts)
- notified[msg] = true
- end
- end
-end
-
----@private
-function vim.register_keystroke_callback()
- error('vim.register_keystroke_callback is deprecated, instead use: vim.on_key')
-end
-
-local on_key_cbs = {}
-
---- Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
---- yes every, input key.
----
---- The Nvim command-line option |-w| is related but does not support callbacks
---- and cannot be toggled dynamically.
----
----@param fn function: Callback function. It should take one string argument.
---- On each key press, Nvim passes the key char to fn(). |i_CTRL-V|
---- If {fn} is nil, it removes the callback for the associated {ns_id}
----@param ns_id number? Namespace ID. If nil or 0, generates and returns a new
---- |nvim_create_namespace()| id.
----
----@return number Namespace id associated with {fn}. Or count of all callbacks
----if on_key() is called without arguments.
----
----@note {fn} will be removed if an error occurs while calling.
----@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
----@note {fn} will receive the keys after mappings have been evaluated
-function vim.on_key(fn, ns_id)
- if fn == nil and ns_id == nil then
- return #on_key_cbs
- end
-
- vim.validate {
- fn = { fn, 'c', true},
- ns_id = { ns_id, 'n', true }
- }
-
- if ns_id == nil or ns_id == 0 then
- ns_id = vim.api.nvim_create_namespace('')
- end
-
- on_key_cbs[ns_id] = fn
- return ns_id
-end
-
---- Executes the on_key callbacks.
----@private
-function vim._on_key(char)
- local failed_ns_ids = {}
- local failed_messages = {}
- for k, v in pairs(on_key_cbs) do
- local ok, err_msg = pcall(v, char)
- if not ok then
- vim.on_key(nil, k)
- table.insert(failed_ns_ids, k)
- table.insert(failed_messages, err_msg)
- end
- end
-
- if failed_ns_ids[1] then
- error(string.format(
- "Error executing 'on_key' with ns_ids '%s'\n Messages: %s",
- table.concat(failed_ns_ids, ", "),
- table.concat(failed_messages, "\n")))
- end
-end
-
---- Generate a list of possible completions for the string.
---- String starts with ^ and then has the pattern.
----
---- 1. Can we get it to just return things in the global namespace with that name prefix
---- 2. Can we get it to return things from global namespace even with `print(` in front.
-function vim._expand_pat(pat, env)
- env = env or _G
-
- pat = string.sub(pat, 2, #pat)
-
- if pat == '' then
- local result = vim.tbl_keys(env)
- table.sort(result)
- return result, 0
- end
-
- -- TODO: We can handle spaces in [] ONLY.
- -- We should probably do that at some point, just for cooler completion.
- -- TODO: We can suggest the variable names to go in []
- -- This would be difficult as well.
- -- Probably just need to do a smarter match than just `:match`
-
- -- Get the last part of the pattern
- local last_part = pat:match("[%w.:_%[%]'\"]+$")
- if not last_part then return {}, 0 end
-
- local parts, search_index = vim._expand_pat_get_parts(last_part)
-
- local match_part = string.sub(last_part, search_index, #last_part)
- local prefix_match_pat = string.sub(pat, 1, #pat - #match_part) or ''
-
- local final_env = env
-
- for _, part in ipairs(parts) do
- if type(final_env) ~= 'table' then
- return {}, 0
- end
- local key
-
- -- Normally, we just have a string
- -- Just attempt to get the string directly from the environment
- if type(part) == "string" then
- key = part
- else
- -- However, sometimes you want to use a variable, and complete on it
- -- With this, you have the power.
-
- -- MY_VAR = "api"
- -- vim[MY_VAR]
- -- -> _G[MY_VAR] -> "api"
- local result_key = part[1]
- if not result_key then
- return {}, 0
- end
-
- local result = rawget(env, result_key)
-
- if result == nil then
- return {}, 0
- end
-
- key = result
- end
- local field = rawget(final_env, key)
- if field == nil then
- local mt = getmetatable(final_env)
- if mt and type(mt.__index) == "table" then
- field = rawget(mt.__index, key)
- end
- end
- final_env = field
-
- if not final_env then
- return {}, 0
- end
- end
-
- local keys = {}
- ---@private
- local function insert_keys(obj)
- for k,_ in pairs(obj) do
- if type(k) == "string" and string.sub(k,1,string.len(match_part)) == match_part then
- table.insert(keys,k)
- end
- end
- end
-
- if type(final_env) == "table" then
- insert_keys(final_env)
- end
- local mt = getmetatable(final_env)
- if mt and type(mt.__index) == "table" then
- insert_keys(mt.__index)
- end
-
- table.sort(keys)
-
- return keys, #prefix_match_pat
-end
-
-vim._expand_pat_get_parts = function(lua_string)
- local parts = {}
-
- local accumulator, search_index = '', 1
- local in_brackets, bracket_end = false, -1
- local string_char = nil
- for idx = 1, #lua_string do
- local s = lua_string:sub(idx, idx)
-
- if not in_brackets and (s == "." or s == ":") then
- table.insert(parts, accumulator)
- accumulator = ''
-
- search_index = idx + 1
- elseif s == "[" then
- in_brackets = true
-
- table.insert(parts, accumulator)
- accumulator = ''
-
- search_index = idx + 1
- elseif in_brackets then
- if idx == bracket_end then
- in_brackets = false
- search_index = idx + 1
-
- if string_char == "VAR" then
- table.insert(parts, { accumulator })
- accumulator = ''
-
- string_char = nil
- end
- elseif not string_char then
- bracket_end = string.find(lua_string, ']', idx, true)
-
- if s == '"' or s == "'" then
- string_char = s
- elseif s ~= ' ' then
- string_char = "VAR"
- accumulator = s
- end
- elseif string_char then
- if string_char ~= s then
- accumulator = accumulator .. s
- else
- table.insert(parts, accumulator)
- accumulator = ''
-
- string_char = nil
- end
- end
- else
- accumulator = accumulator .. s
- end
- end
-
- parts = vim.tbl_filter(function(val) return #val > 0 end, parts)
-
- return parts, search_index
-end
-
----Prints given arguments in human-readable format.
----Example:
----<pre>
---- -- Print highlight group Normal and store it's contents in a variable.
---- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true))
----</pre>
----@see |vim.inspect()|
----@return given arguments.
-function vim.pretty_print(...)
- local objects = {}
- for i = 1, select('#', ...) do
- local v = select(i, ...)
- table.insert(objects, vim.inspect(v))
- end
-
- print(table.concat(objects, ' '))
- return ...
-end
-
-return module
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index b2e971f9f3..71f85385b6 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -74,7 +74,7 @@ static int hunk_locations_cb(long start_a, long count_a, long start_b, long coun
lua_pushinteger(lstate, count_b);
lua_rawseti(lstate, -2, 4);
- lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2)+1);
+ lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1);
return 0;
}
@@ -171,6 +171,7 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg,
goto exit_1;
}
if (strequal("unified", v->data.string.data)) {
+ // the default
} else if (strequal("indices", v->data.string.data)) {
had_result_type_indices = true;
} else {
@@ -184,11 +185,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg,
if (strequal("myers", v->data.string.data)) {
// default
} else if (strequal("minimal", v->data.string.data)) {
- cfg->flags |= XDF_NEED_MINIMAL;
+ params->flags |= XDF_NEED_MINIMAL;
} else if (strequal("patience", v->data.string.data)) {
- cfg->flags |= XDF_PATIENCE_DIFF;
+ params->flags |= XDF_PATIENCE_DIFF;
} else if (strequal("histogram", v->data.string.data)) {
- cfg->flags |= XDF_HISTOGRAM_DIFF;
+ params->flags |= XDF_HISTOGRAM_DIFF;
} else {
api_set_error(err, kErrorTypeValidation, "not a valid algorithm");
goto exit_1;