diff options
Diffstat (limited to 'src/nvim/lua')
| -rw-r--r-- | src/nvim/lua/converter.c | 37 | ||||
| -rw-r--r-- | src/nvim/lua/executor.c | 673 | ||||
| -rw-r--r-- | src/nvim/lua/executor.h | 25 | ||||
| -rw-r--r-- | src/nvim/lua/spell.c | 2 | ||||
| -rw-r--r-- | src/nvim/lua/stdlib.c | 93 | ||||
| -rw-r--r-- | src/nvim/lua/treesitter.c | 4 | ||||
| -rw-r--r-- | src/nvim/lua/vim.lua | 105 | ||||
| -rw-r--r-- | src/nvim/lua/xdiff.c | 6 | 
8 files changed, 743 insertions, 202 deletions
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index b6792a5a97..2b54e56df1 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -156,7 +156,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)              && ret.string_keys_num == 0)) {        ret.type = kObjectTypeArray;        if (tsize == 0 && lua_getmetatable(lstate, -1)) { -        nlua_pushref(lstate, nlua_empty_dict_ref); +        nlua_pushref(lstate, nlua_global_refs->empty_dict_ref);          if (lua_rawequal(lstate, -2, -1)) {            ret.type = kObjectTypeDictionary;          } @@ -316,7 +316,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,7 +389,7 @@ 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, @@ -401,7 +401,7 @@ nlua_pop_typval_table_processing_end:      }      case LUA_TUSERDATA: {        // TODO(bfredl): check mt.__call and convert to function? -      nlua_pushref(lstate, nlua_nil_ref); +      nlua_pushref(lstate, nlua_global_refs->nil_ref);        bool is_nil = lua_rawequal(lstate, -2, -1);        lua_pop(lstate, 1);        if (is_nil) { @@ -445,7 +445,7 @@ static bool typval_conv_special = false;      if (typval_conv_special) { \        lua_pushnil(lstate); \      } else { \ -      nlua_pushref(lstate, nlua_nil_ref); \ +      nlua_pushref(lstate, nlua_global_refs->nil_ref); \      } \    } while (0) @@ -495,7 +495,7 @@ static bool typval_conv_special = false;        nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \      } else { \        lua_createtable(lstate, 0, 0); \ -      nlua_pushref(lstate, nlua_empty_dict_ref); \ +      nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); \        lua_setmetatable(lstate, -2); \      } \    } while (0) @@ -617,6 +617,14 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special)      semsg(_("E1502: Lua failed to grow stack to %i"), initial_size + 4);      return false;    } +  if (tv->v_type == VAR_FUNC) { +    ufunc_T *fp = find_func(tv->vval.v_string); +    assert(fp != NULL); +    if (fp->uf_cb == nlua_CFunction_func_call) { +      nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); +      return true; +    } +  }    if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) {      return false;    } @@ -726,7 +734,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, bool special    } else {      lua_createtable(lstate, 0, (int)dict.size);      if (dict.size == 0 && !special) { -      nlua_pushref(lstate, nlua_empty_dict_ref); +      nlua_pushref(lstate, nlua_global_refs->empty_dict_ref);        lua_setmetatable(lstate, -2);      }    } @@ -774,7 +782,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)      if (special) {        lua_pushnil(lstate);      } else { -      nlua_pushref(lstate, nlua_nil_ref); +      nlua_pushref(lstate, nlua_global_refs->nil_ref);      }      break;    case kObjectTypeLuaRef: { @@ -1191,14 +1199,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 +1240,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;  } @@ -1242,7 +1250,12 @@ LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)    FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \    { \      type ret; \ -    ret = (type)lua_tonumber(lstate, -1); \ +    if (lua_type(lstate, -1) != LUA_TNUMBER) { \ +      api_set_error(err, kErrorTypeValidation, "Expected Lua number"); \ +      ret = (type)-1; \ +    } else { \ +      ret = (type)lua_tonumber(lstate, -1); \ +    } \      lua_pop(lstate, 1); \      return ret; \    } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 107ff22913..7ac80f01f0 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" @@ -15,9 +16,11 @@  #include "nvim/change.h"  #include "nvim/cursor.h"  #include "nvim/eval/userfunc.h" +#include "nvim/eval/typval.h"  #include "nvim/event/loop.h"  #include "nvim/event/time.h"  #include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h"  #include "nvim/ex_getln.h"  #include "nvim/extmark.h"  #include "nvim/func_attr.h" @@ -43,6 +46,8 @@ static int in_fast_callback = 0;  // Initialized in nlua_init().  static lua_State *global_lstate = NULL; +static uv_thread_t main_thread; +  typedef struct {    Error err;    String lua_err_str; @@ -63,11 +68,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. @@ -120,8 +130,21 @@ static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL  static void nlua_luv_error_event(void **argv)  {    char *error = (char *)argv[0]; +  luv_err_t type = (luv_err_t)(intptr_t)argv[1];    msg_ext_set_kind("lua_error"); -  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);  } @@ -146,7 +169,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 @@ -160,12 +183,112 @@ 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"));    } @@ -182,7 +305,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); @@ -300,6 +423,152 @@ 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 void nlua_common_package_init(lua_State *lstate) +  FUNC_ATTR_NONNULL_ALL +{ +  { +    const char *code = (char *)&shared_module[0]; +    if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua") +        || nlua_pcall(lstate, 0, 0)) { +      nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n")); +      return; +    } +  } + +  { +    const char *code = (char *)&lua_load_package_module[0]; +    if (luaL_loadbuffer(lstate, code, sizeof(lua_load_package_module) - 1, "@vim/_load_package.lua") +        || lua_pcall(lstate, 0, 0, 0)) { +      nlua_error(lstate, _("E5106: Error while creating _load_package module: %.*s")); +      return; +    } +  } + +  { +    lua_getglobal(lstate, "package");  // [package] +    lua_getfield(lstate, -1, "loaded");  // [package, loaded] + +    const char *code = (char *)&inspect_module[0]; +    if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua") +        || nlua_pcall(lstate, 0, 1)) { +      nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n")); +      return; +    } + +    // [package, loaded, inspect] +    lua_setfield(lstate, -2, "vim.inspect");  // [package, loaded] +  } + +  { +    const char *code = (char *)&lua_F_module[0]; +    if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua") +        || nlua_pcall(lstate, 0, 1)) { +      nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n")); +      return; +    } +    // [package, loaded, module] +    lua_setfield(lstate, -2, "vim.F");  // [package, loaded] + +    lua_pop(lstate, 2);  // [] +  } +} +  /// Initialize lua interpreter state  ///  /// Called by lua interpreter itself to initialize state. @@ -360,78 +629,38 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL    lua_pushcfunction(lstate, &nlua_wait);    lua_setfield(lstate, -2, "wait"); -  // vim.NIL -  lua_newuserdata(lstate, 0); -  lua_createtable(lstate, 0, 0); -  lua_pushcfunction(lstate, &nlua_nil_tostring); -  lua_setfield(lstate, -2, "__tostring"); -  lua_setmetatable(lstate, -2); -  nlua_nil_ref = nlua_ref(lstate, -1); -  lua_pushvalue(lstate, -1); -  lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL"); -  lua_setfield(lstate, -2, "NIL"); - -  // vim._empty_dict_mt -  lua_createtable(lstate, 0, 0); -  lua_pushcfunction(lstate, &nlua_empty_dict_tostring); -  lua_setfield(lstate, -2, "__tostring"); -  nlua_empty_dict_ref = nlua_ref(lstate, -1); -  lua_pushvalue(lstate, -1); -  lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict"); -  lua_setfield(lstate, -2, "_empty_dict_mt"); +  nlua_common_vim_init(lstate, false);    // internal vim._treesitter... API    nlua_add_treesitter(lstate); -  // vim.loop -  luv_set_loop(lstate, &main_loop.uv); -  luv_set_callback(lstate, nlua_luv_cfpcall); -  luaopen_luv(lstate); -  lua_pushvalue(lstate, -1); -  lua_setfield(lstate, -3, "loop"); - -  // package.loaded.luv = vim.loop -  // otherwise luv will be reinitialized when require'luv' -  lua_getglobal(lstate, "package"); -  lua_getfield(lstate, -1, "loaded"); -  lua_pushvalue(lstate, -3); -  lua_setfield(lstate, -2, "luv"); -  lua_pop(lstate, 3); - -  nlua_state_add_stdlib(lstate); +  nlua_state_add_stdlib(lstate, false);    lua_setglobal(lstate, "vim"); -  { -    const char *code = (char *)&shared_module[0]; -    if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua") -        || nlua_pcall(lstate, 0, 0)) { -      nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n")); -      return 1; -    } -  } +  nlua_common_package_init(lstate);    {      lua_getglobal(lstate, "package");  // [package]      lua_getfield(lstate, -1, "loaded");  // [package, loaded] -    const char *code = (char *)&inspect_module[0]; -    if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua") +    char *code = (char *)&lua_filetype_module[0]; +    if (luaL_loadbuffer(lstate, code, sizeof(lua_filetype_module) - 1, "@vim/filetype.lua")          || nlua_pcall(lstate, 0, 1)) { -      nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n")); +      nlua_error(lstate, _("E5106: Error while creating vim.filetype module: %.*s"));        return 1;      } -    // [package, loaded, inspect] -    lua_setfield(lstate, -2, "vim.inspect");  // [package, loaded] +    // [package, loaded, module] +    lua_setfield(lstate, -2, "vim.filetype");  // [package, loaded] -    code = (char *)&lua_F_module[0]; -    if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua") +    code = (char *)&lua_keymap_module[0]; +    if (luaL_loadbuffer(lstate, code, sizeof(lua_keymap_module) - 1, "@vim/keymap.lua")          || nlua_pcall(lstate, 0, 1)) { -      nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n")); +      nlua_error(lstate, _("E5106: Error while creating vim.keymap module: %.*s"));        return 1;      }      // [package, loaded, module] -    lua_setfield(lstate, -2, "vim.F");  // [package, loaded] +    lua_setfield(lstate, -2, "vim.keymap");  // [package, loaded]      lua_pop(lstate, 2);  // []    } @@ -484,9 +713,60 @@ void nlua_init(void)    luaL_openlibs(lstate);    nlua_state_init(lstate); +  luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem); +    global_lstate = lstate; + +  main_thread = uv_thread_self();  } +static lua_State *nlua_thread_acquire_vm(void) +{ +  // If it is called from the main thread, it will attempt to rebuild the cache. +  const uv_thread_t self = uv_thread_self(); +  if (uv_thread_equal(&main_thread, &self)) { +    runtime_search_path_validate(); +  } + +  lua_State *lstate = luaL_newstate(); + +  // Add in the lua standard libraries +  luaL_openlibs(lstate); + +  // print +  lua_pushcfunction(lstate, &nlua_print); +  lua_setglobal(lstate, "print"); + +  lua_pushinteger(lstate, 0); +  lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount"); + +  // vim +  lua_newtable(lstate); + +  nlua_common_vim_init(lstate, true); + +  nlua_state_add_stdlib(lstate, true); + +  lua_setglobal(lstate, "vim"); + +  nlua_common_package_init(lstate); + +  lua_getglobal(lstate, "vim"); +  lua_getglobal(lstate, "package"); +  lua_getfield(lstate, -1, "loaded"); +  lua_getfield(lstate, -1, "vim.inspect"); +  lua_setfield(lstate, -4, "inspect"); +  lua_pop(lstate, 3); + +  lua_getglobal(lstate, "vim"); +  lua_createtable(lstate, 0, 0); +  lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime); +  lua_setfield(lstate, -2, "nvim__get_runtime"); +  lua_setfield(lstate, -2, "api"); +  lua_pop(lstate, 1); + +  return lstate; +}  void nlua_free_all_mem(void)  { @@ -494,26 +774,30 @@ void nlua_free_all_mem(void)      return;    }    lua_State *lstate = global_lstate; +  nlua_common_free_all_mem(lstate); +} -  nlua_unref(lstate, nlua_nil_ref); -  nlua_unref(lstate, nlua_empty_dict_ref); +static void nlua_common_free_all_mem(lua_State *lstate) +  FUNC_ATTR_NONNULL_ALL +{ +  nlua_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]; @@ -591,9 +875,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 }); @@ -602,10 +895,12 @@ static int nlua_print(lua_State *const lstate)  nlua_print_error:    ga_clear(&msg_ga); +  char *buff = xmalloc(IOSIZE);    const char *fmt = _("E5114: Error while converting print argument #%i: %.*s"); -  size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx, +  size_t len = (size_t)vim_snprintf(buff, IOSIZE, fmt, curargidx,                                      (int)errmsg_len, errmsg); -  lua_pushlstring(lstate, (char *)IObuff, len); +  lua_pushlstring(lstate, buff, len); +  xfree(buff);    return lua_error(lstate);  } @@ -671,7 +966,7 @@ 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); @@ -736,7 +1031,7 @@ static int nlua_rpc(lua_State *lstate, bool request)    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); @@ -795,40 +1090,53 @@ static int nlua_getenv(lua_State *lstate)  /// 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 @@ -850,7 +1158,7 @@ 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;  } @@ -914,6 +1222,24 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg    }  } +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, (char *)xp->xp_pattern); +  lua_pushstring(lstate, (char *)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)  { @@ -1034,6 +1360,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) @@ -1096,11 +1435,23 @@ void ex_lua(exarg_T *const eap)    FUNC_ATTR_NONNULL_ALL  {    size_t len; -  char *const code = script_get(eap, &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); @@ -1200,6 +1551,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) @@ -1208,11 +1562,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; @@ -1227,6 +1600,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); @@ -1248,6 +1627,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) @@ -1319,6 +1701,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) @@ -1335,7 +1724,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);  } @@ -1385,7 +1774,7 @@ 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); @@ -1432,3 +1821,123 @@ void nlua_execute_on_key(int c)  #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((char_u *)source_path, current); +  xfree(source_path); +  current->sc_lnum = info->currentline; +  current->sc_seq = -1; + +cleanup: +  xfree(info); +} + +void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap) +{ +  lua_State *const lstate = global_lstate; + +  nlua_pushref(lstate, cmd->uc_luaref); + +  lua_newtable(lstate); +  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) { +    // Commands where nargs = 1 or "?" fargs is the same as args +    lua_rawseti(lstate, -2, 1); +  } else { +    // Commands with more than one possible argument we split +    lua_pop(lstate, 1);  // Pop the reference of opts.args +    int length = (int)STRLEN(eap->arg); +    int start = 0; +    int end = 0; +    int i = 1; +    bool res = true; +    while (res) { +      res = uc_split_args_iter(eap->arg, i, &start, &end, length); +      lua_pushlstring(lstate, (const char *)eap->arg + start, (size_t)(end - start + 1)); +      lua_rawseti(lstate, -2, i); +      i++; +    } +  } +  lua_setfield(lstate, -2, "fargs"); + +  lua_pushstring(lstate, (const char *)&eap->regname); +  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); +  lua_pushstring(lstate, buf); +  lua_setfield(lstate, -2, "mods"); + +  if (nlua_pcall(lstate, 1, 0)) { +    nlua_error(lstate, _("Error executing Lua callback: %.*s")); +  } +} + diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index a1f66bd02b..47ac51dadb 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -4,27 +4,25 @@  #include <lauxlib.h>  #include <lua.h> +#include "nvim/assert.h"  #include "nvim/api/private/defs.h"  #include "nvim/eval/typval.h"  #include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h"  #include "nvim/func_attr.h"  #include "nvim/lua/converter.h"  // 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 { \ @@ -38,4 +36,7 @@ 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); +  #endif  // NVIM_LUA_EXECUTOR_H diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c index b84124bc19..3a63f61200 100644 --- a/src/nvim/lua/spell.c +++ b/src/nvim/lua/spell.c @@ -1,3 +1,5 @@ +// 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> diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 18a579ed0f..c2ce899a74 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -25,6 +25,7 @@  #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/stdlib.h" @@ -408,6 +409,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    } @@ -464,43 +471,52 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL  } -void nlua_state_add_stdlib(lua_State *const lstate) +void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)  { -  // 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 +535,7 @@ void nlua_state_add_stdlib(lua_State *const lstate)    lua_pushcfunction(lstate, &nlua_xdl_diff);    lua_setfield(lstate, -2, "diff"); -  // vim.spell -  luaopen_spell(lstate); -  lua_setfield(lstate, -2, "spell"); - +  // vim.json    lua_cjson_new(lstate);    lua_setfield(lstate, -2, "json");  } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 60a000843f..f4067ad02f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -129,6 +129,10 @@ void tslua_init(lua_State *L)    build_meta(L, TS_META_QUERY, query_meta);    build_meta(L, TS_META_QUERYCURSOR, querycursor_meta);    build_meta(L, TS_META_TREECURSOR, treecursor_meta); + +#ifdef NVIM_TS_HAS_SET_ALLOCATOR +  ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree); +#endif  }  int tslua_has_language(lua_State *L) diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index c1a1e7f162..c0247ad996 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -40,51 +40,11 @@ assert(vim)  vim.inspect = package.loaded['vim.inspect']  assert(vim.inspect) -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 +vim.filetype = package.loaded['vim.filetype'] +assert(vim.filetype) -  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) +vim.keymap = package.loaded['vim.keymap'] +assert(vim.keymap)  -- These are for loading runtime modules lazily since they aren't available in  -- the nvim binary as specified in executor.c @@ -419,23 +379,43 @@ function vim.defer_fn(fn, timeout)  end ---- Notification provider +--- Display a notification to the user.  --- ---- Without a runtime, writes to :Messages ----@see :help nvim_notify ----@param msg string Content of the notification to show to the user ----@param log_level number|nil enum from vim.log.levels ----@param opts table|nil additional options (timeout, etc) -function vim.notify(msg, log_level, opts) -- luacheck: no unused -  if log_level == vim.log.levels.ERROR then +--- 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 log_level == vim.log.levels.WARN then +  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() @@ -663,4 +643,23 @@ vim._expand_pat_get_parts = function(lua_string)    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..ea7a700e1e 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -184,11 +184,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;  | 
