Skip to content

Instantly share code, notes, and snippets.

@dmitmel
Last active November 10, 2021 20:42
Show Gist options
  • Save dmitmel/0b852399b6ef6f69741cb5179757c8ce to your computer and use it in GitHub Desktop.
Save dmitmel/0b852399b6ef6f69741cb5179757c8ce to your computer and use it in GitHub Desktop.
// 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 "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/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds2.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/misc1.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"
static int in_fast_callback = 0;
// Initialized in nlua_init().
static lua_State *global_lstate = NULL;
typedef struct {
Error err;
String lua_err_str;
} LuaError;
#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 PMap(handle_T) nlua_ref_markers = MAP_INIT;
static bool nlua_track_refs = false;
# define NLUA_TRACK_REFS
#endif
/// 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 *const 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];
msg_ext_set_kind("lua_error");
semsg_multiline("Error executing luv callback:\n%s", error);
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,
1, xstrdup(error));
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 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);
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(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;
}
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
// 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");
// 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");
// 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);
lua_setglobal(lstate, "vim");
{
const char *code = (char *)&shared_module[0];
if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/shared.lua")
|| nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("E5106: Error while creating shared module: %.*s"));
return 1;
}
}
{
lua_getglobal(lstate, "package"); // [package]
lua_getfield(lstate, -1, "loaded"); // [package, loaded]
const char *code = (char *)&inspect_module[0];
if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/inspect.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s"));
return 1;
}
// [package, loaded, inspect]
lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
code = (char *)&lua_F_module[0];
if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/F.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s"));
return 1;
}
// [package, loaded, module]
lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
lua_pop(lstate, 2); // []
}
{
const char *code = (char *)&vim_module[0];
if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
|| nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
return 1;
}
}
{
lua_getglobal(lstate, "package"); // [package]
lua_getfield(lstate, -1, "loaded"); // [package, loaded]
const char *code = (char *)&lua_meta_module[0];
if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/_meta.lua")
|| nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("E5106: Error while creating vim._meta module: %.*s"));
return 1;
}
// [package, loaded, module]
lua_setfield(lstate, -2, "vim._meta"); // [package, loaded]
lua_pop(lstate, 2); // []
}
return 0;
}
/// 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) {
emsg(_("E970: Failed to initialize lua interpreter"));
preserve_exit();
}
luaL_openlibs(lstate);
nlua_state_init(lstate);
global_lstate = lstate;
}
void nlua_free_all_mem(void)
{
if (!global_lstate) {
return;
}
lua_State *lstate = global_lstate;
nlua_unref(lstate, nlua_nil_ref);
nlua_unref(lstate, nlua_empty_dict_ref);
#ifdef NLUA_TRACK_REFS
if (nlua_refcount) {
fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
}
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);
}
#endif
nlua_refcount = 0;
lua_close(lstate);
}
static void nlua_print_event(void **argv)
{
char *str = argv[0];
const size_t len = (size_t)(intptr_t)argv[1]-1; // exclude final NUL
for (size_t i = 0; i < len;) {
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);
if (in_fast_callback) {
multiqueue_put(main_loop.events, nlua_print_event,
2, msg_ga.ga_data, 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);
const char *fmt = _("E5114: Error while converting print argument #%i: %.*s");
size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx,
(int)errmsg_len, errmsg);
lua_pushlstring(lstate, (char *)IObuff, len);
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 = (char_u *)"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;
}
int nlua_call(lua_State *lstate)
{
Error err = ERROR_INIT;
size_t name_len;
const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
if (!nlua_is_deferred_safe()) {
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, (int)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(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, (int)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
LuaRef nlua_ref(lua_State *lstate, int index)
{
lua_pushvalue(lstate, index);
LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
if (ref > 0) {
nlua_refcount++;
#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));
}
#endif
}
return ref;
}
/// remove the value from the registry
void nlua_unref(lua_State *lstate, LuaRef ref)
{
if (ref > 0) {
nlua_refcount--;
#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));
}
#endif
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
}
}
void api_free_luaref(LuaRef ref)
{
nlua_unref(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(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);
}
}
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 = fgetline(0, cookie, 0, false)) != NULL) {
GA_APPEND(char_u *, &ga, line);
}
char *code = (char *)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);
}
/// 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 *const code = script_get(eap, &len);
if (eap->skip) {
xfree(code);
return;
}
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, (char_u *)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.
///
/// @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;
if (luaL_loadfile(lstate, path)) {
nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s"));
return false;
}
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;
}
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");
}
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,
vim_strsave((char_u *)v.data.string.data));
}
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;
}
// 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(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(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
}
[
{
"range": {
"end": {
"character": 21,
"line": 39
},
"start": {
"character": 0,
"line": 3
}
},
"newText": "#include \"nvim/lua/executor.h\"\n\n#include <lauxlib.h>\n#include <lua.h>\n#include <lualib.h>\n\n#include \"luv/luv.h\"\n#include \"nvim/api/private/defs.h\"\n#include \"nvim/api/private/helpers.h\"\n#include \"nvim/api/vim.h\"\n#include \"nvim/ascii.h\"\n#include \"nvim/assert.h\"\n#include \"nvim/buffer_defs.h\"\n#include \"nvim/change.h\"\n#include \"nvim/cursor.h\"\n#include \"nvim/eval/userfunc.h\"\n#include \"nvim/event/loop.h\"\n#include \"nvim/event/time.h\"\n#include \"nvim/ex_cmds2.h\"\n#include \"nvim/ex_getln.h\"\n#include \"nvim/extmark.h\"\n#include \"nvim/func_attr.h\"\n#include \"nvim/garray.h\"\n#include \"nvim/getchar.h\"\n#include \"nvim/lua/converter.h\"\n#include \"nvim/lua/stdlib.h\"\n#include \"nvim/lua/treesitter.h\"\n#include \"nvim/macros.h\"\n#include \"nvim/map.h\"\n#include \"nvim/memline.h\"\n#include \"nvim/message.h\"\n#include \"nvim/misc1.h\"\n#include \"nvim/msgpack_rpc/channel.h\"\n#include \"nvim/os/os.h\"\n#include \"nvim/screen.h\"\n#include \"nvim/undo.h\"\n#include \"nvim/version.h\"\n#include \"nvim/vim.h\""
},
{
"range": {
"end": {
"character": 2,
"line": 52
},
"start": {
"character": 1,
"line": 52
}
},
"newText": ""
},
{
"range": {
"end": {
"character": 2,
"line": 53
},
"start": {
"character": 1,
"line": 53
}
},
"newText": ""
},
{
"range": {
"end": {
"character": 2,
"line": 57
},
"start": {
"character": 57,
"line": 56
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 4,
"line": 58
},
"start": {
"character": 38,
"line": 57
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 6,
"line": 59
},
"start": {
"character": 40,
"line": 58
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 4,
"line": 60
},
"start": {
"character": 26,
"line": 59
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 6,
"line": 61
},
"start": {
"character": 12,
"line": 60
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 4,
"line": 62
},
"start": {
"character": 50,
"line": 61
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 2,
"line": 63
},
"start": {
"character": 5,
"line": 62
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 2,
"line": 68
},
"start": {
"character": 1,
"line": 68
}
},
"newText": ""
},
{
"range": {
"end": {
"character": 2,
"line": 76
},
"start": {
"character": 70,
"line": 75
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 0,
"line": 108
},
"start": {
"character": 1,
"line": 105
}
},
"newText": "\n\n"
},
{
"range": {
"end": {
"character": 0,
"line": 120
},
"start": {
"character": 1,
"line": 117
}
},
"newText": "\n\n"
},
{
"range": {
"end": {
"character": 2,
"line": 129
},
"start": {
"character": 81,
"line": 128
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 19,
"line": 149
},
"start": {
"character": 58,
"line": 148
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 178
},
"start": {
"character": 49,
"line": 177
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 17,
"line": 188
},
"start": {
"character": 55,
"line": 187
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 217
},
"start": {
"character": 39,
"line": 216
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 22,
"line": 239
},
"start": {
"character": 29,
"line": 238
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 17,
"line": 254
},
"start": {
"character": 15,
"line": 254
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 26,
"line": 257
},
"start": {
"character": 25,
"line": 257
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 4,
"line": 258
},
"start": {
"character": 61,
"line": 257
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 21,
"line": 267
},
"start": {
"character": 24,
"line": 266
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 21,
"line": 268
},
"start": {
"character": 40,
"line": 267
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 21,
"line": 269
},
"start": {
"character": 40,
"line": 268
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 28,
"line": 275
},
"start": {
"character": 39,
"line": 274
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 28,
"line": 276
},
"start": {
"character": 40,
"line": 275
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 40,
"line": 277
},
"start": {
"character": 39,
"line": 277
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 62,
"line": 278
},
"start": {
"character": 69,
"line": 277
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 62,
"line": 279
},
"start": {
"character": 76,
"line": 278
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 80,
"line": 279
},
"start": {
"character": 79,
"line": 279
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 39,
"line": 415
},
"start": {
"character": 37,
"line": 415
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 8,
"line": 429
},
"start": {
"character": 65,
"line": 428
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 8,
"line": 442
},
"start": {
"character": 63,
"line": 441
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 39,
"line": 449
},
"start": {
"character": 37,
"line": 449
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 8,
"line": 454
},
"start": {
"character": 69,
"line": 453
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 0,
"line": 491
},
"start": {
"character": 1,
"line": 488
}
},
"newText": "\n\n"
},
{
"range": {
"end": {
"character": 46,
"line": 520
},
"start": {
"character": 46,
"line": 520
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 47,
"line": 520
},
"start": {
"character": 47,
"line": 520
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 6,
"line": 526
},
"start": {
"character": 23,
"line": 525
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 527
},
"start": {
"character": 15,
"line": 526
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 528
},
"start": {
"character": 20,
"line": 527
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 529
},
"start": {
"character": 12,
"line": 528
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 6,
"line": 530
},
"start": {
"character": 17,
"line": 529
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 531
},
"start": {
"character": 14,
"line": 530
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 532
},
"start": {
"character": 76,
"line": 531
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 533
},
"start": {
"character": 75,
"line": 532
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 534
},
"start": {
"character": 21,
"line": 533
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 535
},
"start": {
"character": 12,
"line": 534
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 6,
"line": 536
},
"start": {
"character": 14,
"line": 535
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 537
},
"start": {
"character": 14,
"line": 536
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 538
},
"start": {
"character": 12,
"line": 537
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 2,
"line": 554
},
"start": {
"character": 46,
"line": 553
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 557
},
"start": {
"character": 24,
"line": 556
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 4,
"line": 558
},
"start": {
"character": 6,
"line": 557
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 4,
"line": 559
},
"start": {
"character": 17,
"line": 558
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 4,
"line": 560
},
"start": {
"character": 33,
"line": 559
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 2,
"line": 561
},
"start": {
"character": 26,
"line": 560
}
},
"newText": " \\\n "
},
{
"range": {
"end": {
"character": 32,
"line": 570
},
"start": {
"character": 30,
"line": 570
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 19,
"line": 593
},
"start": {
"character": 54,
"line": 592
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 33,
"line": 595
},
"start": {
"character": 32,
"line": 595
}
},
"newText": ""
},
{
"range": {
"end": {
"character": 33,
"line": 596
},
"start": {
"character": 48,
"line": 595
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 65,
"line": 596
},
"start": {
"character": 64,
"line": 596
}
},
"newText": ""
},
{
"range": {
"end": {
"character": 13,
"line": 603
},
"start": {
"character": 12,
"line": 603
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 36,
"line": 604
},
"start": {
"character": 75,
"line": 603
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 613
},
"start": {
"character": 40,
"line": 612
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 4,
"line": 616
},
"start": {
"character": 33,
"line": 615
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 6,
"line": 617
},
"start": {
"character": 5,
"line": 616
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 6,
"line": 618
},
"start": {
"character": 26,
"line": 617
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 6,
"line": 619
},
"start": {
"character": 27,
"line": 618
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 4,
"line": 620
},
"start": {
"character": 47,
"line": 619
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 4,
"line": 621
},
"start": {
"character": 6,
"line": 620
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 6,
"line": 622
},
"start": {
"character": 5,
"line": 621
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 4,
"line": 623
},
"start": {
"character": 28,
"line": 622
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 8,
"line": 631
},
"start": {
"character": 34,
"line": 630
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 8,
"line": 632
},
"start": {
"character": 38,
"line": 631
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 24,
"line": 638
},
"start": {
"character": 66,
"line": 637
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 53,
"line": 638
},
"start": {
"character": 52,
"line": 638
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 32,
"line": 663
},
"start": {
"character": 32,
"line": 663
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 33,
"line": 663
},
"start": {
"character": 33,
"line": 663
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 32,
"line": 671
},
"start": {
"character": 32,
"line": 671
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 33,
"line": 671
},
"start": {
"character": 33,
"line": 671
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 20,
"line": 674
},
"start": {
"character": 46,
"line": 673
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 53,
"line": 674
},
"start": {
"character": 53,
"line": 674
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 54,
"line": 674
},
"start": {
"character": 54,
"line": 674
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 32,
"line": 731
},
"start": {
"character": 32,
"line": 731
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 33,
"line": 731
},
"start": {
"character": 33,
"line": 731
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 32,
"line": 736
},
"start": {
"character": 32,
"line": 736
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 33,
"line": 736
},
"start": {
"character": 33,
"line": 736
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 20,
"line": 753
},
"start": {
"character": 47,
"line": 752
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 0,
"line": 780
},
"start": {
"character": 1,
"line": 777
}
},
"newText": "\n\n"
},
{
"range": {
"end": {
"character": 0,
"line": 794
},
"start": {
"character": 6,
"line": 791
}
},
"newText": "\n\n"
},
{
"range": {
"end": {
"character": 0,
"line": 838
},
"start": {
"character": 1,
"line": 835
}
},
"newText": "\n\n"
},
{
"range": {
"end": {
"character": 0,
"line": 856
},
"start": {
"character": 1,
"line": 853
}
},
"newText": "\n\n"
},
{
"range": {
"end": {
"character": 40,
"line": 865
},
"start": {
"character": 39,
"line": 865
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 61,
"line": 865
},
"start": {
"character": 60,
"line": 865
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 2,
"line": 866
},
"start": {
"character": 84,
"line": 865
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 39,
"line": 887
},
"start": {
"character": 38,
"line": 887
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 51,
"line": 887
},
"start": {
"character": 50,
"line": 887
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 73,
"line": 887
},
"start": {
"character": 72,
"line": 887
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 2,
"line": 889
},
"start": {
"character": 39,
"line": 888
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 9,
"line": 903
},
"start": {
"character": 57,
"line": 902
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 47,
"line": 914
},
"start": {
"character": 46,
"line": 914
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 64,
"line": 914
},
"start": {
"character": 63,
"line": 914
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 51,
"line": 915
},
"start": {
"character": 50,
"line": 915
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 65,
"line": 915
},
"start": {
"character": 64,
"line": 915
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 79,
"line": 915
},
"start": {
"character": 78,
"line": 915
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 48,
"line": 979
},
"start": {
"character": 47,
"line": 979
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 68,
"line": 979
},
"start": {
"character": 67,
"line": 979
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 82,
"line": 979
},
"start": {
"character": 81,
"line": 979
}
},
"newText": "\n "
},
{
"range": {
"end": {
"character": 18,
"line": 1015
},
"start": {
"character": 44,
"line": 1014
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 18,
"line": 1027
},
"start": {
"character": 43,
"line": 1026
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 20,
"line": 1062
},
"start": {
"character": 45,
"line": 1061
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 1093
},
"start": {
"character": 31,
"line": 1092
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 1112
},
"start": {
"character": 33,
"line": 1111
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 27,
"line": 1126
},
"start": {
"character": 34,
"line": 1125
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 27,
"line": 1127
},
"start": {
"character": 50,
"line": 1126
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 1193
},
"start": {
"character": 35,
"line": 1192
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 1204
},
"start": {
"character": 37,
"line": 1203
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 15,
"line": 1267
},
"start": {
"character": 22,
"line": 1266
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 14,
"line": 1299
},
"start": {
"character": 23,
"line": 1298
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 14,
"line": 1300
},
"start": {
"character": 28,
"line": 1299
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 34,
"line": 1327
},
"start": {
"character": 66,
"line": 1326
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 32,
"line": 1388
},
"start": {
"character": 58,
"line": 1387
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 2,
"line": 1391
},
"start": {
"character": 66,
"line": 1388
}
},
"newText": "\n\n "
},
{
"range": {
"end": {
"character": 15,
"line": 1420
},
"start": {
"character": 22,
"line": 1419
}
},
"newText": " "
},
{
"range": {
"end": {
"character": 0,
"line": 1432
},
"start": {
"character": 1,
"line": 1430
}
},
"newText": "\n"
}
]
set rtp+=~/.config/nvim/plugged/nvim-lspconfig
lua <<EOF
require('lspconfig')['ccls'].setup({})
function _G.UndoDestroyer_save(dump_path)
dump_path = dump_path or 'UndoDestroyer.json'
local lsp = require('vim.lsp')
local bufnr = vim.api.nvim_get_current_buf()
local clients = lsp.buf_get_clients(bufnr)
assert(#clients == 1)
clients[1].request(
'textDocument/formatting',
lsp.util.make_formatting_params(),
function(_, result)
vim.fn.writefile({vim.fn.json_encode(result)}, dump_path)
end,
bufnr
)
end
function _G.UndoDestroyer_load(dump_path)
dump_path = dump_path or 'UndoDestroyer.json'
local lsp = require('vim.lsp')
local bufnr = vim.api.nvim_get_current_buf()
local result = vim.fn.json_decode(vim.fn.readfile(dump_path))
lsp.util.apply_text_edits(result, bufnr)
end
EOF
command! -nargs=? UndoDestroyerSave call v:lua.UndoDestroyer_save(<f-args>)
command! -nargs=? UndoDestroyerLoad call v:lua.UndoDestroyer_load(<f-args>)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment