text: // This is an open source non-commercial project. Dear PVS-Studio, please check
  // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
  
  #include <lauxlib.h>
  #include <lua.h>
  #include <lualib.h>
  #include <tree_sitter/api.h>
  
  #include "luv/luv.h"
  #include "nvim/api/private/defs.h"
  #include "nvim/api/private/helpers.h"
  #include "nvim/api/vim.h"
  #include "nvim/ascii.h"
  #include "nvim/assert.h"
  #include "nvim/buffer_defs.h"
  #include "nvim/change.h"
  #include "nvim/cursor.h"
  #include "nvim/eval/typval.h"
  #include "nvim/eval/userfunc.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"
  #include "nvim/garray.h"
  #include "nvim/getchar.h"
  #include "nvim/lua/converter.h"
  #include "nvim/lua/executor.h"
  #include "nvim/lua/stdlib.h"
  #include "nvim/lua/treesitter.h"
  #include "nvim/macros.h"
  #include "nvim/map.h"
  #include "nvim/memline.h"
  #include "nvim/message.h"
  #include "nvim/msgpack_rpc/channel.h"
  #include "nvim/os/os.h"
  #include "nvim/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 uv_thread_t main_thread;
  
  typedef struct {
    Error err;
    String lua_err_str;
  } LuaError;
  
  typedef struct {
    char *name;
    const uint8_t *data;
    size_t size;
  } ModuleDef;
  
  #ifdef INCLUDE_GENERATED_DECLARATIONS
  # include "lua/executor.c.generated.h"
  # include "lua/vim_module.generated.h"
  #endif
  
  #define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \
    for (int i = 0; i < argcount; i++) { \
      if (args[i].v_type == VAR_UNKNOWN) { \
        lua_pushnil(lstate); \
      } else { \
        nlua_push_typval(lstate, &args[i], special); \
      } \
    }
  
  #if __has_feature(address_sanitizer)
  static bool nlua_track_refs = false;
  # define NLUA_TRACK_REFS
  #endif
  
  typedef enum luv_err_type {
    kCallback,
    kThread,
    kThreadCallback,
  } luv_err_t;
  
  /// Convert lua error into a Vim error message
  ///
  /// @param  lstate  Lua interpreter state.
  /// @param[in]  msg  Message base, must contain one `%s`.
  static void nlua_error(lua_State *const lstate, const char *const msg)
    FUNC_ATTR_NONNULL_ALL
  {
    size_t len;
    const char *str = NULL;
  
    if (luaL_getmetafield(lstate, -1, "__tostring")) {
      if (lua_isfunction(lstate, -1) && luaL_callmeta(lstate, -2, "__tostring")) {
        // call __tostring, convert the result and pop result.
        str = lua_tolstring(lstate, -1, &len);
        lua_pop(lstate, 1);
      }
      // pop __tostring.
      lua_pop(lstate, 1);
    }
  
    if (!str) {
      // defer to lua default conversion, this will render tables as [NULL].
      str = lua_tolstring(lstate, -1, &len);
    }
  
    msg_ext_set_kind("lua_error");
    semsg_multiline(msg, (int)len, str);
  
    lua_pop(lstate, 1);
  }
  
  /// Like lua_pcall, but use debug.traceback as errfunc.
  ///
  /// @param lstate Lua interpreter state
  /// @param[in] nargs Number of arguments expected by the function being called.
  /// @param[in] nresults Number of results the function returns.
  static int nlua_pcall(lua_State *lstate, int nargs, int nresults)
  {
    lua_getglobal(lstate, "debug");
    lua_getfield(lstate, -1, "traceback");
    lua_remove(lstate, -2);
    lua_insert(lstate, -2 - nargs);
    int status = lua_pcall(lstate, nargs, nresults, -2 - nargs);
    if (status) {
      lua_remove(lstate, -2);
    } else {
      lua_remove(lstate, -1 - nresults);
    }
    return status;
  }
  
  /// Gets the version of the current Nvim build.
  ///
  /// @param  lstate  Lua interpreter state.
  static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
  {
    Dictionary version = version_dict();
    nlua_push_Dictionary(lstate, version, true);
    api_free_dictionary(version);
    return 1;
  }
  
  static void nlua_luv_error_event(void **argv)
  {
    char *error = (char *)argv[0];
    luv_err_t type = (luv_err_t)(intptr_t)argv[1];
    msg_ext_set_kind("lua_error");
    switch (type) {
    case kCallback:
      semsg_multiline("Error executing luv callback:\n%s", error);
      break;
    case kThread:
      semsg_multiline("Error in luv thread:\n%s", error);
      break;
    case kThreadCallback:
      semsg_multiline("Error in luv callback, thread:\n%s", error);
      break;
    default:
      break;
    }
    xfree(error);
  }
  
  static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags)
    FUNC_ATTR_NONNULL_ALL
  {
    int retval;
  
    // luv callbacks might be executed at any os_breakcheck/line_breakcheck
    // call, so using the API directly here is not safe.
    in_fast_callback++;
  
    int top = lua_gettop(lstate);
    int status = nlua_pcall(lstate, nargs, nresult);
    if (status) {
      if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
        // consider out of memory errors unrecoverable, just like xmalloc()
        mch_errmsg(e_outofmem);
        mch_errmsg("\n");
        preserve_exit();
      }
      const char *error = lua_tostring(lstate, -1);
  
      multiqueue_put(main_loop.events, nlua_luv_error_event,
                     2, xstrdup(error), (intptr_t)kCallback);
      lua_pop(lstate, 1);  // error message
      retval = -status;
    } else {  // LUA_OK
      if (nresult == LUA_MULTRET) {
        nresult = lua_gettop(lstate) - top + nargs + 1;
      }
      retval = nresult;
    }
  
    in_fast_callback--;
    return retval;
  }
  
  static int nlua_luv_thread_cb_cfpcall(lua_State *lstate, int nargs, int nresult, int flags)
  {
    return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, true);
  }
  
  static int nlua_luv_thread_cfpcall(lua_State *lstate, int nargs, int nresult, int flags)
    FUNC_ATTR_NONNULL_ALL
  {
    return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, false);
  }
  
  static int nlua_luv_thread_cfcpcall(lua_State *lstate, lua_CFunction func, void *ud, int flags)
    FUNC_ATTR_NONNULL_ARG(1, 2)
  {
    lua_pushcfunction(lstate, func);
    lua_pushlightuserdata(lstate, ud);
    int retval = nlua_luv_thread_cfpcall(lstate, 1, 0, flags);
    return retval;
  }
  
  static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nresult, int flags,
                                            bool is_callback)
    FUNC_ATTR_NONNULL_ALL
  {
    int retval;
  
    int top = lua_gettop(lstate);
    int status = lua_pcall(lstate, nargs, nresult, 0);
    if (status) {
      if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
        // Terminate this thread, as the main thread may be able to continue
        // execution.
        mch_errmsg(e_outofmem);
        mch_errmsg("\n");
        lua_close(lstate);
  #ifdef 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_global(lstate, cb);
    if (nlua_pcall(lstate, 0, 0)) {
      nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
    }
  }
  
  /// Schedule Lua callback on main loop's event queue
  ///
  /// @param  lstate  Lua interpreter state.
  static int nlua_schedule(lua_State *const lstate)
    FUNC_ATTR_NONNULL_ALL
  {
    if (lua_type(lstate, 1) != LUA_TFUNCTION) {
      lua_pushliteral(lstate, "vim.schedule: expected function");
      return lua_error(lstate);
    }
  
    LuaRef cb = nlua_ref_global(lstate, 1);
  
    multiqueue_put(main_loop.events, nlua_schedule_event,
                   1, (void *)(ptrdiff_t)cb);
    return 0;
  }
  
  // Dummy timer callback. Used by f_wait().
  static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
  {}
  
  // Dummy timer close callback. Used by f_wait().
  static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
  {
    xfree(tw);
  }
  
  static bool nlua_wait_condition(lua_State *lstate, int *status, bool *callback_result)
  {
    lua_pushvalue(lstate, 2);
    *status = nlua_pcall(lstate, 0, 1);
    if (*status) {
      return true;  // break on error, but keep error on stack
    }
    *callback_result = lua_toboolean(lstate, -1);
    lua_pop(lstate, 1);
    return *callback_result;  // break if true
  }
  
  /// "vim.wait(timeout, condition[, interval])" function
  static int nlua_wait(lua_State *lstate)
    FUNC_ATTR_NONNULL_ALL
  {
    intptr_t timeout = luaL_checkinteger(lstate, 1);
    if (timeout < 0) {
      return luaL_error(lstate, "timeout must be > 0");
    }
  
    int lua_top = lua_gettop(lstate);
  
    // Check if condition can be called.
    bool is_function = false;
    if (lua_top >= 2 && !lua_isnil(lstate, 2)) {
      is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
  
      // Check if condition is callable table
      if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
        is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
        lua_pop(lstate, 1);
      }
  
      if (!is_function) {
        lua_pushliteral(lstate,
                        "vim.wait: if passed, condition must be a function");
        return lua_error(lstate);
      }
    }
  
    intptr_t interval = 200;
    if (lua_top >= 3 && !lua_isnil(lstate, 3)) {
      interval = luaL_checkinteger(lstate, 3);
      if (interval < 0) {
        return luaL_error(lstate, "interval must be > 0");
      }
    }
  
    bool fast_only = false;
    if (lua_top >= 4) {
      fast_only =  lua_toboolean(lstate, 4);
    }
  
    MultiQueue *loop_events = fast_only || in_fast_callback > 0
      ? main_loop.fast_events : main_loop.events;
  
    TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
  
    // Start dummy timer.
    time_watcher_init(&main_loop, tw, NULL);
    tw->events = loop_events;
    tw->blockable = true;
    time_watcher_start(tw,
                       dummy_timer_due_cb,
                       (uint64_t)interval,
                       (uint64_t)interval);
  
    int pcall_status = 0;
    bool callback_result = false;
  
    LOOP_PROCESS_EVENTS_UNTIL(&main_loop,
                              loop_events,
                              (int)timeout,
                              is_function ? nlua_wait_condition(lstate,
                                                                &pcall_status,
                                                                &callback_result) : false || got_int);
  
    // Stop dummy timer
    time_watcher_stop(tw);
    time_watcher_close(tw, dummy_timer_close_cb);
  
    if (pcall_status) {
      return lua_error(lstate);
    } else if (callback_result) {
      lua_pushboolean(lstate, 1);
      lua_pushnil(lstate);
    } else if (got_int) {
      got_int = false;
      vgetc();
      lua_pushboolean(lstate, 0);
      lua_pushinteger(lstate, -2);
    } else {
      lua_pushboolean(lstate, 0);
      lua_pushinteger(lstate, -1);
    }
  
    return 2;
  }
  
  static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread)
    FUNC_ATTR_NONNULL_ALL
  {
    nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state));
    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 bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
  {
    // print
    lua_pushcfunction(lstate, &nlua_print);
    lua_setglobal(lstate, "print");
  
    // debug.debug
    lua_getglobal(lstate, "debug");
    lua_pushcfunction(lstate, &nlua_debug);
    lua_setfield(lstate, -2, "debug");
    lua_pop(lstate, 1);
  
  #ifdef WIN32
    // os.getenv
    lua_getglobal(lstate, "os");
    lua_pushcfunction(lstate, &nlua_getenv);
    lua_setfield(lstate, -2, "getenv");
    lua_pop(lstate, 1);
  #endif
  
    // vim
    lua_newtable(lstate);
  
    // vim.api
    nlua_add_api_functions(lstate);
  
    // vim.types, vim.type_idx, vim.val_idx
    nlua_init_types(lstate);
  
    // neovim version
    lua_pushcfunction(lstate, &nlua_nvim_version);
    lua_setfield(lstate, -2, "version");
  
    // schedule
    lua_pushcfunction(lstate, &nlua_schedule);
    lua_setfield(lstate, -2, "schedule");
  
    // in_fast_event
    lua_pushcfunction(lstate, &nlua_in_fast_event);
    lua_setfield(lstate, -2, "in_fast_event");
  
    // call
    lua_pushcfunction(lstate, &nlua_call);
    lua_setfield(lstate, -2, "call");
  
    // rpcrequest
    lua_pushcfunction(lstate, &nlua_rpcrequest);
    lua_setfield(lstate, -2, "rpcrequest");
  
    // rpcnotify
    lua_pushcfunction(lstate, &nlua_rpcnotify);
    lua_setfield(lstate, -2, "rpcnotify");
  
    // wait
    lua_pushcfunction(lstate, &nlua_wait);
    lua_setfield(lstate, -2, "wait");
  
    nlua_common_vim_init(lstate, false);
  
    // internal vim._treesitter... API
    nlua_add_treesitter(lstate);
  
    nlua_state_add_stdlib(lstate, false);
  
    lua_setglobal(lstate, "vim");
  
    if (!nlua_init_packages(lstate)) {
      return false;
    }
  
    return true;
  }
  
  /// Initialize global lua interpreter
  ///
  /// Crashes Nvim if initialization fails.
  void nlua_init(void)
  {
  #ifdef NLUA_TRACK_REFS
    const char *env = os_getenv("NVIM_LUA_NOTRACK");
    if (!env || !*env) {
      nlua_track_refs = true;
    }
  #endif
  
    lua_State *lstate = luaL_newstate();
    if (lstate == NULL) {
      mch_errmsg(_("E970: Failed to initialize lua interpreter\n"));
      os_exit(1);
    }
    luaL_openlibs(lstate);
    if (!nlua_state_init(lstate)) {
      mch_errmsg(_("E970: Failed to initialize builtin lua modules\n"));
      os_exit(1);
    }
  
    luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem);
  
    global_lstate = lstate;
  
    main_thread = uv_thread_self();
  }
  
  static lua_State *nlua_thread_acquire_vm(void)
  {
    // If it is called from the main thread, it will attempt to rebuild the cache.
    const uv_thread_t self = uv_thread_self();
    if (uv_thread_equal(&main_thread, &self)) {
      runtime_search_path_validate();
    }
  
    lua_State *lstate = luaL_newstate();
  
    // Add in the lua standard libraries
    luaL_openlibs(lstate);
  
    // print
    lua_pushcfunction(lstate, &nlua_print);
    lua_setglobal(lstate, "print");
  
    lua_pushinteger(lstate, 0);
    lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount");
  
    // vim
    lua_newtable(lstate);
  
    nlua_common_vim_init(lstate, true);
  
    nlua_state_add_stdlib(lstate, true);
  
    lua_createtable(lstate, 0, 0);
    lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime);
    lua_setfield(lstate, -2, "nvim__get_runtime");
    lua_setfield(lstate, -2, "api");
  
    lua_setglobal(lstate, "vim");
  
    nlua_init_packages(lstate);
  
    lua_getglobal(lstate, "package");
    lua_getfield(lstate, -1, "loaded");
    lua_getglobal(lstate, "vim");
    lua_setfield(lstate, -2, "vim");
    lua_pop(lstate, 2);
  
    return lstate;
  }
  
  void nlua_free_all_mem(void)
  {
    if (!global_lstate) {
      return;
    }
    lua_State *lstate = global_lstate;
    nlua_common_free_all_mem(lstate);
  }
  
  static void nlua_common_free_all_mem(lua_State *lstate)
    FUNC_ATTR_NONNULL_ALL
  {
    nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
    nlua_unref(lstate, ref_state, ref_state->nil_ref);
    nlua_unref(lstate, ref_state, ref_state->empty_dict_ref);
  
  #ifdef NLUA_TRACK_REFS
    if (ref_state->ref_count) {
      fprintf(stderr, "%d lua references were leaked!", ref_state->ref_count);
    }
  
    if (nlua_track_refs) {
      // in case there are leaked luarefs, leak the associated memory
      // to get LeakSanitizer stacktraces on exit
      pmap_destroy(handle_T)(&ref_state->ref_markers);
    }
  #endif
  
    lua_close(lstate);
  }
  static void nlua_print_event(void **argv)
  {
    char *str = argv[0];
    const size_t len = (size_t)(intptr_t)argv[1] - 1;  // exclude final NUL
  
    for (size_t i = 0; i < len;) {
      if (got_int) {
        break;
      }
      const size_t start = i;
      while (i < len) {
        switch (str[i]) {
        case NUL:
          str[i] = NL;
          i++;
          continue;
        case NL:
          // TODO(bfredl): use proper multiline msg? Probably should implement
          // print() in lua in terms of nvim_message(), when it is available.
          str[i] = NUL;
          i++;
          break;
        default:
          i++;
          continue;
        }
        break;
      }
      msg(str + start);
    }
    if (len && str[len - 1] == NUL) {  // Last was newline
      msg("");
    }
    xfree(str);
  }
  
  /// Print as a Vim message
  ///
  /// @param  lstate  Lua interpreter state.
  static int nlua_print(lua_State *const lstate)
    FUNC_ATTR_NONNULL_ALL
  {
  #define PRINT_ERROR(msg) \
    do { \
      errmsg = msg; \
      errmsg_len = sizeof(msg) - 1; \
      goto nlua_print_error; \
    } while (0)
    const int nargs = lua_gettop(lstate);
    lua_getglobal(lstate, "tostring");
    const char *errmsg = NULL;
    size_t errmsg_len = 0;
    garray_T msg_ga;
    ga_init(&msg_ga, 1, 80);
    int curargidx = 1;
    for (; curargidx <= nargs; curargidx++) {
      lua_pushvalue(lstate, -1);  // tostring
      lua_pushvalue(lstate, curargidx);  // arg
      // Do not use nlua_pcall here to avoid duplicate stack trace information
      if (lua_pcall(lstate, 1, 1, 0)) {
        errmsg = lua_tolstring(lstate, -1, &errmsg_len);
        goto nlua_print_error;
      }
      size_t len;
      const char *const s = lua_tolstring(lstate, -1, &len);
      if (s == NULL) {
        PRINT_ERROR("<Unknown error: lua_tolstring returned NULL for tostring result>");
      }
      ga_concat_len(&msg_ga, s, len);
      if (curargidx < nargs) {
        ga_append(&msg_ga, ' ');
      }
      lua_pop(lstate, 1);
    }
  #undef PRINT_ERROR
    ga_append(&msg_ga, NUL);
  
    lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
    bool is_thread = lua_toboolean(lstate, -1);
    lua_pop(lstate, 1);
  
    if (is_thread) {
      loop_schedule_deferred(&main_loop,
                             event_create(nlua_print_event, 2,
                                          msg_ga.ga_data,
                                          (intptr_t)msg_ga.ga_len));
    } else if (in_fast_callback) {
      multiqueue_put(main_loop.events, nlua_print_event,
                     2, msg_ga.ga_data, (intptr_t)msg_ga.ga_len);
    } else {
      nlua_print_event((void *[]){ msg_ga.ga_data,
                                   (void *)(intptr_t)msg_ga.ga_len });
    }
    return 0;
  
  nlua_print_error:
    ga_clear(&msg_ga);
    char *buff = xmalloc(IOSIZE);
    const char *fmt = _("E5114: Error while converting print argument #%i: %.*s");
    size_t len = (size_t)vim_snprintf(buff, IOSIZE, fmt, curargidx,
                                      (int)errmsg_len, errmsg);
    lua_pushlstring(lstate, buff, len);
    xfree(buff);
    return lua_error(lstate);
  }
  
  /// debug.debug: interaction with user while debugging.
  ///
  /// @param  lstate  Lua interpreter state.
  static int nlua_debug(lua_State *lstate)
    FUNC_ATTR_NONNULL_ALL
  {
    const typval_T input_args[] = {
      {
        .v_lock = VAR_FIXED,
        .v_type = VAR_STRING,
        .vval.v_string = "lua_debug> ",
      },
      {
        .v_type = VAR_UNKNOWN,
      },
    };
    for (;;) {
      lua_settop(lstate, 0);
      typval_T input;
      get_user_input(input_args, &input, false, false);
      msg_putchar('\n');  // Avoid outputting on input line.
      if (input.v_type != VAR_STRING
          || input.vval.v_string == NULL
          || *input.vval.v_string == NUL
          || STRCMP(input.vval.v_string, "cont") == 0) {
        tv_clear(&input);
        return 0;
      }
      if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string,
                          STRLEN(input.vval.v_string), "=(debug command)")) {
        nlua_error(lstate, _("E5115: Error while loading debug string: %.*s"));
      } else if (nlua_pcall(lstate, 0, 0)) {
        nlua_error(lstate, _("E5116: Error while calling debug string: %.*s"));
      }
      tv_clear(&input);
    }
    return 0;
  }
  
  int nlua_in_fast_event(lua_State *lstate)
  {
    lua_pushboolean(lstate, in_fast_callback > 0);
    return 1;
  }
  
  static bool viml_func_is_fast(const char *name)
  {
    const EvalFuncDef *const fdef = find_internal_func((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 *name = luaL_checklstring(lstate, 1, &name_len);
    if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) {
      return luaL_error(lstate, e_luv_api_disabled, "vimL function");
    }
  
    int nargs = lua_gettop(lstate) - 1;
    if (nargs > MAX_FUNC_ARGS) {
      return luaL_error(lstate, "Function called with too many arguments");
    }
  
    typval_T vim_args[MAX_FUNC_ARGS + 1];
    int i = 0;  // also used for freeing the variables
    for (; i < nargs; i++) {
      lua_pushvalue(lstate, i + 2);
      if (!nlua_pop_typval(lstate, &vim_args[i])) {
        api_set_error(&err, kErrorTypeException,
                      "error converting argument %d", i + 1);
        goto free_vim_args;
      }
    }
  
    TRY_WRAP({
      // TODO(bfredl): this should be simplified in error handling refactor
      force_abort = false;
      suppress_errthrow = false;
      current_exception = NULL;
      did_emsg = false;
  
      try_start();
      typval_T rettv;
      funcexe_T funcexe = FUNCEXE_INIT;
      funcexe.firstline = curwin->w_cursor.lnum;
      funcexe.lastline = curwin->w_cursor.lnum;
      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((char *)name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
      if (!try_end(&err)) {
        nlua_push_typval(lstate, &rettv, false);
      }
      tv_clear(&rettv);
    });
  
  free_vim_args:
    while (i > 0) {
      tv_clear(&vim_args[--i]);
    }
    if (ERROR_SET(&err)) {
      lua_pushstring(lstate, err.msg);
      api_clear_error(&err);
      return lua_error(lstate);
    }
    return 1;
  }
  
  static int nlua_rpcrequest(lua_State *lstate)
  {
    if (!nlua_is_deferred_safe()) {
      return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
    }
    return nlua_rpc(lstate, true);
  }
  
  static int nlua_rpcnotify(lua_State *lstate)
  {
    return nlua_rpc(lstate, false);
  }
  
  static int nlua_rpc(lua_State *lstate, bool request)
  {
    size_t name_len;
    uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1);
    const char *name = luaL_checklstring(lstate, 2, &name_len);
    int nargs = lua_gettop(lstate) - 2;
    Error err = ERROR_INIT;
    Array args = ARRAY_DICT_INIT;
  
    for (int i = 0; i < nargs; i++) {
      lua_pushvalue(lstate, i + 3);
      ADD(args, nlua_pop_Object(lstate, false, &err));
      if (ERROR_SET(&err)) {
        api_free_array(args);
        goto check_err;
      }
    }
  
    if (request) {
      Object result = rpc_send_call(chan_id, name, args, &err);
      if (!ERROR_SET(&err)) {
        nlua_push_Object(lstate, result, false);
        api_free_object(result);
      }
    } else {
      if (!rpc_send_event(chan_id, name, args)) {
        api_set_error(&err, kErrorTypeValidation,
                      "Invalid channel: %" PRIu64, chan_id);
      }
    }
  
  check_err:
    if (ERROR_SET(&err)) {
      lua_pushstring(lstate, err.msg);
      api_clear_error(&err);
      return lua_error(lstate);
    }
  
    return request ? 1 : 0;
  }
  
  static int nlua_nil_tostring(lua_State *lstate)
  {
    lua_pushstring(lstate, "vim.NIL");
    return 1;
  }
  
  static int nlua_empty_dict_tostring(lua_State *lstate)
  {
    lua_pushstring(lstate, "vim.empty_dict()");
    return 1;
  }
  
  #ifdef WIN32
  /// os.getenv: override os.getenv to maintain coherency. #9681
  ///
  /// uv_os_setenv uses SetEnvironmentVariableW which does not update _environ.
  ///
  /// @param  lstate  Lua interpreter state.
  static int nlua_getenv(lua_State *lstate)
  {
    lua_pushstring(lstate, os_getenv(luaL_checkstring(lstate, 1)));
    return 1;
  }
  #endif
  
  /// add the value to the registry
  /// The current implementation does not support calls from threads.
  LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index)
  {
    lua_pushvalue(lstate, index);
    LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
    if (ref > 0) {
      ref_state->ref_count++;
  #ifdef NLUA_TRACK_REFS
      if (nlua_track_refs) {
        // dummy allocation to make LeakSanitizer track our luarefs
        pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3));
      }
  #endif
    }
    return ref;
  }
  
  LuaRef nlua_ref_global(lua_State *lstate, int index)
  {
    return nlua_ref(lstate, nlua_global_refs, index);
  }
  
  /// remove the value from the registry
  void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref)
  {
    if (ref > 0) {
      ref_state->ref_count--;
  #ifdef NLUA_TRACK_REFS
      // NB: don't remove entry from map to track double-unref
      if (nlua_track_refs) {
        xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref));
      }
  #endif
      luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
    }
  }
  
  void nlua_unref_global(lua_State *lstate, LuaRef ref)
  {
    nlua_unref(lstate, nlua_global_refs, ref);
  }
  
  void api_free_luaref(LuaRef ref)
  {
    nlua_unref_global(global_lstate, ref);
  }
  
  /// push a value referenced in the registry
  void nlua_pushref(lua_State *lstate, LuaRef ref)
  {
    lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
  }
  
  /// Gets a new reference to an object stored at original_ref
  ///
  /// NOTE: It does not copy the value, it creates a new ref to the lua object.
  ///       Leaves the stack unchanged.
  LuaRef api_new_luaref(LuaRef original_ref)
  {
    if (original_ref == LUA_NOREF) {
      return LUA_NOREF;
    }
  
    lua_State *const lstate = global_lstate;
    nlua_pushref(lstate, original_ref);
    LuaRef new_ref = nlua_ref_global(lstate,  -1);
    lua_pop(lstate, 1);
    return new_ref;
  }
  
  /// Evaluate lua string
  ///
  /// Used for luaeval().
  ///
  /// @param[in]  str  String to execute.
  /// @param[in]  arg  Second argument to `luaeval()`.
  /// @param[out]  ret_tv  Location where result will be saved.
  ///
  /// @return Result of the execution.
  void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret_tv)
    FUNC_ATTR_NONNULL_ALL
  {
  #define EVALHEADER "local _A=select(1,...) return ("
    const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1;
    char *lcmd;
    if (lcmd_len < IOSIZE) {
      lcmd = (char *)IObuff;
    } else {
      lcmd = xmalloc(lcmd_len);
    }
    memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1);
    memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size);
    lcmd[lcmd_len - 1] = ')';
  #undef EVALHEADER
    nlua_typval_exec(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv);
  
    if (lcmd != (char *)IObuff) {
      xfree(lcmd);
    }
  }
  
  void nlua_typval_call(const char *str, size_t len, typval_T *const args, int argcount,
                        typval_T *ret_tv)
    FUNC_ATTR_NONNULL_ALL
  {
  #define CALLHEADER "return "
  #define CALLSUFFIX "(...)"
    const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1;
    char *lcmd;
    if (lcmd_len < IOSIZE) {
      lcmd = (char *)IObuff;
    } else {
      lcmd = xmalloc(lcmd_len);
    }
    memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1);
    memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len);
    memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX,
           sizeof(CALLSUFFIX) - 1);
  #undef CALLHEADER
  #undef CALLSUFFIX
  
    nlua_typval_exec(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv);
  
    if (lcmd != (char *)IObuff) {
      xfree(lcmd);
    }
  }
  
  void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv)
    FUNC_ATTR_NONNULL_ALL
  {
    lua_State *const lstate = global_lstate;
  
    nlua_pushref(lstate, xp->xp_luaref);
    lua_pushstring(lstate, xp->xp_pattern);
    lua_pushstring(lstate, xp->xp_line);
    lua_pushinteger(lstate, xp->xp_col);
  
    if (nlua_pcall(lstate, 3, 1)) {
      nlua_error(lstate, _("E5108: Error executing Lua function: %.*s"));
      return;
    }
  
    nlua_pop_typval(lstate, ret_tv);
  }
  
  static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name,
                               typval_T *const args, int argcount, bool special, typval_T *ret_tv)
  {
    if (check_secure()) {
      if (ret_tv) {
        ret_tv->v_type = VAR_NUMBER;
        ret_tv->vval.v_number = 0;
      }
      return;
    }
  
    lua_State *const lstate = global_lstate;
    if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) {
      nlua_error(lstate, _("E5107: Error loading lua %.*s"));
      return;
    }
  
    PUSH_ALL_TYPVALS(lstate, args, argcount, special);
  
    if (nlua_pcall(lstate, argcount, ret_tv ? 1 : 0)) {
      nlua_error(lstate, _("E5108: Error executing lua %.*s"));
      return;
    }
  
    if (ret_tv) {
      nlua_pop_typval(lstate, ret_tv);
    }
  }
  
  int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name)
  {
    const linenr_T save_sourcing_lnum = sourcing_lnum;
    const sctx_T save_current_sctx = current_sctx;
    current_sctx.sc_sid = SID_STR;
    current_sctx.sc_seq = 0;
    current_sctx.sc_lnum = 0;
    sourcing_lnum = 0;
  
    garray_T ga;
    char_u *line = NULL;
  
    ga_init(&ga, (int)sizeof(char_u *), 10);
    while ((line = (char_u *)fgetline(0, cookie, 0, false)) != NULL) {
      GA_APPEND(char_u *, &ga, line);
    }
    char *code = ga_concat_strings_sep(&ga, "\n");
    size_t len = strlen(code);
    nlua_typval_exec(code, len, name, NULL, 0, false, NULL);
  
    sourcing_lnum = save_sourcing_lnum;
    current_sctx = save_current_sctx;
    ga_clear_strings(&ga);
    xfree(code);
    return OK;
  }
  
  /// Call a LuaCallable given some typvals
  ///
  /// Used to call any lua callable passed from Lua into VimL
  ///
  /// @param[in]  lstate Lua State
  /// @param[in]  lua_cb Lua Callable
  /// @param[in]  argcount Count of typval arguments
  /// @param[in]  argvars Typval Arguments
  /// @param[out] rettv The return value from the called function.
  int typval_exec_lua_callable(lua_State *lstate, LuaCallable lua_cb, int argcount, typval_T *argvars,
                               typval_T *rettv)
  {
    LuaRef cb = lua_cb.func_ref;
  
    nlua_pushref(lstate, cb);
  
    PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
  
    if (nlua_pcall(lstate, argcount, 1)) {
      nlua_print(lstate);
      return ERROR_OTHER;
    }
  
    nlua_pop_typval(lstate, rettv);
  
    return ERROR_NONE;
  }
  
  /// Execute Lua string
  ///
  /// Used for nvim_exec_lua() and internally to execute a lua string.
  ///
  /// @param[in]  str  String to execute.
  /// @param[in]  args array of ... args
  /// @param[out]  err  Location where error will be saved.
  ///
  /// @return Return value of the execution.
  Object nlua_exec(const String str, const Array args, Error *err)
  {
    lua_State *const lstate = global_lstate;
  
    if (luaL_loadbuffer(lstate, str.data, str.size, "<nvim>")) {
      size_t len;
      const char *errstr = lua_tolstring(lstate, -1, &len);
      api_set_error(err, kErrorTypeValidation,
                    "Error loading lua: %.*s", (int)len, errstr);
      return NIL;
    }
  
    for (size_t i = 0; i < args.size; i++) {
      nlua_push_Object(lstate, args.items[i], false);
    }
  
    if (nlua_pcall(lstate, (int)args.size, 1)) {
      size_t len;
      const char *errstr = lua_tolstring(lstate, -1, &len);
      api_set_error(err, kErrorTypeException,
                    "Error executing lua: %.*s", (int)len, errstr);
      return NIL;
    }
  
    return nlua_pop_Object(lstate, false, err);
  }
  
  bool nlua_ref_is_function(LuaRef ref)
  {
    lua_State *const lstate = global_lstate;
    nlua_pushref(lstate, ref);
  
    // TODO(tjdevries): This should probably check for callable tables as well.
    //                    We should put some work maybe into simplifying how all of that works
    bool is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
    lua_pop(lstate, 1);
  
    return is_function;
  }
  
  /// call a LuaRef as a function (or table with __call metamethod)
  ///
  /// @param ref     the reference to call (not consumed)
  /// @param name    if non-NULL, sent to callback as first arg
  ///                if NULL, only args are used
  /// @param retval  if true, convert return value to Object
  ///                if false, discard return value
  /// @param err     Error details, if any (if NULL, errors are echoed)
  /// @return        Return value of function, if retval was set. Otherwise NIL.
  Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Error *err)
  {
    lua_State *const lstate = global_lstate;
    nlua_pushref(lstate, ref);
    int nargs = (int)args.size;
    if (name != NULL) {
      lua_pushstring(lstate, name);
      nargs++;
    }
    for (size_t i = 0; i < args.size; i++) {
      nlua_push_Object(lstate, args.items[i], false);
    }
  
    if (nlua_pcall(lstate, nargs, retval ? 1 : 0)) {
      // if err is passed, the caller will deal with the error.
      if (err) {
        size_t len;
        const char *errstr = lua_tolstring(lstate, -1, &len);
        api_set_error(err, kErrorTypeException,
                      "Error executing lua: %.*s", (int)len, errstr);
      } else {
        nlua_error(lstate, _("Error executing lua callback: %.*s"));
      }
      return NIL;
    }
  
    if (retval) {
      Error dummy = ERROR_INIT;
      if (err == NULL) {
        err = &dummy;
      }
      return nlua_pop_Object(lstate, false, err);
    } else {
      return NIL;
    }
  }
  
  /// check if the current execution context is safe for calling deferred API
  /// methods. Luv callbacks are unsafe as they are called inside the uv loop.
  bool nlua_is_deferred_safe(void)
  {
    return in_fast_callback == 0;
  }
  
  /// Run lua string
  ///
  /// Used for :lua.
  ///
  /// @param  eap  VimL command being run.
  void ex_lua(exarg_T *const eap)
    FUNC_ATTR_NONNULL_ALL
  {
    size_t len;
    char *code = script_get(eap, &len);
    if (eap->skip) {
      xfree(code);
      return;
    }
    // When =expr is used transform it to print(vim.inspect(expr))
    if (code[0] == '=') {
      len += sizeof("vim.pretty_print()") - sizeof("=");
      // code_buf needs to be 1 char larger then len for null byte in the end.
      // lua nlua_typval_exec doesn't expect null terminated string so len
      // needs to end before null byte.
      char *code_buf = xmallocz(len);
      vim_snprintf(code_buf, len + 1, "vim.pretty_print(%s)", code + 1);
      xfree(code);
      code = code_buf;
    }
  
    nlua_typval_exec(code, len, ":lua", NULL, 0, false, NULL);
  
    xfree(code);
  }
  
  /// Run lua string for each line in range
  ///
  /// Used for :luado.
  ///
  /// @param  eap  VimL command being run.
  void ex_luado(exarg_T *const eap)
    FUNC_ATTR_NONNULL_ALL
  {
    if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) {
      emsg(_("cannot save undo information"));
      return;
    }
    const char *const cmd = (const char *)eap->arg;
    const size_t cmd_len = strlen(cmd);
  
    lua_State *const lstate = global_lstate;
  
  #define DOSTART "return function(line, linenr) "
  #define DOEND " end"
    const size_t lcmd_len = (cmd_len
                             + (sizeof(DOSTART) - 1)
                             + (sizeof(DOEND) - 1));
    char *lcmd;
    if (lcmd_len < IOSIZE) {
      lcmd = (char *)IObuff;
    } else {
      lcmd = xmalloc(lcmd_len + 1);
    }
    memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1);
    memcpy(lcmd + sizeof(DOSTART) - 1, cmd, cmd_len);
    memcpy(lcmd + sizeof(DOSTART) - 1 + cmd_len, DOEND, sizeof(DOEND) - 1);
  #undef DOSTART
  #undef DOEND
  
    if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) {
      nlua_error(lstate, _("E5109: Error loading lua: %.*s"));
      if (lcmd_len >= IOSIZE) {
        xfree(lcmd);
      }
      return;
    }
    if (lcmd_len >= IOSIZE) {
      xfree(lcmd);
    }
    if (nlua_pcall(lstate, 0, 1)) {
      nlua_error(lstate, _("E5110: Error executing lua: %.*s"));
      return;
    }
    for (linenr_T l = eap->line1; l <= eap->line2; l++) {
      if (l > curbuf->b_ml.ml_line_count) {
        break;
      }
      lua_pushvalue(lstate, -1);
      const char *old_line = (const char *)ml_get_buf(curbuf, l, false);
      lua_pushstring(lstate, old_line);
      lua_pushnumber(lstate, (lua_Number)l);
      if (nlua_pcall(lstate, 2, 1)) {
        nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
        break;
      }
      if (lua_isstring(lstate, -1)) {
        size_t old_line_len = STRLEN(old_line);
  
        size_t new_line_len;
        const char *const new_line = lua_tolstring(lstate, -1, &new_line_len);
        char *const new_line_transformed = xmemdupz(new_line, new_line_len);
        for (size_t i = 0; i < new_line_len; i++) {
          if (new_line_transformed[i] == NUL) {
            new_line_transformed[i] = '\n';
          }
        }
        ml_replace(l, new_line_transformed, false);
        inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len);
      }
      lua_pop(lstate, 1);
    }
    lua_pop(lstate, 1);
    check_cursor();
    update_screen(NOT_VALID);
  }
  
  /// Run lua file
  ///
  /// Used for :luafile.
  ///
  /// @param  eap  VimL command being run.
  void ex_luafile(exarg_T *const eap)
    FUNC_ATTR_NONNULL_ALL
  {
    nlua_exec_file((const char *)eap->arg);
  }
  
  /// execute lua code from a file.
  ///
  /// Note: we call the lua global loadfile as opposed to calling luaL_loadfile
  /// in case loadfile has been overridden in the users environment.
  ///
  /// @param  path  path of the file
  ///
  /// @return  true if everything ok, false if there was an error (echoed)
  bool nlua_exec_file(const char *path)
    FUNC_ATTR_NONNULL_ALL
  {
    lua_State *const lstate = global_lstate;
  
    lua_getglobal(lstate, "loadfile");
    lua_pushstring(lstate, path);
  
    if (nlua_pcall(lstate, 1, 2)) {
      nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
      return false;
    }
  
    // loadstring() returns either:
    //  1. nil, error
    //  2. chunk, nil
  
    if (lua_isnil(lstate, -2)) {
      // 1
      nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s"));
      assert(lua_isnil(lstate, -1));
      lua_pop(lstate, 1);
      return false;
    }
  
    // 2
    assert(lua_isnil(lstate, -1));
    lua_pop(lstate, 1);
  
    if (nlua_pcall(lstate, 0, 0)) {
      nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s"));
      return false;
    }
  
    return true;
  }
  
  int tslua_get_language_version(lua_State *L)
  {
    lua_pushnumber(L, TREE_SITTER_LANGUAGE_VERSION);
    return 1;
  }
  
  int tslua_get_minimum_language_version(lua_State *L)
  {
    lua_pushnumber(L, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION);
    return 1;
  }
  
  static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
  {
    tslua_init(lstate);
  
    lua_pushcfunction(lstate, tslua_push_parser);
    lua_setfield(lstate, -2, "_create_ts_parser");
  
    lua_pushcfunction(lstate, tslua_add_language);
    lua_setfield(lstate, -2, "_ts_add_language");
  
    lua_pushcfunction(lstate, tslua_has_language);
    lua_setfield(lstate, -2, "_ts_has_language");
  
    lua_pushcfunction(lstate, tslua_inspect_lang);
    lua_setfield(lstate, -2, "_ts_inspect_language");
  
    lua_pushcfunction(lstate, tslua_parse_query);
    lua_setfield(lstate, -2, "_ts_parse_query");
  
    lua_pushcfunction(lstate, tslua_get_language_version);
    lua_setfield(lstate, -2, "_ts_get_language_version");
  
    lua_pushcfunction(lstate, tslua_get_minimum_language_version);
    lua_setfield(lstate, -2, "_ts_get_minimum_language_version");
  }
  
  int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char_u ***results)
  {
    lua_State *const lstate = global_lstate;
    int ret = OK;
  
    // [ vim ]
    lua_getglobal(lstate, "vim");
  
    // [ vim, vim._expand_pat ]
    lua_getfield(lstate, -1, "_expand_pat");
    luaL_checktype(lstate, -1, LUA_TFUNCTION);
  
    // [ vim, vim._on_key, buf ]
    lua_pushlstring(lstate, (const char *)pat, STRLEN(pat));
  
    if (nlua_pcall(lstate, 1, 2) != 0) {
      nlua_error(lstate,
                 _("Error executing vim._expand_pat: %.*s"));
      return FAIL;
    }
  
    Error err = ERROR_INIT;
  
    *num_results = 0;
    *results = NULL;
  
    int prefix_len = (int)nlua_pop_Integer(lstate, &err);
    if (ERROR_SET(&err)) {
      ret = FAIL;
      goto cleanup;
    }
  
    Array completions = nlua_pop_Array(lstate, &err);
    if (ERROR_SET(&err)) {
      ret = FAIL;
      goto cleanup_array;
    }
  
    garray_T result_array;
    ga_init(&result_array, (int)sizeof(char *), 80);
    for (size_t i = 0; i < completions.size; i++) {
      Object v = completions.items[i];
  
      if (v.type != kObjectTypeString) {
        ret = FAIL;
        goto cleanup_array;
      }
  
      GA_APPEND(char_u *, &result_array, (char_u *)string_to_cstr(v.data.string));
    }
  
    xp->xp_pattern += prefix_len;
    *results = result_array.ga_data;
    *num_results = result_array.ga_len;
  
  cleanup_array:
    api_free_array(completions);
  
  cleanup:
  
    if (ret == FAIL) {
      ga_clear(&result_array);
    }
  
    return ret;
  }
  
  static int nlua_is_thread(lua_State *lstate)
  {
    lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
  
    return 1;
  }
  
  // Required functions for lua c functions as VimL callbacks
  
  int nlua_CFunction_func_call(int argcount, typval_T *argvars, typval_T *rettv, void *state)
  {
    lua_State *const lstate = global_lstate;
    LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
  
    return typval_exec_lua_callable(lstate, funcstate->lua_callable,
                                    argcount, argvars, rettv);
  }
  
  void nlua_CFunction_func_free(void *state)
  {
    lua_State *const lstate = global_lstate;
    LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
  
    nlua_unref_global(lstate, funcstate->lua_callable.func_ref);
    xfree(funcstate);
  }
  
  bool nlua_is_table_from_lua(typval_T *const arg)
  {
    if (arg->v_type == VAR_DICT) {
      return arg->vval.v_dict->lua_table_ref != LUA_NOREF;
    } else if (arg->v_type == VAR_LIST) {
      return arg->vval.v_list->lua_table_ref != LUA_NOREF;
    } else {
      return false;
    }
  }
  
  char_u *nlua_register_table_as_callable(typval_T *const arg)
  {
    LuaRef table_ref = LUA_NOREF;
    if (arg->v_type == VAR_DICT) {
      table_ref = arg->vval.v_dict->lua_table_ref;
    } else if (arg->v_type == VAR_LIST) {
      table_ref = arg->vval.v_list->lua_table_ref;
    }
  
    if (table_ref == LUA_NOREF) {
      return NULL;
    }
  
    lua_State *const lstate = global_lstate;
  
  #ifndef NDEBUG
    int top = lua_gettop(lstate);
  #endif
  
    nlua_pushref(lstate, table_ref);  // [table]
    if (!lua_getmetatable(lstate, -1)) {
      lua_pop(lstate, 1);
      assert(top == lua_gettop(lstate));
      return NULL;
    }  // [table, mt]
  
    lua_getfield(lstate, -1, "__call");  // [table, mt, mt.__call]
    if (!lua_isfunction(lstate, -1)) {
      lua_pop(lstate, 3);
      assert(top == lua_gettop(lstate));
      return NULL;
    }
    lua_pop(lstate, 2);  // [table]
  
    LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
    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));
  
    return name;
  }
  
  void nlua_execute_on_key(int c)
  {
    char_u buf[NUMBUFLEN];
    size_t buf_len = special_to_buf(c, mod_mask, false, buf);
  
    lua_State *const lstate = global_lstate;
  
  #ifndef NDEBUG
    int top = lua_gettop(lstate);
  #endif
  
    // [ vim ]
    lua_getglobal(lstate, "vim");
  
    // [ vim, vim._on_key]
    lua_getfield(lstate, -1, "_on_key");
    luaL_checktype(lstate, -1, LUA_TFUNCTION);
  
    // [ vim, vim._on_key, buf ]
    lua_pushlstring(lstate, (const char *)buf, buf_len);
  
    if (nlua_pcall(lstate, 1, 0)) {
      nlua_error(lstate,
                 _("Error executing  vim.on_key Lua callback: %.*s"));
    }
  
    // [ vim ]
    lua_pop(lstate, 1);
  
  #ifndef NDEBUG
    // [ ]
    assert(top == lua_gettop(lstate));
  #endif
  }
  
  // Sets the editor "script context" during Lua execution. Used by :verbose.
  // @param[out] current
  void nlua_set_sctx(sctx_T *current)
  {
    if (p_verbose <= 0 || current->sc_sid != SID_LUA) {
      return;
    }
    lua_State *const lstate = global_lstate;
    lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug));
  
    // Files where internal wrappers are defined so we can ignore them
    // like vim.o/opt etc are defined in _meta.lua
    char *ignorelist[] = {
      "vim/_meta.lua",
      "vim/keymap.lua",
    };
    int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]);
  
    for (int level = 1; true; level++) {
      if (lua_getstack(lstate, level, info) != 1) {
        goto cleanup;
      }
      if (lua_getinfo(lstate, "nSl", info) == 0) {
        goto cleanup;
      }
  
      bool is_ignored = false;
      if (info->what[0] == 'C' || info->source[0] != '@') {
        is_ignored = true;
      } else {
        for (int i = 0; i < ignorelist_size; i++) {
          if (strncmp(ignorelist[i], info->source + 1, strlen(ignorelist[i])) == 0) {
            is_ignored = true;
            break;
          }
        }
      }
      if (is_ignored) {
        continue;
      }
      break;
    }
    char *source_path = fix_fname(info->source + 1);
    get_current_script_id((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 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");
  
    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");
  
    lua_newtable(lstate);  // smods table
  
    lua_pushinteger(lstate, cmdmod.tab);
    lua_setfield(lstate, -2, "tab");
    lua_pushinteger(lstate, p_verbose);
    lua_setfield(lstate, -2, "verbose");
  
    if (cmdmod.split & WSP_ABOVE) {
      lua_pushstring(lstate, "aboveleft");
    } else if (cmdmod.split & WSP_BELOW) {
      lua_pushstring(lstate, "belowright");
    } else if (cmdmod.split & WSP_TOP) {
      lua_pushstring(lstate, "topleft");
    } else if (cmdmod.split & WSP_BOT) {
      lua_pushstring(lstate, "botright");
    } else {
      lua_pushstring(lstate, "");
    }
    lua_setfield(lstate, -2, "split");
  
    lua_pushboolean(lstate, cmdmod.split & WSP_VERT);
    lua_setfield(lstate, -2, "vertical");
    lua_pushboolean(lstate, msg_silent != 0);
    lua_setfield(lstate, -2, "silent");
    lua_pushboolean(lstate, emsg_silent != 0);
    lua_setfield(lstate, -2, "emsg_silent");
    lua_pushboolean(lstate, eap->did_sandbox);
    lua_setfield(lstate, -2, "sandbox");
    lua_pushboolean(lstate, cmdmod.save_ei != NULL);
    lua_setfield(lstate, -2, "noautocmd");
  
    typedef struct {
      bool *set;
      char *name;
    } mod_entry_T;
    static mod_entry_T mod_entries[] = {
      { &cmdmod.browse, "browse" },
      { &cmdmod.confirm, "confirm" },
      { &cmdmod.hide, "hide" },
      { &cmdmod.keepalt, "keepalt" },
      { &cmdmod.keepjumps, "keepjumps" },
      { &cmdmod.keepmarks, "keepmarks" },
      { &cmdmod.keeppatterns, "keeppatterns" },
      { &cmdmod.lockmarks, "lockmarks" },
      { &cmdmod.noswapfile, "noswapfile" }
    };
  
    // The modifiers that are simple flags
    for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
      lua_pushboolean(lstate, *mod_entries[i].set);
      lua_setfield(lstate, -2, mod_entries[i].name);
    }
  
    lua_setfield(lstate, -2, "smods");
  
    if (nlua_pcall(lstate, 1, 0)) {
      nlua_error(lstate, _("Error executing Lua callback: %.*s"));
    }
  }
  