aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt2
-rw-r--r--src/nvim/api/buffer.c7
-rw-r--r--src/nvim/api/tabpage.c1
-rw-r--r--src/nvim/api/vim.c16
-rw-r--r--src/nvim/api/window.c5
-rw-r--r--src/nvim/edit.c8
-rw-r--r--src/nvim/eval.c1148
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval_defs.h5
-rw-r--r--src/nvim/event/defs.h39
-rw-r--r--src/nvim/event/loop.c95
-rw-r--r--src/nvim/event/loop.h39
-rw-r--r--src/nvim/event/process.c92
-rw-r--r--src/nvim/event/process.h6
-rw-r--r--src/nvim/event/pty_process.c30
-rw-r--r--src/nvim/event/pty_process.h4
-rw-r--r--src/nvim/event/queue.c208
-rw-r--r--src/nvim/event/queue.h19
-rw-r--r--src/nvim/event/rstream.c38
-rw-r--r--src/nvim/event/signal.c9
-rw-r--r--src/nvim/event/signal.h1
-rw-r--r--src/nvim/event/socket.c12
-rw-r--r--src/nvim/event/socket.h1
-rw-r--r--src/nvim/event/stream.c13
-rw-r--r--src/nvim/event/stream.h9
-rw-r--r--src/nvim/event/time.c9
-rw-r--r--src/nvim/event/time.h1
-rw-r--r--src/nvim/event/uv_process.h4
-rw-r--r--src/nvim/event/wstream.c2
-rw-r--r--src/nvim/ex_cmds.c4
-rw-r--r--src/nvim/ex_cmds2.c2
-rw-r--r--src/nvim/ex_getln.c7
-rw-r--r--src/nvim/fileio.c8
-rw-r--r--src/nvim/func_attr.h2
-rw-r--r--src/nvim/getchar.c2
-rw-r--r--src/nvim/globals.h8
-rw-r--r--src/nvim/lib/queue.h92
-rw-r--r--src/nvim/main.c4
-rw-r--r--src/nvim/map.c2
-rw-r--r--src/nvim/memline.c3
-rw-r--r--src/nvim/msgpack_rpc/channel.c49
-rw-r--r--src/nvim/msgpack_rpc/defs.h5
-rw-r--r--src/nvim/msgpack_rpc/remote_ui.c23
-rw-r--r--src/nvim/normal.c7
-rw-r--r--src/nvim/ops.c6
-rw-r--r--src/nvim/os/fs.c25
-rw-r--r--src/nvim/os/input.c29
-rw-r--r--src/nvim/os/shell.c57
-rw-r--r--src/nvim/os/signal.c11
-rw-r--r--src/nvim/os/time.c2
-rw-r--r--src/nvim/os/users.c5
-rw-r--r--src/nvim/os/win_defs.h8
-rw-r--r--src/nvim/path.c22
-rw-r--r--src/nvim/regexp.c6
-rw-r--r--src/nvim/regexp_nfa.c138
-rw-r--r--src/nvim/terminal.c72
-rw-r--r--src/nvim/tui/term_input.inl12
-rw-r--r--src/nvim/tui/tui.c78
-rw-r--r--src/nvim/ui.c41
-rw-r--r--src/nvim/ui.h3
-rw-r--r--src/nvim/version.c17
-rw-r--r--src/nvim/vim.h2
62 files changed, 1981 insertions, 595 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 598f19d19d..6e6a890cd1 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -237,7 +237,7 @@ install_helper(TARGETS nvim)
if(CLANG_ASAN_UBSAN)
message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ")
- set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-fno-sanitize-recover -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined ")
+ set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-fno-sanitize-recover -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/.asan-blacklist")
set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ")
elseif(CLANG_MSAN)
message(STATUS "Enabling Clang memory sanitizer for nvim.")
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 915c5f74d7..12c97cfffb 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -70,7 +70,6 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// @param line The new line.
/// @param[out] err Details of an error that may have occurred
void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
- FUNC_ATTR_DEFERRED
{
Object l = STRING_OBJ(line);
Array array = {.items = &l, .size = 1};
@@ -83,7 +82,6 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
/// @param index The line index
/// @param[out] err Details of an error that may have occurred
void buffer_del_line(Buffer buffer, Integer index, Error *err)
- FUNC_ATTR_DEFERRED
{
Array array = ARRAY_DICT_INIT;
buffer_set_line_slice(buffer, index, index, true, true, array, err);
@@ -171,7 +169,6 @@ void buffer_set_line_slice(Buffer buffer,
Boolean include_end,
ArrayOf(String) replacement,
Error *err)
- FUNC_ATTR_DEFERRED
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -339,7 +336,6 @@ Object buffer_get_var(Buffer buffer, String name, Error *err)
/// @param[out] err Details of an error that may have occurred
/// @return The old value
Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
- FUNC_ATTR_DEFERRED
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -375,7 +371,6 @@ Object buffer_get_option(Buffer buffer, String name, Error *err)
/// @param value The option value
/// @param[out] err Details of an error that may have occurred
void buffer_set_option(Buffer buffer, String name, Object value, Error *err)
- FUNC_ATTR_DEFERRED
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -426,7 +421,6 @@ String buffer_get_name(Buffer buffer, Error *err)
/// @param name The buffer name
/// @param[out] err Details of an error that may have occurred
void buffer_set_name(Buffer buffer, String name, Error *err)
- FUNC_ATTR_DEFERRED
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -472,7 +466,6 @@ void buffer_insert(Buffer buffer,
Integer lnum,
ArrayOf(String) lines,
Error *err)
- FUNC_ATTR_DEFERRED
{
buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err);
}
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 1c958118e1..126ee4072d 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -62,7 +62,6 @@ Object tabpage_get_var(Tabpage tabpage, String name, Error *err)
/// @param[out] err Details of an error that may have occurred
/// @return The tab page handle
Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)
- FUNC_ATTR_DEFERRED
{
tabpage_T *tab = find_tab_by_handle(tabpage, err);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index f5dadb00ea..b9900b5d5a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -37,7 +37,6 @@
/// @param str The command str
/// @param[out] err Details of an error that may have occurred
void vim_command(String str, Error *err)
- FUNC_ATTR_DEFERRED
{
// Run the command
try_start();
@@ -54,7 +53,6 @@ void vim_command(String str, Error *err)
/// @see feedkeys()
/// @see vim_strsave_escape_csi
void vim_feedkeys(String keys, String mode, Boolean escape_csi)
- FUNC_ATTR_DEFERRED
{
bool remap = true;
bool insert = false;
@@ -100,6 +98,7 @@ void vim_feedkeys(String keys, String mode, Boolean escape_csi)
/// @return The number of bytes actually written, which can be lower than
/// requested if the buffer becomes full.
Integer vim_input(String keys)
+ FUNC_ATTR_ASYNC
{
return (Integer)input_enqueue(keys);
}
@@ -143,7 +142,6 @@ String vim_command_output(String str, Error *err)
/// @param[out] err Details of an error that may have occurred
/// @return The expanded object
Object vim_eval(String str, Error *err)
- FUNC_ATTR_DEFERRED
{
Object rv = OBJECT_INIT;
// Evaluate the expression
@@ -171,7 +169,6 @@ Object vim_eval(String str, Error *err)
/// @param[out] err Details of an error that may have occurred
/// @return Result of the function call
Object vim_call_function(String fname, Array args, Error *err)
- FUNC_ATTR_DEFERRED
{
Object rv = OBJECT_INIT;
if (args.size > MAX_FUNC_ARGS) {
@@ -312,7 +309,6 @@ String vim_get_current_line(Error *err)
/// @param line The line contents
/// @param[out] err Details of an error that may have occurred
void vim_set_current_line(String line, Error *err)
- FUNC_ATTR_DEFERRED
{
buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);
}
@@ -321,7 +317,6 @@ void vim_set_current_line(String line, Error *err)
///
/// @param[out] err Details of an error that may have occurred
void vim_del_current_line(Error *err)
- FUNC_ATTR_DEFERRED
{
buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
}
@@ -343,7 +338,6 @@ Object vim_get_var(String name, Error *err)
/// @param[out] err Details of an error that may have occurred
/// @return the old value if any
Object vim_set_var(String name, Object value, Error *err)
- FUNC_ATTR_DEFERRED
{
return dict_set_value(&globvardict, name, value, err);
}
@@ -374,7 +368,6 @@ Object vim_get_option(String name, Error *err)
/// @param value The new option value
/// @param[out] err Details of an error that may have occurred
void vim_set_option(String name, Object value, Error *err)
- FUNC_ATTR_DEFERRED
{
set_option_to(NULL, SREQ_GLOBAL, name, value, err);
}
@@ -383,7 +376,6 @@ void vim_set_option(String name, Object value, Error *err)
///
/// @param str The message
void vim_out_write(String str)
- FUNC_ATTR_DEFERRED
{
write_msg(str, false);
}
@@ -392,7 +384,6 @@ void vim_out_write(String str)
///
/// @param str The message
void vim_err_write(String str)
- FUNC_ATTR_DEFERRED
{
write_msg(str, true);
}
@@ -402,7 +393,6 @@ void vim_err_write(String str)
///
/// @param str The message
void vim_report_error(String str)
- FUNC_ATTR_DEFERRED
{
vim_err_write(str);
vim_err_write((String) {.data = "\n", .size = 1});
@@ -442,7 +432,6 @@ Buffer vim_get_current_buffer(void)
/// @param id The buffer handle
/// @param[out] err Details of an error that may have occurred
void vim_set_current_buffer(Buffer buffer, Error *err)
- FUNC_ATTR_DEFERRED
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -493,7 +482,6 @@ Window vim_get_current_window(void)
///
/// @param handle The window handle
void vim_set_current_window(Window window, Error *err)
- FUNC_ATTR_DEFERRED
{
win_T *win = find_window_by_handle(window, err);
@@ -545,7 +533,6 @@ Tabpage vim_get_current_tabpage(void)
/// @param handle The tab page handle
/// @param[out] err Details of an error that may have occurred
void vim_set_current_tabpage(Tabpage tabpage, Error *err)
- FUNC_ATTR_DEFERRED
{
tabpage_T *tp = find_tab_by_handle(tabpage, err);
@@ -609,6 +596,7 @@ Dictionary vim_get_color_map(void)
Array vim_get_api_info(uint64_t channel_id)
+ FUNC_ATTR_ASYNC
{
Array rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 5034c26c83..aad616c7bf 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -54,7 +54,6 @@ ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err)
/// @param pos the (row, col) tuple representing the new position
/// @param[out] err Details of an error that may have occurred
void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
- FUNC_ATTR_DEFERRED
{
win_T *win = find_window_by_handle(window, err);
@@ -118,7 +117,6 @@ Integer window_get_height(Window window, Error *err)
/// @param height the new height in rows
/// @param[out] err Details of an error that may have occurred
void window_set_height(Window window, Integer height, Error *err)
- FUNC_ATTR_DEFERRED
{
win_T *win = find_window_by_handle(window, err);
@@ -162,7 +160,6 @@ Integer window_get_width(Window window, Error *err)
/// @param width the new width in columns
/// @param[out] err Details of an error that may have occurred
void window_set_width(Window window, Integer width, Error *err)
- FUNC_ATTR_DEFERRED
{
win_T *win = find_window_by_handle(window, err);
@@ -208,7 +205,6 @@ Object window_get_var(Window window, String name, Error *err)
/// @param[out] err Details of an error that may have occurred
/// @return The old value
Object window_set_var(Window window, String name, Object value, Error *err)
- FUNC_ATTR_DEFERRED
{
win_T *win = find_window_by_handle(window, err);
@@ -244,7 +240,6 @@ Object window_get_option(Window window, String name, Error *err)
/// @param value The option value
/// @param[out] err Details of an error that may have occurred
void window_set_option(Window window, String name, Object value, Error *err)
- FUNC_ATTR_DEFERRED
{
win_T *win = find_window_by_handle(window, err);
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 390d62210b..6bcf5e804a 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -254,7 +254,7 @@ edit (
)
{
if (curbuf->terminal) {
- terminal_enter(true);
+ terminal_enter();
return false;
}
@@ -601,15 +601,15 @@ edit (
* Get a character for Insert mode. Ignore K_IGNORE.
*/
lastc = c; /* remember previous char for CTRL-D */
- loop_enable_deferred_events(&loop);
+ input_enable_events();
do {
c = safe_vgetc();
} while (c == K_IGNORE);
- loop_disable_deferred_events(&loop);
+ input_disable_events();
if (c == K_EVENT) {
c = lastc;
- loop_process_event(&loop);
+ queue_process_events(loop.events);
continue;
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 18188696b9..bf9a219e28 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -20,6 +20,7 @@
#include <stdbool.h>
#include <math.h>
#include <limits.h>
+#include <msgpack.h>
#include "nvim/assert.h"
#include "nvim/vim.h"
@@ -90,11 +91,13 @@
#include "nvim/os/time.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/server.h"
+#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/os/dl.h"
#include "nvim/os/input.h"
#include "nvim/event/loop.h"
+#include "nvim/lib/kvec.h"
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
@@ -160,6 +163,13 @@ typedef struct lval_S {
char_u *ll_newkey; /* New key for Dict in alloc. mem or NULL. */
} lval_T;
+/// Structure defining state for read_from_list()
+typedef struct {
+ const listitem_T *li; ///< Item currently read.
+ size_t offset; ///< Byte offset inside the read item.
+ size_t li_length; ///< Length of the string inside the read item.
+} ListReaderState;
+
static char *e_letunexp = N_("E18: Unexpected characters in :let");
static char *e_listidx = N_("E684: list index out of range: %" PRId64);
@@ -429,6 +439,7 @@ static struct vimvar {
{VV_NAME("progpath", VAR_STRING), VV_RO},
{VV_NAME("command_output", VAR_STRING), 0},
{VV_NAME("completed_item", VAR_DICT), VV_RO},
+ {VV_NAME("msgpack_types", VAR_DICT), VV_RO},
};
/* shorthand */
@@ -457,17 +468,32 @@ typedef struct {
dict_T *self;
int *status_ptr;
uint64_t id;
+ Queue *events;
} TerminalJobData;
+/// Structure representing current VimL to messagepack conversion state
+typedef struct {
+ enum {
+ kMPConvDict, ///< Convert dict_T *dictionary.
+ kMPConvList, ///< Convert list_T *list.
+ kMPConvPairs, ///< Convert mapping represented as a list_T* of pairs.
+ } type;
+ union {
+ struct {
+ const dict_T *dict; ///< Currently converted dictionary.
+ const hashitem_T *hi; ///< Currently converted dictionary item.
+ size_t todo; ///< Amount of items left to process.
+ } d; ///< State of dictionary conversion.
+ struct {
+ const list_T *list; ///< Currently converted list.
+ const listitem_T *li; ///< Currently converted list item.
+ } l; ///< State of list or generic mapping conversion.
+ } data; ///< Data to convert.
+} MPConvStackVal;
+
+/// Stack used to convert VimL values to messagepack.
+typedef kvec_t(MPConvStackVal) MPConvStack;
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "eval.c.generated.h"
-#endif
-
-#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
-#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
- valid character */
-// Memory pool for reusing JobEvent structures
typedef struct {
TerminalJobData *data;
ufunc_T *callback;
@@ -475,10 +501,51 @@ typedef struct {
list_T *received;
int status;
} JobEvent;
-static int disable_job_defer = 0;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval.c.generated.h"
+#endif
+
+#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
+#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
+ valid character */
static uint64_t current_job_id = 1;
static PMap(uint64_t) *jobs = NULL;
+typedef enum {
+ kMPNil,
+ kMPBoolean,
+ kMPInteger,
+ kMPFloat,
+ kMPString,
+ kMPBinary,
+ kMPArray,
+ kMPMap,
+ kMPExt,
+} MessagePackType;
+static const char *const msgpack_type_names[] = {
+ [kMPNil] = "nil",
+ [kMPBoolean] = "boolean",
+ [kMPInteger] = "integer",
+ [kMPFloat] = "float",
+ [kMPString] = "string",
+ [kMPBinary] = "binary",
+ [kMPArray] = "array",
+ [kMPMap] = "map",
+ [kMPExt] = "ext",
+};
+static const list_T *msgpack_type_lists[] = {
+ [kMPNil] = NULL,
+ [kMPBoolean] = NULL,
+ [kMPInteger] = NULL,
+ [kMPFloat] = NULL,
+ [kMPString] = NULL,
+ [kMPBinary] = NULL,
+ [kMPArray] = NULL,
+ [kMPMap] = NULL,
+ [kMPExt] = NULL,
+};
+
/*
* Initialize the global and v: variables.
*/
@@ -511,6 +578,27 @@ void eval_init(void)
/* add to compat scope dict */
hash_add(&compat_hashtab, p->vv_di.di_key);
}
+
+ dict_T *const msgpack_types_dict = dict_alloc();
+ for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) {
+ list_T *const type_list = list_alloc();
+ type_list->lv_lock = VAR_FIXED;
+ type_list->lv_refcount = 1;
+ dictitem_T *const di = dictitem_alloc((char_u *) msgpack_type_names[i]);
+ di->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ di->di_tv = (typval_T) {
+ .v_type = VAR_LIST,
+ .vval = { .v_list = type_list, },
+ };
+ msgpack_type_lists[i] = type_list;
+ if (dict_add(msgpack_types_dict, di) == FAIL) {
+ // There must not be duplicate items in this dictionary by definition.
+ assert(false);
+ }
+ }
+ msgpack_types_dict->dv_lock = VAR_FIXED;
+
+ set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict);
set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc());
set_vim_var_nr(VV_SEARCHFORWARD, 1L);
set_vim_var_nr(VV_HLSEARCH, 1L);
@@ -5156,24 +5244,40 @@ void list_append_dict(list_T *list, dict_T *dict)
++dict->dv_refcount;
}
-/*
- * Make a copy of "str" and append it as an item to list "l".
- * When "len" >= 0 use "str[len]".
- */
-void list_append_string(list_T *l, char_u *str, int len)
+/// Make a copy of "str" and append it as an item to list "l"
+///
+/// @param[out] l List to append to.
+/// @param[in] str String to append.
+/// @param[in] len Length of the appended string. May be negative, in this
+/// case string is considered to be usual zero-terminated
+/// string.
+void list_append_string(list_T *l, const char_u *str, int len)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (str == NULL) {
+ list_append_allocated_string(l, NULL);
+ } else {
+ list_append_allocated_string(l, (len >= 0
+ ? xmemdupz((char *) str, len)
+ : xstrdup((char *) str)));
+ }
+}
+
+/// Append given string to the list
+///
+/// Unlike list_append_string this function does not copy the string.
+///
+/// @param[out] l List to append to.
+/// @param[in] str String to append.
+void list_append_allocated_string(list_T *l, char *const str)
+ FUNC_ATTR_NONNULL_ARG(1)
{
listitem_T *li = listitem_alloc();
list_append(l, li);
li->li_tv.v_type = VAR_STRING;
li->li_tv.v_lock = 0;
-
- if (str == NULL) {
- li->li_tv.vval.v_string = NULL;
- } else {
- li->li_tv.vval.v_string = (len >= 0) ? vim_strnsave(str, len)
- : vim_strsave(str);
- }
+ li->li_tv.vval.v_string = (char_u *) str;
}
/*
@@ -6582,6 +6686,8 @@ static struct fst {
{"min", 1, 1, f_min},
{"mkdir", 1, 3, f_mkdir},
{"mode", 0, 1, f_mode},
+ {"msgpackdump", 1, 1, f_msgpackdump},
+ {"msgpackparse", 1, 1, f_msgpackparse},
{"nextnonblank", 1, 1, f_nextnonblank},
{"nr2char", 1, 2, f_nr2char},
{"or", 2, 2, f_or},
@@ -8456,27 +8562,12 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv)
}
}
-/*
- * "filereadable()" function
- */
+/// "filereadable()" function
static void f_filereadable(typval_T *argvars, typval_T *rettv)
{
- int fd;
- char_u *p;
- int n;
-
-#ifndef O_NONBLOCK
-# define O_NONBLOCK 0
-#endif
- p = get_tv_string(&argvars[0]);
- if (*p && !os_isdir(p) && (fd = os_open((char *)p,
- O_RDONLY | O_NONBLOCK, 0)) >= 0) {
- n = TRUE;
- close(fd);
- } else
- n = FALSE;
-
- rettv->vval.v_number = n;
+ char_u *p = get_tv_string(&argvars[0]);
+ rettv->vval.v_number =
+ (*p && !os_isdir(p) && os_file_is_readable((char*)p));
}
/*
@@ -10671,7 +10762,7 @@ static void f_jobclose(typval_T *argvars, typval_T *rettv)
return;
}
- TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number);
+ TerminalJobData *data = find_job(argvars[0].vval.v_number);
if (!data) {
EMSG(_(e_invjob));
return;
@@ -10712,7 +10803,7 @@ static void f_jobsend(typval_T *argvars, typval_T *rettv)
return;
}
- TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number);
+ TerminalJobData *data = find_job(argvars[0].vval.v_number);
if (!data) {
EMSG(_(e_invjob));
return;
@@ -10753,7 +10844,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv)
}
- TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number);
+ TerminalJobData *data = find_job(argvars[0].vval.v_number);
if (!data) {
EMSG(_(e_invjob));
return;
@@ -10900,8 +10991,8 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv)
}
- TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number);
- if (!data || data->stopped) {
+ TerminalJobData *data = find_job(argvars[0].vval.v_number);
+ if (!data) {
EMSG(_(e_invjob));
return;
}
@@ -10931,28 +11022,24 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv)
list_T *rv = list_alloc();
ui_busy_start();
- // disable breakchecks, which could result in job callbacks being executed
- // at unexpected places
- disable_breakcheck++;
- // disable job event deferring so the callbacks are processed while waiting.
- if (!disable_job_defer++) {
- // process any pending job events in the deferred queue, but only do this if
- // deferred is not disabled(at the top-level `jobwait()` call)
- loop_process_event(&loop);
- }
+ Queue *waiting_jobs = queue_new_parent(loop_on_put, &loop);
// For each item in the input list append an integer to the output list. -3
// is used to represent an invalid job id, -2 is for a interrupted job and
// -1 for jobs that were skipped or timed out.
for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) {
TerminalJobData *data = NULL;
if (arg->li_tv.v_type != VAR_NUMBER
- || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) {
+ || !(data = find_job(arg->li_tv.vval.v_number))) {
list_append_number(rv, -3);
} else {
// append the list item and set the status pointer so we'll collect the
// status code when the job exits
list_append_number(rv, -1);
data->status_ptr = &rv->lv_last->li_tv.vval.v_number;
+ // Process any pending events for the job because we'll temporarily
+ // replace the parent queue
+ queue_process_events(data->events);
+ queue_replace_parent(data->events, waiting_jobs);
}
}
@@ -10970,10 +11057,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv)
break;
}
if (arg->li_tv.v_type != VAR_NUMBER
- || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) {
+ || !(data = find_job(arg->li_tv.vval.v_number))) {
continue;
}
- int status = process_wait((Process *)&data->proc, remaining);
+ int status = process_wait((Process *)&data->proc, remaining, waiting_jobs);
if (status < 0) {
// interrupted or timed out, skip remaining jobs.
if (status == -2) {
@@ -10993,23 +11080,31 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv)
}
}
- // poll to ensure any pending callbacks from the last job are invoked
- loop_poll_events(&loop, 0);
-
for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) {
TerminalJobData *data = NULL;
if (arg->li_tv.v_type != VAR_NUMBER
- || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) {
+ || !(data = find_job(arg->li_tv.vval.v_number))) {
continue;
}
// remove the status pointer because the list may be freed before the
// job exits
data->status_ptr = NULL;
}
- disable_job_defer--;
- disable_breakcheck--;
- ui_busy_stop();
+ // restore the parent queue for any jobs still alive
+ for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) {
+ TerminalJobData *data = NULL;
+ if (arg->li_tv.v_type != VAR_NUMBER
+ || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) {
+ continue;
+ }
+ // restore the parent queue for the job
+ queue_process_events(data->events);
+ queue_replace_parent(data->events, loop.events);
+ }
+
+ queue_free(waiting_jobs);
+ ui_busy_stop();
rv->lv_refcount++;
rettv->v_type = VAR_LIST;
rettv->vval.v_list = rv;
@@ -11782,6 +11877,827 @@ static void f_mode(typval_T *argvars, typval_T *rettv)
rettv->v_type = VAR_STRING;
}
+/// Msgpack callback for writing to readfile()-style list
+static int msgpack_list_write(void *data, const char *buf, size_t len)
+{
+ if (len == 0) {
+ return 0;
+ }
+ list_T *const list = (list_T *) data;
+ const char *const end = buf + len;
+ const char *line_end = buf;
+ if (list->lv_last == NULL) {
+ list_append_string(list, NULL, 0);
+ }
+ listitem_T *li = list->lv_last;
+ do {
+ const char *line_start = line_end;
+ line_end = xmemscan(line_start, NL, end - line_start);
+ if (line_end == line_start) {
+ list_append_allocated_string(list, NULL);
+ } else {
+ const size_t line_length = line_end - line_start;
+ char *str;
+ if (li == NULL) {
+ str = xmemdupz(line_start, line_length);
+ } else {
+ const size_t li_len = (li->li_tv.vval.v_string == NULL
+ ? 0
+ : STRLEN(li->li_tv.vval.v_string));
+ li->li_tv.vval.v_string = xrealloc(li->li_tv.vval.v_string,
+ li_len + line_length + 1);
+ str = (char *) li->li_tv.vval.v_string + li_len;
+ memmove(str, line_start, line_length);
+ str[line_length] = 0;
+ }
+ for (size_t i = 0; i < line_length; i++) {
+ if (str[i] == NUL) {
+ str[i] = NL;
+ }
+ }
+ if (li == NULL) {
+ list_append_allocated_string(list, str);
+ } else {
+ li = NULL;
+ }
+ if (line_end == end - 1) {
+ list_append_allocated_string(list, NULL);
+ }
+ }
+ line_end++;
+ } while (line_end < end);
+ return 0;
+}
+
+/// Convert readfile()-style list to a char * buffer with length
+///
+/// @param[in] list Converted list.
+/// @param[out] ret_len Resulting buffer length.
+/// @param[out] ret_buf Allocated buffer with the result or NULL if ret_len is
+/// zero.
+///
+/// @return true in case of success, false in case of failure.
+static inline bool vim_list_to_buf(const list_T *const list,
+ size_t *const ret_len, char **const ret_buf)
+ FUNC_ATTR_NONNULL_ARG(2,3) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t len = 0;
+ if (list != NULL) {
+ for (const listitem_T *li = list->lv_first;
+ li != NULL;
+ li = li->li_next) {
+ if (li->li_tv.v_type != VAR_STRING) {
+ return false;
+ }
+ len++;
+ if (li->li_tv.vval.v_string != 0) {
+ len += STRLEN(li->li_tv.vval.v_string);
+ }
+ }
+ if (len) {
+ len--;
+ }
+ }
+ *ret_len = len;
+ if (len == 0) {
+ *ret_buf = NULL;
+ return true;
+ }
+ ListReaderState lrstate = init_lrstate(list);
+ char *const buf = xmalloc(len);
+ size_t read_bytes;
+ if (read_from_list(&lrstate, buf, len, &read_bytes) != OK) {
+ assert(false);
+ }
+ assert(len == read_bytes);
+ *ret_buf = buf;
+ return true;
+}
+
+/// Convert one VimL value to msgpack
+///
+/// @param packer Messagepack packer.
+/// @param[out] mpstack Stack with values to convert. Only used for pushing
+/// values to it.
+/// @param[in] tv Converted value.
+///
+/// @return OK in case of success, FAIL otherwise.
+static int convert_one_value(msgpack_packer *const packer,
+ MPConvStack *const mpstack,
+ const typval_T *const tv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ switch (tv->v_type) {
+#define CHECK_SELF_REFERENCE(conv_type, vval_name, ptr) \
+ do { \
+ for (size_t i = 0; i < kv_size(*mpstack); i++) { \
+ if (kv_A(*mpstack, i).type == conv_type \
+ && kv_A(*mpstack, i).data.vval_name == ptr) { \
+ EMSG2(_(e_invarg2), "container references itself"); \
+ return FAIL; \
+ } \
+ } \
+ } while (0)
+ case VAR_STRING: {
+ if (tv->vval.v_string == NULL) {
+ msgpack_pack_bin(packer, 0);
+ } else {
+ const size_t len = STRLEN(tv->vval.v_string);
+ msgpack_pack_bin(packer, len);
+ msgpack_pack_bin_body(packer, tv->vval.v_string, len);
+ }
+ break;
+ }
+ case VAR_NUMBER: {
+ msgpack_pack_int64(packer, (int64_t) tv->vval.v_number);
+ break;
+ }
+ case VAR_FLOAT: {
+ msgpack_pack_double(packer, (double) tv->vval.v_float);
+ break;
+ }
+ case VAR_FUNC: {
+ EMSG2(_(e_invarg2), "attempt to dump function reference");
+ return FAIL;
+ }
+ case VAR_LIST: {
+ if (tv->vval.v_list == NULL) {
+ msgpack_pack_array(packer, 0);
+ break;
+ }
+ CHECK_SELF_REFERENCE(kMPConvList, l.list, tv->vval.v_list);
+ msgpack_pack_array(packer, tv->vval.v_list->lv_len);
+ kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) {
+ .type = kMPConvList,
+ .data = {
+ .l = {
+ .list = tv->vval.v_list,
+ .li = tv->vval.v_list->lv_first,
+ },
+ },
+ }));
+ break;
+ }
+ case VAR_DICT: {
+ if (tv->vval.v_dict == NULL) {
+ msgpack_pack_map(packer, 0);
+ break;
+ }
+ const dictitem_T *type_di;
+ const dictitem_T *val_di;
+ if (tv->vval.v_dict->dv_hashtab.ht_used == 2
+ && (type_di = dict_find((dict_T *) tv->vval.v_dict,
+ (char_u *) "_TYPE", -1)) != NULL
+ && type_di->di_tv.v_type == VAR_LIST
+ && (val_di = dict_find((dict_T *) tv->vval.v_dict,
+ (char_u *) "_VAL", -1)) != NULL) {
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(msgpack_type_lists); i++) {
+ if (type_di->di_tv.vval.v_list == msgpack_type_lists[i]) {
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(msgpack_type_lists)) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ switch ((MessagePackType) i) {
+ case kMPNil: {
+ msgpack_pack_nil(packer);
+ break;
+ }
+ case kMPBoolean: {
+ if (val_di->di_tv.v_type != VAR_NUMBER) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ if (val_di->di_tv.vval.v_number) {
+ msgpack_pack_true(packer);
+ } else {
+ msgpack_pack_false(packer);
+ }
+ break;
+ }
+ case kMPInteger: {
+ const list_T *val_list;
+ varnumber_T sign;
+ varnumber_T highest_bits;
+ varnumber_T high_bits;
+ varnumber_T low_bits;
+ // List of 4 integers; first is signed (should be 1 or -1, but this
+ // is not checked), second is unsigned and have at most one (sign is
+ // -1) or two (sign is 1) non-zero bits (number of bits is not
+ // checked), other unsigned and have at most 31 non-zero bits
+ // (number of bits is not checked).
+ if (val_di->di_tv.v_type != VAR_LIST
+ || (val_list = val_di->di_tv.vval.v_list) == NULL
+ || val_list->lv_len != 4
+ || val_list->lv_first->li_tv.v_type != VAR_NUMBER
+ || (sign = val_list->lv_first->li_tv.vval.v_number) == 0
+ || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER
+ || (highest_bits =
+ val_list->lv_first->li_next->li_tv.vval.v_number) < 0
+ || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER
+ || (high_bits =
+ val_list->lv_last->li_prev->li_tv.vval.v_number) < 0
+ || val_list->lv_last->li_tv.v_type != VAR_NUMBER
+ || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62)
+ | (uint64_t) (((uint64_t) high_bits) << 31)
+ | (uint64_t) low_bits);
+ if (sign > 0) {
+ msgpack_pack_uint64(packer, number);
+ } else {
+ msgpack_pack_int64(packer, (int64_t) (-number));
+ }
+ break;
+ }
+ case kMPFloat: {
+ if (val_di->di_tv.v_type != VAR_FLOAT) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ msgpack_pack_double(packer, val_di->di_tv.vval.v_float);
+ break;
+ }
+ case kMPString:
+ case kMPBinary: {
+ const bool is_string = ((MessagePackType) i == kMPString);
+ if (val_di->di_tv.v_type != VAR_LIST) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ size_t len;
+ char *buf;
+ if (!vim_list_to_buf(val_di->di_tv.vval.v_list, &len, &buf)) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ if (is_string) {
+ msgpack_pack_str(packer, len);
+ } else {
+ msgpack_pack_bin(packer, len);
+ }
+ if (len == 0) {
+ break;
+ }
+ if (is_string) {
+ msgpack_pack_str_body(packer, buf, len);
+ } else {
+ msgpack_pack_bin_body(packer, buf, len);
+ }
+ xfree(buf);
+ break;
+ }
+ case kMPArray: {
+ if (val_di->di_tv.v_type != VAR_LIST) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ CHECK_SELF_REFERENCE(kMPConvList, l.list,
+ val_di->di_tv.vval.v_list);
+ msgpack_pack_array(packer, val_di->di_tv.vval.v_list->lv_len);
+ kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) {
+ .type = kMPConvList,
+ .data = {
+ .l = {
+ .list = val_di->di_tv.vval.v_list,
+ .li = val_di->di_tv.vval.v_list->lv_first,
+ },
+ },
+ }));
+ break;
+ }
+ case kMPMap: {
+ if (val_di->di_tv.v_type != VAR_LIST) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ if (val_di->di_tv.vval.v_list == NULL) {
+ msgpack_pack_map(packer, 0);
+ break;
+ }
+ const list_T *const val_list = val_di->di_tv.vval.v_list;
+ for (const listitem_T *li = val_list->lv_first; li != NULL;
+ li = li->li_next) {
+ if (li->li_tv.v_type != VAR_LIST
+ || li->li_tv.vval.v_list->lv_len != 2) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ }
+ CHECK_SELF_REFERENCE(kMPConvPairs, l.list, val_list);
+ msgpack_pack_map(packer, val_list->lv_len);
+ kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) {
+ .type = kMPConvPairs,
+ .data = {
+ .l = {
+ .list = val_list,
+ .li = val_list->lv_first,
+ },
+ },
+ }));
+ break;
+ }
+ case kMPExt: {
+ const list_T *val_list;
+ varnumber_T type;
+ if (val_di->di_tv.v_type != VAR_LIST
+ || (val_list = val_di->di_tv.vval.v_list) == NULL
+ || val_list->lv_len != 2
+ || (val_list->lv_first->li_tv.v_type != VAR_NUMBER)
+ || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX
+ || type < INT8_MIN
+ || (val_list->lv_last->li_tv.v_type != VAR_LIST)) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ size_t len;
+ char *buf;
+ if (!vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list,
+ &len, &buf)) {
+ goto vim_to_msgpack_regural_dict;
+ }
+ msgpack_pack_ext(packer, len, (int8_t) type);
+ msgpack_pack_ext_body(packer, buf, len);
+ xfree(buf);
+ break;
+ }
+ }
+ break;
+ }
+vim_to_msgpack_regural_dict:
+ CHECK_SELF_REFERENCE(kMPConvDict, d.dict, tv->vval.v_dict);
+ msgpack_pack_map(packer, tv->vval.v_dict->dv_hashtab.ht_used);
+ kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) {
+ .type = kMPConvDict,
+ .data = {
+ .d = {
+ .dict = tv->vval.v_dict,
+ .hi = tv->vval.v_dict->dv_hashtab.ht_array,
+ .todo = tv->vval.v_dict->dv_hashtab.ht_used,
+ },
+ },
+ }));
+ break;
+ }
+ }
+#undef CHECK_SELF_REFERENCE
+ return OK;
+}
+
+/// Convert typval_T to messagepack
+static int vim_to_msgpack(msgpack_packer *const packer,
+ const typval_T *const tv)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ MPConvStack mpstack;
+ kv_init(mpstack);
+ if (convert_one_value(packer, &mpstack, tv) == FAIL) {
+ goto vim_to_msgpack_error_ret;
+ }
+ while (kv_size(mpstack)) {
+ MPConvStackVal *cur_mpsv = &kv_A(mpstack, kv_size(mpstack) - 1);
+ const typval_T *cur_tv = NULL;
+ switch (cur_mpsv->type) {
+ case kMPConvDict: {
+ if (!cur_mpsv->data.d.todo) {
+ (void) kv_pop(mpstack);
+ continue;
+ }
+ while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) {
+ cur_mpsv->data.d.hi++;
+ }
+ const dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi);
+ cur_mpsv->data.d.todo--;
+ cur_mpsv->data.d.hi++;
+ const size_t key_len = STRLEN(&di->di_key[0]);
+ msgpack_pack_str(packer, key_len);
+ msgpack_pack_str_body(packer, &di->di_key[0], key_len);
+ cur_tv = &di->di_tv;
+ break;
+ }
+ case kMPConvList: {
+ if (cur_mpsv->data.l.li == NULL) {
+ (void) kv_pop(mpstack);
+ continue;
+ }
+ cur_tv = &cur_mpsv->data.l.li->li_tv;
+ cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next;
+ break;
+ }
+ case kMPConvPairs: {
+ if (cur_mpsv->data.l.li == NULL) {
+ (void) kv_pop(mpstack);
+ continue;
+ }
+ const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list;
+ if (convert_one_value(packer, &mpstack, &kv_pair->lv_first->li_tv)
+ == FAIL) {
+ goto vim_to_msgpack_error_ret;
+ }
+ cur_tv = &kv_pair->lv_last->li_tv;
+ cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next;
+ break;
+ }
+ }
+ if (convert_one_value(packer, &mpstack, cur_tv) == FAIL) {
+ goto vim_to_msgpack_error_ret;
+ }
+ }
+ kv_destroy(mpstack);
+ return OK;
+vim_to_msgpack_error_ret:
+ kv_destroy(mpstack);
+ return FAIL;
+}
+
+/// "msgpackdump()" function
+static void f_msgpackdump(typval_T *argvars, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "msgpackdump()");
+ return;
+ }
+ list_T *ret_list = rettv_list_alloc(rettv);
+ const list_T *list = argvars[0].vval.v_list;
+ if (list == NULL) {
+ return;
+ }
+ msgpack_packer *lpacker = msgpack_packer_new(ret_list, &msgpack_list_write);
+ for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) {
+ if (vim_to_msgpack(lpacker, &li->li_tv) == FAIL) {
+ break;
+ }
+ }
+ msgpack_packer_free(lpacker);
+}
+
+/// Read bytes from list
+///
+/// @param[in,out] state Structure describing position in list from which
+/// reading should start. Is updated to reflect position
+/// at which reading ended.
+/// @param[out] buf Buffer to write to.
+/// @param[in] nbuf Buffer length.
+/// @param[out] read_bytes Is set to amount of bytes read.
+///
+/// @return OK when reading was finished, FAIL in case of error (i.e. list item
+/// was not a string), NOTDONE if reading was successfull, but there are
+/// more bytes to read.
+static int read_from_list(ListReaderState *const state, char *const buf,
+ const size_t nbuf, size_t *const read_bytes)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char *const buf_end = buf + nbuf;
+ char *p = buf;
+ while (p < buf_end) {
+ for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) {
+ const char ch = state->li->li_tv.vval.v_string[state->offset++];
+ *p++ = (ch == NL ? NUL : ch);
+ }
+ if (p < buf_end) {
+ state->li = state->li->li_next;
+ if (state->li == NULL) {
+ *read_bytes = (size_t) (p - buf);
+ return OK;
+ }
+ *p++ = NL;
+ if (state->li->li_tv.v_type != VAR_STRING) {
+ *read_bytes = (size_t) (p - buf);
+ return FAIL;
+ }
+ state->offset = 0;
+ state->li_length = (state->li->li_tv.vval.v_string == NULL
+ ? 0
+ : STRLEN(state->li->li_tv.vval.v_string));
+ }
+ }
+ *read_bytes = nbuf;
+ return (state->offset < state->li_length || state->li->li_next != NULL
+ ? NOTDONE
+ : OK);
+}
+
+/// Initialize ListReaderState structure
+static inline ListReaderState init_lrstate(const list_T *const list)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return (ListReaderState) {
+ .li = list->lv_first,
+ .offset = 0,
+ .li_length = (list->lv_first->li_tv.vval.v_string == NULL
+ ? 0
+ : STRLEN(list->lv_first->li_tv.vval.v_string)),
+ };
+}
+
+/// Convert msgpack object to a VimL one
+static int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+#define INIT_SPECIAL_DICT(tv, type, val) \
+ do { \
+ dict_T *const dict = dict_alloc(); \
+ dictitem_T *const type_di = dictitem_alloc((char_u *) "_TYPE"); \
+ type_di->di_tv.v_type = VAR_LIST; \
+ type_di->di_tv.v_lock = 0; \
+ type_di->di_tv.vval.v_list = (list_T *) msgpack_type_lists[type]; \
+ type_di->di_tv.vval.v_list->lv_refcount++; \
+ dict_add(dict, type_di); \
+ dictitem_T *const val_di = dictitem_alloc((char_u *) "_VAL"); \
+ val_di->di_tv = val; \
+ dict_add(dict, val_di); \
+ tv->v_type = VAR_DICT; \
+ dict->dv_refcount++; \
+ tv->vval.v_dict = dict; \
+ } while (0)
+ switch (mobj.type) {
+ case MSGPACK_OBJECT_NIL: {
+ INIT_SPECIAL_DICT(rettv, kMPNil, ((typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = 0,
+ .vval = { .v_number = 0 },
+ }));
+ break;
+ }
+ case MSGPACK_OBJECT_BOOLEAN: {
+ INIT_SPECIAL_DICT(rettv, kMPBoolean,
+ ((typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = 0,
+ .vval = {
+ .v_number = (varnumber_T) mobj.via.boolean,
+ },
+ }));
+ break;
+ }
+ case MSGPACK_OBJECT_POSITIVE_INTEGER: {
+ if (mobj.via.u64 <= VARNUMBER_MAX) {
+ *rettv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = 0,
+ .vval = { .v_number = (varnumber_T) mobj.via.u64 },
+ };
+ } else {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ INIT_SPECIAL_DICT(rettv, kMPInteger,
+ ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = 0,
+ .vval = { .v_list = list },
+ }));
+ uint64_t n = mobj.via.u64;
+ list_append_number(list, 1);
+ list_append_number(list, (varnumber_T) ((n >> 62) & 0x3));
+ list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF));
+ list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF));
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_NEGATIVE_INTEGER: {
+ if (mobj.via.i64 >= VARNUMBER_MIN) {
+ *rettv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = 0,
+ .vval = { .v_number = (varnumber_T) mobj.via.i64 },
+ };
+ } else {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ INIT_SPECIAL_DICT(rettv, kMPInteger,
+ ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = 0,
+ .vval = { .v_list = list },
+ }));
+ uint64_t n = -((uint64_t) mobj.via.i64);
+ list_append_number(list, -1);
+ list_append_number(list, (varnumber_T) ((n >> 62) & 0x3));
+ list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF));
+ list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF));
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_FLOAT: {
+ *rettv = (typval_T) {
+ .v_type = VAR_FLOAT,
+ .v_lock = 0,
+ .vval = { .v_float = mobj.via.f64 },
+ };
+ break;
+ }
+ case MSGPACK_OBJECT_STR: {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ INIT_SPECIAL_DICT(rettv, kMPString,
+ ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = 0,
+ .vval = { .v_list = list },
+ }));
+ if (msgpack_list_write((void *) list, mobj.via.str.ptr, mobj.via.str.size)
+ == -1) {
+ return FAIL;
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_BIN: {
+ if (memchr(mobj.via.bin.ptr, NUL, mobj.via.bin.size) == NULL) {
+ *rettv = (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = 0,
+ .vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) },
+ };
+ break;
+ }
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ INIT_SPECIAL_DICT(rettv, kMPBinary,
+ ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = 0,
+ .vval = { .v_list = list },
+ }));
+ if (msgpack_list_write((void *) list, mobj.via.bin.ptr, mobj.via.bin.size)
+ == -1) {
+ return FAIL;
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_ARRAY: {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ *rettv = (typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = 0,
+ .vval = { .v_list = list },
+ };
+ for (size_t i = 0; i < mobj.via.array.size; i++) {
+ listitem_T *const li = listitem_alloc();
+ li->li_tv.v_type = VAR_UNKNOWN;
+ list_append(list, li);
+ if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) {
+ return FAIL;
+ }
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_MAP: {
+ for (size_t i = 0; i < mobj.via.map.size; i++) {
+ if (mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR
+ || mobj.via.map.ptr[i].key.via.str.size == 0
+ || memchr(mobj.via.map.ptr[i].key.via.str.ptr, NUL,
+ mobj.via.map.ptr[i].key.via.str.size) != NULL) {
+ goto msgpack_to_vim_generic_map;
+ }
+ }
+ dict_T *const dict = dict_alloc();
+ dict->dv_refcount++;
+ *rettv = (typval_T) {
+ .v_type = VAR_DICT,
+ .v_lock = 0,
+ .vval = { .v_dict = dict },
+ };
+ for (size_t i = 0; i < mobj.via.map.size; i++) {
+ dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key)
+ + mobj.via.map.ptr[i].key.via.str.size);
+ memcpy(&di->di_key[0], mobj.via.map.ptr[i].key.via.str.ptr,
+ mobj.via.map.ptr[i].key.via.str.size);
+ di->di_tv.v_type = VAR_UNKNOWN;
+ if (dict_add(dict, di) == FAIL) {
+ // Duplicate key: fallback to generic map
+ clear_tv(rettv);
+ xfree(di);
+ goto msgpack_to_vim_generic_map;
+ }
+ if (msgpack_to_vim(mobj.via.map.ptr[i].val, &di->di_tv) == FAIL) {
+ return FAIL;
+ }
+ }
+ break;
+msgpack_to_vim_generic_map: {}
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ INIT_SPECIAL_DICT(rettv, kMPMap,
+ ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = 0,
+ .vval = { .v_list = list },
+ }));
+ for (size_t i = 0; i < mobj.via.map.size; i++) {
+ list_T *const kv_pair = list_alloc();
+ list_append_list(list, kv_pair);
+ listitem_T *const key_li = listitem_alloc();
+ key_li->li_tv.v_type = VAR_UNKNOWN;
+ list_append(kv_pair, key_li);
+ listitem_T *const val_li = listitem_alloc();
+ val_li->li_tv.v_type = VAR_UNKNOWN;
+ list_append(kv_pair, val_li);
+ if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) {
+ return FAIL;
+ }
+ if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) {
+ return FAIL;
+ }
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_EXT: {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ list_append_number(list, mobj.via.ext.type);
+ list_T *const ext_val_list = list_alloc();
+ list_append_list(list, ext_val_list);
+ INIT_SPECIAL_DICT(rettv, kMPExt,
+ ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = 0,
+ .vval = { .v_list = list },
+ }));
+ if (msgpack_list_write((void *) ext_val_list, mobj.via.ext.ptr,
+ mobj.via.ext.size) == -1) {
+ return FAIL;
+ }
+ break;
+ }
+ }
+#undef INIT_SPECIAL_DICT
+ return OK;
+}
+
+/// "msgpackparse" function
+static void f_msgpackparse(typval_T *argvars, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "msgpackparse()");
+ }
+ list_T *ret_list = rettv_list_alloc(rettv);
+ const list_T *list = argvars[0].vval.v_list;
+ if (list == NULL || list->lv_first == NULL) {
+ return;
+ }
+ if (list->lv_first->li_tv.v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "List item is not a string");
+ return;
+ }
+ ListReaderState lrstate = init_lrstate(list);
+ msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE);
+ if (unpacker == NULL) {
+ EMSG(_(e_outofmem));
+ return;
+ }
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ do {
+ if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
+ EMSG(_(e_outofmem));
+ goto f_msgpackparse_exit;
+ }
+ size_t read_bytes;
+ const int rlret = read_from_list(
+ &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
+ if (rlret == FAIL) {
+ EMSG2(_(e_invarg2), "List item is not a string");
+ goto f_msgpackparse_exit;
+ }
+ msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
+ if (read_bytes == 0) {
+ break;
+ }
+ while (unpacker->off < unpacker->used) {
+ const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
+ &unpacked);
+ if (result == MSGPACK_UNPACK_PARSE_ERROR) {
+ EMSG2(_(e_invarg2), "Failed to parse msgpack string");
+ goto f_msgpackparse_exit;
+ }
+ if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
+ EMSG(_(e_outofmem));
+ goto f_msgpackparse_exit;
+ }
+ if (result == MSGPACK_UNPACK_SUCCESS) {
+ listitem_T *li = listitem_alloc();
+ li->li_tv.v_type = VAR_UNKNOWN;
+ list_append(ret_list, li);
+ if (msgpack_to_vim(unpacked.data, &li->li_tv) == FAIL) {
+ EMSG2(_(e_invarg2), "Failed to convert msgpack string");
+ goto f_msgpackparse_exit;
+ }
+ }
+ if (result == MSGPACK_UNPACK_CONTINUE) {
+ if (rlret == OK) {
+ EMSG2(_(e_invarg2), "Incomplete msgpack string");
+ }
+ break;
+ }
+ }
+ if (rlret == OK) {
+ break;
+ }
+ } while (true);
+
+f_msgpackparse_exit:
+ msgpack_unpacked_destroy(&unpacked);
+ msgpack_unpacker_free(unpacker);
+ return;
+}
/*
* "nextnonblank()" function
@@ -20125,17 +21041,21 @@ static inline TerminalJobData *common_job_init(char **argv, ufunc_T *on_stdout,
data->on_stderr = on_stderr;
data->on_exit = on_exit;
data->self = self;
+ data->events = queue_new_child(loop.events);
if (pty) {
- data->proc.pty = pty_process_init(data);
+ data->proc.pty = pty_process_init(&loop, data);
} else {
- data->proc.uv = uv_process_init(data);
+ data->proc.uv = uv_process_init(&loop, data);
}
Process *proc = (Process *)&data->proc;
proc->argv = argv;
proc->in = &data->in;
proc->out = &data->out;
- proc->err = &data->err;
+ if (!pty) {
+ proc->err = &data->err;
+ }
proc->cb = on_process_exit;
+ proc->events = data->events;
return data;
}
@@ -20166,8 +21086,12 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv)
{
data->refcount++;
Process *proc = (Process *)&data->proc;
- if (!process_spawn(&loop, proc)) {
+ if (!process_spawn(proc)) {
EMSG(_(e_jobexe));
+ if (proc->type == kProcessTypePty) {
+ xfree(data->proc.pty.term_name);
+ free_term_job_data(data);
+ }
return false;
}
@@ -20186,7 +21110,9 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv)
return true;
}
-static inline void free_term_job_data(TerminalJobData *data) {
+static inline void free_term_job_data_event(void **argv)
+{
+ TerminalJobData *data = argv[0];
if (data->on_stdout) {
user_func_unref(data->on_stdout);
}
@@ -20201,17 +21127,25 @@ static inline void free_term_job_data(TerminalJobData *data) {
data->self->internal_refcount--;
dict_unref(data->self);
}
+ queue_free(data->events);
xfree(data);
}
+static inline void free_term_job_data(TerminalJobData *data)
+{
+ // data->queue may still be used after this function returns(process_wait), so
+ // only free in the next event loop iteration
+ queue_put(loop.fast_events, free_term_job_data_event, 1, data);
+}
+
// vimscript job callbacks must be executed on Nvim main loop
-static inline void push_job_event(TerminalJobData *data, ufunc_T *callback,
+static inline void process_job_event(TerminalJobData *data, ufunc_T *callback,
const char *type, char *buf, size_t count, int status)
{
- JobEvent *event_data = xmalloc(sizeof(JobEvent));
- event_data->received = NULL;
+ JobEvent event_data;
+ event_data.received = NULL;
if (buf) {
- event_data->received = list_alloc();
+ event_data.received = list_alloc();
char *ptr = buf;
size_t remaining = count;
size_t off = 0;
@@ -20219,7 +21153,7 @@ static inline void push_job_event(TerminalJobData *data, ufunc_T *callback,
while (off < remaining) {
// append the line
if (ptr[off] == NL) {
- list_append_string(event_data->received, (uint8_t *)ptr, off);
+ list_append_string(event_data.received, (uint8_t *)ptr, off);
size_t skip = off + 1;
ptr += skip;
remaining -= skip;
@@ -20232,51 +21166,53 @@ static inline void push_job_event(TerminalJobData *data, ufunc_T *callback,
}
off++;
}
- list_append_string(event_data->received, (uint8_t *)ptr, off);
+ list_append_string(event_data.received, (uint8_t *)ptr, off);
} else {
- event_data->status = status;
+ event_data.status = status;
}
- event_data->data = data;
- event_data->callback = callback;
- event_data->type = type;
- loop_push_event(&loop, (Event) {
- .handler = on_job_event,
- .data = event_data
- }, !disable_job_defer);
+ event_data.data = data;
+ event_data.callback = callback;
+ event_data.type = type;
+ on_job_event(&event_data);
}
-static void on_job_stdout(Stream *stream, RBuffer *buf, void *job, bool eof)
+static void on_job_stdout(Stream *stream, RBuffer *buf, size_t count,
+ void *job, bool eof)
{
TerminalJobData *data = job;
- on_job_output(stream, job, buf, eof, data->on_stdout, "stdout");
+ on_job_output(stream, job, buf, count, eof, data->on_stdout, "stdout");
}
-static void on_job_stderr(Stream *stream, RBuffer *buf, void *job, bool eof)
+static void on_job_stderr(Stream *stream, RBuffer *buf, size_t count,
+ void *job, bool eof)
{
TerminalJobData *data = job;
- on_job_output(stream, job, buf, eof, data->on_stderr, "stderr");
+ on_job_output(stream, job, buf, count, eof, data->on_stderr, "stderr");
}
static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf,
- bool eof, ufunc_T *callback, const char *type)
+ size_t count, bool eof, ufunc_T *callback, const char *type)
{
if (eof) {
return;
}
- RBUFFER_UNTIL_EMPTY(buf, ptr, len) {
- // The order here matters, the terminal must receive the data first because
- // push_job_event will modify the read buffer(convert NULs into NLs)
- if (data->term) {
- terminal_receive(data->term, ptr, len);
- }
+ // stub variable, to keep reading consistent with the order of events, only
+ // consider the count parameter.
+ size_t r;
+ char *ptr = rbuffer_read_ptr(buf, &r);
- if (callback) {
- push_job_event(data, callback, type, ptr, len, 0);
- }
+ // The order here matters, the terminal must receive the data first because
+ // process_job_event will modify the read buffer(convert NULs into NLs)
+ if (data->term) {
+ terminal_receive(data->term, ptr, count);
+ }
- rbuffer_consumed(buf, len);
+ if (callback) {
+ process_job_event(data, callback, type, ptr, count, 0);
}
+
+ rbuffer_consumed(buf, count);
}
static void on_process_exit(Process *proc, int status, void *d)
@@ -20292,7 +21228,7 @@ static void on_process_exit(Process *proc, int status, void *d)
*data->status_ptr = status;
}
- push_job_event(data, data->on_exit, "exit", NULL, 0, status);
+ process_job_event(data, data->on_exit, "exit", NULL, 0, status);
}
static void term_write(char *buf, size_t size, void *d)
@@ -20326,10 +21262,8 @@ static void term_job_data_decref(TerminalJobData *data)
}
}
-static void on_job_event(Event event)
+static void on_job_event(JobEvent *ev)
{
- JobEvent *ev = event.data;
-
if (!ev->callback) {
goto end;
}
@@ -20374,7 +21308,15 @@ end:
pmap_del(uint64_t)(jobs, ev->data->id);
term_job_data_decref(ev->data);
}
- xfree(ev);
+}
+
+static TerminalJobData *find_job(uint64_t id)
+{
+ TerminalJobData *data = pmap_get(uint64_t)(jobs, id);
+ if (!data || data->stopped) {
+ return NULL;
+ }
+ return data;
}
static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index bf48bc74b8..8f065eda33 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -65,6 +65,7 @@ enum {
VV_PROGPATH,
VV_COMMAND_OUTPUT,
VV_COMPLETED_ITEM,
+ VV_MSGPACK_TYPES,
VV_LEN, /* number of v: vars */
};
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 34a36004d6..0faf860588 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -1,11 +1,16 @@
#ifndef NVIM_EVAL_DEFS_H
#define NVIM_EVAL_DEFS_H
+#include <limits.h>
+
#include "nvim/hashtab.h"
typedef int varnumber_T;
typedef double float_T;
+#define VARNUMBER_MAX INT_MAX
+#define VARNUMBER_MIN INT_MIN
+
typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T;
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
new file mode 100644
index 0000000000..5126d52241
--- /dev/null
+++ b/src/nvim/event/defs.h
@@ -0,0 +1,39 @@
+#ifndef NVIM_EVENT_DEFS_H
+#define NVIM_EVENT_DEFS_H
+
+#include <assert.h>
+#include <stdarg.h>
+
+#define EVENT_HANDLER_MAX_ARGC 4
+
+typedef void (*argv_callback)(void **argv);
+typedef struct message {
+ int priority;
+ argv_callback handler;
+ void *argv[EVENT_HANDLER_MAX_ARGC];
+} Event;
+
+#define VA_EVENT_INIT(event, p, h, a) \
+ do { \
+ assert(a <= EVENT_HANDLER_MAX_ARGC); \
+ (event)->priority = p; \
+ (event)->handler = h; \
+ if (a) { \
+ va_list args; \
+ va_start(args, a); \
+ for (int i = 0; i < a; i++) { \
+ (event)->argv[i] = va_arg(args, void *); \
+ } \
+ va_end(args); \
+ } \
+ } while (0)
+
+static inline Event event_create(int priority, argv_callback cb, int argc, ...)
+{
+ assert(argc <= EVENT_HANDLER_MAX_ARGC);
+ Event event;
+ VA_EVENT_INIT(&event, priority, cb, argc);
+ return event;
+}
+
+#endif // NVIM_EVENT_DEFS_H
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index d90565002e..3d3288f858 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -1,3 +1,4 @@
+#include <stdarg.h>
#include <stdint.h>
#include <uv.h>
@@ -9,17 +10,23 @@
# include "event/loop.c.generated.h"
#endif
+typedef struct idle_event {
+ uv_idle_t idle;
+ Event event;
+} IdleEvent;
+
void loop_init(Loop *loop, void *data)
{
uv_loop_init(&loop->uv);
loop->uv.data = loop;
- loop->deferred_events = kl_init(Event);
- loop->immediate_events = kl_init(Event);
loop->children = kl_init(WatcherPtr);
loop->children_stop_requests = 0;
+ loop->events = queue_new_parent(loop_on_put, loop);
+ loop->fast_events = queue_new_child(loop->events);
uv_signal_init(&loop->uv, &loop->children_watcher);
uv_timer_init(&loop->uv, &loop->children_kill_timer);
+ uv_timer_init(&loop->uv, &loop->poll_timer);
}
void loop_poll_events(Loop *loop, int ms)
@@ -30,89 +37,36 @@ void loop_poll_events(Loop *loop, int ms)
abort(); // Should not re-enter uv_run
}
- bool wait = true;
- uv_timer_t timer;
+ uv_run_mode mode = UV_RUN_ONCE;
if (ms > 0) {
- uv_timer_init(&loop->uv, &timer);
// Use a repeating timeout of ms milliseconds to make sure
// we do not block indefinitely for I/O.
- uv_timer_start(&timer, timer_cb, (uint64_t)ms, (uint64_t)ms);
+ uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms);
} else if (ms == 0) {
// For ms == 0, we need to do a non-blocking event poll by
// setting the run mode to UV_RUN_NOWAIT.
- wait = false;
+ mode = UV_RUN_NOWAIT;
}
- if (wait) {
- loop_run_once(loop);
- } else {
- loop_run_nowait(loop);
- }
+ uv_run(&loop->uv, mode);
if (ms > 0) {
- // Ensure the timer handle is closed and run the event loop
- // once more to let libuv perform it's cleanup
- uv_timer_stop(&timer);
- uv_close((uv_handle_t *)&timer, NULL);
- loop_run_nowait(loop);
+ uv_timer_stop(&loop->poll_timer);
}
recursive--; // Can re-enter uv_run now
- process_events_from(loop->immediate_events);
-}
-
-bool loop_has_deferred_events(Loop *loop)
-{
- return loop->deferred_events_allowed && !kl_empty(loop->deferred_events);
+ queue_process_events(loop->fast_events);
}
-void loop_enable_deferred_events(Loop *loop)
-{
- ++loop->deferred_events_allowed;
-}
-
-void loop_disable_deferred_events(Loop *loop)
-{
- --loop->deferred_events_allowed;
-}
-
-// Queue an event
-void loop_push_event(Loop *loop, Event event, bool deferred)
+void loop_on_put(Queue *queue, void *data)
{
+ Loop *loop = data;
// Sometimes libuv will run pending callbacks(timer for example) before
// blocking for a poll. If this happens and the callback pushes a event to one
// of the queues, the event would only be processed after the poll
// returns(user hits a key for example). To avoid this scenario, we call
// uv_stop when a event is enqueued.
- loop_stop(loop);
- kl_push(Event, deferred ? loop->deferred_events : loop->immediate_events,
- event);
-}
-
-void loop_process_event(Loop *loop)
-{
- process_events_from(loop->deferred_events);
-}
-
-
-void loop_run(Loop *loop)
-{
- uv_run(&loop->uv, UV_RUN_DEFAULT);
-}
-
-void loop_run_once(Loop *loop)
-{
- uv_run(&loop->uv, UV_RUN_ONCE);
-}
-
-void loop_run_nowait(Loop *loop)
-{
- uv_run(&loop->uv, UV_RUN_NOWAIT);
-}
-
-void loop_stop(Loop *loop)
-{
uv_stop(&loop->uv);
}
@@ -120,25 +74,12 @@ void loop_close(Loop *loop)
{
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
+ uv_close((uv_handle_t *)&loop->poll_timer, NULL);
do {
uv_run(&loop->uv, UV_RUN_DEFAULT);
} while (uv_loop_close(&loop->uv));
}
-void loop_process_all_events(Loop *loop)
-{
- process_events_from(loop->immediate_events);
- process_events_from(loop->deferred_events);
-}
-
-static void process_events_from(klist_t(Event) *queue)
-{
- while (!kl_empty(queue)) {
- Event event = kl_shift(Event, queue);
- event.handler(event);
- }
-}
-
static void timer_cb(uv_timer_t *handle)
{
}
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index 5eb4d32ca8..9212a45aa4 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -7,38 +7,39 @@
#include "nvim/lib/klist.h"
#include "nvim/os/time.h"
-
-typedef struct event Event;
-typedef void (*event_handler)(Event event);
-
-struct event {
- void *data;
- event_handler handler;
-};
+#include "nvim/event/queue.h"
typedef void * WatcherPtr;
#define _noop(x)
KLIST_INIT(WatcherPtr, WatcherPtr, _noop)
-KLIST_INIT(Event, Event, _noop)
typedef struct loop {
uv_loop_t uv;
- klist_t(Event) *deferred_events, *immediate_events;
- int deferred_events_allowed;
+ Queue *events, *fast_events;
klist_t(WatcherPtr) *children;
uv_signal_t children_watcher;
- uv_timer_t children_kill_timer;
+ uv_timer_t children_kill_timer, poll_timer;
size_t children_stop_requests;
} Loop;
+#define CREATE_EVENT(queue, handler, argc, ...) \
+ do { \
+ if (queue) { \
+ queue_put((queue), (handler), argc, __VA_ARGS__); \
+ } else { \
+ void *argv[argc] = {__VA_ARGS__}; \
+ (handler)(argv); \
+ } \
+ } while (0)
+
// Poll for events until a condition or timeout
-#define LOOP_POLL_EVENTS_UNTIL(loop, timeout, condition) \
+#define LOOP_PROCESS_EVENTS_UNTIL(loop, queue, timeout, condition) \
do { \
int remaining = timeout; \
uint64_t before = (remaining > 0) ? os_hrtime() : 0; \
while (!(condition)) { \
- loop_poll_events(loop, remaining); \
+ LOOP_PROCESS_EVENTS(loop, queue, remaining); \
if (remaining == 0) { \
break; \
} else if (remaining > 0) { \
@@ -52,6 +53,16 @@ typedef struct loop {
} \
} while (0)
+#define LOOP_PROCESS_EVENTS(loop, queue, timeout) \
+ do { \
+ if (queue && !queue_empty(queue)) { \
+ queue_process_events(queue); \
+ } else { \
+ loop_poll_events(loop, timeout); \
+ } \
+ } while (0)
+
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/loop.h.generated.h"
#endif
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 2b1f1ae096..81d4e690c3 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -22,7 +22,7 @@
#define TERM_TIMEOUT 1000000000
#define KILL_TIMEOUT (TERM_TIMEOUT * 2)
-#define CLOSE_PROC_STREAM(proc, stream) \
+#define CLOSE_PROC_STREAM(proc, stream) \
do { \
if (proc->stream && !proc->stream->closed) { \
stream_close(proc->stream, NULL); \
@@ -30,19 +30,18 @@
} while (0)
-bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL
+bool process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL
{
- proc->loop = loop;
if (proc->in) {
- uv_pipe_init(&loop->uv, &proc->in->uv.pipe, 0);
+ uv_pipe_init(&proc->loop->uv, &proc->in->uv.pipe, 0);
}
if (proc->out) {
- uv_pipe_init(&loop->uv, &proc->out->uv.pipe, 0);
+ uv_pipe_init(&proc->loop->uv, &proc->out->uv.pipe, 0);
}
if (proc->err) {
- uv_pipe_init(&loop->uv, &proc->err->uv.pipe, 0);
+ uv_pipe_init(&proc->loop->uv, &proc->err->uv.pipe, 0);
}
bool success;
@@ -77,6 +76,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL
if (proc->in) {
stream_init(NULL, proc->in, -1, (uv_stream_t *)&proc->in->uv.pipe, data);
+ proc->in->events = proc->events;
proc->in->internal_data = proc;
proc->in->internal_close_cb = on_process_stream_close;
proc->refcount++;
@@ -84,6 +84,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL
if (proc->out) {
stream_init(NULL, proc->out, -1, (uv_stream_t *)&proc->out->uv.pipe, data);
+ proc->out->events = proc->events;
proc->out->internal_data = proc;
proc->out->internal_close_cb = on_process_stream_close;
proc->refcount++;
@@ -91,6 +92,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL
if (proc->err) {
stream_init(NULL, proc->err, -1, (uv_stream_t *)&proc->err->uv.pipe, data);
+ proc->err->events = proc->events;
proc->err->internal_data = proc;
proc->err->internal_close_cb = on_process_stream_close;
proc->refcount++;
@@ -99,7 +101,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL
proc->internal_exit_cb = on_process_exit;
proc->internal_close_cb = decref;
proc->refcount++;
- kl_push(WatcherPtr, loop->children, proc);
+ kl_push(WatcherPtr, proc->loop->children, proc);
return true;
}
@@ -113,7 +115,7 @@ void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
}
// Wait until all children exit
- LOOP_POLL_EVENTS_UNTIL(loop, -1, kl_empty(loop->children));
+ LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1, kl_empty(loop->children));
pty_process_teardown(loop);
}
@@ -150,16 +152,24 @@ void process_close_err(Process *proc) FUNC_ATTR_NONNULL_ALL
/// indistinguishable from the process returning -1 by itself. Which
/// is possible on some OS. Returns -2 if an user has interruped the
/// wait.
-int process_wait(Process *proc, int ms) FUNC_ATTR_NONNULL_ALL
+int process_wait(Process *proc, int ms, Queue *events) FUNC_ATTR_NONNULL_ARG(1)
{
// The default status is -1, which represents a timeout
int status = -1;
bool interrupted = false;
+ if (!proc->refcount) {
+ LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0);
+ return proc->status;
+ }
+
+ if (!events) {
+ events = proc->events;
+ }
// Increase refcount to stop the exit callback from being called(and possibly
// being freed) before we have a chance to get the status.
proc->refcount++;
- LOOP_POLL_EVENTS_UNTIL(proc->loop, ms,
+ LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, ms,
// Until...
got_int || // interrupted by the user
proc->refcount == 1); // job exited
@@ -171,12 +181,12 @@ int process_wait(Process *proc, int ms) FUNC_ATTR_NONNULL_ALL
got_int = false;
process_stop(proc);
if (ms == -1) {
- // We can only return, if all streams/handles are closed and the job
-
+ // We can only return if all streams/handles are closed and the job
// exited.
- LOOP_POLL_EVENTS_UNTIL(proc->loop, -1, proc->refcount == 1);
+ LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, -1,
+ proc->refcount == 1);
} else {
- loop_poll_events(proc->loop, 0);
+ LOOP_PROCESS_EVENTS(proc->loop, events, 0);
}
}
@@ -185,6 +195,10 @@ int process_wait(Process *proc, int ms) FUNC_ATTR_NONNULL_ALL
// resources
status = interrupted ? -2 : proc->status;
decref(proc);
+ if (events) {
+ // the decref call created an exit event, process it now
+ queue_process_events(events);
+ }
} else {
proc->refcount--;
}
@@ -250,6 +264,18 @@ static void children_kill_cb(uv_timer_t *handle)
}
}
+static void process_close_event(void **argv)
+{
+ Process *proc = argv[0];
+ shell_free_argv(proc->argv);
+ if (proc->type == kProcessTypePty) {
+ xfree(((PtyProcess *)proc)->term_name);
+ }
+ if (proc->cb) {
+ proc->cb(proc, proc->status, proc->data);
+ }
+}
+
static void decref(Process *proc)
{
if (--proc->refcount != 0) {
@@ -264,16 +290,9 @@ static void decref(Process *proc)
break;
}
}
-
assert(node);
kl_shift_at(WatcherPtr, loop->children, node);
- shell_free_argv(proc->argv);
- if (proc->type == kProcessTypePty) {
- xfree(((PtyProcess *)proc)->term_name);
- }
- if (proc->cb) {
- proc->cb(proc, proc->status, proc->data);
- }
+ CREATE_EVENT(proc->events, process_close_event, 1, proc);
}
static void process_close(Process *proc)
@@ -293,28 +312,27 @@ static void process_close(Process *proc)
}
}
-static void on_process_exit(Process *proc)
+static void process_close_handles(void **argv)
{
- if (exiting) {
- on_process_exit_event((Event) {.data = proc});
- } else {
- loop_push_event(proc->loop,
- (Event) {.handler = on_process_exit_event, .data = proc}, false);
- }
+ Process *proc = argv[0];
+ process_close_streams(proc);
+ process_close(proc);
+}
+static void on_process_exit(Process *proc)
+{
Loop *loop = proc->loop;
- if (loop->children_stop_requests && !--loop->children_stop_requests) {
+ if (proc->stopped_time && loop->children_stop_requests
+ && !--loop->children_stop_requests) {
// Stop the timer if no more stop requests are pending
DLOG("Stopping process kill timer");
uv_timer_stop(&loop->children_kill_timer);
}
-}
-
-static void on_process_exit_event(Event event)
-{
- Process *proc = event.data;
- process_close_streams(proc);
- process_close(proc);
+ // Process handles are closed in the next event loop tick. This is done to
+ // give libuv more time to read data from the OS after the process exits(If
+ // process_close_streams is called with data still in the OS buffer, we lose
+ // it)
+ CREATE_EVENT(proc->events, process_close_handles, 1, proc);
}
static void on_process_stream_close(Stream *stream, void *data)
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index 5c84a7d1d0..45edc46b95 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -26,14 +26,16 @@ struct process {
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
bool closed, term_sent;
+ Queue *events;
};
-static inline Process process_init(ProcessType type, void *data)
+static inline Process process_init(Loop *loop, ProcessType type, void *data)
{
return (Process) {
.type = type,
.data = data,
- .loop = NULL,
+ .loop = loop,
+ .events = NULL,
.pid = 0,
.status = 0,
.refcount = 0,
diff --git a/src/nvim/event/pty_process.c b/src/nvim/event/pty_process.c
index 1e24d7c919..8eef72f12f 100644
--- a/src/nvim/event/pty_process.c
+++ b/src/nvim/event/pty_process.c
@@ -33,17 +33,18 @@
# include "event/pty_process.c.generated.h"
#endif
-static const unsigned int KILL_RETRIES = 5;
-static const unsigned int KILL_TIMEOUT = 2; // seconds
-
bool pty_process_spawn(PtyProcess *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
+ static struct termios termios;
+ if (!termios.c_cflag) {
+ init_termios(&termios);
+ }
+
Process *proc = (Process *)ptyproc;
+ assert(!proc->err);
uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
ptyproc->winsize = (struct winsize){ptyproc->height, ptyproc->width, 0, 0};
- struct termios termios;
- init_termios(&termios);
uv_disable_stdio_inheritance();
int master;
int pid = forkpty(&master, NULL, &termios, &ptyproc->winsize);
@@ -73,9 +74,6 @@ bool pty_process_spawn(PtyProcess *ptyproc)
if (proc->out && !set_duplicating_descriptor(master, &proc->out->uv.pipe)) {
goto error;
}
- if (proc->err && !set_duplicating_descriptor(master, &proc->err->uv.pipe)) {
- goto error;
- }
ptyproc->tty_fd = master;
proc->pid = pid;
@@ -83,19 +81,8 @@ bool pty_process_spawn(PtyProcess *ptyproc)
error:
close(master);
-
- // terminate spawned process
- kill(pid, SIGTERM);
- int status, child;
- unsigned int try = 0;
- while (try++ < KILL_RETRIES && !(child = waitpid(pid, &status, WNOHANG))) {
- sleep(KILL_TIMEOUT);
- }
- if (child != pid) {
- kill(pid, SIGKILL);
- waitpid(pid, NULL, 0);
- }
-
+ kill(pid, SIGKILL);
+ waitpid(pid, NULL, 0);
return false;
}
@@ -152,7 +139,6 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
{
- memset(termios, 0, sizeof(struct termios));
// Taken from pangoterm
termios->c_iflag = ICRNL|IXON;
termios->c_oflag = OPOST|ONLCR;
diff --git a/src/nvim/event/pty_process.h b/src/nvim/event/pty_process.h
index a12b5489c5..446d7fd3c8 100644
--- a/src/nvim/event/pty_process.h
+++ b/src/nvim/event/pty_process.h
@@ -13,10 +13,10 @@ typedef struct pty_process {
int tty_fd;
} PtyProcess;
-static inline PtyProcess pty_process_init(void *data)
+static inline PtyProcess pty_process_init(Loop *loop, void *data)
{
PtyProcess rv;
- rv.process = process_init(kProcessTypePty, data);
+ rv.process = process_init(loop, kProcessTypePty, data);
rv.term_name = NULL;
rv.width = 80;
rv.height = 24;
diff --git a/src/nvim/event/queue.c b/src/nvim/event/queue.c
new file mode 100644
index 0000000000..19eca14144
--- /dev/null
+++ b/src/nvim/event/queue.c
@@ -0,0 +1,208 @@
+// Queue for selective async event processing. Instances of this queue support a
+// parent/child relationship with the following properties:
+//
+// - pushing a node to a child queue will push a corresponding link node to the
+// parent queue
+// - removing a link node from a parent queue will remove the next node
+// in the linked child queue
+// - removing a node from a child queue will remove the corresponding link node
+// in the parent queue
+//
+// These properties allow neovim to organize and process events from different
+// sources with a certain degree of control. Here's how the queue is used:
+//
+// +----------------+
+// | Main loop |
+// +----------------+
+// ^
+// |
+// +----------------+
+// +-------------->| Event loop |<------------+
+// | +--+-------------+ |
+// | ^ ^ |
+// | | | |
+// +-----------+ +-----------+ +---------+ +---------+
+// | Channel 1 | | Channel 2 | | Job 1 | | Job 2 |
+// +-----------+ +-----------+ +---------+ +---------+
+//
+//
+// In the above diagram, the lower boxes represents event emitters, each with
+// it's own private queue that have the event loop queue as the parent.
+//
+// When idle, the main loop spins the event loop which queues events from many
+// sources(channels, jobs, user...). Each event emitter pushes events to its own
+// private queue which is propagated to the event loop queue. When the main loop
+// consumes an event, the corresponding event is removed from the emitter's
+// queue.
+//
+// The main reason for this queue hierarchy is to allow focusing on a single
+// event emitter while blocking the main loop. For example, if the `jobwait`
+// vimscript function is called on job1, the main loop will temporarily stop
+// polling the event loop queue and poll job1 queue instead. Same with channels,
+// when calling `rpcrequest`, we want to temporarily stop processing events from
+// other sources and focus on a specific channel.
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+
+#include <uv.h>
+
+#include "nvim/event/queue.h"
+#include "nvim/memory.h"
+#include "nvim/os/time.h"
+
+typedef struct queue_item QueueItem;
+struct queue_item {
+ union {
+ Queue *queue;
+ struct {
+ Event event;
+ QueueItem *parent;
+ } item;
+ } data;
+ bool link; // this is just a link to a node in a child queue
+ QUEUE node;
+};
+
+struct queue {
+ Queue *parent;
+ QUEUE headtail;
+ put_callback put_cb;
+ void *data;
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/queue.c.generated.h"
+#endif
+
+static Event NILEVENT = {.handler = NULL, .argv = {NULL}};
+
+Queue *queue_new_parent(put_callback put_cb, void *data)
+{
+ return queue_new(NULL, put_cb, data);
+}
+
+Queue *queue_new_child(Queue *parent)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(!parent->parent);
+ return queue_new(parent, NULL, NULL);
+}
+
+static Queue *queue_new(Queue *parent, put_callback put_cb, void *data)
+{
+ Queue *rv = xmalloc(sizeof(Queue));
+ QUEUE_INIT(&rv->headtail);
+ rv->parent = parent;
+ rv->put_cb = put_cb;
+ rv->data = data;
+ return rv;
+}
+
+void queue_free(Queue *queue)
+{
+ assert(queue);
+ if (queue->parent) {
+ while (!QUEUE_EMPTY(&queue->headtail)) {
+ QUEUE *q = QUEUE_HEAD(&queue->headtail);
+ QueueItem *item = queue_node_data(q);
+ assert(!item->link);
+ QUEUE_REMOVE(&item->data.item.parent->node);
+ xfree(item->data.item.parent);
+ QUEUE_REMOVE(q);
+ xfree(item);
+ }
+ }
+
+ xfree(queue);
+}
+
+Event queue_get(Queue *queue)
+{
+ return queue_empty(queue) ? NILEVENT : queue_remove(queue);
+}
+
+void queue_put_event(Queue *queue, Event event)
+{
+ assert(queue);
+ assert(queue->parent); // don't push directly to the parent queue
+ queue_push(queue, event);
+ if (queue->parent->put_cb) {
+ queue->parent->put_cb(queue->parent, queue->parent->data);
+ }
+}
+
+void queue_process_events(Queue *queue)
+{
+ assert(queue);
+ while (!queue_empty(queue)) {
+ Event event = queue_get(queue);
+ if (event.handler) {
+ event.handler(event.argv);
+ }
+ }
+}
+
+bool queue_empty(Queue *queue)
+{
+ assert(queue);
+ return QUEUE_EMPTY(&queue->headtail);
+}
+
+void queue_replace_parent(Queue *queue, Queue *new_parent)
+{
+ assert(queue_empty(queue));
+ queue->parent = new_parent;
+}
+
+static Event queue_remove(Queue *queue)
+{
+ assert(!queue_empty(queue));
+ QUEUE *h = QUEUE_HEAD(&queue->headtail);
+ QUEUE_REMOVE(h);
+ QueueItem *item = queue_node_data(h);
+ Event rv;
+
+ if (item->link) {
+ assert(!queue->parent);
+ // remove the next node in the linked queue
+ Queue *linked = item->data.queue;
+ assert(!queue_empty(linked));
+ QueueItem *child =
+ queue_node_data(QUEUE_HEAD(&linked->headtail));
+ QUEUE_REMOVE(&child->node);
+ rv = child->data.item.event;
+ xfree(child);
+ } else {
+ assert(queue->parent);
+ assert(!queue_empty(queue->parent));
+ // remove the corresponding link node in the parent queue
+ QUEUE_REMOVE(&item->data.item.parent->node);
+ xfree(item->data.item.parent);
+ rv = item->data.item.event;
+ }
+
+ xfree(item);
+ return rv;
+}
+
+static void queue_push(Queue *queue, Event event)
+{
+ QueueItem *item = xmalloc(sizeof(QueueItem));
+ item->link = false;
+ item->data.item.event = event;
+ QUEUE_INSERT_TAIL(&queue->headtail, &item->node);
+ // push link node to the parent queue
+ item->data.item.parent = xmalloc(sizeof(QueueItem));
+ item->data.item.parent->link = true;
+ item->data.item.parent->data.queue = queue;
+ QUEUE_INSERT_TAIL(&queue->parent->headtail, &item->data.item.parent->node);
+}
+
+static QueueItem *queue_node_data(QUEUE *q)
+{
+ return QUEUE_DATA(q, QueueItem, node);
+}
diff --git a/src/nvim/event/queue.h b/src/nvim/event/queue.h
new file mode 100644
index 0000000000..85fc59f8b2
--- /dev/null
+++ b/src/nvim/event/queue.h
@@ -0,0 +1,19 @@
+#ifndef NVIM_EVENT_QUEUE_H
+#define NVIM_EVENT_QUEUE_H
+
+#include <uv.h>
+
+#include "nvim/event/defs.h"
+#include "nvim/lib/queue.h"
+
+typedef struct queue Queue;
+typedef void (*put_callback)(Queue *queue, void *data);
+
+#define queue_put(q, h, ...) \
+ queue_put_event(q, event_create(1, h, __VA_ARGS__));
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/queue.h.generated.h"
+#endif
+#endif // NVIM_EVENT_QUEUE_H
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index 9d2439ac2b..9f3fbc25ff 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -49,6 +49,7 @@ void rstream_init(Stream *stream, size_t bufsize)
///
/// @param stream The `Stream` instance
void rstream_start(Stream *stream, stream_read_cb cb)
+ FUNC_ATTR_NONNULL_ARG(1)
{
stream->read_cb = cb;
if (stream->uvstream) {
@@ -62,6 +63,7 @@ void rstream_start(Stream *stream, stream_read_cb cb)
///
/// @param stream The `Stream` instance
void rstream_stop(Stream *stream)
+ FUNC_ATTR_NONNULL_ALL
{
if (stream->uvstream) {
uv_read_stop(stream->uvstream);
@@ -112,7 +114,7 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
// Read error or EOF, either way stop the stream and invoke the callback
// with eof == true
uv_read_stop(uvstream);
- invoke_read_cb(stream, true);
+ invoke_read_cb(stream, 0, true);
}
return;
}
@@ -122,7 +124,7 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
// Data was already written, so all we need is to update 'wpos' to reflect
// the space actually used in the buffer.
rbuffer_produced(stream->buffer, nread);
- invoke_read_cb(stream, false);
+ invoke_read_cb(stream, nread, false);
}
// Called by the by the 'idle' handle to emulate a reading event
@@ -156,7 +158,7 @@ static void fread_idle_cb(uv_idle_t *handle)
if (req.result <= 0) {
uv_idle_stop(&stream->uv.idle);
- invoke_read_cb(stream, true);
+ invoke_read_cb(stream, 0, true);
return;
}
@@ -164,12 +166,36 @@ static void fread_idle_cb(uv_idle_t *handle)
size_t nread = (size_t) req.result;
rbuffer_produced(stream->buffer, nread);
stream->fpos += nread;
- invoke_read_cb(stream, false);
+ invoke_read_cb(stream, nread, false);
}
-static void invoke_read_cb(Stream *stream, bool eof)
+static void read_event(void **argv)
{
+ Stream *stream = argv[0];
if (stream->read_cb) {
- stream->read_cb(stream, stream->buffer, stream->data, eof);
+ size_t count = (uintptr_t)argv[1];
+ bool eof = (uintptr_t)argv[2];
+ stream->read_cb(stream, stream->buffer, count, stream->data, eof);
+ }
+ stream->pending_reqs--;
+ if (stream->closed && !stream->pending_reqs) {
+ stream_close_handle(stream);
}
}
+
+static void invoke_read_cb(Stream *stream, size_t count, bool eof)
+{
+ if (stream->closed) {
+ return;
+ }
+
+ // Don't let the stream be closed before the event is processed.
+ stream->pending_reqs++;
+
+ CREATE_EVENT(stream->events,
+ read_event,
+ 3,
+ stream,
+ (void *)(uintptr_t *)count,
+ (void *)(uintptr_t)eof);
+}
diff --git a/src/nvim/event/signal.c b/src/nvim/event/signal.c
index 63133b4f57..11ce15a882 100644
--- a/src/nvim/event/signal.c
+++ b/src/nvim/event/signal.c
@@ -15,6 +15,7 @@ void signal_watcher_init(Loop *loop, SignalWatcher *watcher, void *data)
watcher->uv.data = watcher;
watcher->data = data;
watcher->cb = NULL;
+ watcher->events = loop->fast_events;
}
void signal_watcher_start(SignalWatcher *watcher, signal_cb cb, int signum)
@@ -37,10 +38,16 @@ void signal_watcher_close(SignalWatcher *watcher, signal_close_cb cb)
uv_close((uv_handle_t *)&watcher->uv, close_cb);
}
+static void signal_event(void **argv)
+{
+ SignalWatcher *watcher = argv[0];
+ watcher->cb(watcher, watcher->uv.signum, watcher->data);
+}
+
static void signal_watcher_cb(uv_signal_t *handle, int signum)
{
SignalWatcher *watcher = handle->data;
- watcher->cb(watcher, signum, watcher->data);
+ CREATE_EVENT(watcher->events, signal_event, 1, watcher);
}
static void close_cb(uv_handle_t *handle)
diff --git a/src/nvim/event/signal.h b/src/nvim/event/signal.h
index c269fa9d95..e32608acc0 100644
--- a/src/nvim/event/signal.h
+++ b/src/nvim/event/signal.h
@@ -14,6 +14,7 @@ struct signal_watcher {
void *data;
signal_cb cb;
signal_close_cb close_cb;
+ Queue *events;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index bdc632abf0..347e464d25 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -77,6 +77,7 @@ void socket_watcher_init(Loop *loop, SocketWatcher *watcher,
watcher->stream->data = watcher;
watcher->cb = NULL;
watcher->close_cb = NULL;
+ watcher->events = NULL;
}
int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
@@ -113,6 +114,7 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
}
int socket_watcher_accept(SocketWatcher *watcher, Stream *stream, void *data)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2)
{
uv_stream_t *client;
@@ -142,10 +144,18 @@ void socket_watcher_close(SocketWatcher *watcher, socket_close_cb cb)
uv_close((uv_handle_t *)watcher->stream, close_cb);
}
+static void connection_event(void **argv)
+{
+ SocketWatcher *watcher = argv[0];
+ int status = (int)(uintptr_t)(argv[1]);
+ watcher->cb(watcher, status, watcher->data);
+}
+
static void connection_cb(uv_stream_t *handle, int status)
{
SocketWatcher *watcher = handle->data;
- watcher->cb(watcher, status, watcher->data);
+ CREATE_EVENT(watcher->events, connection_event, 2, watcher,
+ (void *)(uintptr_t)status);
}
static void close_cb(uv_handle_t *handle)
diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h
index 17fd39f33b..ad59fdbe3a 100644
--- a/src/nvim/event/socket.h
+++ b/src/nvim/event/socket.h
@@ -30,6 +30,7 @@ struct socket_watcher {
void *data;
socket_cb cb;
socket_close_cb close_cb;
+ Queue *events;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 959b532146..6caad6fdcc 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -32,6 +32,7 @@ int stream_set_blocking(int fd, bool blocking)
void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream,
void *data)
+ FUNC_ATTR_NONNULL_ARG(2)
{
stream->uvstream = uvstream;
@@ -55,6 +56,7 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream,
if (stream->uvstream) {
stream->uvstream->data = stream;
+ loop = stream->uvstream->loop->data;
}
stream->data = data;
@@ -69,16 +71,13 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream,
stream->internal_close_cb = NULL;
stream->closed = false;
stream->buffer = NULL;
+ stream->events = NULL;
}
void stream_close(Stream *stream, stream_close_cb on_stream_close)
+ FUNC_ATTR_NONNULL_ARG(1)
{
assert(!stream->closed);
-
- if (stream->buffer) {
- rbuffer_free(stream->buffer);
- }
-
stream->closed = true;
stream->close_cb = on_stream_close;
@@ -88,6 +87,7 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close)
}
void stream_close_handle(Stream *stream)
+ FUNC_ATTR_NONNULL_ALL
{
if (stream->uvstream) {
uv_close((uv_handle_t *)stream->uvstream, close_cb);
@@ -99,6 +99,9 @@ void stream_close_handle(Stream *stream)
static void close_cb(uv_handle_t *handle)
{
Stream *stream = handle->data;
+ if (stream->buffer) {
+ rbuffer_free(stream->buffer);
+ }
if (stream->close_cb) {
stream->close_cb(stream, stream->data);
}
diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h
index 37410b2036..c6baac0db7 100644
--- a/src/nvim/event/stream.h
+++ b/src/nvim/event/stream.h
@@ -14,10 +14,14 @@ typedef struct stream Stream;
///
/// @param stream The Stream instance
/// @param rbuffer The associated RBuffer instance
+/// @param count Number of bytes to read. This must be respected if keeping
+/// the order of events is a requirement. This is because events
+/// may be queued and only processed later when more data is copied
+/// into to the buffer, so one read may starve another.
/// @param data User-defined data
/// @param eof If the stream reached EOF.
-typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, void *data,
- bool eof);
+typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count,
+ void *data, bool eof);
/// Type of function called when the Stream has information about a write
/// request.
@@ -47,6 +51,7 @@ struct stream {
size_t pending_reqs;
void *data, *internal_data;
bool closed;
+ Queue *events;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c
index ce33cdfc10..7bf333bcea 100644
--- a/src/nvim/event/time.c
+++ b/src/nvim/event/time.c
@@ -16,6 +16,7 @@ void time_watcher_init(Loop *loop, TimeWatcher *watcher, void *data)
uv_timer_init(&loop->uv, &watcher->uv);
watcher->uv.data = watcher;
watcher->data = data;
+ watcher->events = loop->fast_events;
}
void time_watcher_start(TimeWatcher *watcher, time_cb cb, uint64_t timeout,
@@ -39,11 +40,17 @@ void time_watcher_close(TimeWatcher *watcher, time_cb cb)
uv_close((uv_handle_t *)&watcher->uv, close_cb);
}
+static void time_event(void **argv)
+{
+ TimeWatcher *watcher = argv[0];
+ watcher->cb(watcher, watcher->data);
+}
+
static void time_watcher_cb(uv_timer_t *handle)
FUNC_ATTR_NONNULL_ALL
{
TimeWatcher *watcher = handle->data;
- watcher->cb(watcher, watcher->data);
+ CREATE_EVENT(watcher->events, time_event, 1, watcher);
}
static void close_cb(uv_handle_t *handle)
diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h
index ee50e53d11..7882b2b627 100644
--- a/src/nvim/event/time.h
+++ b/src/nvim/event/time.h
@@ -12,6 +12,7 @@ struct time_watcher {
uv_timer_t uv;
void *data;
time_cb cb, close_cb;
+ Queue *events;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/uv_process.h b/src/nvim/event/uv_process.h
index a17f1446b3..5ee73044b5 100644
--- a/src/nvim/event/uv_process.h
+++ b/src/nvim/event/uv_process.h
@@ -12,10 +12,10 @@ typedef struct uv_process {
uv_stdio_container_t uvstdio[3];
} UvProcess;
-static inline UvProcess uv_process_init(void *data)
+static inline UvProcess uv_process_init(Loop *loop, void *data)
{
UvProcess rv;
- rv.process = process_init(kProcessTypeUv, data);
+ rv.process = process_init(loop, kProcessTypeUv, data);
return rv;
}
diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c
index 5fcb724fe3..8028e35e6b 100644
--- a/src/nvim/event/wstream.c
+++ b/src/nvim/event/wstream.c
@@ -118,6 +118,7 @@ WBuffer *wstream_new_buffer(char *data,
size_t size,
size_t refcount,
wbuffer_data_finalizer cb)
+ FUNC_ATTR_NONNULL_ARG(1)
{
WBuffer *rv = xmalloc(sizeof(WBuffer));
rv->size = size;
@@ -151,6 +152,7 @@ static void write_cb(uv_write_t *req, int status)
}
void wstream_release_wbuffer(WBuffer *buffer)
+ FUNC_ATTR_NONNULL_ALL
{
if (!--buffer->refcount) {
if (buffer->cb) {
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 2c92113404..802a378b07 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2395,7 +2395,7 @@ static int check_readonly(int *forceit, buf_T *buf)
* the file exists and permissions are read-only. */
if (!*forceit && (buf->b_p_ro
|| (os_file_exists(buf->b_ffname)
- && os_file_is_readonly((char *)buf->b_ffname)))) {
+ && !os_file_is_writable((char *)buf->b_ffname)))) {
if ((p_confirm || cmdmod.confirm) && buf->b_fname != NULL) {
char_u buff[DIALOG_MSG_SIZE];
@@ -6125,7 +6125,7 @@ char_u * sign_typenr2name(int typenr)
/*
* Undefine/free all signs.
*/
-void free_signs()
+void free_signs(void)
{
while (first_sign != NULL)
sign_undefine(first_sign, NULL);
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index d610aa6d19..efa18aa681 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -2624,7 +2624,7 @@ char_u *get_scriptname(scid_T id)
}
# if defined(EXITFREE)
-void free_scriptnames()
+void free_scriptnames(void)
{
# define FREE_SCRIPTNAME(item) xfree((item)->sn_name)
GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 785db1dbd1..03116d454f 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -62,6 +62,7 @@
#include "nvim/tag.h"
#include "nvim/window.h"
#include "nvim/ui.h"
+#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/event/loop.h"
@@ -298,14 +299,14 @@ getcmdline (
/* Get a character. Ignore K_IGNORE, it should not do anything, such
* as stop completion. */
- loop_enable_deferred_events(&loop);
+ input_enable_events();
do {
c = safe_vgetc();
} while (c == K_IGNORE);
- loop_disable_deferred_events(&loop);
+ input_disable_events();
if (c == K_EVENT) {
- loop_process_event(&loop);
+ queue_process_events(loop.events);
continue;
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index dee9680230..70cdac7947 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -415,14 +415,10 @@ readfile (
msg_scroll = TRUE; /* don't overwrite previous file message */
/*
- * If the name ends in a path separator, we can't open it. Check here,
- * because reading the file may actually work, but then creating the swap
- * file may destroy it! Reported on MS-DOS and Win 95.
* If the name is too long we might crash further on, quit here.
*/
if (fname != NULL && *fname != NUL) {
- p = fname + STRLEN(fname);
- if (after_pathsep((char *)fname, (char *)p) || STRLEN(fname) >= MAXPATHL) {
+ if (STRLEN(fname) >= MAXPATHL) {
filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0);
msg_end();
msg_scroll = msg_save;
@@ -2627,7 +2623,7 @@ buf_write (
* Check if the file is really writable (when renaming the file to
* make a backup we won't discover it later).
*/
- file_readonly = os_file_is_readonly((char *)fname);
+ file_readonly = !os_file_is_writable((char *)fname);
if (!forceit && file_readonly) {
if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h
index 519f61c763..c31d21ec6d 100644
--- a/src/nvim/func_attr.h
+++ b/src/nvim/func_attr.h
@@ -179,7 +179,7 @@
#endif
#ifdef DEFINE_FUNC_ATTRIBUTES
- #define FUNC_ATTR_DEFERRED
+ #define FUNC_ATTR_ASYNC
#define FUNC_ATTR_MALLOC REAL_FATTR_MALLOC
#define FUNC_ATTR_ALLOC_SIZE(x) REAL_FATTR_ALLOC_SIZE(x)
#define FUNC_ATTR_ALLOC_SIZE_PROD(x,y) REAL_FATTR_ALLOC_SIZE_PROD(x,y)
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index bbeef376b0..6a6e4f2214 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1758,7 +1758,7 @@ static int vgetorpeek(int advance)
if (c1 == K_SPECIAL)
nolmaplen = 2;
else {
- LANGMAP_ADJUST(c1, (State & INSERT) == 0);
+ LANGMAP_ADJUST(c1, (State & (CMDLINE | INSERT)) == 0);
nolmaplen = 0;
}
/* First try buffer-local mappings. */
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index e4dcad9afb..68cb923e42 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -898,14 +898,6 @@ EXTERN FILE *scriptout INIT(= NULL); /* stream to write script to */
/* volatile because it is used in signal handler catch_sigint(). */
EXTERN volatile int got_int INIT(= FALSE); /* set to TRUE when interrupt
signal occurred */
-EXTERN int disable_breakcheck INIT(= 0); // > 0 if breakchecks should be
- // ignored. FIXME(tarruda): Hacky
- // way to run functions that would
- // result in *_breakcheck calls
- // while events that would normally
- // be deferred are being processed
- // immediately. Ref:
- // neovim/neovim#2371
EXTERN int bangredo INIT(= FALSE); /* set to TRUE with ! command */
EXTERN int searchcmdlen; /* length of previous search cmd */
EXTERN int reg_do_extmatch INIT(= 0); /* Used when compiling regexp:
diff --git a/src/nvim/lib/queue.h b/src/nvim/lib/queue.h
new file mode 100644
index 0000000000..fe02b454ea
--- /dev/null
+++ b/src/nvim/lib/queue.h
@@ -0,0 +1,92 @@
+/* Copyright (c) 2013, Ben Noordhuis <info@bnoordhuis.nl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef QUEUE_H_
+#define QUEUE_H_
+
+typedef void *QUEUE[2];
+
+/* Private macros. */
+#define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0]))
+#define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1]))
+#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q)))
+#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q)))
+
+/* Public macros. */
+#define QUEUE_DATA(ptr, type, field) \
+ ((type *) ((char *) (ptr) - ((char *) &((type *) 0)->field)))
+
+#define QUEUE_FOREACH(q, h) \
+ for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q))
+
+#define QUEUE_EMPTY(q) \
+ ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q))
+
+#define QUEUE_HEAD(q) \
+ (QUEUE_NEXT(q))
+
+#define QUEUE_INIT(q) \
+ do { \
+ QUEUE_NEXT(q) = (q); \
+ QUEUE_PREV(q) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_ADD(h, n) \
+ do { \
+ QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \
+ QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \
+ QUEUE_PREV(h) = QUEUE_PREV(n); \
+ QUEUE_PREV_NEXT(h) = (h); \
+ } \
+ while (0)
+
+#define QUEUE_SPLIT(h, q, n) \
+ do { \
+ QUEUE_PREV(n) = QUEUE_PREV(h); \
+ QUEUE_PREV_NEXT(n) = (n); \
+ QUEUE_NEXT(n) = (q); \
+ QUEUE_PREV(h) = QUEUE_PREV(q); \
+ QUEUE_PREV_NEXT(h) = (h); \
+ QUEUE_PREV(q) = (n); \
+ } \
+ while (0)
+
+#define QUEUE_INSERT_HEAD(h, q) \
+ do { \
+ QUEUE_NEXT(q) = QUEUE_NEXT(h); \
+ QUEUE_PREV(q) = (h); \
+ QUEUE_NEXT_PREV(q) = (q); \
+ QUEUE_NEXT(h) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_INSERT_TAIL(h, q) \
+ do { \
+ QUEUE_NEXT(q) = (h); \
+ QUEUE_PREV(q) = QUEUE_PREV(h); \
+ QUEUE_PREV_NEXT(q) = (q); \
+ QUEUE_PREV(h) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_REMOVE(q) \
+ do { \
+ QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \
+ QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \
+ } \
+ while (0)
+
+#endif /* QUEUE_H_ */
diff --git a/src/nvim/main.c b/src/nvim/main.c
index bfaeada6de..e11db16c61 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -157,11 +157,11 @@ void event_init(void)
void event_teardown(void)
{
- if (!loop.deferred_events) {
+ if (!loop.events) {
return;
}
- loop_process_all_events(&loop);
+ queue_process_events(loop.events);
input_stop();
channel_teardown();
process_teardown(&loop);
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 5d83020619..ed7bda4cce 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -116,5 +116,5 @@ MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
-#define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .defer = false}
+#define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .async = false}
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 53f9a544b4..d90e91be5d 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -3934,8 +3934,9 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp)
size += lnum - 1;
/* Don't count the last line break if 'bin' and 'noeol'. */
- if (buf->b_p_bin && !buf->b_p_eol)
+ if (buf->b_p_bin && !buf->b_p_eol && buf->b_ml.ml_line_count == lnum) {
size -= ffdos + 1;
+ }
}
return size;
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 861614f147..0e3b8200c9 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -68,6 +68,7 @@ typedef struct {
uint64_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack;
kvec_t(WBuffer *) delayed_notifications;
+ Queue *events;
} Channel;
typedef struct {
@@ -123,14 +124,14 @@ void channel_teardown(void)
uint64_t channel_from_process(char **argv)
{
Channel *channel = register_channel(kChannelTypeProc);
- channel->data.process.uvproc = uv_process_init(channel);
+ channel->data.process.uvproc = uv_process_init(&loop, channel);
Process *proc = &channel->data.process.uvproc.process;
proc->argv = argv;
proc->in = &channel->data.process.in;
proc->out = &channel->data.process.out;
proc->err = &channel->data.process.err;
proc->cb = process_exit;
- if (!process_spawn(&loop, proc)) {
+ if (!process_spawn(proc)) {
loop_poll_events(&loop, 0);
decref(channel);
return 0;
@@ -153,6 +154,9 @@ void channel_from_connection(SocketWatcher *watcher)
{
Channel *channel = register_channel(kChannelTypeSocket);
socket_watcher_accept(watcher, &channel->data.stream, channel);
+ incref(channel); // close channel only after the stream is closed
+ channel->data.stream.internal_close_cb = close_cb;
+ channel->data.stream.internal_data = channel;
wstream_init(&channel->data.stream, 0);
rstream_init(&channel->data.stream, CHANNEL_BUFFER_SIZE);
rstream_start(&channel->data.stream, parse_msgpack);
@@ -221,7 +225,7 @@ Object channel_send_call(uint64_t id,
ChannelCallFrame frame = {request_id, false, false, NIL};
kv_push(ChannelCallFrame *, channel->call_stack, &frame);
channel->pending_requests++;
- LOOP_POLL_EVENTS_UNTIL(&loop, -1, frame.returned);
+ LOOP_PROCESS_EVENTS_UNTIL(&loop, channel->events, -1, frame.returned);
(void)kv_pop(channel->call_stack);
channel->pending_requests--;
@@ -324,7 +328,8 @@ static void channel_from_stdio(void)
wstream_init_fd(&loop, &channel->data.std.out, 1, 0, NULL);
}
-static void forward_stderr(Stream *stream, RBuffer *rbuf, void *data, bool eof)
+static void forward_stderr(Stream *stream, RBuffer *rbuf, size_t count,
+ void *data, bool eof)
{
while (rbuffer_size(rbuf)) {
char buf[256];
@@ -339,7 +344,8 @@ static void process_exit(Process *proc, int status, void *data)
decref(data);
}
-static void parse_msgpack(Stream *stream, RBuffer *rbuf, void *data, bool eof)
+static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data,
+ bool eof)
{
Channel *channel = data;
incref(channel);
@@ -447,31 +453,31 @@ static void handle_request(Channel *channel, msgpack_object *request)
method->via.bin.size);
} else {
handler.fn = msgpack_rpc_handle_missing_method;
- handler.defer = false;
+ handler.async = true;
}
Array args = ARRAY_DICT_INIT;
if (!msgpack_rpc_to_array(msgpack_rpc_args(request), &args)) {
handler.fn = msgpack_rpc_handle_invalid_arguments;
- handler.defer = false;
+ handler.async = true;
}
- bool defer = (!kv_size(channel->call_stack) && handler.defer);
RequestEvent *event_data = xmalloc(sizeof(RequestEvent));
event_data->channel = channel;
event_data->handler = handler;
event_data->args = args;
event_data->request_id = request_id;
incref(channel);
- loop_push_event(&loop, (Event) {
- .handler = on_request_event,
- .data = event_data
- }, defer);
+ if (handler.async) {
+ on_request_event((void **)&event_data);
+ } else {
+ queue_put(channel->events, on_request_event, 1, event_data);
+ }
}
-static void on_request_event(Event event)
+static void on_request_event(void **argv)
{
- RequestEvent *e = event.data;
+ RequestEvent *e = argv[0];
Channel *channel = e->channel;
MsgpackRpcRequestHandler handler = e->handler;
Array args = e->args;
@@ -636,7 +642,7 @@ static void close_channel(Channel *channel)
switch (channel->type) {
case kChannelTypeSocket:
- stream_close(&channel->data.stream, close_cb);
+ stream_close(&channel->data.stream, NULL);
break;
case kChannelTypeProc:
if (!channel->data.process.uvproc.process.closed) {
@@ -646,9 +652,8 @@ static void close_channel(Channel *channel)
case kChannelTypeStdio:
stream_close(&channel->data.std.in, NULL);
stream_close(&channel->data.std.out, NULL);
- loop_push_event(&loop,
- (Event) { .handler = on_stdio_close, .data = channel }, false);
- break;
+ queue_put(loop.fast_events, exit_event, 1, channel);
+ return;
default:
abort();
}
@@ -656,9 +661,9 @@ static void close_channel(Channel *channel)
decref(channel);
}
-static void on_stdio_close(Event e)
+static void exit_event(void **argv)
{
- decref(e.data);
+ decref(argv[0]);
if (!exiting) {
mch_exit(0);
@@ -680,17 +685,19 @@ static void free_channel(Channel *channel)
pmap_free(cstr_t)(channel->subscribed_events);
kv_destroy(channel->call_stack);
kv_destroy(channel->delayed_notifications);
+ queue_free(channel->events);
xfree(channel);
}
static void close_cb(Stream *stream, void *data)
{
- xfree(data);
+ decref(data);
}
static Channel *register_channel(ChannelType type)
{
Channel *rv = xmalloc(sizeof(Channel));
+ rv->events = queue_new_child(loop.events);
rv->type = type;
rv->refcount = 1;
rv->closed = false;
diff --git a/src/nvim/msgpack_rpc/defs.h b/src/nvim/msgpack_rpc/defs.h
index 0492a65290..d97cf28ca1 100644
--- a/src/nvim/msgpack_rpc/defs.h
+++ b/src/nvim/msgpack_rpc/defs.h
@@ -11,9 +11,8 @@ typedef struct {
uint64_t request_id,
Array args,
Error *error);
- bool defer; // Should the call be deferred to the main loop? This should
- // be true if the function mutates editor data structures such
- // as buffers, windows, tabs, or if it executes vimscript code.
+ bool async; // function is always safe to run immediately instead of being
+ // put in a request queue for handling when nvim waits for input.
} MsgpackRpcRequestHandler;
/// Initializes the msgpack-rpc method table
diff --git a/src/nvim/msgpack_rpc/remote_ui.c b/src/nvim/msgpack_rpc/remote_ui.c
index 07d78dd9d7..3334b0e6af 100644
--- a/src/nvim/msgpack_rpc/remote_ui.c
+++ b/src/nvim/msgpack_rpc/remote_ui.c
@@ -28,7 +28,7 @@ void remote_ui_init(void)
connected_uis = pmap_new(uint64_t)();
// Add handler for "attach_ui"
String method = cstr_as_string("ui_attach");
- MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .defer = false};
+ MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .async = true};
msgpack_rpc_add_method_handler(method, handler);
method = cstr_as_string("ui_detach");
handler.fn = remote_ui_detach;
@@ -86,8 +86,7 @@ static Object remote_ui_attach(uint64_t channel_id, uint64_t request_id,
ui->busy_stop = remote_ui_busy_stop;
ui->mouse_on = remote_ui_mouse_on;
ui->mouse_off = remote_ui_mouse_off;
- ui->insert_mode = remote_ui_insert_mode;
- ui->normal_mode = remote_ui_normal_mode;
+ ui->mode_change = remote_ui_mode_change;
ui->set_scroll_region = remote_ui_set_scroll_region;
ui->scroll = remote_ui_scroll;
ui->highlight_set = remote_ui_highlight_set;
@@ -214,16 +213,18 @@ static void remote_ui_mouse_off(UI *ui)
push_call(ui, "mouse_off", args);
}
-static void remote_ui_insert_mode(UI *ui)
+static void remote_ui_mode_change(UI *ui, int mode)
{
Array args = ARRAY_DICT_INIT;
- push_call(ui, "insert_mode", args);
-}
-
-static void remote_ui_normal_mode(UI *ui)
-{
- Array args = ARRAY_DICT_INIT;
- push_call(ui, "normal_mode", args);
+ if (mode == INSERT) {
+ ADD(args, STRING_OBJ(cstr_to_string("insert")));
+ } else if (mode == REPLACE) {
+ ADD(args, STRING_OBJ(cstr_to_string("replace")));
+ } else {
+ assert(mode == NORMAL);
+ ADD(args, STRING_OBJ(cstr_to_string("normal")));
+ }
+ push_call(ui, "mode_change", args);
}
static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left,
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 95e1c3d113..5b35af9209 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -63,6 +63,7 @@
#include "nvim/window.h"
#include "nvim/event/loop.h"
#include "nvim/os/time.h"
+#include "nvim/os/input.h"
/*
* The Visual area is remembered for reselection.
@@ -487,12 +488,12 @@ normal_cmd (
/*
* Get the command character from the user.
*/
- loop_enable_deferred_events(&loop);
+ input_enable_events();
c = safe_vgetc();
- loop_disable_deferred_events(&loop);
+ input_disable_events();
if (c == K_EVENT) {
- loop_process_event(&loop);
+ queue_process_events(loop.events);
return;
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 8a7ced138b..766b5720d9 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -5278,11 +5278,11 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet)
}
yankreg_T *target;
if (cb_flags & CB_UNNAMEDPLUS) {
- *name = '+';
- target = &y_regs[STAR_REGISTER];
+ *name = cb_flags & CB_UNNAMED ? '"': '+';
+ target = &y_regs[PLUS_REGISTER];
} else {
*name = '*';
- target = &y_regs[PLUS_REGISTER];
+ target = &y_regs[STAR_REGISTER];
}
return target; // unnamed register
}
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 5eeb275701..785c79127f 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -270,13 +270,16 @@ bool os_file_exists(const char_u *name)
return os_stat((char *)name, &statbuf);
}
-/// Check if a file is readonly.
+/// Check if a file is readable.
///
-/// @return `true` if `name` is readonly.
-bool os_file_is_readonly(const char *name)
- FUNC_ATTR_NONNULL_ALL
+/// @return true if `name` is readable, otherwise false.
+bool os_file_is_readable(const char *name)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- return access(name, W_OK) != 0;
+ uv_fs_t req;
+ int r = uv_fs_access(&fs_loop, &req, name, R_OK, NULL);
+ uv_fs_req_cleanup(&req);
+ return (r == 0);
}
/// Check if a file is writable.
@@ -285,13 +288,13 @@ bool os_file_is_readonly(const char *name)
/// @return `1` if `name` is writable,
/// @return `2` for a directory which we have rights to write into.
int os_file_is_writable(const char *name)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (access(name, W_OK) == 0) {
- if (os_isdir((char_u *)name)) {
- return 2;
- }
- return 1;
+ uv_fs_t req;
+ int r = uv_fs_access(&fs_loop, &req, name, W_OK, NULL);
+ uv_fs_req_cleanup(&req);
+ if (r == 0) {
+ return os_isdir((char_u *)name) ? 2 : 1;
}
return 0;
}
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index b0e0f57e60..09f162f79d 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -33,6 +33,7 @@ static Stream read_stream = {.closed = true};
static RBuffer *input_buffer = NULL;
static bool input_eof = false;
static int global_fd = 0;
+static int events_enabled = 0;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/input.c.generated.h"
@@ -110,8 +111,8 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt)
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
}
- // If there are deferred events, return the keys directly
- if (loop_has_deferred_events(&loop)) {
+ // If there are events, return the keys directly
+ if (pending_events()) {
return push_event_key(buf, maxlen);
}
@@ -131,11 +132,21 @@ bool os_char_avail(void)
// Check for CTRL-C typed by reading all available characters.
void os_breakcheck(void)
{
- if (!disable_breakcheck && !got_int) {
+ if (!got_int) {
loop_poll_events(&loop, 0);
}
}
+void input_enable_events(void)
+{
+ events_enabled++;
+}
+
+void input_disable_events(void)
+{
+ events_enabled--;
+}
+
/// Test whether a file descriptor refers to a terminal.
///
/// @param fd File descriptor.
@@ -281,7 +292,7 @@ static bool input_poll(int ms)
prof_inchar_enter();
}
- LOOP_POLL_EVENTS_UNTIL(&loop, ms, input_ready() || input_eof);
+ LOOP_PROCESS_EVENTS_UNTIL(&loop, NULL, ms, input_ready() || input_eof);
if (do_profiling == PROF_YES && ms) {
prof_inchar_exit();
@@ -305,7 +316,8 @@ static InbufPollResult inbuf_poll(int ms)
return input_eof ? kInputEof : kInputNone;
}
-static void read_cb(Stream *stream, RBuffer *buf, void *data, bool at_eof)
+static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
+ bool at_eof)
{
if (at_eof) {
input_eof = true;
@@ -358,7 +370,7 @@ static bool input_ready(void)
{
return typebuf_was_filled || // API call filled typeahead
rbuffer_size(input_buffer) || // Input buffer filled
- loop_has_deferred_events(&loop); // Events must be processed
+ pending_events(); // Events must be processed
}
// Exit because of an input read error.
@@ -369,3 +381,8 @@ static void read_error_exit(void)
STRCPY(IObuff, _("Vim: Error reading input, exiting...\n"));
preserve_exit();
}
+
+static bool pending_events(void)
+{
+ return events_enabled && !queue_empty(loop.events);
+}
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index e0d67d4951..2d97c4bf4f 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -205,13 +205,15 @@ static int do_os_system(char **argv,
xstrlcpy(prog, argv[0], MAXPATHL);
Stream in, out, err;
- UvProcess uvproc = uv_process_init(&buf);
+ UvProcess uvproc = uv_process_init(&loop, &buf);
Process *proc = &uvproc.process;
+ Queue *events = queue_new_child(loop.events);
+ proc->events = events;
proc->argv = argv;
proc->in = input != NULL ? &in : NULL;
proc->out = &out;
proc->err = &err;
- if (!process_spawn(&loop, proc)) {
+ if (!process_spawn(proc)) {
loop_poll_events(&loop, 0);
// Failed, probably due to `sh` not being executable
if (!silent) {
@@ -219,14 +221,22 @@ static int do_os_system(char **argv,
msg_outtrans((char_u *)prog);
msg_putchar('\n');
}
+ queue_free(events);
return -1;
}
+ // We want to deal with stream events as fast a possible while queueing
+ // process events, so reset everything to NULL. It prevents closing the
+ // streams while there's still data in the OS buffer(due to the process
+ // exiting before all data is read).
if (input != NULL) {
+ proc->in->events = NULL;
wstream_init(proc->in, 0);
}
+ proc->out->events = NULL;
rstream_init(proc->out, 0);
rstream_start(proc->out, data_cb);
+ proc->err->events = NULL;
rstream_init(proc->err, 0);
rstream_start(proc->err, data_cb);
@@ -247,7 +257,7 @@ static int do_os_system(char **argv,
// the UI
ui_busy_start();
ui_flush();
- int status = process_wait(proc, -1);
+ int status = process_wait(proc, -1, NULL);
ui_busy_stop();
// prepare the out parameters if requested
@@ -267,6 +277,9 @@ static int do_os_system(char **argv,
}
}
+ assert(queue_empty(events));
+ queue_free(events);
+
return status;
}
@@ -285,7 +298,8 @@ static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired)
buf->data = xrealloc(buf->data, buf->cap);
}
-static void system_data_cb(Stream *stream, RBuffer *buf, void *data, bool eof)
+static void system_data_cb(Stream *stream, RBuffer *buf, size_t count,
+ void *data, bool eof)
{
DynamicBuffer *dbuf = data;
@@ -295,16 +309,25 @@ static void system_data_cb(Stream *stream, RBuffer *buf, void *data, bool eof)
dbuf->len += nread;
}
-static void out_data_cb(Stream *stream, RBuffer *buf, void *data, bool eof)
+static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data,
+ bool eof)
{
- RBUFFER_UNTIL_EMPTY(buf, ptr, len) {
- size_t written = write_output(ptr, len, false,
- eof && len <= rbuffer_size(buf));
- if (written) {
- rbuffer_consumed(buf, written);
- } else {
- break;
- }
+ size_t cnt;
+ char *ptr = rbuffer_read_ptr(buf, &cnt);
+
+ if (!cnt) {
+ return;
+ }
+
+ size_t written = write_output(ptr, cnt, false, eof);
+ // No output written, force emptying the Rbuffer if it is full.
+ if (!written && rbuffer_size(buf) == rbuffer_capacity(buf)) {
+ screen_del_lines(0, 0, 1, (int)Rows, NULL);
+ screen_puts_len((char_u *)ptr, (int)cnt, (int)Rows - 1, 0, 0);
+ written = cnt;
+ }
+ if (written) {
+ rbuffer_consumed(buf, written);
}
}
@@ -421,6 +444,7 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
if (!output) {
return 0;
}
+ char replacement_NUL = to_buffer ? NL : 1;
char *start = output;
size_t off = 0;
@@ -428,9 +452,10 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
while (off < remaining) {
if (output[off] == NL) {
// Insert the line
- output[off] = NUL;
if (to_buffer) {
- ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
+ output[off] = NUL;
+ ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1,
+ false);
} else {
screen_del_lines(0, 0, 1, (int)Rows, NULL);
screen_puts_len((char_u *)output, (int)off, lastrow, 0, 0);
@@ -444,7 +469,7 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
if (output[off] == NUL) {
// Translate NUL to NL
- output[off] = NL;
+ output[off] = replacement_NUL;
}
off++;
}
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index 6de3435c4c..7158721433 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -115,16 +115,6 @@ static void deadly_signal(int signum)
static void on_signal(SignalWatcher *handle, int signum, void *data)
{
assert(signum >= 0);
- loop_push_event(&loop, (Event) {
- .handler = on_signal_event,
- .data = (void *)(uintptr_t)signum
- }, false);
-}
-
-static void on_signal_event(Event event)
-{
- int signum = (int)(uintptr_t)event.data;
-
switch (signum) {
#ifdef SIGPWR
case SIGPWR:
@@ -148,4 +138,3 @@ static void on_signal_event(Event event)
break;
}
}
-
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 6b5d4359db..ee17938afc 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -43,7 +43,7 @@ void os_delay(uint64_t milliseconds, bool ignoreinput)
if (milliseconds > INT_MAX) {
milliseconds = INT_MAX;
}
- LOOP_POLL_EVENTS_UNTIL(&loop, (int)milliseconds, got_int);
+ LOOP_PROCESS_EVENTS_UNTIL(&loop, NULL, (int)milliseconds, got_int);
} else {
os_microdelay(milliseconds * 1000);
}
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
index a57ba41af1..637a86c74f 100644
--- a/src/nvim/os/users.c
+++ b/src/nvim/os/users.c
@@ -41,7 +41,12 @@ int os_get_usernames(garray_T *users)
// Return OK if a name found.
int os_get_user_name(char *s, size_t len)
{
+#ifdef UNIX
return os_get_uname(getuid(), s, len);
+#else
+ // TODO(equalsraf): Windows GetUserName()
+ return os_get_uname(0, s, len);
+#endif
}
// Insert user name for "uid" in s[len].
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index 19d796bd08..a51898c9e7 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -21,4 +21,12 @@
// - SYS_VIMRC_FILE
// - SPECIAL_WILDCHAR
+// _access(): https://msdn.microsoft.com/en-us/library/1w06ktdy.aspx
+#ifndef R_OK
+# define R_OK 4
+#endif
+#ifndef W_OK
+# define W_OK 2
+#endif
+
#endif // NVIM_OS_WIN_DEFS_H
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 152154e5f4..72980fcd0e 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -481,6 +481,21 @@ static size_t path_expand(garray_T *gap, const char_u *path, int flags)
return do_path_expand(gap, path, 0, flags, false);
}
+static const char *scandir_next_with_dots(Directory *dir)
+{
+ static int count = 0;
+ if (dir == NULL) { // initialize
+ count = 0;
+ return NULL;
+ }
+
+ count += 1;
+ if (count == 1 || count == 2) {
+ return (count == 1) ? "." : "..";
+ }
+ return os_scandir_next(dir);
+}
+
/// Implementation of path_expand().
///
/// Chars before `path + wildoff` do not get expanded.
@@ -597,11 +612,12 @@ static size_t do_path_expand(garray_T *gap, const char_u *path,
*s = NUL;
Directory dir;
- // open the directory for scanning
- if (os_scandir(&dir, *buf == NUL ? "." : (char *)buf)) {
+ if (os_scandir(&dir, *buf == NUL ? "." : (char *)buf)
+ || os_isdir(*buf == NUL ? (char_u *)"." : (char_u *)buf)) {
// Find all matching entries.
char_u *name;
- while((name = (char_u *) os_scandir_next(&dir))) {
+ scandir_next_with_dots(NULL /* initialize */);
+ while((name = (char_u *) scandir_next_with_dots(&dir)) && name != NULL) {
if ((name[0] != '.' || starts_with_dot)
&& ((regmatch.regprog != NULL && vim_regexec(&regmatch, name, 0))
|| ((flags & EW_NOTWILD)
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 4724a07895..e2c4b590d0 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -961,10 +961,10 @@ static void reg_equi_class(int c)
REGMBC(0x107) REGMBC(0x109) REGMBC(0x10b)
REGMBC(0x10d)
return;
- case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1d0b)
- CASEMBC(0x1e11)
+ case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b)
+ CASEMBC(0x1e0f) CASEMBC(0x1e11)
regmbc('d'); REGMBC(0x10f) REGMBC(0x111)
- REGMBC(0x1e0b) REGMBC(0x01e0f) REGMBC(0x1e11)
+ REGMBC(0x1e0b) REGMBC(0x1e0f) REGMBC(0x1e11)
return;
case 'e': case '\350': case '\351': case '\352': case '\353':
CASEMBC(0x113) CASEMBC(0x115) CASEMBC(0x117) CASEMBC(0x119)
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 42cebb0198..484dfe1e1f 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -270,8 +270,10 @@ typedef struct {
/* When REG_MULTI is TRUE list.multi is used, otherwise list.line. */
union {
struct multipos {
- lpos_T start;
- lpos_T end;
+ linenr_T start_lnum;
+ linenr_T end_lnum;
+ colnr_T start_col;
+ colnr_T end_col;
} multi[NSUBEXP];
struct linepos {
char_u *start;
@@ -716,8 +718,8 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
*/
static void nfa_emit_equi_class(int c)
{
-#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT);
-# define EMITMBC(c) EMIT(c); EMIT(NFA_CONCAT);
+#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT);
+#define EMITMBC(c) EMIT(c); EMIT(NFA_CONCAT);
if (enc_utf8 || STRCMP(p_enc, "latin1") == 0
|| STRCMP(p_enc, "iso-8859-15") == 0) {
@@ -906,10 +908,10 @@ static void nfa_emit_equi_class(int c)
EMITMBC(0x10b) EMITMBC(0x10d)
return;
- case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1d0b)
- CASEMBC(0x1e11)
+ case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b)
+ CASEMBC(0x1e0f) CASEMBC(0x1e11)
EMIT2('d'); EMITMBC(0x10f) EMITMBC(0x111) EMITMBC(0x1e0b)
- EMITMBC(0x01e0f) EMITMBC(0x1e11)
+ EMITMBC(0x1e0f) EMITMBC(0x1e11)
return;
case 'e': case 0350: case 0351: case 0352: case 0353:
@@ -1391,7 +1393,7 @@ static int nfa_regatom(void)
* matched an unlimited number of times. NFA_NOPEN is
* added only once at a position, while NFA_SPLIT is
* added multiple times. This is more efficient than
- * not allowsing NFA_SPLIT multiple times, it is used
+ * not allowing NFA_SPLIT multiple times, it is used
* a lot. */
EMIT(NFA_NOPEN);
break;
@@ -3431,10 +3433,10 @@ static void log_subexpr(regsub_T *sub)
if (REG_MULTI)
fprintf(log_fd, "*** group %d, start: c=%d, l=%d, end: c=%d, l=%d\n",
j,
- sub->list.multi[j].start.col,
- (int)sub->list.multi[j].start.lnum,
- sub->list.multi[j].end.col,
- (int)sub->list.multi[j].end.lnum);
+ sub->list.multi[j].start_col,
+ (int)sub->list.multi[j].start_lnum,
+ sub->list.multi[j].end_col,
+ (int)sub->list.multi[j].end_lnum);
else {
char *s = (char *)sub->list.line[j].start;
char *e = (char *)sub->list.line[j].end;
@@ -3535,8 +3537,10 @@ static void copy_ze_off(regsub_T *to, regsub_T *from)
{
if (nfa_has_zend) {
if (REG_MULTI) {
- if (from->list.multi[0].end.lnum >= 0)
- to->list.multi[0].end = from->list.multi[0].end;
+ if (from->list.multi[0].end_lnum >= 0){
+ to->list.multi[0].end_lnum = from->list.multi[0].end_lnum;
+ to->list.multi[0].end_col = from->list.multi[0].end_col;
+ }
} else {
if (from->list.line[0].end != NULL)
to->list.line[0].end = from->list.line[0].end;
@@ -3559,27 +3563,27 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
if (REG_MULTI) {
for (i = 0; i < todo; ++i) {
if (i < sub1->in_use)
- s1 = sub1->list.multi[i].start.lnum;
+ s1 = sub1->list.multi[i].start_lnum;
else
s1 = -1;
if (i < sub2->in_use)
- s2 = sub2->list.multi[i].start.lnum;
+ s2 = sub2->list.multi[i].start_lnum;
else
s2 = -1;
if (s1 != s2)
return FALSE;
- if (s1 != -1 && sub1->list.multi[i].start.col
- != sub2->list.multi[i].start.col)
+ if (s1 != -1 && sub1->list.multi[i].start_col
+ != sub2->list.multi[i].start_col)
return FALSE;
if (nfa_has_backref) {
if (i < sub1->in_use) {
- s1 = sub1->list.multi[i].end.lnum;
+ s1 = sub1->list.multi[i].end_lnum;
} else {
s1 = -1;
}
if (i < sub2->in_use) {
- s2 = sub2->list.multi[i].end.lnum;
+ s2 = sub2->list.multi[i].end_lnum;
} else {
s2 = -1;
}
@@ -3587,7 +3591,7 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
return FALSE;
}
if (s1 != -1
- && sub1->list.multi[i].end.col != sub2->list.multi[i].end.col) {
+ && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) {
return FALSE;
}
}
@@ -3637,7 +3641,7 @@ static void report_state(char *action,
if (sub->in_use <= 0)
col = -1;
else if (REG_MULTI)
- col = sub->list.multi[0].start.col;
+ col = sub->list.multi[0].start_col;
else
col = (int)(sub->list.line[0].start - regline);
nfa_set_code(state->c);
@@ -4023,22 +4027,24 @@ skip_add:
* and restore it when it was in use. Otherwise fill any gap. */
if (REG_MULTI) {
if (subidx < sub->in_use) {
- save_lpos = sub->list.multi[subidx].start;
+ save_lpos.lnum = sub->list.multi[subidx].start_lnum;
+ save_lpos.col = sub->list.multi[subidx].start_col;
save_in_use = -1;
} else {
save_in_use = sub->in_use;
for (i = sub->in_use; i < subidx; ++i) {
- sub->list.multi[i].start.lnum = -1;
- sub->list.multi[i].end.lnum = -1;
+ sub->list.multi[i].start_lnum = -1;
+ sub->list.multi[i].end_lnum = -1;
}
sub->in_use = subidx + 1;
}
if (off == -1) {
- sub->list.multi[subidx].start.lnum = reglnum + 1;
- sub->list.multi[subidx].start.col = 0;
+ sub->list.multi[subidx].start_lnum = reglnum + 1;
+ sub->list.multi[subidx].start_col = 0;
} else {
- sub->list.multi[subidx].start.lnum = reglnum;
- sub->list.multi[subidx].start.col =
+
+ sub->list.multi[subidx].start_lnum = reglnum;
+ sub->list.multi[subidx].start_col =
(colnr_T)(reginput - regline + off);
}
} else {
@@ -4064,8 +4070,10 @@ skip_add:
sub = &subs->norm;
if (save_in_use == -1) {
- if (REG_MULTI)
- sub->list.multi[subidx].start = save_lpos;
+ if (REG_MULTI){
+ sub->list.multi[subidx].start_lnum = save_lpos.lnum;
+ sub->list.multi[subidx].start_col = save_lpos.col;
+ }
else
sub->list.line[subidx].start = save_ptr;
} else
@@ -4074,7 +4082,7 @@ skip_add:
case NFA_MCLOSE:
if (nfa_has_zend && (REG_MULTI
- ? subs->norm.list.multi[0].end.lnum >= 0
+ ? subs->norm.list.multi[0].end_lnum >= 0
: subs->norm.list.line[0].end != NULL)) {
/* Do not overwrite the position set by \ze. */
subs = addstate(l, state->out, subs, pim, off);
@@ -4117,13 +4125,14 @@ skip_add:
if (sub->in_use <= subidx)
sub->in_use = subidx + 1;
if (REG_MULTI) {
- save_lpos = sub->list.multi[subidx].end;
+ save_lpos.lnum = sub->list.multi[subidx].end_lnum;
+ save_lpos.col = sub->list.multi[subidx].end_col;
if (off == -1) {
- sub->list.multi[subidx].end.lnum = reglnum + 1;
- sub->list.multi[subidx].end.col = 0;
+ sub->list.multi[subidx].end_lnum = reglnum + 1;
+ sub->list.multi[subidx].end_col = 0;
} else {
- sub->list.multi[subidx].end.lnum = reglnum;
- sub->list.multi[subidx].end.col =
+ sub->list.multi[subidx].end_lnum = reglnum;
+ sub->list.multi[subidx].end_col =
(colnr_T)(reginput - regline + off);
}
/* avoid compiler warnings */
@@ -4143,8 +4152,10 @@ skip_add:
else
sub = &subs->norm;
- if (REG_MULTI)
- sub->list.multi[subidx].end = save_lpos;
+ if (REG_MULTI){
+ sub->list.multi[subidx].end_lnum = save_lpos.lnum;
+ sub->list.multi[subidx].end_col = save_lpos.col;
+ }
else
sub->list.line[subidx].end = save_ptr;
sub->in_use = save_in_use;
@@ -4319,24 +4330,24 @@ retempty:
}
if (REG_MULTI) {
- if (sub->list.multi[subidx].start.lnum < 0
- || sub->list.multi[subidx].end.lnum < 0)
+ if (sub->list.multi[subidx].start_lnum < 0
+ || sub->list.multi[subidx].end_lnum < 0)
goto retempty;
- if (sub->list.multi[subidx].start.lnum == reglnum
- && sub->list.multi[subidx].end.lnum == reglnum) {
- len = sub->list.multi[subidx].end.col
- - sub->list.multi[subidx].start.col;
- if (cstrncmp(regline + sub->list.multi[subidx].start.col,
+ if (sub->list.multi[subidx].start_lnum == reglnum
+ && sub->list.multi[subidx].end_lnum == reglnum) {
+ len = sub->list.multi[subidx].end_col
+ - sub->list.multi[subidx].start_col;
+ if (cstrncmp(regline + sub->list.multi[subidx].start_col,
reginput, &len) == 0) {
*bytelen = len;
return TRUE;
}
} else {
if (match_with_backref(
- sub->list.multi[subidx].start.lnum,
- sub->list.multi[subidx].start.col,
- sub->list.multi[subidx].end.lnum,
- sub->list.multi[subidx].end.col,
+ sub->list.multi[subidx].start_lnum,
+ sub->list.multi[subidx].start_col,
+ sub->list.multi[subidx].end_lnum,
+ sub->list.multi[subidx].end_col,
bytelen) == RA_MATCH)
return TRUE;
}
@@ -4873,8 +4884,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
* it's the first MOPEN. */
if (toplevel) {
if (REG_MULTI) {
- m->norm.list.multi[0].start.lnum = reglnum;
- m->norm.list.multi[0].start.col = (colnr_T)(reginput - regline);
+ m->norm.list.multi[0].start_lnum = reglnum;
+ m->norm.list.multi[0].start_col = (colnr_T)(reginput - regline);
} else
m->norm.list.line[0].start = reginput;
m->norm.in_use = 1;
@@ -4962,7 +4973,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
if (t->subs.norm.in_use <= 0)
col = -1;
else if (REG_MULTI)
- col = t->subs.norm.list.multi[0].start.col;
+ col = t->subs.norm.list.multi[0].start_col;
else
col = (int)(t->subs.norm.list.line[0].start - regline);
nfa_set_code(t->state->c);
@@ -5214,7 +5225,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
* continue with what follows. */
if (REG_MULTI)
/* TODO: multi-line match */
- bytelen = m->norm.list.multi[0].end.col
+ bytelen = m->norm.list.multi[0].end_col
- (int)(reginput - regline);
else
bytelen = (int)(m->norm.list.line[0].end - reginput);
@@ -6018,7 +6029,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
if (add) {
if (REG_MULTI)
- m->norm.list.multi[0].start.col =
+ m->norm.list.multi[0].start_col =
(colnr_T)(reginput - regline) + clen;
else
m->norm.list.line[0].start = reginput + clen;
@@ -6123,8 +6134,11 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col)
cleanup_subexpr();
if (REG_MULTI) {
for (i = 0; i < subs.norm.in_use; i++) {
- reg_startpos[i] = subs.norm.list.multi[i].start;
- reg_endpos[i] = subs.norm.list.multi[i].end;
+ reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum;
+ reg_startpos[i].col = subs.norm.list.multi[i].start_col;
+
+ reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum;
+ reg_endpos[i].col = subs.norm.list.multi[i].end_col;
}
if (reg_startpos[0].lnum < 0) {
@@ -6162,12 +6176,12 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col)
struct multipos *mpos = &subs.synt.list.multi[i];
// Only accept single line matches that are valid.
- if (mpos->start.lnum >= 0
- && mpos->start.lnum == mpos->end.lnum
- && mpos->end.col >= mpos->start.col) {
+ if (mpos->start_lnum >= 0
+ && mpos->start_lnum == mpos->end_lnum
+ && mpos->end_col >= mpos->start_col) {
re_extmatch_out->matches[i] =
- vim_strnsave(reg_getline(mpos->start.lnum) + mpos->start.col,
- mpos->end.col - mpos->start.col);
+ vim_strnsave(reg_getline(mpos->start_lnum) + mpos->start_col,
+ mpos->end_col - mpos->start_col);
}
} else {
struct linepos *lpos = &subs.synt.list.line[i];
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 47fef692db..b9bc4c6d78 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -69,6 +69,7 @@
#include "nvim/fileio.h"
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
+#include "nvim/os/input.h"
#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -324,7 +325,7 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height)
invalidate_terminal(term, -1, -1);
}
-void terminal_enter(bool process_deferred)
+void terminal_enter(void)
{
Terminal *term = curbuf->terminal;
assert(term && "should only be called when curbuf has a terminal");
@@ -353,15 +354,9 @@ void terminal_enter(bool process_deferred)
bool got_bs = false; // True if the last input was <C-\>
while (term->buf == curbuf) {
- if (process_deferred) {
- loop_enable_deferred_events(&loop);
- }
-
+ input_enable_events();
c = safe_vgetc();
-
- if (process_deferred) {
- loop_disable_deferred_events(&loop);
- }
+ input_disable_events();
switch (c) {
case K_LEFTMOUSE:
@@ -381,7 +376,7 @@ void terminal_enter(bool process_deferred)
break;
case K_EVENT:
- loop_process_event(&loop);
+ queue_process_events(loop.events);
break;
case Ctrl_N:
@@ -427,7 +422,13 @@ void terminal_destroy(Terminal *term)
term->buf->terminal = NULL;
}
term->buf = NULL;
- pmap_del(ptr_t)(invalidated_terminals, term);
+ if (pmap_has(ptr_t)(invalidated_terminals, term)) {
+ // flush any pending changes to the buffer
+ block_autocmds();
+ refresh_terminal(term);
+ unblock_autocmds();
+ pmap_del(ptr_t)(invalidated_terminals, term);
+ }
for (size_t i = 0 ; i < term->sb_current; i++) {
xfree(term->sb_buffer[i]);
}
@@ -883,48 +884,47 @@ static void invalidate_terminal(Terminal *term, int start_row, int end_row)
}
}
+static void refresh_terminal(Terminal *term)
+{
+ // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid.
+ bool valid = true;
+ if (!term->buf || !(valid = buf_valid(term->buf))) {
+ // destroyed by `close_buffer`. Dont do anything else
+ if (!valid) {
+ term->buf = NULL;
+ }
+ return;
+ }
+ bool pending_resize = term->pending_resize;
+ WITH_BUFFER(term->buf, {
+ refresh_size(term);
+ refresh_scrollback(term);
+ refresh_screen(term);
+ redraw_buf_later(term->buf, NOT_VALID);
+ });
+ adjust_topline(term, pending_resize);
+}
// libuv timer callback. This will enqueue on_refresh to be processed as an
// event.
static void refresh_timer_cb(TimeWatcher *watcher, void *data)
{
- loop_push_event(&loop, (Event) {.handler = on_refresh}, false);
- refresh_pending = false;
-}
-
-// Refresh all invalidated terminals
-static void on_refresh(Event event)
-{
if (exiting) {
// bad things can happen if we redraw when exiting, and there's no need to
// update the buffer.
- return;
+ goto end;
}
Terminal *term;
void *stub; (void)(stub);
// don't process autocommands while updating terminal buffers
block_autocmds();
map_foreach(invalidated_terminals, term, stub, {
- // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid.
- bool valid = true;
- if (!term->buf || !(valid = buf_valid(term->buf))) {
- // destroyed by `close_buffer`. Dont do anything else
- if (!valid) {
- term->buf = NULL;
- }
- continue;
- }
- bool pending_resize = term->pending_resize;
- WITH_BUFFER(term->buf, {
- refresh_size(term);
- refresh_scrollback(term);
- refresh_screen(term);
- redraw_buf_later(term->buf, NOT_VALID);
- });
- adjust_topline(term, pending_resize);
+ refresh_terminal(term);
});
pmap_clear(ptr_t)(invalidated_terminals);
unblock_autocmds();
redraw(true);
+end:
+ refresh_pending = false;
}
static void refresh_size(Terminal *term)
diff --git a/src/nvim/tui/term_input.inl b/src/nvim/tui/term_input.inl
index 0a84a3688b..c396557160 100644
--- a/src/nvim/tui/term_input.inl
+++ b/src/nvim/tui/term_input.inl
@@ -206,9 +206,10 @@ static bool handle_forced_escape(TermInput *input)
return false;
}
-static void restart_reading(Event event);
+static void restart_reading(void **argv);
-static void read_cb(Stream *stream, RBuffer *buf, void *data, bool eof)
+static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
+ bool eof)
{
TermInput *input = data;
@@ -226,8 +227,7 @@ static void read_cb(Stream *stream, RBuffer *buf, void *data, bool eof)
// ls *.md | xargs nvim
input->in_fd = 2;
stream_close(&input->read_stream, NULL);
- loop_push_event(&loop,
- (Event) { .data = input, .handler = restart_reading }, false);
+ queue_put(loop.fast_events, restart_reading, 1, input);
} else {
input_done();
}
@@ -272,9 +272,9 @@ static void read_cb(Stream *stream, RBuffer *buf, void *data, bool eof)
rbuffer_reset(input->read_stream.buffer);
}
-static void restart_reading(Event event)
+static void restart_reading(void **argv)
{
- TermInput *input = event.data;
+ TermInput *input = argv[0];
rstream_init_fd(&loop, &input->read_stream, input->in_fd, 0xfff, input);
rstream_start(&input->read_stream, read_cb);
}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index a1f56d2695..57a2b896f7 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -57,14 +57,17 @@ typedef struct {
bool busy;
HlAttrs attrs, print_attrs;
Cell **screen;
+ int showing_mode;
struct {
int enable_mouse, disable_mouse;
int enable_bracketed_paste, disable_bracketed_paste;
- int enter_insert_mode, exit_insert_mode;
+ int enter_insert_mode, enter_replace_mode, exit_insert_mode;
int set_rgb_foreground, set_rgb_background;
} unibi_ext;
} TUIData;
+static bool volatile got_winch = false;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/tui.c.generated.h"
#endif
@@ -98,11 +101,13 @@ UI *tui_start(void)
data->can_use_terminal_scroll = true;
data->bufpos = 0;
data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE;
+ data->showing_mode = 0;
data->unibi_ext.enable_mouse = -1;
data->unibi_ext.disable_mouse = -1;
data->unibi_ext.enable_bracketed_paste = -1;
data->unibi_ext.disable_bracketed_paste = -1;
data->unibi_ext.enter_insert_mode = -1;
+ data->unibi_ext.enter_replace_mode = -1;
data->unibi_ext.exit_insert_mode = -1;
// write output to stderr if stdout is not a tty
@@ -148,8 +153,7 @@ UI *tui_start(void)
ui->busy_stop = tui_busy_stop;
ui->mouse_on = tui_mouse_on;
ui->mouse_off = tui_mouse_off;
- ui->insert_mode = tui_insert_mode;
- ui->normal_mode = tui_normal_mode;
+ ui->mode_change = tui_mode_change;
ui->set_scroll_region = tui_set_scroll_region;
ui->scroll = tui_scroll;
ui->highlight_set = tui_highlight_set;
@@ -178,7 +182,7 @@ static void tui_stop(UI *ui)
// Destroy input stuff
term_input_destroy(data->input);
// Destroy output stuff
- tui_normal_mode(ui);
+ tui_mode_change(ui, NORMAL);
tui_mouse_off(ui);
unibi_out(ui, unibi_exit_attribute_mode);
// cursor should be set to normal before exiting alternate screen
@@ -201,23 +205,14 @@ static void tui_stop(UI *ui)
xfree(ui);
}
-static void try_resize(Event ev)
+static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
{
- UI *ui = ev.data;
+ got_winch = true;
+ UI *ui = data;
update_size(ui);
ui_refresh();
}
-static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
-{
- // Queue the event because resizing can result in recursive event_poll calls
- // FIXME(blueyed): TUI does not resize properly when not deferred. Why? #2322
- loop_push_event(&loop, (Event) {
- .data = data,
- .handler = try_resize
- }, true);
-}
-
static bool attrs_differ(HlAttrs a1, HlAttrs a2)
{
return a1.foreground != a2.foreground || a1.background != a2.background
@@ -352,10 +347,13 @@ static void tui_resize(UI *ui, int width, int height)
data->scroll_region.right = width - 1;
data->row = data->col = 0;
- // try to resize the terminal window
- char r[16]; // enough for 9999x9999
- snprintf(r, sizeof(r), "\x1b[8;%d;%dt", height, width);
- out(ui, r, strlen(r));
+ if (!got_winch) { // Try to resize the terminal window.
+ char r[16]; // enough for 9999x9999
+ snprintf(r, sizeof(r), "\x1b[8;%d;%dt", height, width);
+ out(ui, r, strlen(r));
+ } else { // Already handled the SIGWINCH signal; avoid double-resize.
+ got_winch = false;
+ }
}
static void tui_clear(UI *ui)
@@ -404,16 +402,25 @@ static void tui_mouse_off(UI *ui)
data->mouse_enabled = false;
}
-static void tui_insert_mode(UI *ui)
+static void tui_mode_change(UI *ui, int mode)
{
TUIData *data = ui->data;
- unibi_out(ui, data->unibi_ext.enter_insert_mode);
-}
-static void tui_normal_mode(UI *ui)
-{
- TUIData *data = ui->data;
- unibi_out(ui, data->unibi_ext.exit_insert_mode);
+ if (mode == INSERT) {
+ if (data->showing_mode != INSERT) {
+ unibi_out(ui, data->unibi_ext.enter_insert_mode);
+ }
+ } else if (mode == REPLACE) {
+ if (data->showing_mode != REPLACE) {
+ unibi_out(ui, data->unibi_ext.enter_replace_mode);
+ }
+ } else {
+ assert(mode == NORMAL);
+ if (data->showing_mode != NORMAL) {
+ unibi_out(ui, data->unibi_ext.exit_insert_mode);
+ }
+ }
+ data->showing_mode = mode;
}
static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
@@ -798,18 +805,25 @@ static void fix_terminfo(TUIData *data)
#define TMUX_WRAP(seq) (inside_tmux ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
// Support changing cursor shape on some popular terminals.
const char *term_prog = os_getenv("TERM_PROGRAM");
+ const char *vte_version = os_getenv("VTE_VERSION");
- if ((term_prog && !strcmp(term_prog, "iTerm.app"))
- || os_getenv("ITERM_SESSION_ID") != NULL) {
- // iterm
+ if ((term_prog && !strcmp(term_prog, "Konsole"))
+ || os_getenv("KONSOLE_DBUS_SESSION") != NULL) {
+ // Konsole uses a proprietary escape code to set the cursor shape
+ // and does not suppport DECSCUSR.
data->unibi_ext.enter_insert_mode = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=1;BlinkingCursorEnabled=1\x07"));
+ data->unibi_ext.enter_replace_mode = (int)unibi_add_ext_str(ut, NULL,
+ TMUX_WRAP("\x1b]50;CursorShape=2;BlinkingCursorEnabled=1\x07"));
data->unibi_ext.exit_insert_mode = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=0;BlinkingCursorEnabled=0\x07"));
- } else {
- // xterm-like sequences for blinking bar and solid block
+ } else if (!vte_version || atoi(vte_version) >= 3900) {
+ // Assume that the terminal supports DECSCUSR unless it is an
+ // old VTE based terminal
data->unibi_ext.enter_insert_mode = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b[5 q"));
+ data->unibi_ext.enter_replace_mode = (int)unibi_add_ext_str(ut, NULL,
+ TMUX_WRAP("\x1b[3 q"));
data->unibi_ext.exit_insert_mode = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b[2 q"));
}
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index dc2bc0898c..ad875367c9 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -121,7 +121,7 @@ void ui_update_encoding(void)
// May update the shape of the cursor.
void ui_cursor_shape(void)
{
- ui_change_mode();
+ ui_mode_change();
}
void ui_refresh(void)
@@ -214,9 +214,9 @@ void ui_detach(UI *ui)
shift_index++;
}
- ui_count--;
- // schedule a refresh
- loop_push_event(&loop, (Event) { .handler = refresh }, false);
+ if (--ui_count) {
+ ui_refresh();
+ }
}
void ui_clear(void)
@@ -469,32 +469,19 @@ static void flush_cursor_update(void)
// Notify that the current mode has changed. Can be used to change cursor
// shape, for example.
-static void ui_change_mode(void)
+static void ui_mode_change(void)
{
- static int showing_insert_mode = MAYBE;
-
+ int mode;
if (!full_screen) {
return;
}
-
- if (State & INSERT) {
- if (showing_insert_mode != TRUE) {
- UI_CALL(insert_mode);
- }
- showing_insert_mode = TRUE;
- } else {
- if (showing_insert_mode != FALSE) {
- UI_CALL(normal_mode);
- }
- showing_insert_mode = FALSE;
- }
+ /* Get a simple UI mode out of State. */
+ if ((State & REPLACE) == REPLACE)
+ mode = REPLACE;
+ else if (State & INSERT)
+ mode = INSERT;
+ else
+ mode = NORMAL;
+ UI_CALL(mode_change, mode);
conceal_check_cursur_line();
}
-
-static void refresh(Event event)
-{
- if (ui_count) {
- ui_refresh();
- }
-}
-
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 76ceec7775..9cfd99c096 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -24,8 +24,7 @@ struct ui_t {
void (*busy_stop)(UI *ui);
void (*mouse_on)(UI *ui);
void (*mouse_off)(UI *ui);
- void (*insert_mode)(UI *ui);
- void (*normal_mode)(UI *ui);
+ void (*mode_change)(UI *ui, int mode);
void (*set_scroll_region)(UI *ui, int top, int bot, int left, int right);
void (*scroll)(UI *ui, int count);
void (*highlight_set)(UI *ui, HlAttrs attrs);
diff --git a/src/nvim/version.c b/src/nvim/version.c
index a496af13b4..7ab7467661 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -80,15 +80,15 @@ static int included_patches[] = {
784,
//783,
//782,
- //781,
+ 781,
//780,
//779,
//778,
//777,
- //776,
+ 776,
775,
774,
- //773,
+ 773,
//772,
//771,
//770,
@@ -261,7 +261,7 @@ static int included_patches[] = {
//603,
//602,
601,
- //600,
+ 600,
599,
//598,
597,
@@ -1098,6 +1098,15 @@ void intro_message(int colon)
N_("by Bram Moolenaar et al."),
N_("Vim is open source and freely distributable"),
"",
+ N_("First time using a vi-like editor?"),
+ N_("Type :Tutor<Enter> to get started!"),
+ "",
+ N_("Already know your way around Vim?"),
+ N_("See :help nvim-intro for an introduction to Neovim."),
+ "",
+ N_("Still have questions?"),
+ N_("Reach out to the Neovim community at neovim.io/community."),
+ "",
N_("Help poor children in Uganda!"),
N_("type :help iccf<Enter> for information "),
"",
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 17f9cbc310..9fbc6dd300 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -6,7 +6,7 @@
*/
#ifndef NVIM_VIM_H
-# define NVIM_VIM_H
+#define NVIM_VIM_H
#define MIN(X, Y) (X < Y ? X : Y)
#define MAX(X, Y) (X > Y ? X : Y)