mirror of
https://github.com/neovim/neovim.git
synced 2025-09-11 22:08:18 +00:00
buffer updates: add on_reload callback and handle it in treesitter parser
This commit is contained in:
@@ -51,7 +51,11 @@ function M._create_parser(bufnr, lang, opts)
|
|||||||
self:_on_detach(...)
|
self:_on_detach(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, preview=true})
|
local function reload_cb(_, ...)
|
||||||
|
self:_on_reload(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, on_reload=reload_cb, preview=true})
|
||||||
|
|
||||||
self:parse()
|
self:parse()
|
||||||
|
|
||||||
|
@@ -46,11 +46,16 @@ function LanguageTree.new(source, lang, opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Invalidates this parser and all its children
|
-- Invalidates this parser and all its children
|
||||||
function LanguageTree:invalidate()
|
function LanguageTree:invalidate(reload)
|
||||||
self._valid = false
|
self._valid = false
|
||||||
|
|
||||||
|
-- buffer was reloaded, reparse all trees
|
||||||
|
if reload then
|
||||||
|
self._trees = {}
|
||||||
|
end
|
||||||
|
|
||||||
for _, child in ipairs(self._children) do
|
for _, child in ipairs(self._children) do
|
||||||
child:invalidate()
|
child:invalidate(reload)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -398,8 +403,13 @@ function LanguageTree:_on_bytes(bufnr, changed_tick,
|
|||||||
new_row, new_col, new_byte)
|
new_row, new_col, new_byte)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function LanguageTree:_on_reload()
|
||||||
|
self:invalidate(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function LanguageTree:_on_detach(...)
|
function LanguageTree:_on_detach(...)
|
||||||
self:invalidate()
|
self:invalidate(true)
|
||||||
self:_do_callback('detach', ...)
|
self:_do_callback('detach', ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -119,6 +119,10 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
|
|||||||
/// - on_detach: Lua callback invoked on detach. Args:
|
/// - on_detach: Lua callback invoked on detach. Args:
|
||||||
/// - the string "detach"
|
/// - the string "detach"
|
||||||
/// - buffer handle
|
/// - buffer handle
|
||||||
|
/// - on_reload: Lua callback invoked on reload. The entire buffer
|
||||||
|
/// content should be considered changed. Args:
|
||||||
|
/// - the string "detach"
|
||||||
|
/// - buffer handle
|
||||||
/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
|
/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
|
||||||
/// region, as args to `on_lines`.
|
/// region, as args to `on_lines`.
|
||||||
/// - preview: also attach to command preview (i.e. 'inccommand')
|
/// - preview: also attach to command preview (i.e. 'inccommand')
|
||||||
@@ -141,50 +145,57 @@ Boolean nvim_buf_attach(uint64_t channel_id,
|
|||||||
|
|
||||||
bool is_lua = (channel_id == LUA_INTERNAL_CALL);
|
bool is_lua = (channel_id == LUA_INTERNAL_CALL);
|
||||||
BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT;
|
BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT;
|
||||||
|
struct {
|
||||||
|
const char *name;
|
||||||
|
LuaRef *dest;
|
||||||
|
} cbs[] = {
|
||||||
|
{ "on_lines", &cb.on_lines },
|
||||||
|
{ "on_bytes", &cb.on_bytes },
|
||||||
|
{ "on_changedtick", &cb.on_changedtick },
|
||||||
|
{ "on_detach", &cb.on_detach },
|
||||||
|
{ "on_reload", &cb.on_reload },
|
||||||
|
{ NULL, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
for (size_t i = 0; i < opts.size; i++) {
|
for (size_t i = 0; i < opts.size; i++) {
|
||||||
String k = opts.items[i].key;
|
String k = opts.items[i].key;
|
||||||
Object *v = &opts.items[i].value;
|
Object *v = &opts.items[i].value;
|
||||||
if (is_lua && strequal("on_lines", k.data)) {
|
bool key_used = false;
|
||||||
|
if (is_lua) {
|
||||||
|
for (size_t j = 0; cbs[j].name; j++) {
|
||||||
|
if (strequal(cbs[j].name, k.data)) {
|
||||||
if (v->type != kObjectTypeLuaRef) {
|
if (v->type != kObjectTypeLuaRef) {
|
||||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"%s is not a function", cbs[j].name);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
cb.on_lines = v->data.luaref;
|
*(cbs[j].dest) = v->data.luaref;
|
||||||
v->data.luaref = LUA_NOREF;
|
v->data.luaref = LUA_NOREF;
|
||||||
} else if (is_lua && strequal("on_bytes", k.data)) {
|
key_used = true;
|
||||||
if (v->type != kObjectTypeLuaRef) {
|
break;
|
||||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
|
||||||
goto error;
|
|
||||||
}
|
}
|
||||||
cb.on_bytes = v->data.luaref;
|
|
||||||
v->data.luaref = LUA_NOREF;
|
|
||||||
} else if (is_lua && strequal("on_changedtick", k.data)) {
|
|
||||||
if (v->type != kObjectTypeLuaRef) {
|
|
||||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
|
||||||
goto error;
|
|
||||||
}
|
}
|
||||||
cb.on_changedtick = v->data.luaref;
|
|
||||||
v->data.luaref = LUA_NOREF;
|
if (key_used) {
|
||||||
} else if (is_lua && strequal("on_detach", k.data)) {
|
continue;
|
||||||
if (v->type != kObjectTypeLuaRef) {
|
} else if (strequal("utf_sizes", k.data)) {
|
||||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
cb.on_detach = v->data.luaref;
|
|
||||||
v->data.luaref = LUA_NOREF;
|
|
||||||
} else if (is_lua && strequal("utf_sizes", k.data)) {
|
|
||||||
if (v->type != kObjectTypeBoolean) {
|
if (v->type != kObjectTypeBoolean) {
|
||||||
api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
|
api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
cb.utf_sizes = v->data.boolean;
|
cb.utf_sizes = v->data.boolean;
|
||||||
} else if (is_lua && strequal("preview", k.data)) {
|
key_used = true;
|
||||||
|
} else if (strequal("preview", k.data)) {
|
||||||
if (v->type != kObjectTypeBoolean) {
|
if (v->type != kObjectTypeBoolean) {
|
||||||
api_set_error(err, kErrorTypeValidation, "preview must be boolean");
|
api_set_error(err, kErrorTypeValidation, "preview must be boolean");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
cb.preview = v->data.boolean;
|
cb.preview = v->data.boolean;
|
||||||
} else {
|
key_used = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key_used) {
|
||||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@@ -596,7 +596,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
|
|||||||
|
|
||||||
// Disable buffer-updates for the current buffer.
|
// Disable buffer-updates for the current buffer.
|
||||||
// No need to check `unload_buf`: in that case the function returned above.
|
// No need to check `unload_buf`: in that case the function returned above.
|
||||||
buf_updates_unregister_all(buf);
|
buf_updates_unload(buf, false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Remove the buffer from the list.
|
* Remove the buffer from the list.
|
||||||
@@ -821,7 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
|
|||||||
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
|
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
|
||||||
XFREE_CLEAR(buf->b_start_fenc);
|
XFREE_CLEAR(buf->b_start_fenc);
|
||||||
|
|
||||||
buf_updates_unregister_all(buf);
|
buf_updates_unload(buf, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -488,11 +488,12 @@ typedef struct {
|
|||||||
LuaRef on_bytes;
|
LuaRef on_bytes;
|
||||||
LuaRef on_changedtick;
|
LuaRef on_changedtick;
|
||||||
LuaRef on_detach;
|
LuaRef on_detach;
|
||||||
|
LuaRef on_reload;
|
||||||
bool utf_sizes;
|
bool utf_sizes;
|
||||||
bool preview;
|
bool preview;
|
||||||
} BufUpdateCallbacks;
|
} BufUpdateCallbacks;
|
||||||
#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \
|
#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \
|
||||||
LUA_NOREF, false, false }
|
LUA_NOREF, LUA_NOREF, false, false }
|
||||||
|
|
||||||
EXTERN int curbuf_splice_pending INIT(= 0);
|
EXTERN int curbuf_splice_pending INIT(= 0);
|
||||||
|
|
||||||
|
@@ -135,7 +135,7 @@ void buf_updates_unregister(buf_T *buf, uint64_t channelid)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void buf_updates_unregister_all(buf_T *buf)
|
void buf_updates_unload(buf_T *buf, bool can_reload)
|
||||||
{
|
{
|
||||||
size_t size = kv_size(buf->update_channels);
|
size_t size = kv_size(buf->update_channels);
|
||||||
if (size) {
|
if (size) {
|
||||||
@@ -146,9 +146,20 @@ void buf_updates_unregister_all(buf_T *buf)
|
|||||||
kv_init(buf->update_channels);
|
kv_init(buf->update_channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t j = 0;
|
||||||
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
|
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
|
||||||
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
|
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
|
||||||
if (cb.on_detach != LUA_NOREF) {
|
LuaRef thecb = LUA_NOREF;
|
||||||
|
|
||||||
|
bool keep = false;
|
||||||
|
if (can_reload && cb.on_reload != LUA_NOREF) {
|
||||||
|
keep = true;
|
||||||
|
thecb = cb.on_reload;
|
||||||
|
} else if (cb.on_detach != LUA_NOREF) {
|
||||||
|
thecb = cb.on_detach;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thecb != LUA_NOREF) {
|
||||||
Array args = ARRAY_DICT_INIT;
|
Array args = ARRAY_DICT_INIT;
|
||||||
Object items[1];
|
Object items[1];
|
||||||
args.size = 1;
|
args.size = 1;
|
||||||
@@ -158,15 +169,24 @@ void buf_updates_unregister_all(buf_T *buf)
|
|||||||
args.items[0] = BUFFER_OBJ(buf->handle);
|
args.items[0] = BUFFER_OBJ(buf->handle);
|
||||||
|
|
||||||
textlock++;
|
textlock++;
|
||||||
nlua_call_ref(cb.on_detach, "detach", args, false, NULL);
|
nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL);
|
||||||
textlock--;
|
textlock--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keep) {
|
||||||
|
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
|
||||||
|
} else {
|
||||||
free_update_callbacks(cb);
|
free_update_callbacks(cb);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
kv_size(buf->update_callbacks) = j;
|
||||||
|
if (kv_size(buf->update_callbacks) == 0) {
|
||||||
kv_destroy(buf->update_callbacks);
|
kv_destroy(buf->update_callbacks);
|
||||||
kv_init(buf->update_callbacks);
|
kv_init(buf->update_callbacks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void buf_updates_send_changes(buf_T *buf,
|
void buf_updates_send_changes(buf_T *buf,
|
||||||
linenr_T firstline,
|
linenr_T firstline,
|
||||||
int64_t num_added,
|
int64_t num_added,
|
||||||
|
@@ -2537,13 +2537,13 @@ int do_ecmd(
|
|||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
u_unchanged(curbuf);
|
u_unchanged(curbuf);
|
||||||
buf_updates_unregister_all(curbuf);
|
buf_updates_unload(curbuf, false);
|
||||||
buf_freeall(curbuf, BFA_KEEP_UNDO);
|
buf_freeall(curbuf, BFA_KEEP_UNDO);
|
||||||
|
|
||||||
// Tell readfile() not to clear or reload undo info.
|
// Tell readfile() not to clear or reload undo info.
|
||||||
readfile_flags = READ_KEEP_UNDO;
|
readfile_flags = READ_KEEP_UNDO;
|
||||||
} else {
|
} else {
|
||||||
buf_updates_unregister_all(curbuf);
|
buf_updates_unload(curbuf, false);
|
||||||
buf_freeall(curbuf, 0); // Free all things for buffer.
|
buf_freeall(curbuf, 0); // Free all things for buffer.
|
||||||
}
|
}
|
||||||
// If autocommands deleted the buffer we were going to re-edit, give
|
// If autocommands deleted the buffer we were going to re-edit, give
|
||||||
|
@@ -5076,7 +5076,8 @@ void buf_reload(buf_T *buf, int orig_mode)
|
|||||||
// Mark all undo states as changed.
|
// Mark all undo states as changed.
|
||||||
u_unchanged(curbuf);
|
u_unchanged(curbuf);
|
||||||
}
|
}
|
||||||
buf_updates_unregister_all(curbuf);
|
buf_updates_unload(curbuf, true);
|
||||||
|
curbuf->b_mod_set = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
xfree(ea.cmd);
|
xfree(ea.cmd);
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
-- Test suite for testing interactions with API bindings
|
-- Test suite for testing interactions with API bindings
|
||||||
local helpers = require('test.functional.helpers')(after_each)
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
local lfs = require('lfs')
|
||||||
|
|
||||||
local command = helpers.command
|
local command = helpers.command
|
||||||
local meths = helpers.meths
|
local meths = helpers.meths
|
||||||
@@ -9,8 +10,9 @@ local eq = helpers.eq
|
|||||||
local fail = helpers.fail
|
local fail = helpers.fail
|
||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
local feed = helpers.feed
|
local feed = helpers.feed
|
||||||
local deepcopy = helpers.deepcopy
|
|
||||||
local expect_events = helpers.expect_events
|
local expect_events = helpers.expect_events
|
||||||
|
local write_file = helpers.write_file
|
||||||
|
local dedent = helpers.dedent
|
||||||
|
|
||||||
local origlines = {"original line 1",
|
local origlines = {"original line 1",
|
||||||
"original line 2",
|
"original line 2",
|
||||||
@@ -33,7 +35,7 @@ before_each(function ()
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes, preview=preview}
|
local opts = {[evname]=callback, on_detach=callback, on_reload=callback, utf_sizes=utf_sizes, preview=preview}
|
||||||
if changedtick then
|
if changedtick then
|
||||||
opts.on_changedtick = callback
|
opts.on_changedtick = callback
|
||||||
end
|
end
|
||||||
@@ -294,9 +296,12 @@ describe('lua: nvim_buf_attach on_bytes', function()
|
|||||||
-- nvim_buf_get_offset forces a flush of the memline). To be safe run the
|
-- nvim_buf_get_offset forces a flush of the memline). To be safe run the
|
||||||
-- test both ways.
|
-- test both ways.
|
||||||
local function setup_eventcheck(verify, start_txt)
|
local function setup_eventcheck(verify, start_txt)
|
||||||
|
if start_txt then
|
||||||
meths.buf_set_lines(0, 0, -1, true, start_txt)
|
meths.buf_set_lines(0, 0, -1, true, start_txt)
|
||||||
local shadow = deepcopy(start_txt)
|
else
|
||||||
local shadowbytes = table.concat(shadow, '\n') .. '\n'
|
start_txt = meths.buf_get_lines(0, 0, -1, true)
|
||||||
|
end
|
||||||
|
local shadowbytes = table.concat(start_txt, '\n') .. '\n'
|
||||||
-- TODO: while we are brewing the real strong coffe,
|
-- TODO: while we are brewing the real strong coffe,
|
||||||
-- verify should check buf_get_offset after every check_events
|
-- verify should check buf_get_offset after every check_events
|
||||||
if verify then
|
if verify then
|
||||||
@@ -330,6 +335,8 @@ describe('lua: nvim_buf_attach on_bytes', function()
|
|||||||
local unknown = string.rep('\255', new_byte)
|
local unknown = string.rep('\255', new_byte)
|
||||||
local after = string.sub(shadowbytes, start_byte + old_byte + 1)
|
local after = string.sub(shadowbytes, start_byte + old_byte + 1)
|
||||||
shadowbytes = before .. unknown .. after
|
shadowbytes = before .. unknown .. after
|
||||||
|
elseif event[1] == verify_name and event[2] == "reload" then
|
||||||
|
shadowbytes = table.concat(meths.buf_get_lines(0, 0, -1, true), '\n') .. '\n'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -522,8 +529,6 @@ describe('lua: nvim_buf_attach on_bytes', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('inccomand=nosplit and substitute', function()
|
it('inccomand=nosplit and substitute', function()
|
||||||
if verify then pending("Verification can't be done when previewing") end
|
|
||||||
|
|
||||||
local check_events = setup_eventcheck(verify, {"abcde"})
|
local check_events = setup_eventcheck(verify, {"abcde"})
|
||||||
meths.set_option('inccommand', 'nosplit')
|
meths.set_option('inccommand', 'nosplit')
|
||||||
|
|
||||||
@@ -603,6 +608,38 @@ describe('lua: nvim_buf_attach on_bytes', function()
|
|||||||
"original line 5", "original line 6" },
|
"original line 5", "original line 6" },
|
||||||
meths.buf_get_lines(0, 0, -1, true))
|
meths.buf_get_lines(0, 0, -1, true))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('checktime autoread', function()
|
||||||
|
write_file("Xtest-reload", dedent [[
|
||||||
|
old line 1
|
||||||
|
old line 2]])
|
||||||
|
lfs.touch("Xtest-reload", os.time() - 10)
|
||||||
|
command "e Xtest-reload"
|
||||||
|
command "set autoread"
|
||||||
|
|
||||||
|
local check_events = setup_eventcheck(verify, nil)
|
||||||
|
|
||||||
|
write_file("Xtest-reload", dedent [[
|
||||||
|
new line 1
|
||||||
|
new line 2
|
||||||
|
new line 3]])
|
||||||
|
|
||||||
|
command "checktime"
|
||||||
|
check_events {
|
||||||
|
{ "test1", "reload", 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
feed 'ggJ'
|
||||||
|
check_events {
|
||||||
|
{ "test1", "bytes", 1, 5, 0, 10, 10, 1, 0, 1, 0, 1, 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
eq({'new line 1 new line 2', 'new line 3'}, meths.buf_get_lines(0, 0, -1, true))
|
||||||
|
end)
|
||||||
|
|
||||||
|
teardown(function()
|
||||||
|
os.remove "Xtest-reload"
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe('(with verify) handles', function()
|
describe('(with verify) handles', function()
|
||||||
|
Reference in New Issue
Block a user