Merge pull request #9170 from bfredl/lua_cb

lua callbacks for nvim_buf_attach
This commit is contained in:
Björn Linse 2019-06-04 14:54:44 +02:00 committed by GitHub
commit 3adb8a10b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 301 additions and 59 deletions

View File

@ -199,6 +199,21 @@ paste a block of 6 lines, emits: >
User reloads the buffer with ":edit", emits: > User reloads the buffer with ":edit", emits: >
nvim_buf_detach_event[{buf}] nvim_buf_detach_event[{buf}]
*api-buffer-updates-lua*
In-process lua plugins can also recieve buffer updates, in the form of lua
callbacks. These callbacks are called frequently in various contexts, buffer
contents or window layout should not be changed inside these |textlock|.
|nvim_buf_attach| will take keyword args for the callbacks. "on_lines" will
receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline}, {new_lastline}).
Unlike remote channels the text contents are not passed. The new text can be
accessed inside the callback as
`vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true)`
"on_changedtick" is invoked when |b:changedtick| was incremented but no text
was changed. The parameters recieved are ("changedtick", {buf}, {changedtick}).
============================================================================== ==============================================================================
Buffer highlighting *api-highlights* Buffer highlighting *api-highlights*

View File

@ -2539,6 +2539,8 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
r'(?<!\bkbtree_t)' r'(?<!\bkbtree_t)'
r'(?<!\bkbitr_t)' r'(?<!\bkbitr_t)'
r'(?<!\bPMap)' r'(?<!\bPMap)'
r'(?<!\bArrayOf)'
r'(?<!\bDictionaryOf)'
r'\((?:const )?(?:struct )?[a-zA-Z_]\w*(?: *\*(?:const)?)*\)' r'\((?:const )?(?:struct )?[a-zA-Z_]\w*(?: *\*(?:const)?)*\)'
r' +' r' +'
r'-?(?:\*+|&)?(?:\w+|\+\+|--|\()', cast_line) r'-?(?:\*+|&)?(?:\w+|\+\+|--|\()', cast_line)

View File

@ -7,6 +7,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <limits.h> #include <limits.h>
#include <lauxlib.h>
#include "nvim/api/buffer.h" #include "nvim/api/buffer.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
@ -98,37 +99,62 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
return rv; return rv;
} }
/// Activates buffer-update events on the channel. /// Activates buffer-update events on a channel, or as lua callbacks.
/// ///
/// @param channel_id /// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer /// @param buffer Buffer handle, or 0 for current buffer
/// @param send_buffer Set to true if the initial notification should contain /// @param send_buffer Set to true if the initial notification should contain
/// the whole buffer. If so, the first notification will be a /// the whole buffer. If so, the first notification will be a
/// `nvim_buf_lines_event`. Otherwise, the first notification will be /// `nvim_buf_lines_event`. Otherwise, the first notification will be
/// a `nvim_buf_changedtick_event` /// a `nvim_buf_changedtick_event`. Not used for lua callbacks.
/// @param opts Optional parameters. Reserved for future use. /// @param opts Optional parameters.
/// `on_lines`: lua callback received on change.
/// `on_changedtick`: lua callback received on changedtick
/// increment without text change.
/// See |api-buffer-updates-lua| for more information
/// @param[out] err Error details, if any /// @param[out] err Error details, if any
/// @return False when updates couldn't be enabled because the buffer isn't /// @return False when updates couldn't be enabled because the buffer isn't
/// loaded or `opts` contained an invalid key; otherwise True. /// loaded or `opts` contained an invalid key; otherwise True.
/// TODO: LUA_API_NO_EVAL
Boolean nvim_buf_attach(uint64_t channel_id, Boolean nvim_buf_attach(uint64_t channel_id,
Buffer buffer, Buffer buffer,
Boolean send_buffer, Boolean send_buffer,
Dictionary opts, DictionaryOf(LuaRef) opts,
Error *err) Error *err)
FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY FUNC_API_SINCE(4)
{ {
if (opts.size > 0) {
api_set_error(err, kErrorTypeValidation, "dict isn't empty");
return false;
}
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) { if (!buf) {
return false; return false;
} }
return buf_updates_register(buf, channel_id, send_buffer); bool is_lua = (channel_id == LUA_INTERNAL_CALL);
BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (is_lua && strequal("on_lines", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
return false;
}
cb.on_lines = v->data.luaref;
v->data.integer = 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");
return false;
}
cb.on_changedtick = v->data.luaref;
v->data.integer = LUA_NOREF;
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
return false;
}
}
return buf_updates_register(buf, channel_id, cb, send_buffer);
} }
/// Deactivates buffer-update events on the channel. /// Deactivates buffer-update events on the channel.
@ -307,7 +333,7 @@ void buffer_set_line_slice(Buffer buffer,
Integer end, Integer end,
Boolean include_start, Boolean include_start,
Boolean include_end, Boolean include_end,
ArrayOf(String) replacement, // NOLINT ArrayOf(String) replacement,
Error *err) Error *err)
{ {
start = convert_index(start) + !include_start; start = convert_index(start) + !include_start;
@ -340,7 +366,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
Integer start, Integer start,
Integer end, Integer end,
Boolean strict_indexing, Boolean strict_indexing,
ArrayOf(String) replacement, // NOLINT ArrayOf(String) replacement,
Error *err) Error *err)
FUNC_API_SINCE(1) FUNC_API_SINCE(1)
{ {

View File

@ -104,6 +104,7 @@ typedef enum {
kObjectTypeString, kObjectTypeString,
kObjectTypeArray, kObjectTypeArray,
kObjectTypeDictionary, kObjectTypeDictionary,
kObjectTypeLuaRef,
// EXT types, cannot be split or reordered, see #EXT_OBJECT_TYPE_SHIFT // EXT types, cannot be split or reordered, see #EXT_OBJECT_TYPE_SHIFT
kObjectTypeBuffer, kObjectTypeBuffer,
kObjectTypeWindow, kObjectTypeWindow,
@ -119,6 +120,7 @@ struct object {
String string; String string;
Array array; Array array;
Dictionary dictionary; Dictionary dictionary;
LuaRef luaref;
} data; } data;
}; };

View File

@ -11,6 +11,7 @@
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h" #include "nvim/api/private/handle.h"
#include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/helpers.h"
#include "nvim/lua/executor.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/assert.h" #include "nvim/assert.h"
#include "nvim/vim.h" #include "nvim/vim.h"
@ -1147,6 +1148,10 @@ void api_free_object(Object value)
api_free_dictionary(value.data.dictionary); api_free_dictionary(value.data.dictionary);
break; break;
case kObjectTypeLuaRef:
executor_free_luaref(value.data.luaref);
break;
default: default:
abort(); abort();
} }

View File

@ -48,6 +48,10 @@
.type = kObjectTypeDictionary, \ .type = kObjectTypeDictionary, \
.data.dictionary = d }) .data.dictionary = d })
#define LUAREF_OBJ(r) ((Object) { \
.type = kObjectTypeLuaRef, \
.data.luaref = r })
#define NIL ((Object) {.type = kObjectTypeNil}) #define NIL ((Object) {.type = kObjectTypeNil})
#define PUT(dict, k, v) \ #define PUT(dict, k, v) \

View File

@ -1836,6 +1836,8 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
buf->b_p_bl = (flags & BLN_LISTED) ? true : false; // init 'buflisted' buf->b_p_bl = (flags & BLN_LISTED) ? true : false; // init 'buflisted'
kv_destroy(buf->update_channels); kv_destroy(buf->update_channels);
kv_init(buf->update_channels); kv_init(buf->update_channels);
kv_destroy(buf->update_callbacks);
kv_init(buf->update_callbacks);
if (!(flags & BLN_DUMMY)) { if (!(flags & BLN_DUMMY)) {
// Tricky: these autocommands may change the buffer list. They could also // Tricky: these autocommands may change the buffer list. They could also
// split the window with re-using the one empty buffer. This may result in // split the window with re-using the one empty buffer. This may result in

View File

@ -453,6 +453,12 @@ typedef struct {
/// Primary exists so that literals of relevant type can be made. /// Primary exists so that literals of relevant type can be made.
typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem; typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem;
typedef struct {
LuaRef on_lines;
LuaRef on_changedtick;
} BufUpdateCallbacks;
#define BUF_UPDATE_CALLBACKS_INIT { 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
@ -796,6 +802,7 @@ struct file_buffer {
// array of channelids which have asked to receive updates for this // array of channelids which have asked to receive updates for this
// buffer. // buffer.
kvec_t(uint64_t) update_channels; kvec_t(uint64_t) update_channels;
kvec_t(BufUpdateCallbacks) update_callbacks;
int b_diff_failed; // internal diff failed for this buffer int b_diff_failed; // internal diff failed for this buffer
}; };

View File

@ -5,19 +5,30 @@
#include "nvim/memline.h" #include "nvim/memline.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/lua/executor.h"
#include "nvim/assert.h" #include "nvim/assert.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.c.generated.h"
#endif
// Register a channel. Return True if the channel was added, or already added. // Register a channel. Return True if the channel was added, or already added.
// Return False if the channel couldn't be added because the buffer is // Return False if the channel couldn't be added because the buffer is
// unloaded. // unloaded.
bool buf_updates_register(buf_T *buf, uint64_t channel_id, bool send_buffer) bool buf_updates_register(buf_T *buf, uint64_t channel_id,
BufUpdateCallbacks cb, bool send_buffer)
{ {
// must fail if the buffer isn't loaded // must fail if the buffer isn't loaded
if (buf->b_ml.ml_mfp == NULL) { if (buf->b_ml.ml_mfp == NULL) {
return false; return false;
} }
if (channel_id == LUA_INTERNAL_CALL) {
kv_push(buf->update_callbacks, cb);
return true;
}
// count how many channels are currently watching the buffer // count how many channels are currently watching the buffer
size_t size = kv_size(buf->update_channels); size_t size = kv_size(buf->update_channels);
if (size) { if (size) {
@ -69,6 +80,11 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, bool send_buffer)
return true; return true;
} }
bool buf_updates_active(buf_T *buf)
{
return kv_size(buf->update_channels) || kv_size(buf->update_callbacks);
}
void buf_updates_send_end(buf_T *buf, uint64_t channelid) void buf_updates_send_end(buf_T *buf, uint64_t channelid)
{ {
Array args = ARRAY_DICT_INIT; Array args = ARRAY_DICT_INIT;
@ -125,6 +141,12 @@ void buf_updates_unregister_all(buf_T *buf)
kv_destroy(buf->update_channels); kv_destroy(buf->update_channels);
kv_init(buf->update_channels); kv_init(buf->update_channels);
} }
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
free_update_callbacks(kv_A(buf->update_callbacks, i));
}
kv_destroy(buf->update_callbacks);
kv_init(buf->update_callbacks);
} }
void buf_updates_send_changes(buf_T *buf, void buf_updates_send_changes(buf_T *buf,
@ -133,6 +155,10 @@ void buf_updates_send_changes(buf_T *buf,
int64_t num_removed, int64_t num_removed,
bool send_tick) bool send_tick)
{ {
if (!buf_updates_active(buf)) {
return;
}
// if one the channels doesn't work, put its ID here so we can remove it later // if one the channels doesn't work, put its ID here so we can remove it later
uint64_t badchannelid = 0; uint64_t badchannelid = 0;
@ -183,6 +209,47 @@ void buf_updates_send_changes(buf_T *buf,
ELOG("Disabling buffer updates for dead channel %"PRIu64, badchannelid); ELOG("Disabling buffer updates for dead channel %"PRIu64, badchannelid);
buf_updates_unregister(buf, badchannelid); buf_updates_unregister(buf, badchannelid);
} }
// notify each of the active channels
size_t j = 0;
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_lines != LUA_NOREF) {
Array args = ARRAY_DICT_INIT;
Object items[5];
args.size = 5;
args.items = items;
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
// next argument is b:changedtick
args.items[1] = send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL;
// the first line that changed (zero-indexed)
args.items[2] = INTEGER_OBJ(firstline - 1);
// the last line that was changed
args.items[3] = INTEGER_OBJ(firstline - 1 + num_removed);
// the last line in the updated range
args.items[4] = INTEGER_OBJ(firstline - 1 + num_added);
textlock++;
Object res = executor_exec_lua_cb(cb.on_lines, "lines", args);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
free_update_callbacks(cb);
keep = false;
}
}
if (keep) {
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
}
}
kv_size(buf->update_callbacks) = j;
} }
void buf_updates_changedtick(buf_T *buf) void buf_updates_changedtick(buf_T *buf)
@ -192,6 +259,36 @@ void buf_updates_changedtick(buf_T *buf)
uint64_t channel_id = kv_A(buf->update_channels, i); uint64_t channel_id = kv_A(buf->update_channels, i);
buf_updates_changedtick_single(buf, channel_id); buf_updates_changedtick_single(buf, channel_id);
} }
size_t j = 0;
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_changedtick != LUA_NOREF) {
Array args = ARRAY_DICT_INIT;
Object items[2];
args.size = 2;
args.items = items;
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
// next argument is b:changedtick
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
textlock++;
Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick", args);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
free_update_callbacks(cb);
keep = false;
}
}
if (keep) {
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
}
}
kv_size(buf->update_callbacks) = j;
} }
void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
@ -209,3 +306,9 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
// don't try and clean up dead channels here // don't try and clean up dead channels here
rpc_send_event(channel_id, "nvim_buf_changedtick_event", args); rpc_send_event(channel_id, "nvim_buf_changedtick_event", args);
} }
static void free_update_callbacks(BufUpdateCallbacks cb)
{
executor_free_luaref(cb.on_lines);
executor_free_luaref(cb.on_changedtick);
}

View File

@ -900,9 +900,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
// send update regarding the new lines that were added // send update regarding the new lines that were added
if (kv_size(curbuf->update_channels)) { buf_updates_send_changes(curbuf, dest + 1, num_lines, 0, true);
buf_updates_send_changes(curbuf, dest + 1, num_lines, 0, true);
}
/* /*
* Now we delete the original text -- webb * Now we delete the original text -- webb
@ -939,9 +937,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
} }
// send nvim_buf_lines_event regarding lines that were deleted // send nvim_buf_lines_event regarding lines that were deleted
if (kv_size(curbuf->update_channels)) { buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines, true);
buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines, true);
}
return OK; return OK;
} }
@ -4074,12 +4070,10 @@ skip:
i = curbuf->b_ml.ml_line_count - old_line_count; i = curbuf->b_ml.ml_line_count - old_line_count;
changed_lines(first_line, 0, last_line - i, i, false); changed_lines(first_line, 0, last_line - i, i, false);
if (kv_size(curbuf->update_channels)) { int64_t num_added = last_line - first_line;
int64_t num_added = last_line - first_line; int64_t num_removed = num_added - i;
int64_t num_removed = num_added - i; buf_updates_send_changes(curbuf, first_line, num_added, num_removed,
buf_updates_send_changes(curbuf, first_line, num_added, num_removed, do_buf_event);
do_buf_event);
}
} }
xfree(sub_firstline); /* may have to free allocated copy of the line */ xfree(sub_firstline); /* may have to free allocated copy of the line */

View File

@ -737,15 +737,13 @@ void deleteFold(
changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false); changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false);
// send one nvim_buf_lines_event at the end // send one nvim_buf_lines_event at the end
if (kv_size(curbuf->update_channels)) { // last_lnum is the line *after* the last line of the outermost fold
// last_lnum is the line *after* the last line of the outermost fold // that was modified. Note also that deleting a fold might only require
// that was modified. Note also that deleting a fold might only require // the modification of the *first* line of the fold, but we send through a
// the modification of the *first* line of the fold, but we send through a // notification that includes every line that was part of the fold
// notification that includes every line that was part of the fold int64_t num_changed = last_lnum - first_lnum;
int64_t num_changed = last_lnum - first_lnum; buf_updates_send_changes(curbuf, first_lnum, num_changed,
buf_updates_send_changes(curbuf, first_lnum, num_changed, num_changed, true);
num_changed, true);
}
} }
} }
@ -1584,13 +1582,11 @@ static void foldCreateMarkers(linenr_T start, linenr_T end)
* changed when the start marker is inserted and the end isn't. */ * changed when the start marker is inserted and the end isn't. */
changed_lines(start, (colnr_T)0, end, 0L, false); changed_lines(start, (colnr_T)0, end, 0L, false);
if (kv_size(curbuf->update_channels)) { // Note: foldAddMarker() may not actually change start and/or end if
// Note: foldAddMarker() may not actually change start and/or end if // u_save() is unable to save the buffer line, but we send the
// u_save() is unable to save the buffer line, but we send the // nvim_buf_lines_event anyway since it won't do any harm.
// nvim_buf_lines_event anyway since it won't do any harm. int64_t num_changed = 1 + end - start;
int64_t num_changed = 1 + end - start; buf_updates_send_changes(curbuf, start, num_changed, num_changed, true);
buf_updates_send_changes(curbuf, start, num_changed, num_changed, true);
}
} }
/* foldAddMarker() {{{2 */ /* foldAddMarker() {{{2 */

View File

@ -135,7 +135,7 @@ for i,f in ipairs(shallowcopy(functions)) do
end end
-- don't expose internal attributes like "impl_name" in public metadata -- don't expose internal attributes like "impl_name" in public metadata
exported_attributes = {'name', 'parameters', 'return_type', 'method', exported_attributes = {'name', 'return_type', 'method',
'since', 'deprecated_since'} 'since', 'deprecated_since'}
exported_functions = {} exported_functions = {}
for _,f in ipairs(functions) do for _,f in ipairs(functions) do
@ -144,6 +144,13 @@ for _,f in ipairs(functions) do
for _,attr in ipairs(exported_attributes) do for _,attr in ipairs(exported_attributes) do
f_exported[attr] = f[attr] f_exported[attr] = f[attr]
end end
f_exported.parameters = {}
for i,param in ipairs(f.parameters) do
if param[1] == "DictionaryOf(LuaRef)" then
param = {"Dictionary", param[2]}
end
f_exported.parameters[i] = param
end
exported_functions[#exported_functions+1] = f_exported exported_functions[#exported_functions+1] = f_exported
end end
end end
@ -371,14 +378,18 @@ local function process_function(fn)
param = fn.parameters[j] param = fn.parameters[j]
cparam = string.format('arg%u', j) cparam = string.format('arg%u', j)
param_type = real_type(param[1]) param_type = real_type(param[1])
lc_param_type = param_type:lower() lc_param_type = real_type(param[1]):lower()
extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or ""
if param[1] == "DictionaryOf(LuaRef)" then
extra = "true, "
end
write_shifted_output(output, string.format([[ write_shifted_output(output, string.format([[
const %s %s = nlua_pop_%s(lstate, &err); const %s %s = nlua_pop_%s(lstate, %s&err);
if (ERROR_SET(&err)) { if (ERROR_SET(&err)) {
goto exit_%u; goto exit_%u;
} }
]], param[1], cparam, param_type, #fn.parameters - j)) ]], param[1], cparam, param_type, extra, #fn.parameters - j))
free_code[#free_code + 1] = ('api_free_%s(%s);'):format( free_code[#free_code + 1] = ('api_free_%s(%s);'):format(
lc_param_type, cparam) lc_param_type, cparam)
cparams = cparam .. ', ' .. cparams cparams = cparam .. ', ' .. cparams

View File

@ -706,6 +706,10 @@ void nlua_push_Object(lua_State *lstate, const Object obj)
lua_pushnil(lstate); lua_pushnil(lstate);
break; break;
} }
case kObjectTypeLuaRef: {
nlua_pushref(lstate, obj.data.luaref);
break;
}
#define ADD_TYPE(type, data_key) \ #define ADD_TYPE(type, data_key) \
case kObjectType##type: { \ case kObjectType##type: { \
nlua_push_##type(lstate, obj.data.data_key); \ nlua_push_##type(lstate, obj.data.data_key); \
@ -862,7 +866,7 @@ static Array nlua_pop_Array_unchecked(lua_State *const lstate,
lua_rawgeti(lstate, -1, (int)i); lua_rawgeti(lstate, -1, (int)i);
val = nlua_pop_Object(lstate, err); val = nlua_pop_Object(lstate, false, err);
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
ret.size = i - 1; ret.size = i - 1;
lua_pop(lstate, 1); lua_pop(lstate, 1);
@ -900,6 +904,7 @@ Array nlua_pop_Array(lua_State *lstate, Error *err)
/// @param[out] err Location where error will be saved. /// @param[out] err Location where error will be saved.
static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate,
const LuaTableProps table_props, const LuaTableProps table_props,
bool ref,
Error *err) Error *err)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{ {
@ -923,7 +928,7 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate,
// stack: dict, key, value // stack: dict, key, value
if (!ERROR_SET(err)) { if (!ERROR_SET(err)) {
ret.items[i].value = nlua_pop_Object(lstate, err); ret.items[i].value = nlua_pop_Object(lstate, ref, err);
// stack: dict, key // stack: dict, key
} else { } else {
lua_pop(lstate, 1); lua_pop(lstate, 1);
@ -951,7 +956,7 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate,
/// Convert lua table to dictionary /// Convert lua table to dictionary
/// ///
/// Always pops one value from the stack. /// Always pops one value from the stack.
Dictionary nlua_pop_Dictionary(lua_State *lstate, Error *err) Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Error *err)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{ {
const LuaTableProps table_props = nlua_check_type(lstate, err, const LuaTableProps table_props = nlua_check_type(lstate, err,
@ -961,7 +966,7 @@ Dictionary nlua_pop_Dictionary(lua_State *lstate, Error *err)
return (Dictionary) { .size = 0, .items = NULL }; return (Dictionary) { .size = 0, .items = NULL };
} }
return nlua_pop_Dictionary_unchecked(lstate, table_props, err); return nlua_pop_Dictionary_unchecked(lstate, table_props, ref, err);
} }
/// Helper structure for nlua_pop_Object /// Helper structure for nlua_pop_Object
@ -973,7 +978,7 @@ typedef struct {
/// Convert lua table to object /// Convert lua table to object
/// ///
/// Always pops one value from the stack. /// Always pops one value from the stack.
Object nlua_pop_Object(lua_State *const lstate, Error *const err) Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
{ {
Object ret = NIL; Object ret = NIL;
const int initial_size = lua_gettop(lstate); const int initial_size = lua_gettop(lstate);
@ -1122,7 +1127,18 @@ Object nlua_pop_Object(lua_State *const lstate, Error *const err)
} }
break; break;
} }
case LUA_TFUNCTION: {
if (ref) {
*cur.obj = LUAREF_OBJ(nlua_ref(lstate, -1));
} else {
goto type_error;
}
break;
}
default: { default: {
type_error:
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation,
"Cannot convert given lua type"); "Cannot convert given lua type");
break; break;

View File

@ -363,6 +363,33 @@ static int nlua_getenv(lua_State *lstate)
} }
#endif #endif
/// add the value to the registry
LuaRef nlua_ref(lua_State *lstate, int index)
{
lua_pushvalue(lstate, index);
return luaL_ref(lstate, LUA_REGISTRYINDEX);
}
/// remove the value from the registry
void nlua_unref(lua_State *lstate, LuaRef ref)
{
if (ref > 0) {
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
}
}
void executor_free_luaref(LuaRef ref)
{
lua_State *const lstate = nlua_enter();
nlua_unref(lstate, ref);
}
/// push a value referenced in the regirstry
void nlua_pushref(lua_State *lstate, LuaRef ref)
{
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
}
/// Evaluate lua string /// Evaluate lua string
/// ///
/// Used for luaeval(). /// Used for luaeval().
@ -451,9 +478,29 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
return NIL; return NIL;
} }
return nlua_pop_Object(lstate, err); return nlua_pop_Object(lstate, false, err);
} }
Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args)
{
lua_State *const lstate = nlua_enter();
nlua_pushref(lstate, ref);
lua_pushstring(lstate, name);
for (size_t i = 0; i < args.size; i++) {
nlua_push_Object(lstate, args.items[i]);
}
if (lua_pcall(lstate, (int)args.size+1, 1, 0)) {
// 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
// error instead.
nlua_error(lstate, _("Error executing lua callback: %.*s"));
return NIL;
}
Error err = ERROR_INIT;
return nlua_pop_Object(lstate, false, &err);
}
/// Run lua string /// Run lua string
/// ///

View File

@ -2,6 +2,7 @@
#define NVIM_LUA_EXECUTOR_H #define NVIM_LUA_EXECUTOR_H
#include <lua.h> #include <lua.h>
#include <lauxlib.h>
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/func_attr.h" #include "nvim/func_attr.h"

View File

@ -1847,9 +1847,7 @@ void changed_bytes(linenr_T lnum, colnr_T col)
changedOneline(curbuf, lnum); changedOneline(curbuf, lnum);
changed_common(lnum, col, lnum + 1, 0L); changed_common(lnum, col, lnum + 1, 0L);
// notify any channels that are watching // notify any channels that are watching
if (kv_size(curbuf->update_channels)) { buf_updates_send_changes(curbuf, lnum, 1, 1, true);
buf_updates_send_changes(curbuf, lnum, 1, 1, true);
}
/* Diff highlighting in other diff windows may need to be updated too. */ /* Diff highlighting in other diff windows may need to be updated too. */
if (curwin->w_p_diff) { if (curwin->w_p_diff) {
@ -1973,7 +1971,7 @@ changed_lines(
changed_common(lnum, col, lnume, xtra); changed_common(lnum, col, lnume, xtra);
if (do_buf_event && kv_size(curbuf->update_channels)) { if (do_buf_event) {
int64_t num_added = (int64_t)(lnume + xtra - lnum); int64_t num_added = (int64_t)(lnume + xtra - lnum);
int64_t num_removed = lnume - lnum; int64_t num_removed = lnume - lnum;
buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true); buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true);

View File

@ -253,7 +253,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
case kObjectTypeFloat: case kObjectTypeFloat:
case kObjectTypeString: case kObjectTypeString:
case kObjectTypeArray: case kObjectTypeArray:
case kObjectTypeDictionary: { case kObjectTypeDictionary:
case kObjectTypeLuaRef: {
break; break;
} }
} }
@ -387,6 +388,13 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res)
msgpack_pack_nil(res); msgpack_pack_nil(res);
break; break;
} }
case kObjectTypeLuaRef: {
// TODO(bfredl): could also be an error. Though kObjectTypeLuaRef
// should only appear when the caller has opted in to handle references,
// see nlua_pop_Object.
msgpack_pack_nil(res);
break;
}
case kObjectTypeBoolean: { case kObjectTypeBoolean: {
msgpack_rpc_from_boolean(cur.aobj->data.boolean, res); msgpack_rpc_from_boolean(cur.aobj->data.boolean, res);
break; break;

View File

@ -16,6 +16,11 @@ typedef uint32_t u8char_T;
// Opaque handle used by API clients to refer to various objects in vim // Opaque handle used by API clients to refer to various objects in vim
typedef int handle_T; typedef int handle_T;
// Opaque handle to a lua value. Must be free with `executor_free_luaref` when
// not needed anymore! LUA_NOREF represents missing reference, i e to indicate
// absent callback etc.
typedef int LuaRef;
typedef struct expand expand_T; typedef struct expand expand_T;
#endif // NVIM_TYPES_H #endif // NVIM_TYPES_H

View File

@ -2299,7 +2299,7 @@ static void u_undoredo(int undo, bool do_buf_event)
// because the calls to changed()/unchanged() above will bump changedtick // because the calls to changed()/unchanged() above will bump changedtick
// again, we need to send a nvim_buf_lines_event with just the new value of // again, we need to send a nvim_buf_lines_event with just the new value of
// b:changedtick // b:changedtick
if (do_buf_event && kv_size(curbuf->update_channels)) { if (do_buf_event) {
buf_updates_changedtick(curbuf); buf_updates_changedtick(curbuf);
} }

View File

@ -760,7 +760,7 @@ describe('API: buffer events:', function()
it('returns a proper error on nonempty options dict', function() it('returns a proper error on nonempty options dict', function()
clear() clear()
local b = editoriginal(false) local b = editoriginal(false)
expect_err("dict isn't empty", buffer, 'attach', b, false, {builtin="asfd"}) expect_err("unexpected key: builtin", buffer, 'attach', b, false, {builtin="asfd"})
end) end)
it('nvim_buf_attach returns response after delay #8634', function() it('nvim_buf_attach returns response after delay #8634', function()