diff options
38 files changed, 872 insertions, 302 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 22ad8e0633..a529a9b32e 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -199,6 +199,23 @@ 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|. +|lua-vim.schedule| can be used to defer these operations to the main loop, +where they are allowed. + +|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* diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 92c25ba875..8ee5718349 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -379,6 +379,12 @@ vim.stricmp(a, b) *lua-vim.stricmp* string arguments and returns 0, 1 or -1 if strings are equal, a is greater then b or a is lesser then b respectively. +vim.schedule(callback) *lua-vim.schedule* + Schedule `callback` to be called soon by the main event loop. This is + useful in contexts where some functionality is blocked, like an + autocommand or callback running with |textlock|. Then the scheduled + callback could invoke this functionality later when it is allowed. + vim.type_idx *lua-vim.type_idx* Type index for use in |lua-special-tbl|. Specifying one of the values from |lua-vim.types| allows typing the empty table (it is diff --git a/src/clint.py b/src/clint.py index 3e48ead7bf..862fdbc06b 100755 --- a/src/clint.py +++ b/src/clint.py @@ -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) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 06d7c1810c..81b3851c53 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -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) { diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 978c55691b..f0d48bf145 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -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; }; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 521ec11906..6b05d1ac0a 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -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(); } diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index cc74824402..0ea7667428 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -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) \ diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 0c14656b33..52dc359716 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -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 diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 255aeb82b6..117a9183a4 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -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 }; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 9d9c998a68..2515e3f8aa 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -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); +} diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 49ebf8cef0..a45ca5c63e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20082,9 +20082,15 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, // prevent changing the type. if (ht == &vimvarht) { if (v->di_tv.v_type == VAR_STRING) { - xfree(v->di_tv.vval.v_string); + XFREE_CLEAR(v->di_tv.vval.v_string); if (copy || tv->v_type != VAR_STRING) { - v->di_tv.vval.v_string = (char_u *)xstrdup(tv_get_string(tv)); + const char *const val = tv_get_string(tv); + + // Careful: when assigning to v:errmsg and tv_get_string() + // causes an error message the variable will alrady be set. + if (v->di_tv.vval.v_string == NULL) { + v->di_tv.vval.v_string = (char_u *)xstrdup(val); + } } else { // Take over the string to avoid an extra alloc/free. v->di_tv.vval.v_string = tv->vval.v_string; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8722c03204..1dd76553d4 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -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 */ diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 16118d642b..f4082fc833 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2217,11 +2217,19 @@ static char_u * do_one_cmd(char_u **cmdlinep, ea.arg = skipwhite(p); } - /* - * 7. Switch on command name. - * - * The "ea" structure holds the arguments that can be used. - */ + // The :try command saves the emsg_silent flag, reset it here when + // ":silent! try" was used, it should only apply to :try itself. + if (ea.cmdidx == CMD_try && did_esilent > 0) { + emsg_silent -= did_esilent; + if (emsg_silent < 0) { + emsg_silent = 0; + } + did_esilent = 0; + } + + // 7. Execute the command. + // + // The "ea" structure holds the arguments that can be used. ea.cmdlinep = cmdlinep; ea.getline = fgetline; ea.cookie = cookie; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 39975308d7..72d8c14468 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -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 */ diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 3703b76973..f52d05a4a5 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -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 diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 5da6d2c0a0..3729e42b99 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -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; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 4e94c10283..df08a9dd87 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -108,6 +108,35 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } +static void nlua_schedule_event(void **argv) +{ + LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; + lua_State *const lstate = nlua_enter(); + nlua_pushref(lstate, cb); + nlua_unref(lstate, cb); + if (lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s")); + } +} + +/// Schedule Lua callback on main loop's event queue +/// +/// @param lstate Lua interpreter state. +static int nlua_schedule(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL +{ + if (lua_type(lstate, 1) != LUA_TFUNCTION) { + lua_pushliteral(lstate, "vim.schedule: expected function"); + return lua_error(lstate); + } + + LuaRef cb = nlua_ref(lstate, 1); + + multiqueue_put(main_loop.events, nlua_schedule_event, + 1, (void *)(ptrdiff_t)cb); + return 0; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -143,6 +172,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // stricmp lua_pushcfunction(lstate, &nlua_stricmp); lua_setfield(lstate, -2, "stricmp"); + // schedule + lua_pushcfunction(lstate, &nlua_schedule); + lua_setfield(lstate, -2, "schedule"); lua_setglobal(lstate, "vim"); return 0; @@ -363,6 +395,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 +510,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 /// diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 0cbf290f64..8d356a5600 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -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" diff --git a/src/nvim/main.c b/src/nvim/main.c index ed8788af60..8ff873e127 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -316,28 +316,9 @@ int main(int argc, char **argv) // Set the break level after the terminal is initialized. debug_break_level = params.use_debug_break_level; - // - // Read user-input if any TTY is connected. // Read ex-commands if invoked with "-es". - // - bool reading_tty = !headless_mode - && !embedded_mode - && !silent_mode - && (params.input_isatty || params.output_isatty - || params.err_isatty); - bool reading_excmds = !params.input_isatty - && silent_mode - && exmode_active == EXMODE_NORMAL; - if (reading_tty || reading_excmds) { - // One of the startup commands (arguments, sourced scripts or plugins) may - // prompt the user, so start reading from a tty now. - int fd = STDIN_FILENO; - if (!silent_mode - && (!params.input_isatty || params.edit_type == EDIT_STDIN)) { - // Use stderr or stdout since stdin is being used to read commands. - fd = params.err_isatty ? fileno(stderr) : fileno(stdout); - } - input_start(fd); + if (!params.input_isatty && silent_mode && exmode_active == EXMODE_NORMAL) { + input_start(STDIN_FILENO); } // open terminals when opening files that start with term:// @@ -366,16 +347,22 @@ int main(int argc, char **argv) // startup. This allows an external UI to show messages and prompts from // --cmd and buffer loading (e.g. swap files) bool early_ui = false; - if (embedded_mode && !headless_mode) { - TIME_MSG("waiting for embedder to make request"); - remote_ui_wait_for_attach(); - TIME_MSG("done waiting for embedder"); + bool use_remote_ui = (embedded_mode && !headless_mode); + bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); + if (use_remote_ui || use_builtin_ui) { + TIME_MSG("waiting for UI to make request"); + if (use_remote_ui) { + remote_ui_wait_for_attach(); + } else { + ui_builtin_start(); + } + TIME_MSG("done waiting for UI"); // prepare screen now, so external UIs can display messages starting = NO_BUFFERS; screenclear(); early_ui = true; - TIME_MSG("initialized screen early for embedder"); + TIME_MSG("initialized screen early for UI"); } // Execute --cmd arguments. @@ -469,25 +456,12 @@ int main(int argc, char **argv) read_stdin(); } - if (reading_tty && (need_wait_return || msg_didany)) { - // Because there's no UI yet, error messages would have been printed to - // stdout. Before starting we need confirmation that the user has seen the - // messages and that is done with a call to wait_return. - TIME_MSG("waiting for return"); - wait_return(true); - } - - if (!headless_mode && !embedded_mode && !silent_mode) { - input_stop(); // Stop reading input, let the UI take over. - ui_builtin_start(); - } - setmouse(); // may start using the mouse if (exmode_active || early_ui) { - // Don't clear the screen when starting in Ex mode, or when an - // embedding UI might have displayed messages - must_redraw = CLEAR; + // Don't clear the screen when starting in Ex mode, or when a UI might have + // displayed messages. + redraw_later(VALID); } else { screenclear(); // clear screen TIME_MSG("clearing screen"); diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 4ef0103c4f..45e03681eb 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -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); diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 3925dc546a..3f768dcc0c 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -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; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c59de4f4e3..af2d24e9db 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -7983,8 +7983,14 @@ static void nv_event(cmdarg_T *cap) // not safe to perform garbage collection because there could be unreferenced // lists or dicts being used. may_garbage_collect = false; + bool may_restart = (restart_edit != 0); multiqueue_process_events(main_loop.events); finish_op = false; + if (may_restart) { + // Tricky: if restart_edit was set before the handler we are in ctrl-o mode + // but if not, the event should be allow to trigger :startinsert + cap->retval |= CA_COMMAND_BUSY; // don't call edit() now + } } /* diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 9d4fb52dc3..ced0cf0f80 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3767,10 +3767,6 @@ void ex_cfile(exarg_T *eap) qf_info_T *qi = &ql_info; char_u *au_name = NULL; - if (eap->cmdidx == CMD_lfile || eap->cmdidx == CMD_lgetfile - || eap->cmdidx == CMD_laddfile) - wp = curwin; - switch (eap->cmdidx) { case CMD_cfile: au_name = (char_u *)"cfile"; break; case CMD_cgetfile: au_name = (char_u *)"cgetfile"; break; @@ -3786,6 +3782,13 @@ void ex_cfile(exarg_T *eap) set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; + + if (eap->cmdidx == CMD_lfile + || eap->cmdidx == CMD_lgetfile + || eap->cmdidx == CMD_laddfile) { + wp = curwin; + } + // This function is used by the :cfile, :cgetfile and :caddfile // commands. // :cfile always creates a new quickfix list and jumps to the @@ -3820,6 +3823,18 @@ void ex_cfile(exarg_T *eap) } } +// Return the quickfix/location list number with the given identifier. +// Returns -1 if list is not found. +static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) +{ + for (int qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { + if (qi->qf_lists[qf_idx].qf_id == qfid) { + return qf_idx; + } + } + return -1; +} + /// Return the vimgrep autocmd name. static char_u *vgr_get_auname(cmdidx_T cmdidx) { @@ -3900,32 +3915,25 @@ static buf_T *vgr_load_dummy_buf(char_u *fname, char_u *dirname_start, /// Check whether a quickfix/location list is valid. Autocmds may remove or /// change a quickfix list when vimgrep is running. If the list is not found, /// create a new list. -static bool vgr_qflist_valid(qf_info_T *qi, unsigned save_qfid, - qfline_T *cur_qf_start, int loclist_cmd, +static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char_u *title) { - if (loclist_cmd) { - // Verify that the location list is still valid. An autocmd might have - // freed the location list. - if (!qflist_valid(curwin, save_qfid)) { + // Verify that the quickfix/location list was not freed by an autocmd + if (!qflist_valid(wp, qfid)) { + if (wp != NULL) { + // An autocmd has freed the location list EMSG(_(e_loc_list_changed)); return false; + } else { + // Quickfix list is not found, create a new one. + qf_new_list(qi, title); + return true; } } - if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) { - int idx; + if (qi->qf_lists[qi->qf_curlist].qf_id != qfid) { // Autocommands changed the quickfix list. Find the one we were using // and restore it. - for (idx = 0; idx < LISTCOUNT; idx++) { - if (cur_qf_start == qi->qf_lists[idx].qf_start) { - qi->qf_curlist = idx; - break; - } - } - if (idx == LISTCOUNT) { - // List cannot be found, create a new one. - qf_new_list(qi, title); - } + qi->qf_curlist = qf_id2nr(qi, qfid); } return true; @@ -4025,9 +4033,7 @@ void ex_vimgrep(exarg_T *eap) char_u *p; int fi; qf_info_T *qi = &ql_info; - int loclist_cmd = false; - qfline_T *cur_qf_start; - win_T *wp; + win_T *wp = NULL; buf_T *buf; int duplicate_name = FALSE; int using_dummy; @@ -4056,7 +4062,7 @@ void ex_vimgrep(exarg_T *eap) || eap->cmdidx == CMD_lgrepadd || eap->cmdidx == CMD_lvimgrepadd) { qi = ll_get_or_alloc_list(curwin); - loclist_cmd = true; + wp = curwin; } if (eap->addr_count > 0) @@ -4106,10 +4112,9 @@ void ex_vimgrep(exarg_T *eap) * ":lcd %:p:h" changes the meaning of short path names. */ os_dirname(dirname_start, MAXPATHL); - // Remember the current values of the quickfix list and qf_start, so that - // we can check for autocommands changing the current quickfix list. + // Remember the current quickfix list identifier, so that we can check for + // autocommands changing the current quickfix list. unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; - cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; seconds = (time_t)0; for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { @@ -4134,12 +4139,13 @@ void ex_vimgrep(exarg_T *eap) using_dummy = false; } - // Check whether the quickfix list is still valid - if (!vgr_qflist_valid(qi, save_qfid, cur_qf_start, loclist_cmd, - *eap->cmdlinep)) { + // Check whether the quickfix list is still valid. When loading a + // buffer above, autocommands might have changed the quickfix list. + if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) { + FreeWild(fcount, fnames); goto theend; } - cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; + save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; if (buf == NULL) { if (!got_int) @@ -4150,8 +4156,6 @@ void ex_vimgrep(exarg_T *eap) found_match = vgr_match_buflines(qi, fname, buf, ®match, tomatch, duplicate_name, flags); - cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; - if (using_dummy) { if (found_match && first_match_buf == NULL) first_match_buf = buf; @@ -4222,7 +4226,6 @@ void ex_vimgrep(exarg_T *eap) // The QuickFixCmdPost autocmd may free the quickfix list. Check the list // is still valid. - wp = loclist_cmd ? curwin : NULL; if (!qflist_valid(wp, save_qfid)) { goto theend; } @@ -4543,18 +4546,6 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) return status; } -// Return the quickfix/location list number with the given identifier. -// Returns -1 if list is not found. -static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) -{ - for (int qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { - if (qi->qf_lists[qf_idx].qf_id == qfid) { - return qf_idx; - } - } - return -1; -} - /// Return the quickfix/location list window identifier in the current tabpage. static int qf_winid(qf_info_T *qi) { @@ -5166,6 +5157,16 @@ bool set_ref_in_quickfix(int copyID) return abort; } } + + if (IS_LL_WINDOW(win) && (win->w_llist_ref->qf_refcount == 1)) { + // In a location list window and none of the other windows is + // referring to this location list. Mark the location list + // context as still in use. + abort = mark_quickfix_ctx(win->w_llist_ref, copyID); + if (abort) { + return abort; + } + } } return abort; @@ -5255,8 +5256,14 @@ void ex_cbuffer(exarg_T *eap) qf_list_changed(qi, qi->qf_curlist); } if (au_name != NULL) { + const buf_T *const curbuf_old = curbuf; apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); + if (curbuf != curbuf_old) { + // Autocommands changed buffer, don't jump now, "qi" may + // be invalid. + res = 0; + } } if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer)) { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 9439869b32..84c3f169ef 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -6595,6 +6595,9 @@ void unshowmode(bool force) // Clear the mode message. void clearmode(void) { + const int save_msg_row = msg_row; + const int save_msg_col = msg_col; + msg_ext_ui_flush(); msg_pos_mode(); if (reg_recording != 0) { @@ -6602,6 +6605,9 @@ void clearmode(void) } msg_clr_eos(); msg_ext_flush_showmode(); + + msg_col = save_msg_col; + msg_row = save_msg_row; } static void recording_mode(int attr) diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 19a15590e5..ff8f2e5fc7 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -78,3 +78,24 @@ func Test_string_concatenation() let a..=b call assert_equal('ab', a) endfunc + +func Test_nocatch_restore_silent_emsg() + silent! try + throw 1 + catch + endtry + echoerr 'wrong' + let c1 = nr2char(screenchar(&lines, 1)) + let c2 = nr2char(screenchar(&lines, 2)) + let c3 = nr2char(screenchar(&lines, 3)) + let c4 = nr2char(screenchar(&lines, 4)) + let c5 = nr2char(screenchar(&lines, 5)) + call assert_equal('wrong', c1 . c2 . c3 . c4 . c5) +endfunc + +func Test_let_errmsg() + call assert_fails('let v:errmsg = []', 'E730:') + let v:errmsg = '' + call assert_fails('let v:errmsg = []', 'E730:') + let v:errmsg = '' +endfunc diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 12101ec1f8..8b71d5f03e 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -39,6 +39,27 @@ function Test_messages() endtry endfunction + " Patch 7.4.1696 defined the "clearmode()" command for clearing the mode +" indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message +" output could then be disturbed when 'cmdheight' was greater than one. +" This test ensures that the bugfix for this issue remains in place. +function! Test_stopinsert_does_not_break_message_output() + set cmdheight=2 + redraw! + + stopinsert | echo 'test echo' + call assert_equal(116, screenchar(&lines - 1, 1)) + call assert_equal(32, screenchar(&lines, 1)) + redraw! + + stopinsert | echomsg 'test echomsg' + call assert_equal(116, screenchar(&lines - 1, 1)) + call assert_equal(32, screenchar(&lines, 1)) + redraw! + + set cmdheight& +endfunction + func Test_message_completion() call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"message clear', @:) diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index c6d083dfcc..16fb86ea08 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -28,7 +28,7 @@ func s:setup_commands(cchar) command! -count -nargs=* -bang Xprev <mods><count>cprev<bang> <args> command! -nargs=* -bang Xfirst <mods>cfirst<bang> <args> command! -nargs=* -bang Xlast <mods>clast<bang> <args> - command! -nargs=* -bang Xnfile <mods>cnfile<bang> <args> + command! -nargs=* -bang -range Xnfile <mods><count>cnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args> command! -nargs=* Xexpr <mods>cexpr <args> command! -range -nargs=* Xvimgrep <mods><count>vimgrep <args> @@ -36,6 +36,7 @@ func s:setup_commands(cchar) command! -nargs=* Xgrep <mods> grep <args> command! -nargs=* Xgrepadd <mods> grepadd <args> command! -nargs=* Xhelpgrep helpgrep <args> + command! -nargs=0 -count Xcc <count>cc let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') call setqflist([], 'f') @@ -60,7 +61,7 @@ func s:setup_commands(cchar) command! -count -nargs=* -bang Xprev <mods><count>lprev<bang> <args> command! -nargs=* -bang Xfirst <mods>lfirst<bang> <args> command! -nargs=* -bang Xlast <mods>llast<bang> <args> - command! -nargs=* -bang Xnfile <mods>lnfile<bang> <args> + command! -nargs=* -bang -range Xnfile <mods><count>lnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args> command! -nargs=* Xexpr <mods>lexpr <args> command! -range -nargs=* Xvimgrep <mods><count>lvimgrep <args> @@ -68,6 +69,7 @@ func s:setup_commands(cchar) command! -nargs=* Xgrep <mods> lgrep <args> command! -nargs=* Xgrepadd <mods> lgrepadd <args> command! -nargs=* Xhelpgrep lhelpgrep <args> + command! -nargs=0 -count Xcc <count>ll let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) call setloclist(0, [], 'f') @@ -395,12 +397,18 @@ endfunc func Xtest_browse(cchar) call s:setup_commands(a:cchar) + call g:Xsetlist([], 'f') " Jumping to first or next location list entry without any error should " result in failure - if a:cchar == 'l' - call assert_fails('lfirst', 'E776:') - call assert_fails('lnext', 'E776:') + if a:cchar == 'c' + let err = 'E42:' + else + let err = 'E776:' endif + call assert_fails('Xnext', err) + call assert_fails('Xprev', err) + call assert_fails('Xnfile', err) + call assert_fails('Xpfile', err) call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') @@ -421,6 +429,12 @@ func Xtest_browse(cchar) Xpfile call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(6, line('.')) + 5Xcc + call assert_equal(5, g:Xgetlist({'idx':0}).idx) + 2Xcc + call assert_equal(2, g:Xgetlist({'idx':0}).idx) + 10Xcc + call assert_equal(6, g:Xgetlist({'idx':0}).idx) Xlast Xprev call assert_equal('Xqftestfile2', bufname('%')) @@ -438,6 +452,23 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(5, line('.')) + " Jumping to an error from the error window using cc command + Xgetexpr ['Xqftestfile1:5:Line5', + \ 'Xqftestfile1:6:Line6', + \ 'Xqftestfile2:10:Line10', + \ 'Xqftestfile2:11:Line11'] + Xopen + 10Xcc + call assert_equal(11, line('.')) + call assert_equal('Xqftestfile2', bufname('%')) + + " Jumping to an error from the error window (when only the error window is + " present) + Xopen | only + Xlast 1 + call assert_equal(5, line('.')) + call assert_equal('Xqftestfile1', bufname('%')) + Xexpr "" call assert_fails('Xnext', 'E42:') @@ -1108,18 +1139,18 @@ func Test_efm2() " Test for %o set efm=%f(%o):%l\ %m - cgetexpr ['Xtestfile(Language.PureScript.Types):20 Error'] - call writefile(['Line1'], 'Xtestfile') + cgetexpr ['Xotestfile(Language.PureScript.Types):20 Error'] + call writefile(['Line1'], 'Xotestfile') let l = getqflist() call assert_equal(1, len(l), string(l)) call assert_equal('Language.PureScript.Types', l[0].module) copen call assert_equal('Language.PureScript.Types|20| Error', getline(1)) call feedkeys("\<CR>", 'xn') - call assert_equal('Xtestfile', expand('%:t')) + call assert_equal('Xotestfile', expand('%:t')) cclose bd - call delete("Xtestfile") + call delete("Xotestfile") " The following sequence of commands used to crash Vim set efm=%W%m @@ -1512,13 +1543,18 @@ func Test_switchbuf() set switchbuf=usetab tabedit Xqftestfile1 tabedit Xqftestfile2 + tabedit Xqftestfile3 tabfirst cfirst | cnext call assert_equal(2, tabpagenr()) 2cnext call assert_equal(3, tabpagenr()) - 2cnext - call assert_equal(3, tabpagenr()) + 6cnext + call assert_equal(4, tabpagenr()) + 2cpfile + call assert_equal(2, tabpagenr()) + 2cnfile + call assert_equal(4, tabpagenr()) tabfirst | tabonly | enew set switchbuf=split @@ -2338,7 +2374,7 @@ func XfreeTests(cchar) Xclose endfunc -" Tests for the quickifx free functionality +" Tests for the quickfix free functionality func Test_qf_free() call XfreeTests('c') call XfreeTests('l') @@ -3165,3 +3201,174 @@ func Test_lvimgrep_crash() augroup END enew | only endfunc + +func Xqfjump_tests(cchar) + call s:setup_commands(a:cchar) + + call writefile(["Line1\tFoo", "Line2"], 'F1') + call writefile(["Line1\tBar", "Line2"], 'F2') + call writefile(["Line1\tBaz", "Line2"], 'F3') + + call g:Xsetlist([], 'f') + + " Tests for + " Jumping to a line using a pattern + " Jumping to a column greater than the last column in a line + " Jumping to a line greater than the last line in the file + let l = [] + for i in range(1, 7) + call add(l, {}) + endfor + let l[0].filename='F1' + let l[0].pattern='Line1' + let l[1].filename='F2' + let l[1].pattern='Line1' + let l[2].filename='F3' + let l[2].pattern='Line1' + let l[3].filename='F3' + let l[3].lnum=1 + let l[3].col=9 + let l[3].vcol=1 + let l[4].filename='F3' + let l[4].lnum=99 + let l[5].filename='F3' + let l[5].lnum=1 + let l[5].col=99 + let l[5].vcol=1 + let l[6].filename='F3' + let l[6].pattern='abcxyz' + + call g:Xsetlist([], ' ', {'items' : l}) + Xopen | only + 2Xnext + call assert_equal(3, g:Xgetlist({'idx' : 0}).idx) + call assert_equal('F3', bufname('%')) + Xnext + call assert_equal(7, col('.')) + Xnext + call assert_equal(2, line('.')) + Xnext + call assert_equal(9, col('.')) + 2 + Xnext + call assert_equal(2, line('.')) + + if a:cchar == 'l' + " When jumping to a location list entry in the location list window and + " no usable windows are available, then a new window should be opened. + enew! | new | only + call g:Xsetlist([], 'f') + setlocal buftype=nofile + new + call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']}) + Xopen + let winid = win_getid() + wincmd p + close + call win_gotoid(winid) + Xnext + call assert_equal(3, winnr('$')) + call assert_equal(1, winnr()) + call assert_equal(2, line('.')) + + " When jumping to an entry in the location list window and the window + " associated with the location list is not present and a window containing + " the file is already present, then that window should be used. + close + belowright new + call g:Xsetlist([], 'f') + edit F3 + call win_gotoid(winid) + Xlast + call assert_equal(3, winnr()) + call assert_equal(6, g:Xgetlist({'size' : 1}).size) + call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid) + endif + + " Cleanup + enew! + new | only + + call delete('F1') + call delete('F2') + call delete('F3') +endfunc + +func Test_qfjump() + call Xqfjump_tests('c') + call Xqfjump_tests('l') +endfunc + +" The following test used to crash Vim. +" Open the location list window and close the regular window associated with +" the location list. When the garbage collection runs now, it incorrectly +" marks the location list context as not in use and frees the context. +func Test_ll_window_ctx() + call setloclist(0, [], 'f') + call setloclist(0, [], 'a', {'context' : []}) + lopen | only + call test_garbagecollect_now() + echo getloclist(0, {'context' : 1}).context + enew | only +endfunc + +" The following test used to crash vim +func Test_lfile_crash() + sp Xtest + au QuickFixCmdPre * bw + call assert_fails('lfile', 'E40') + au! QuickFixCmdPre +endfunc + +" Tests for quickfix/location lists changed by autocommands when +" :vimgrep/:lvimgrep commands are running. +func Test_vimgrep_autocmd() + call setqflist([], 'f') + call writefile(['stars'], 'Xtest1.txt') + call writefile(['stars'], 'Xtest2.txt') + + " Test 1: + " When searching for a pattern using :vimgrep, if the quickfix list is + " changed by an autocmd, the results should be added to the correct quickfix + " list. + autocmd BufRead Xtest2.txt cexpr '' | cexpr '' + silent vimgrep stars Xtest*.txt + call assert_equal(1, getqflist({'nr' : 0}).nr) + call assert_equal(3, getqflist({'nr' : '$'}).nr) + call assert_equal('Xtest2.txt', bufname(getqflist()[1].bufnr)) + au! BufRead Xtest2.txt + + " Test 2: + " When searching for a pattern using :vimgrep, if the quickfix list is + " freed, then a error should be given. + silent! %bwipe! + call setqflist([], 'f') + autocmd BufRead Xtest2.txt for i in range(10) | cexpr '' | endfor + call assert_fails('vimgrep stars Xtest*.txt', 'E925:') + au! BufRead Xtest2.txt + + " Test 3: + " When searching for a pattern using :lvimgrep, if the location list is + " freed, then the command should error out. + silent! %bwipe! + let g:save_winid = win_getid() + autocmd BufRead Xtest2.txt call setloclist(g:save_winid, [], 'f') + call assert_fails('lvimgrep stars Xtest*.txt', 'E926:') + au! BufRead Xtest2.txt + + call delete('Xtest1.txt') + call delete('Xtest2.txt') + call setqflist([], 'f') +endfunc + +func Test_lbuffer_with_bwipe() + new + new + augroup nasty + au * * bwipe + augroup END + lbuffer + augroup nasty + au! + augroup END +endfunc diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 6d9023bd79..7a725df0a1 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -48,6 +48,26 @@ void tinput_init(TermInput *input, Loop *loop) int curflags = termkey_get_canonflags(input->tk); termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); + + // If stdin is not a pty, switch to stderr. For cases like: + // echo q | nvim -es + // ls *.md | xargs nvim +#ifdef WIN32 + if (!os_isatty(0)) { + const HANDLE conin_handle = CreateFile("CONIN$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + input->in_fd = _open_osfhandle(conin_handle, _O_RDONLY); + assert(input->in_fd != -1); + } +#else + if (!os_isatty(0) && os_isatty(2)) { + input->in_fd = 2; + } +#endif + // setup input handle rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff); // initialize a timer handle for handling ESC with libtermkey @@ -435,24 +455,7 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, TermInput *input = data; if (eof) { - if (input->in_fd == 0 && !os_isatty(0) && os_isatty(2)) { - // Started reading from stdin which is not a pty but failed. Switch to - // stderr since it is a pty. - // - // This is how we support commands like: - // - // echo q | nvim -es - // - // and - // - // ls *.md | xargs nvim - input->in_fd = 2; - stream_close(&input->read_stream, NULL, NULL); - multiqueue_put(input->loop->fast_events, tinput_restart_reading, 1, - input); - } else { - loop_schedule(&main_loop, event_create(tinput_done_event, 0)); - } + loop_schedule(&main_loop, event_create(tinput_done_event, 0)); return; } @@ -496,10 +499,3 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, // without wrap around, otherwise it could be misinterpreted. rbuffer_reset(input->read_stream.buffer); } - -static void tinput_restart_reading(void **argv) -{ - TermInput *input = argv[0]; - rstream_init_fd(input->loop, &input->read_stream, input->in_fd, 0xfff); - rstream_start(&input->read_stream, tinput_read_cb, input); -} diff --git a/src/nvim/types.h b/src/nvim/types.h index f803b45e27..5bcc0c3e1b 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -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 diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 8c90c4bf30..116bdd02b8 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -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); } diff --git a/src/nvim/version.c b/src/nvim/version.c index 43a63095fc..be7a2ffcad 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -94,10 +94,10 @@ static const int included_patches[] = { // 1830, 1829, 1828, - // 1827, + 1827, 1826, 1825, - // 1824, + 1824, // 1823, 1822, // 1821, @@ -118,7 +118,7 @@ static const int included_patches[] = { // 1806, 1805, // 1804, - // 1803, + 1803, // 1802, // 1801, 1800, @@ -148,7 +148,7 @@ static const int included_patches[] = { // 1776, // 1775, // 1774, - // 1773, + 1773, // 1772, // 1771, // 1770, @@ -234,7 +234,7 @@ static const int included_patches[] = { // 1690, // 1689, // 1688, - // 1687, + 1687, 1686, // 1685, // 1684, @@ -244,8 +244,8 @@ static const int included_patches[] = { // 1680, 1679, 1678, - // 1677, - // 1676, + 1677, + 1676, 1675, 1674, // 1673, @@ -260,14 +260,14 @@ static const int included_patches[] = { // 1664, 1663, // 1662, - // 1661, + 1661, // 1660, 1659, 1658, // 1657, // 1656, // 1655, - // 1654, + 1654, // 1653, // 1652, // 1651, @@ -295,7 +295,7 @@ static const int included_patches[] = { // 1629, // 1628, 1627, - // 1626, + 1626, 1625, // 1624, // 1623, @@ -314,7 +314,7 @@ static const int included_patches[] = { 1610, // 1609, 1608, - // 1607, + 1607, 1606, // 1605, // 1604, @@ -397,7 +397,7 @@ static const int included_patches[] = { // 1527, // 1526, // 1525, - // 1524, + 1524, // 1523, // 1522, // 1521, @@ -420,9 +420,9 @@ static const int included_patches[] = { 1504, 1503, 1502, - // 1501, + 1501, 1500, - // 1499, + 1499, 1498, 1497, 1496, @@ -573,7 +573,7 @@ static const int included_patches[] = { 1351, 1350, // 1349, - // 1348, + 1348, // 1347, // 1346, // 1345, @@ -623,7 +623,7 @@ static const int included_patches[] = { 1301, // 1300, 1299, - // 1298, + 1298, // 1297, // 1296, // 1295, @@ -651,11 +651,11 @@ static const int included_patches[] = { 1273, 1272, 1271, - // 1270, + 1270, 1269, 1268, 1267, - // 1266, + 1266, 1265, // 1264, 1263, @@ -783,7 +783,7 @@ static const int included_patches[] = { 1141, 1140, // 1139, - // 1138, + 1138, 1137, 1136, 1135, @@ -857,10 +857,10 @@ static const int included_patches[] = { 1067, 1066, 1065, - // 1064, - // 1063, + 1064, + 1063, 1062, - // 1061, + 1061, // 1060, 1059, // 1058, @@ -893,7 +893,7 @@ static const int included_patches[] = { 1031, 1030, 1029, - // 1028, + 1028, 1027, 1026, 1025, @@ -969,14 +969,14 @@ static const int included_patches[] = { 955, 954, // 953, - // 952, + 952, 951, 950, - // 949, + 949, 948, // 947, 946, - // 945, + 945, 944, // 943, // 942, @@ -993,7 +993,7 @@ static const int included_patches[] = { // 931, // 930, // 929, - // 928, + 928, // 927, // 926, 925, @@ -1010,13 +1010,13 @@ static const int included_patches[] = { // 914, // 913, // 912, - // 911, + 911, // 910, // 909, // 908, - // 907, + 907, 906, - // 905, + 905, 904, 903, // 902, @@ -1024,16 +1024,16 @@ static const int included_patches[] = { 900, // 899, // 898, - // 897, + 897, // 896, 895, 894, // 893, // 892, - // 891, + 891, 890, - // 889, - // 888, + 889, + 888, // 887, 886, // 885, @@ -1048,12 +1048,12 @@ static const int included_patches[] = { 876, 875, // 874, - // 873, + 873, 872, 871, // 870, // 869, - // 868, + 868, // 867, 866, 865, @@ -1061,113 +1061,113 @@ static const int included_patches[] = { // 863, 862, 861, - // 860, - // 859, + 860, + 859, 858, - // 857, - // 856, - // 855, - // 854, + 857, + 856, + 855, + 854, 853, - // 852, + 852, 851, - // 850, - // 849, - // 848, + 850, + 849, + 848, 847, - // 846, - // 845, + 846, + 845, 844, 843, - // 842, - // 841, + 842, + 841, 840, - // 839, - // 838, + 839, + 838, 837, - // 836, + 836, 835, 834, - // 833, - // 832, + 833, + 832, 831, 830, - // 829, + 829, 828, - // 827, - // 826, + 827, + 826, // 825, - // 824, - // 823, + 824, + 823, 822, // 821, - // 820, - // 819, + 820, + 819, // 818, // 817, // 816, - // 815, + 815, 814, - // 813, + 813, 812, 811, 810, 809, 808, - // 807, + 807, 806, 805, 804, // 803, 802, - // 801, - // 800, + 801, + 800, 799, - // 798, + 798, 797, 796, 795, 794, - // 793, + 793, 792, 791, 790, - // 789, - // 788, - // 787, + 789, + 788, + 787, 786, - // 785, - // 784, - // 783, + 785, + 784, + 783, 782, - // 781, + 781, 780, - // 779, + 779, 778, - // 777, + 777, 776, - // 775, + 775, 774, 773, 772, 771, 770, - // 769, + 769, 768, 767, - // 766, + 766, 765, - // 764, + 764, 763, 762, 761, - // 760, + 760, 759, 758, 757, 756, - // 755, - // 754, + 755, + 754, 753, 752, 751, @@ -1189,7 +1189,7 @@ static const int included_patches[] = { 735, 734, 733, - // 732, + 732, 731, 730, 729, @@ -1206,7 +1206,7 @@ static const int included_patches[] = { 718, 717, 716, - // 715, + 715, 714, 713, 712, @@ -1407,7 +1407,7 @@ static const int included_patches[] = { 517, 516, 515, - // 514, + 514, 513, 512, 511, diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index b894d2facd..89d0c6f000 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -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() diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index f77af836a6..45bfa93b56 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -46,7 +46,7 @@ describe('startup', function() ]]) end) it('in a TTY: has("ttyin")==1 has("ttyout")==1', function() - local screen = Screen.new(25, 3) + local screen = Screen.new(25, 4) screen:attach() if iswin() then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) @@ -58,6 +58,7 @@ describe('startup', function() ..[[, shellescape(v:progpath))]]) screen:expect([[ ^ | + ~ | 1 1 | | ]]) @@ -96,7 +97,7 @@ describe('startup', function() end) end) it('input from pipe (implicit) #7679', function() - local screen = Screen.new(25, 3) + local screen = Screen.new(25, 4) screen:attach() if iswin() then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) @@ -109,6 +110,7 @@ describe('startup', function() ..[[, shellescape(v:progpath))]]) screen:expect([[ ^foo | + ~ | 0 1 | | ]]) diff --git a/test/functional/insert/ctrl_o_spec.lua b/test/functional/insert/ctrl_o_spec.lua index fbdff8a3a0..543d0a7d68 100644 --- a/test/functional/insert/ctrl_o_spec.lua +++ b/test/functional/insert/ctrl_o_spec.lua @@ -5,6 +5,7 @@ local eval = helpers.eval local expect = helpers.expect local feed = helpers.feed local insert = helpers.insert +local meths = helpers.meths describe('insert-mode Ctrl-O', function() before_each(clear) @@ -40,4 +41,14 @@ describe('insert-mode Ctrl-O', function() feed('ooo') expect('hello oooworld') end) + + it("doesn't cancel Ctrl-O mode when processing event", function() + feed('iHello World<c-o>') + eq({mode='niI', blocking=false}, meths.get_mode()) -- fast event + eq(2, eval('1+1')) -- causes K_EVENT key + eq({mode='niI', blocking=false}, meths.get_mode()) -- still in ctrl-o mode + feed('dd') + eq({mode='i', blocking=false}, meths.get_mode()) -- left ctrl-o mode + expect('') -- executed the command + end) end) diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua index 67f5ce24f7..780d3a1565 100644 --- a/test/functional/lua/utility_functions_spec.lua +++ b/test/functional/lua/utility_functions_spec.lua @@ -5,10 +5,13 @@ local funcs = helpers.funcs local meths = helpers.meths local clear = helpers.clear local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed +local meth_pcall = helpers.meth_pcall before_each(clear) -describe('vim.stricmp', function() +describe('lua function', function() -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has -- length 2 (in bytes). -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has @@ -17,7 +20,7 @@ describe('vim.stricmp', function() -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems. -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works -- only on ASCII characters. - it('works', function() + it('vim.stricmp', function() eq(0, funcs.luaeval('vim.stricmp("a", "A")')) eq(0, funcs.luaeval('vim.stricmp("A", "a")')) eq(0, funcs.luaeval('vim.stricmp("a", "a")')) @@ -106,10 +109,35 @@ describe('vim.stricmp', function() eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")')) eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")')) end) -end) -describe("vim.split", function() - it("works", function() + it("vim.schedule", function() + meths.execute_lua([[ + test_table = {} + vim.schedule(function() + table.insert(test_table, "xx") + end) + table.insert(test_table, "yy") + ]], {}) + eq({"yy","xx"}, meths.execute_lua("return test_table", {})) + + -- type checked args + eq({false, 'Error executing lua: vim.schedule: expected function'}, + meth_pcall(meths.execute_lua, "vim.schedule('stringly')", {})) + + eq({false, 'Error executing lua: vim.schedule: expected function'}, + meth_pcall(meths.execute_lua, "vim.schedule()", {})) + + meths.execute_lua([[ + vim.schedule(function() + error("big failure\nvery async") + end) + ]], {}) + + feed("<cr>") + eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg")) + end) + + it("vim.split", function() local split = function(str, sep) return meths.execute_lua('return vim.split(...)', {str, sep}) end @@ -141,10 +169,8 @@ describe("vim.split", function() assert(string.match(err, "Infinite loop detected")) end end) -end) -describe("vim.trim", function() - it('works', function() + it('vim.trim', function() local trim = function(s) return meths.execute_lua('return vim.trim(...)', { s }) end @@ -164,10 +190,8 @@ describe("vim.trim", function() eq(false, status) assert(string.match(err, "Only strings can be trimmed")) end) -end) -describe("vim.inspect", function() - it('works', function() + it('vim.inspect', function() -- just make sure it basically works, it has its own test suite local inspect = function(t, opts) return meths.execute_lua('return vim.inspect(...)', { t, opts }) @@ -187,10 +211,8 @@ describe("vim.inspect", function() end}) ]], {})) end) -end) -describe("vim.deepcopy", function() - it("works", function() + it("vim.deepcopy", function() local is_dc = meths.execute_lua([[ local a = { x = { 1, 2 }, y = 5} local b = vim.deepcopy(a) diff --git a/unicode/CaseFolding.txt b/unicode/CaseFolding.txt index 47949f0f99..7eeb915abf 100644 --- a/unicode/CaseFolding.txt +++ b/unicode/CaseFolding.txt @@ -1,5 +1,5 @@ -# CaseFolding-12.0.0.txt -# Date: 2019-01-22, 08:18:22 GMT +# CaseFolding-12.1.0.txt +# Date: 2019-03-10, 10:53:00 GMT # © 2019 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use, see http://www.unicode.org/terms_of_use.html diff --git a/unicode/EastAsianWidth.txt b/unicode/EastAsianWidth.txt index 424735913b..94d55d6654 100644 --- a/unicode/EastAsianWidth.txt +++ b/unicode/EastAsianWidth.txt @@ -1,5 +1,5 @@ -# EastAsianWidth-12.0.0.txt -# Date: 2019-01-21, 14:12:58 GMT [KW, LI] +# EastAsianWidth-12.1.0.txt +# Date: 2019-03-31, 22:01:58 GMT [KW, LI] # © 2019 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use, see http://www.unicode.org/terms_of_use.html @@ -1477,7 +1477,7 @@ 3280..3289;W # No [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN 328A..32B0;W # So [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT 32B1..32BF;W # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY -32C0..32FE;W # So [63] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..CIRCLED KATAKANA WO +32C0..32FF;W # So [64] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE ERA NAME REIWA 3300..33FF;W # So [256] SQUARE APAATO..SQUARE GAL 3400..4DB5;W # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5 4DB6..4DBF;W # Cn [10] <reserved-4DB6>..<reserved-4DBF> diff --git a/unicode/UnicodeData.txt b/unicode/UnicodeData.txt index d88a60135f..e65aec52f7 100644 --- a/unicode/UnicodeData.txt +++ b/unicode/UnicodeData.txt @@ -11856,6 +11856,7 @@ 32FC;CIRCLED KATAKANA WI;So;0;L;<circle> 30F0;;;;N;;;;; 32FD;CIRCLED KATAKANA WE;So;0;L;<circle> 30F1;;;;N;;;;; 32FE;CIRCLED KATAKANA WO;So;0;L;<circle> 30F2;;;;N;;;;; +32FF;SQUARE ERA NAME REIWA;So;0;L;<square> 4EE4 548C;;;;N;;;;; 3300;SQUARE APAATO;So;0;L;<square> 30A2 30D1 30FC 30C8;;;;N;SQUARED APAATO;;;; 3301;SQUARE ARUHUA;So;0;L;<square> 30A2 30EB 30D5 30A1;;;;N;SQUARED ARUHUA;;;; 3302;SQUARE ANPEA;So;0;L;<square> 30A2 30F3 30DA 30A2;;;;N;SQUARED ANPEA;;;; |