mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 19:38:20 +00:00
Merge pull request #10231 from bfredl/bufcb_end
api/lua: add on_detach to nvim_buf_attach
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
#include "nvim/api/buffer.h"
|
#include "nvim/api/buffer.h"
|
||||||
#include "nvim/api/private/helpers.h"
|
#include "nvim/api/private/helpers.h"
|
||||||
#include "nvim/api/private/defs.h"
|
#include "nvim/api/private/defs.h"
|
||||||
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
@@ -137,24 +138,38 @@ Boolean nvim_buf_attach(uint64_t channel_id,
|
|||||||
if (is_lua && strequal("on_lines", k.data)) {
|
if (is_lua && strequal("on_lines", 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, "callback is not a function");
|
||||||
return false;
|
goto error;
|
||||||
}
|
}
|
||||||
cb.on_lines = v->data.luaref;
|
cb.on_lines = v->data.luaref;
|
||||||
v->data.integer = LUA_NOREF;
|
v->data.integer = LUA_NOREF;
|
||||||
} else if (is_lua && strequal("on_changedtick", k.data)) {
|
} else if (is_lua && strequal("on_changedtick", 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, "callback is not a function");
|
||||||
return false;
|
goto error;
|
||||||
}
|
}
|
||||||
cb.on_changedtick = v->data.luaref;
|
cb.on_changedtick = v->data.luaref;
|
||||||
v->data.integer = LUA_NOREF;
|
v->data.integer = LUA_NOREF;
|
||||||
|
} else if (is_lua && strequal("on_detach", k.data)) {
|
||||||
|
if (v->type != kObjectTypeLuaRef) {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
cb.on_detach = v->data.luaref;
|
||||||
|
v->data.integer = LUA_NOREF;
|
||||||
} else {
|
} else {
|
||||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||||
return false;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf_updates_register(buf, channel_id, cb, send_buffer);
|
return buf_updates_register(buf, channel_id, cb, send_buffer);
|
||||||
|
|
||||||
|
error:
|
||||||
|
// TODO(bfredl): ASAN build should check that the ref table is empty?
|
||||||
|
executor_free_luaref(cb.on_lines);
|
||||||
|
executor_free_luaref(cb.on_changedtick);
|
||||||
|
executor_free_luaref(cb.on_detach);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deactivates buffer-update events on the channel.
|
/// Deactivates buffer-update events on the channel.
|
||||||
|
@@ -456,8 +456,9 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem;
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
LuaRef on_lines;
|
LuaRef on_lines;
|
||||||
LuaRef on_changedtick;
|
LuaRef on_changedtick;
|
||||||
|
LuaRef on_detach;
|
||||||
} BufUpdateCallbacks;
|
} BufUpdateCallbacks;
|
||||||
#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF }
|
#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF }
|
||||||
|
|
||||||
#define BUF_HAS_QF_ENTRY 1
|
#define BUF_HAS_QF_ENTRY 1
|
||||||
#define BUF_HAS_LL_ENTRY 2
|
#define BUF_HAS_LL_ENTRY 2
|
||||||
|
@@ -143,7 +143,21 @@ void buf_updates_unregister_all(buf_T *buf)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
|
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
|
||||||
free_update_callbacks(kv_A(buf->update_callbacks, i));
|
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
|
||||||
|
if (cb.on_detach != LUA_NOREF) {
|
||||||
|
Array args = ARRAY_DICT_INIT;
|
||||||
|
Object items[1];
|
||||||
|
args.size = 1;
|
||||||
|
args.items = items;
|
||||||
|
|
||||||
|
// the first argument is always the buffer handle
|
||||||
|
args.items[0] = BUFFER_OBJ(buf->handle);
|
||||||
|
|
||||||
|
textlock++;
|
||||||
|
executor_exec_lua_cb(cb.on_detach, "detach", args, false);
|
||||||
|
textlock--;
|
||||||
|
}
|
||||||
|
free_update_callbacks(cb);
|
||||||
}
|
}
|
||||||
kv_destroy(buf->update_callbacks);
|
kv_destroy(buf->update_callbacks);
|
||||||
kv_init(buf->update_callbacks);
|
kv_init(buf->update_callbacks);
|
||||||
@@ -237,13 +251,14 @@ void buf_updates_send_changes(buf_T *buf,
|
|||||||
args.items[4] = INTEGER_OBJ(firstline - 1 + num_added);
|
args.items[4] = INTEGER_OBJ(firstline - 1 + num_added);
|
||||||
|
|
||||||
textlock++;
|
textlock++;
|
||||||
Object res = executor_exec_lua_cb(cb.on_lines, "lines", args);
|
Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true);
|
||||||
textlock--;
|
textlock--;
|
||||||
|
|
||||||
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
|
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
|
||||||
free_update_callbacks(cb);
|
free_update_callbacks(cb);
|
||||||
keep = false;
|
keep = false;
|
||||||
}
|
}
|
||||||
|
api_free_object(res);
|
||||||
}
|
}
|
||||||
if (keep) {
|
if (keep) {
|
||||||
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
|
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
|
||||||
@@ -276,13 +291,15 @@ void buf_updates_changedtick(buf_T *buf)
|
|||||||
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
|
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
|
||||||
|
|
||||||
textlock++;
|
textlock++;
|
||||||
Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick", args);
|
Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick",
|
||||||
|
args, true);
|
||||||
textlock--;
|
textlock--;
|
||||||
|
|
||||||
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
|
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
|
||||||
free_update_callbacks(cb);
|
free_update_callbacks(cb);
|
||||||
keep = false;
|
keep = false;
|
||||||
}
|
}
|
||||||
|
api_free_object(res);
|
||||||
}
|
}
|
||||||
if (keep) {
|
if (keep) {
|
||||||
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
|
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
|
||||||
|
@@ -520,7 +520,8 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
|
|||||||
return nlua_pop_Object(lstate, false, err);
|
return nlua_pop_Object(lstate, false, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args)
|
Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
|
||||||
|
bool retval)
|
||||||
{
|
{
|
||||||
lua_State *const lstate = nlua_enter();
|
lua_State *const lstate = nlua_enter();
|
||||||
nlua_pushref(lstate, ref);
|
nlua_pushref(lstate, ref);
|
||||||
@@ -529,7 +530,7 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args)
|
|||||||
nlua_push_Object(lstate, args.items[i]);
|
nlua_push_Object(lstate, args.items[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lua_pcall(lstate, (int)args.size+1, 1, 0)) {
|
if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) {
|
||||||
// TODO(bfredl): callbacks:s might not always be msg-safe, for instance
|
// TODO(bfredl): callbacks:s might not always be msg-safe, for instance
|
||||||
// lua callbacks for redraw events. Later on let the caller deal with the
|
// lua callbacks for redraw events. Later on let the caller deal with the
|
||||||
// error instead.
|
// error instead.
|
||||||
@@ -538,7 +539,11 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args)
|
|||||||
}
|
}
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
|
|
||||||
return nlua_pop_Object(lstate, false, &err);
|
if (retval) {
|
||||||
|
return nlua_pop_Object(lstate, false, &err);
|
||||||
|
} else {
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run lua string
|
/// Run lua string
|
||||||
|
92
test/functional/lua/buffer_updates_spec.lua
Normal file
92
test/functional/lua/buffer_updates_spec.lua
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
-- Test suite for testing interactions with API bindings
|
||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local command = helpers.command
|
||||||
|
local meths = helpers.meths
|
||||||
|
local clear = helpers.clear
|
||||||
|
local eq = helpers.eq
|
||||||
|
|
||||||
|
local origlines = {"original line 1",
|
||||||
|
"original line 2",
|
||||||
|
"original line 3",
|
||||||
|
"original line 4",
|
||||||
|
"original line 5",
|
||||||
|
"original line 6"}
|
||||||
|
|
||||||
|
describe('lua: buffer event callbacks', function()
|
||||||
|
before_each(function()
|
||||||
|
clear()
|
||||||
|
meths.execute_lua([[
|
||||||
|
local events = {}
|
||||||
|
|
||||||
|
function test_register(bufnr, id, changedtick)
|
||||||
|
local function callback(...)
|
||||||
|
table.insert(events, {id, ...})
|
||||||
|
if test_unreg == id then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local opts = {on_lines=callback, on_detach=callback}
|
||||||
|
if changedtick then
|
||||||
|
opts.on_changedtick = callback
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_attach(bufnr, false, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_events()
|
||||||
|
local ret_events = events
|
||||||
|
events = {}
|
||||||
|
return ret_events
|
||||||
|
end
|
||||||
|
]], {})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works', function()
|
||||||
|
meths.buf_set_lines(0, 0, -1, true, origlines)
|
||||||
|
meths.execute_lua("return test_register(...)", {0, "test1"})
|
||||||
|
local tick = meths.buf_get_changedtick(0)
|
||||||
|
|
||||||
|
command('normal! GyyggP')
|
||||||
|
tick = tick + 1
|
||||||
|
eq({{ "test1", "lines", 1, tick, 0, 0, 1 }},
|
||||||
|
meths.execute_lua("return get_events(...)", {}))
|
||||||
|
|
||||||
|
meths.buf_set_lines(0, 3, 5, true, {"changed line"})
|
||||||
|
tick = tick + 1
|
||||||
|
eq({{ "test1", "lines", 1, tick, 3, 5, 4 }},
|
||||||
|
meths.execute_lua("return get_events(...)", {}))
|
||||||
|
|
||||||
|
meths.execute_lua("return test_register(...)", {0, "test2", true})
|
||||||
|
tick = tick + 1
|
||||||
|
command('undo')
|
||||||
|
|
||||||
|
-- plugins can opt in to receive changedtick events, or choose
|
||||||
|
-- to only recieve actual changes.
|
||||||
|
eq({{ "test1", "lines", 1, tick, 3, 4, 5 },
|
||||||
|
{ "test2", "lines", 1, tick, 3, 4, 5 },
|
||||||
|
{ "test2", "changedtick", 1, tick+1 } },
|
||||||
|
meths.execute_lua("return get_events(...)", {}))
|
||||||
|
tick = tick + 1
|
||||||
|
|
||||||
|
-- simulate next callback returning true
|
||||||
|
meths.execute_lua("test_unreg = 'test1'", {})
|
||||||
|
|
||||||
|
meths.buf_set_lines(0, 6, 7, true, {"x1","x2","x3"})
|
||||||
|
tick = tick + 1
|
||||||
|
|
||||||
|
-- plugins can opt in to receive changedtick events, or choose
|
||||||
|
-- to only recieve actual changes.
|
||||||
|
eq({{ "test1", "lines", 1, tick, 6, 7, 9 },
|
||||||
|
{ "test2", "lines", 1, tick, 6, 7, 9 }},
|
||||||
|
meths.execute_lua("return get_events(...)", {}))
|
||||||
|
|
||||||
|
meths.buf_set_lines(0, 1, 1, true, {"added"})
|
||||||
|
tick = tick + 1
|
||||||
|
eq({{ "test2", "lines", 1, tick, 1, 1, 2 }},
|
||||||
|
meths.execute_lua("return get_events(...)", {}))
|
||||||
|
|
||||||
|
command('bwipe!')
|
||||||
|
eq({{ "test2", "detach", 1 }},
|
||||||
|
meths.execute_lua("return get_events(...)", {}))
|
||||||
|
end)
|
||||||
|
end)
|
Reference in New Issue
Block a user