mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	api: allow nvim_buf_attach from lua using callbacks
This commit is contained in:
		| @@ -199,6 +199,21 @@ paste a block of 6 lines, emits: > | ||||
| User reloads the buffer with ":edit", emits: > | ||||
|   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* | ||||
|  | ||||
|   | ||||
| @@ -2539,6 +2539,8 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): | ||||
|                    r'(?<!\bkbtree_t)' | ||||
|                    r'(?<!\bkbitr_t)' | ||||
|                    r'(?<!\bPMap)' | ||||
|                    r'(?<!\bArrayOf)' | ||||
|                    r'(?<!\bDictionaryOf)' | ||||
|                    r'\((?:const )?(?:struct )?[a-zA-Z_]\w*(?: *\*(?:const)?)*\)' | ||||
|                    r' +' | ||||
|                    r'-?(?:\*+|&)?(?:\w+|\+\+|--|\()', cast_line) | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #include <stdint.h> | ||||
| #include <stdlib.h> | ||||
| #include <limits.h> | ||||
| #include <lauxlib.h> | ||||
|  | ||||
| #include "nvim/api/buffer.h" | ||||
| #include "nvim/api/private/helpers.h" | ||||
| @@ -98,37 +99,62 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) | ||||
|   return rv; | ||||
| } | ||||
|  | ||||
| /// Activates buffer-update events on the channel. | ||||
| /// Activates buffer-update events on a channel, or as lua callbacks. | ||||
| /// | ||||
| /// @param channel_id | ||||
| /// @param buffer Buffer handle, or 0 for current buffer | ||||
| /// @param send_buffer Set to true if the initial notification should contain | ||||
| ///        the whole buffer. If so, the first notification will be a | ||||
| ///        `nvim_buf_lines_event`. Otherwise, the first notification will be | ||||
| ///        a `nvim_buf_changedtick_event` | ||||
| /// @param  opts  Optional parameters. Reserved for future use. | ||||
| ///        a `nvim_buf_changedtick_event`. Not used for lua callbacks. | ||||
| /// @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 | ||||
| /// @return False when updates couldn't be enabled because the buffer isn't | ||||
| ///         loaded or `opts` contained an invalid key; otherwise True. | ||||
| ///         TODO: LUA_API_NO_EVAL | ||||
| Boolean nvim_buf_attach(uint64_t channel_id, | ||||
|                         Buffer buffer, | ||||
|                         Boolean send_buffer, | ||||
|                         Dictionary opts, | ||||
|                         DictionaryOf(LuaRef) opts, | ||||
|                         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); | ||||
|  | ||||
|   if (!buf) { | ||||
|     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. | ||||
| @@ -307,7 +333,7 @@ void buffer_set_line_slice(Buffer buffer, | ||||
|                            Integer end, | ||||
|                            Boolean include_start, | ||||
|                            Boolean include_end, | ||||
|                            ArrayOf(String) replacement,  // NOLINT | ||||
|                            ArrayOf(String) replacement, | ||||
|                            Error *err) | ||||
| { | ||||
|   start = convert_index(start) + !include_start; | ||||
| @@ -340,7 +366,7 @@ void nvim_buf_set_lines(uint64_t channel_id, | ||||
|                         Integer start, | ||||
|                         Integer end, | ||||
|                         Boolean strict_indexing, | ||||
|                         ArrayOf(String) replacement,  // NOLINT | ||||
|                         ArrayOf(String) replacement, | ||||
|                         Error *err) | ||||
|   FUNC_API_SINCE(1) | ||||
| { | ||||
|   | ||||
| @@ -104,6 +104,7 @@ typedef enum { | ||||
|   kObjectTypeString, | ||||
|   kObjectTypeArray, | ||||
|   kObjectTypeDictionary, | ||||
|   kObjectTypeLuaRef, | ||||
|   // EXT types, cannot be split or reordered, see #EXT_OBJECT_TYPE_SHIFT | ||||
|   kObjectTypeBuffer, | ||||
|   kObjectTypeWindow, | ||||
| @@ -119,6 +120,7 @@ struct object { | ||||
|     String string; | ||||
|     Array array; | ||||
|     Dictionary dictionary; | ||||
|     LuaRef luaref; | ||||
|   } data; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include "nvim/api/private/defs.h" | ||||
| #include "nvim/api/private/handle.h" | ||||
| #include "nvim/msgpack_rpc/helpers.h" | ||||
| #include "nvim/lua/executor.h" | ||||
| #include "nvim/ascii.h" | ||||
| #include "nvim/assert.h" | ||||
| #include "nvim/vim.h" | ||||
| @@ -1147,6 +1148,10 @@ void api_free_object(Object value) | ||||
|       api_free_dictionary(value.data.dictionary); | ||||
|       break; | ||||
|  | ||||
|     case kObjectTypeLuaRef: | ||||
|       executor_free_luaref(value.data.luaref); | ||||
|       break; | ||||
|  | ||||
|     default: | ||||
|       abort(); | ||||
|   } | ||||
|   | ||||
| @@ -48,6 +48,10 @@ | ||||
|     .type = kObjectTypeDictionary, \ | ||||
|     .data.dictionary = d }) | ||||
|  | ||||
| #define LUAREF_OBJ(r) ((Object) { \ | ||||
|     .type = kObjectTypeLuaRef, \ | ||||
|     .data.luaref = r }) | ||||
|  | ||||
| #define NIL ((Object) {.type = kObjectTypeNil}) | ||||
|  | ||||
| #define PUT(dict, k, v) \ | ||||
|   | ||||
| @@ -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' | ||||
|   kv_destroy(buf->update_channels); | ||||
|   kv_init(buf->update_channels); | ||||
|   kv_destroy(buf->update_callbacks); | ||||
|   kv_init(buf->update_callbacks); | ||||
|   if (!(flags & BLN_DUMMY)) { | ||||
|     // 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 | ||||
|   | ||||
| @@ -453,6 +453,12 @@ typedef struct { | ||||
| /// Primary exists so that literals of relevant type can be made. | ||||
| 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_LL_ENTRY 2 | ||||
|  | ||||
| @@ -796,6 +802,7 @@ struct file_buffer { | ||||
|   // array of channelids which have asked to receive updates for this | ||||
|   // buffer. | ||||
|   kvec_t(uint64_t) update_channels; | ||||
|   kvec_t(BufUpdateCallbacks) update_callbacks; | ||||
|  | ||||
|   int b_diff_failed;    // internal diff failed for this buffer | ||||
| }; | ||||
|   | ||||
| @@ -5,19 +5,30 @@ | ||||
| #include "nvim/memline.h" | ||||
| #include "nvim/api/private/helpers.h" | ||||
| #include "nvim/msgpack_rpc/channel.h" | ||||
| #include "nvim/lua/executor.h" | ||||
| #include "nvim/assert.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. | ||||
| // Return False if the channel couldn't be added because the buffer is | ||||
| // 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 | ||||
|   if (buf->b_ml.ml_mfp == NULL) { | ||||
|     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 | ||||
|   size_t size = kv_size(buf->update_channels); | ||||
|   if (size) { | ||||
| @@ -69,6 +80,11 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, bool send_buffer) | ||||
|   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) | ||||
| { | ||||
|     Array args = ARRAY_DICT_INIT; | ||||
| @@ -125,6 +141,12 @@ void buf_updates_unregister_all(buf_T *buf) | ||||
|     kv_destroy(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, | ||||
| @@ -133,6 +155,10 @@ void buf_updates_send_changes(buf_T *buf, | ||||
|                               int64_t num_removed, | ||||
|                               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 | ||||
|   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); | ||||
|     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) | ||||
| @@ -192,6 +259,36 @@ void buf_updates_changedtick(buf_T *buf) | ||||
|     uint64_t channel_id = kv_A(buf->update_channels, i); | ||||
|     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) | ||||
| @@ -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 | ||||
|     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); | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   // 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 | ||||
| @@ -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 | ||||
|   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; | ||||
| } | ||||
| @@ -4074,12 +4070,10 @@ skip: | ||||
|     i = curbuf->b_ml.ml_line_count - old_line_count; | ||||
|     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_removed = num_added - i; | ||||
|       buf_updates_send_changes(curbuf, first_line, num_added, num_removed, | ||||
|                                do_buf_event); | ||||
|     } | ||||
|     int64_t num_added = last_line - first_line; | ||||
|     int64_t num_removed = num_added - i; | ||||
|     buf_updates_send_changes(curbuf, first_line, num_added, num_removed, | ||||
|                              do_buf_event); | ||||
|   } | ||||
|  | ||||
|   xfree(sub_firstline);   /* may have to free allocated copy of the line */ | ||||
|   | ||||
| @@ -737,15 +737,13 @@ void deleteFold( | ||||
|     changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false); | ||||
|  | ||||
|     // 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 | ||||
|       // 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 | ||||
|       // notification that includes every line that was part of the fold | ||||
|       int64_t num_changed = last_lnum - first_lnum; | ||||
|       buf_updates_send_changes(curbuf, first_lnum, num_changed, | ||||
|                                num_changed, true); | ||||
|     } | ||||
|     // 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 | ||||
|     // 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 | ||||
|     int64_t num_changed = last_lnum - first_lnum; | ||||
|     buf_updates_send_changes(curbuf, first_lnum, num_changed, | ||||
|                              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_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 | ||||
|     // 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. | ||||
|     int64_t num_changed = 1 + end - start; | ||||
|     buf_updates_send_changes(curbuf, start, num_changed, num_changed, true); | ||||
|   } | ||||
|   // Note: foldAddMarker() may not actually change start and/or end if | ||||
|   // 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. | ||||
|   int64_t num_changed = 1 + end - start; | ||||
|   buf_updates_send_changes(curbuf, start, num_changed, num_changed, true); | ||||
| } | ||||
|  | ||||
| /* foldAddMarker() {{{2 */ | ||||
|   | ||||
| @@ -135,7 +135,7 @@ for i,f in ipairs(shallowcopy(functions)) do | ||||
| end | ||||
|  | ||||
| -- 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'} | ||||
| exported_functions = {} | ||||
| for _,f in ipairs(functions) do | ||||
| @@ -144,6 +144,13 @@ for _,f in ipairs(functions) do | ||||
|     for _,attr in ipairs(exported_attributes) do | ||||
|       f_exported[attr] = f[attr] | ||||
|     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 | ||||
|   end | ||||
| end | ||||
| @@ -371,14 +378,18 @@ local function process_function(fn) | ||||
|     param = fn.parameters[j] | ||||
|     cparam = string.format('arg%u', j) | ||||
|     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([[ | ||||
|     const %s %s = nlua_pop_%s(lstate, &err); | ||||
|     const %s %s = nlua_pop_%s(lstate, %s&err); | ||||
|  | ||||
|     if (ERROR_SET(&err)) { | ||||
|       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( | ||||
|       lc_param_type, cparam) | ||||
|     cparams = cparam .. ', ' .. cparams | ||||
|   | ||||
| @@ -706,6 +706,10 @@ void nlua_push_Object(lua_State *lstate, const Object obj) | ||||
|       lua_pushnil(lstate); | ||||
|       break; | ||||
|     } | ||||
|     case kObjectTypeLuaRef: { | ||||
|       nlua_pushref(lstate, obj.data.luaref); | ||||
|       break; | ||||
|     } | ||||
| #define ADD_TYPE(type, data_key) \ | ||||
|     case kObjectType##type: { \ | ||||
|       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); | ||||
|  | ||||
|     val = nlua_pop_Object(lstate, err); | ||||
|     val = nlua_pop_Object(lstate, false, err); | ||||
|     if (ERROR_SET(err)) { | ||||
|       ret.size = i - 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. | ||||
| static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, | ||||
|                                                 const LuaTableProps table_props, | ||||
|                                                 bool ref, | ||||
|                                                 Error *err) | ||||
|   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 | ||||
|  | ||||
|       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 | ||||
|       } else { | ||||
|         lua_pop(lstate, 1); | ||||
| @@ -951,7 +956,7 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, | ||||
| /// Convert lua table to dictionary | ||||
| /// | ||||
| /// 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 | ||||
| { | ||||
|   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 nlua_pop_Dictionary_unchecked(lstate, table_props, err); | ||||
|   return nlua_pop_Dictionary_unchecked(lstate, table_props, ref, err); | ||||
| } | ||||
|  | ||||
| /// Helper structure for nlua_pop_Object | ||||
| @@ -973,7 +978,7 @@ typedef struct { | ||||
| /// Convert lua table to object | ||||
| /// | ||||
| /// 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; | ||||
|   const int initial_size = lua_gettop(lstate); | ||||
| @@ -1122,7 +1127,18 @@ Object nlua_pop_Object(lua_State *const lstate, Error *const err) | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       case LUA_TFUNCTION: { | ||||
|         if (ref) { | ||||
|           *cur.obj = LUAREF_OBJ(nlua_ref(lstate, -1)); | ||||
|         } else { | ||||
|           goto type_error; | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       default: { | ||||
| type_error: | ||||
|         api_set_error(err, kErrorTypeValidation, | ||||
|                       "Cannot convert given lua type"); | ||||
|         break; | ||||
|   | ||||
| @@ -363,6 +363,33 @@ static int nlua_getenv(lua_State *lstate) | ||||
| } | ||||
| #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 | ||||
| /// | ||||
| /// Used for luaeval(). | ||||
| @@ -451,9 +478,29 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err) | ||||
|     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 | ||||
| /// | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #define NVIM_LUA_EXECUTOR_H | ||||
|  | ||||
| #include <lua.h> | ||||
| #include <lauxlib.h> | ||||
|  | ||||
| #include "nvim/api/private/defs.h" | ||||
| #include "nvim/func_attr.h" | ||||
|   | ||||
| @@ -1847,9 +1847,7 @@ void changed_bytes(linenr_T lnum, colnr_T col) | ||||
|   changedOneline(curbuf, lnum); | ||||
|   changed_common(lnum, col, lnum + 1, 0L); | ||||
|   // 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. */ | ||||
|   if (curwin->w_p_diff) { | ||||
| @@ -1973,7 +1971,7 @@ changed_lines( | ||||
|  | ||||
|   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_removed = lnume - lnum; | ||||
|     buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true); | ||||
|   | ||||
| @@ -253,7 +253,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) | ||||
|           case kObjectTypeFloat: | ||||
|           case kObjectTypeString: | ||||
|           case kObjectTypeArray: | ||||
|           case kObjectTypeDictionary: { | ||||
|           case kObjectTypeDictionary: | ||||
|           case kObjectTypeLuaRef: { | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
| @@ -387,6 +388,13 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) | ||||
|         msgpack_pack_nil(res); | ||||
|         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: { | ||||
|         msgpack_rpc_from_boolean(cur.aobj->data.boolean, res); | ||||
|         break; | ||||
|   | ||||
| @@ -16,6 +16,11 @@ typedef uint32_t u8char_T; | ||||
| // Opaque handle used by API clients to refer to various objects in vim | ||||
| 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; | ||||
|  | ||||
| #endif  // NVIM_TYPES_H | ||||
|   | ||||
| @@ -2299,7 +2299,7 @@ static void u_undoredo(int undo, bool do_buf_event) | ||||
|   // 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 | ||||
|   // b:changedtick | ||||
|   if (do_buf_event && kv_size(curbuf->update_channels)) { | ||||
|   if (do_buf_event) { | ||||
|     buf_updates_changedtick(curbuf); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -760,7 +760,7 @@ describe('API: buffer events:', function() | ||||
|   it('returns a proper error on nonempty options dict', function() | ||||
|     clear() | ||||
|     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) | ||||
|  | ||||
|   it('nvim_buf_attach returns response after delay #8634', function() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Björn Linse
					Björn Linse