aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/api.txt17
-rw-r--r--runtime/doc/if_lua.txt6
-rwxr-xr-xsrc/clint.py2
-rw-r--r--src/nvim/api/buffer.c52
-rw-r--r--src/nvim/api/private/defs.h2
-rw-r--r--src/nvim/api/private/helpers.c5
-rw-r--r--src/nvim/api/private/helpers.h4
-rw-r--r--src/nvim/buffer.c2
-rw-r--r--src/nvim/buffer_defs.h7
-rw-r--r--src/nvim/buffer_updates.c105
-rw-r--r--src/nvim/eval.c10
-rw-r--r--src/nvim/ex_cmds.c18
-rw-r--r--src/nvim/ex_docmd.c18
-rw-r--r--src/nvim/fold.c28
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua19
-rw-r--r--src/nvim/lua/converter.c26
-rw-r--r--src/nvim/lua/executor.c81
-rw-r--r--src/nvim/lua/executor.h1
-rw-r--r--src/nvim/main.c58
-rw-r--r--src/nvim/misc1.c6
-rw-r--r--src/nvim/msgpack_rpc/helpers.c10
-rw-r--r--src/nvim/normal.c6
-rw-r--r--src/nvim/quickfix.c103
-rw-r--r--src/nvim/screen.c6
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim21
-rw-r--r--src/nvim/testdir/test_messages.vim21
-rw-r--r--src/nvim/testdir/test_quickfix.vim231
-rw-r--r--src/nvim/tui/input.c46
-rw-r--r--src/nvim/types.h5
-rw-r--r--src/nvim/undo.c2
-rw-r--r--src/nvim/version.c176
-rw-r--r--test/functional/api/buffer_updates_spec.lua2
-rw-r--r--test/functional/core/startup_spec.lua6
-rw-r--r--test/functional/insert/ctrl_o_spec.lua11
-rw-r--r--test/functional/lua/utility_functions_spec.lua50
-rw-r--r--unicode/CaseFolding.txt4
-rw-r--r--unicode/EastAsianWidth.txt6
-rw-r--r--unicode/UnicodeData.txt1
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, &regmatch, 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;;;;