aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt12
-rw-r--r--src/nvim/api/buffer.c2
-rw-r--r--src/nvim/eval.c299
-rw-r--r--src/nvim/eval_defs.h14
-rw-r--r--src/nvim/event/defs.h3
-rw-r--r--src/nvim/event/libuv_process.c3
-rw-r--r--src/nvim/event/loop.c42
-rw-r--r--src/nvim/event/loop.h5
-rw-r--r--src/nvim/event/queue.c38
-rw-r--r--src/nvim/event/stream.c1
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/log.c24
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/move.c4
-rw-r--r--src/nvim/msgpack_rpc/channel.c3
-rw-r--r--src/nvim/option.c17
-rw-r--r--src/nvim/testdir/test10.in1
-rw-r--r--src/nvim/testdir/test39.in14
-rw-r--r--src/nvim/testdir/test39.okbin584 -> 585 bytes
-rw-r--r--src/nvim/testdir/test69.in1
-rw-r--r--src/nvim/testdir/test83.in1
-rw-r--r--src/nvim/testdir/test_eval.in1
-rw-r--r--src/nvim/testdir/unix.vim3
-rw-r--r--src/nvim/tui/input.c (renamed from src/nvim/tui/term_input.inl)141
-rw-r--r--src/nvim/tui/input.h23
-rw-r--r--src/nvim/tui/tui.c438
-rw-r--r--src/nvim/ugrid.c137
-rw-r--r--src/nvim/ugrid.h40
-rw-r--r--src/nvim/ui.c6
-rw-r--r--src/nvim/ui.h1
-rw-r--r--src/nvim/ui_bridge.c335
-rw-r--r--src/nvim/ui_bridge.h40
-rw-r--r--src/nvim/version.c2
33 files changed, 1201 insertions, 458 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 5e547ed290..ad2642ddd4 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -2,12 +2,15 @@ include(CheckLibraryExists)
option(USE_GCOV "Enable gcov support" OFF)
+if(NOT CLANG_TSAN)
+# GCOV and TSAN results in false data race reports
if(USE_GCOV)
message(STATUS "Enabling gcov support")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
endif()
+endif()
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua)
@@ -138,9 +141,16 @@ foreach(sfile ${NEOVIM_SOURCES}
set(gf1 "${GENERATED_DIR}/${r}.c.generated.h")
set(gf2 "${GENERATED_INCLUDES_DIR}/${r}.h.generated.h")
set(gf3 "${GENERATED_DIR}/${r}.i")
+
+ if(MSVC)
+ set(PREPROC_OUTPUT /P /Fi${gf3})
+ else()
+ set(PREPROC_OUTPUT -E -o ${gf3})
+ endif()
+
add_custom_command(
OUTPUT "${gf1}" "${gf2}"
- COMMAND ${CMAKE_C_COMPILER} ${sfile} -o ${gf3} ${gen_cflags} -E ${C_FLAGS_ARRAY}
+ COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY}
COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf1}" "${gf2}" "${gf3}"
DEPENDS "${HEADER_GENERATOR}" "${sfile}"
)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 12c97cfffb..ce3e64def8 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -467,7 +467,7 @@ void buffer_insert(Buffer buffer,
ArrayOf(String) lines,
Error *err)
{
- buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err);
+ buffer_set_line_slice(buffer, lnum, lnum, true, false, lines, err);
}
/// Return a tuple (row,col) representing the position of the named mark
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 02cc9b8f5b..bbe45480d2 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5570,96 +5570,97 @@ static int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int
* http://python.ca/nas/python/gc/
*/
-/*
- * Do garbage collection for lists and dicts.
- * Return TRUE if some memory was freed.
- */
-int garbage_collect(void)
+/// Do garbage collection for lists and dicts.
+///
+/// @returns true if some memory was freed.
+bool garbage_collect(void)
{
- int copyID;
- funccall_T *fc, **pfc;
- int did_free;
- int did_free_funccal = FALSE;
+ bool abort = false;
- /* Only do this once. */
- want_garbage_collect = FALSE;
- may_garbage_collect = FALSE;
- garbage_collect_at_exit = FALSE;
+ // Only do this once.
+ want_garbage_collect = false;
+ may_garbage_collect = false;
+ garbage_collect_at_exit = false;
- /* We advance by two because we add one for items referenced through
- * previous_funccal. */
+ // We advance by two because we add one for items referenced through
+ // previous_funccal.
current_copyID += COPYID_INC;
- copyID = current_copyID;
+ int copyID = current_copyID;
- /*
- * 1. Go through all accessible variables and mark all lists and dicts
- * with copyID.
- */
+ // 1. Go through all accessible variables and mark all lists and dicts
+ // with copyID.
- /* Don't free variables in the previous_funccal list unless they are only
- * referenced through previous_funccal. This must be first, because if
- * the item is referenced elsewhere the funccal must not be freed. */
- for (fc = previous_funccal; fc != NULL; fc = fc->caller) {
- set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1);
- set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1);
+ // Don't free variables in the previous_funccal list unless they are only
+ // referenced through previous_funccal. This must be first, because if
+ // the item is referenced elsewhere the funccal must not be freed.
+ for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
}
- /* script-local variables */
- for (int i = 1; i <= ga_scripts.ga_len; ++i)
- set_ref_in_ht(&SCRIPT_VARS(i), copyID);
+ // script-local variables
+ for (int i = 1; i <= ga_scripts.ga_len; ++i) {
+ abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
+ }
- /* buffer-local variables */
+ // buffer-local variables
FOR_ALL_BUFFERS(buf) {
- set_ref_in_item(&buf->b_bufvar.di_tv, copyID);
+ abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
}
- /* window-local variables */
+ // window-local variables
FOR_ALL_TAB_WINDOWS(tp, wp) {
- set_ref_in_item(&wp->w_winvar.di_tv, copyID);
+ abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, NULL, NULL);
+ }
+ if (aucmd_win != NULL) {
+ abort = abort ||
+ set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL);
}
- if (aucmd_win != NULL)
- set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID);
- /* tabpage-local variables */
+ // tabpage-local variables
FOR_ALL_TABS(tp) {
- set_ref_in_item(&tp->tp_winvar.di_tv, copyID);
+ abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, NULL, NULL);
}
- /* global variables */
- set_ref_in_ht(&globvarht, copyID);
+ // global variables
+ abort = abort || set_ref_in_ht(&globvarht, copyID, NULL);
- /* function-local variables */
- for (fc = current_funccal; fc != NULL; fc = fc->caller) {
- set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID);
- set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID);
+ // function-local variables
+ for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
}
- /* v: vars */
- set_ref_in_ht(&vimvarht, copyID);
+ // v: vars
+ abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
- /*
- * 2. Free lists and dictionaries that are not referenced.
- */
- did_free = free_unref_items(copyID);
-
- /*
- * 3. Check if any funccal can be freed now.
- */
- for (pfc = &previous_funccal; *pfc != NULL; ) {
- if (can_free_funccal(*pfc, copyID)) {
- fc = *pfc;
- *pfc = fc->caller;
- free_funccal(fc, TRUE);
- did_free = TRUE;
- did_free_funccal = TRUE;
- } else
- pfc = &(*pfc)->caller;
+ bool did_free = false;
+ if (!abort) {
+ // 2. Free lists and dictionaries that are not referenced.
+ did_free = free_unref_items(copyID);
+
+ // 3. Check if any funccal can be freed now.
+ bool did_free_funccal = false;
+ for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
+ if (can_free_funccal(*pfc, copyID)) {
+ funccall_T *fc = *pfc;
+ *pfc = fc->caller;
+ free_funccal(fc, true);
+ did_free = true;
+ did_free_funccal = true;
+ } else {
+ pfc = &(*pfc)->caller;
+ }
+ }
+ if (did_free_funccal) {
+ // When a funccal was freed some more items might be garbage
+ // collected, so run again.
+ (void)garbage_collect();
+ }
+ } else if (p_verbose > 0) {
+ verb_msg((char_u *)_(
+ "Not enough memory to set references, garbage collection aborted!"));
}
- if (did_free_funccal)
- /* When a funccal was freed some more items might be garbage
- * collected, so run again. */
- (void)garbage_collect();
-
return did_free;
}
@@ -5711,61 +5712,143 @@ static int free_unref_items(int copyID)
return did_free;
}
-/*
- * Mark all lists and dicts referenced through hashtab "ht" with "copyID".
- */
-void set_ref_in_ht(hashtab_T *ht, int copyID)
-{
- int todo;
- hashitem_T *hi;
+/// Mark all lists and dicts referenced through hashtab "ht" with "copyID".
+///
+/// @param ht Hashtab content will be marked.
+/// @param copyID New mark for lists and dicts.
+/// @param list_stack Used to add lists to be marked. Can be NULL.
+///
+/// @returns true if setting references failed somehow.
+bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
+{
+ bool abort = false;
+ ht_stack_T *ht_stack = NULL;
+
+ hashtab_T *cur_ht = ht;
+ for (;;) {
+ if (!abort) {
+ // Mark each item in the hashtab. If the item contains a hashtab
+ // it is added to ht_stack, if it contains a list it is added to
+ // list_stack.
+ int todo = (int)cur_ht->ht_used;
+ for (hashitem_T *hi = cur_ht->ht_array; todo > 0; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ --todo;
+ abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID, &ht_stack,
+ list_stack);
+ }
+ }
+ }
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0; ++hi)
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- set_ref_in_item(&HI2DI(hi)->di_tv, copyID);
+ if (ht_stack == NULL) {
+ break;
}
+
+ // take an item from the stack
+ cur_ht = ht_stack->ht;
+ ht_stack_T *tempitem = ht_stack;
+ ht_stack = ht_stack->prev;
+ xfree(tempitem);
+ }
+
+ return abort;
}
-/*
- * Mark all lists and dicts referenced through list "l" with "copyID".
- */
-void set_ref_in_list(list_T *l, int copyID)
-{
- listitem_T *li;
+/// Mark all lists and dicts referenced through list "l" with "copyID".
+///
+/// @param l List content will be marked.
+/// @param copyID New mark for lists and dicts.
+/// @param ht_stack Used to add hashtabs to be marked. Can be NULL.
+///
+/// @returns true if setting references failed somehow.
+bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
+{
+ bool abort = false;
+ list_stack_T *list_stack = NULL;
+
+ list_T *cur_l = l;
+ for (;;) {
+ if (!abort) {
+ // Mark each item in the list. If the item contains a hashtab
+ // it is added to ht_stack, if it contains a list it is added to
+ // list_stack.
+ for (listitem_T *li = cur_l->lv_first; !abort && li != NULL;
+ li = li->li_next) {
+ abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack);
+ }
+ }
- for (li = l->lv_first; li != NULL; li = li->li_next)
- set_ref_in_item(&li->li_tv, copyID);
+ if (list_stack == NULL) {
+ break;
+ }
+
+ // take an item from the stack
+ cur_l = list_stack->list;
+ list_stack_T *tempitem = list_stack;
+ list_stack = list_stack->prev;
+ xfree(tempitem);
+ }
+
+ return abort;
}
-/*
- * Mark all lists and dicts referenced through typval "tv" with "copyID".
- */
-void set_ref_in_item(typval_T *tv, int copyID)
+/// Mark all lists and dicts referenced through typval "tv" with "copyID".
+///
+/// @param tv Typval content will be marked.
+/// @param copyID New mark for lists and dicts.
+/// @param ht_stack Used to add hashtabs to be marked. Can be NULL.
+/// @param list_stack Used to add lists to be marked. Can be NULL.
+///
+/// @returns true if setting references failed somehow.
+bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
+ list_stack_T **list_stack)
{
- dict_T *dd;
- list_T *ll;
+ bool abort = false;
switch (tv->v_type) {
- case VAR_DICT:
- dd = tv->vval.v_dict;
- if (dd != NULL && dd->dv_copyID != copyID) {
- /* Didn't see this dict yet. */
- dd->dv_copyID = copyID;
- set_ref_in_ht(&dd->dv_hashtab, copyID);
+ case VAR_DICT: {
+ dict_T *dd = tv->vval.v_dict;
+ if (dd != NULL && dd->dv_copyID != copyID) {
+ // Didn't see this dict yet.
+ dd->dv_copyID = copyID;
+ if (ht_stack == NULL) {
+ abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack);
+ } else {
+ ht_stack_T *newitem = try_malloc(sizeof(ht_stack_T));
+ if (newitem == NULL) {
+ abort = true;
+ } else {
+ newitem->ht = &dd->dv_hashtab;
+ newitem->prev = *ht_stack;
+ *ht_stack = newitem;
+ }
+ }
+ }
+ break;
}
- break;
- case VAR_LIST:
- ll = tv->vval.v_list;
- if (ll != NULL && ll->lv_copyID != copyID) {
- /* Didn't see this list yet. */
- ll->lv_copyID = copyID;
- set_ref_in_list(ll, copyID);
+ case VAR_LIST: {
+ list_T *ll = tv->vval.v_list;
+ if (ll != NULL && ll->lv_copyID != copyID) {
+ // Didn't see this list yet.
+ ll->lv_copyID = copyID;
+ if (list_stack == NULL) {
+ abort = set_ref_in_list(ll, copyID, ht_stack);
+ } else {
+ list_stack_T *newitem = try_malloc(sizeof(list_stack_T));
+ if (newitem == NULL) {
+ abort = true;
+ } else {
+ newitem->list = ll;
+ newitem->prev = *list_stack;
+ *list_stack = newitem;
+ }
+ }
+ }
+ break;
}
- break;
}
- return;
+ return abort;
}
/*
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 0faf860588..a8a8acd048 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -120,4 +120,16 @@ struct dictvar_S {
// prevent garbage collection
};
-#endif // NVIM_EVAL_DEFS_H
+// structure used for explicit stack while garbage collecting hash tables
+typedef struct ht_stack_S {
+ hashtab_T *ht;
+ struct ht_stack_S *prev;
+} ht_stack_T;
+
+// structure used for explicit stack while garbage collecting lists
+typedef struct list_stack_S {
+ list_T *list;
+ struct list_stack_S *prev;
+} list_stack_T;
+
+#endif // NVIM_EVAL_DEFS_H
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
index 5126d52241..b802866a3d 100644
--- a/src/nvim/event/defs.h
+++ b/src/nvim/event/defs.h
@@ -4,7 +4,7 @@
#include <assert.h>
#include <stdarg.h>
-#define EVENT_HANDLER_MAX_ARGC 4
+#define EVENT_HANDLER_MAX_ARGC 6
typedef void (*argv_callback)(void **argv);
typedef struct message {
@@ -12,6 +12,7 @@ typedef struct message {
argv_callback handler;
void *argv[EVENT_HANDLER_MAX_ARGC];
} Event;
+typedef void(*event_scheduler)(Event event, void *data);
#define VA_EVENT_INIT(event, p, h, a) \
do { \
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index e0a108a6e4..44305c69bc 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -19,7 +19,8 @@ bool libuv_process_spawn(LibuvProcess *uvproc)
Process *proc = (Process *)uvproc;
uvproc->uvopts.file = proc->argv[0];
uvproc->uvopts.args = proc->argv;
- uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
+ uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE
+ | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = NULL;
uvproc->uvopts.env = NULL;
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 3d3288f858..088e059d19 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -10,20 +10,19 @@
# 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->recursive = 0;
loop->uv.data = loop;
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);
+ loop->thread_events = queue_new_parent(NULL, NULL);
+ uv_mutex_init(&loop->mutex);
+ uv_async_init(&loop->uv, &loop->async, async_cb);
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);
@@ -31,9 +30,7 @@ void loop_init(Loop *loop, void *data)
void loop_poll_events(Loop *loop, int ms)
{
- static int recursive = 0;
-
- if (recursive++) {
+ if (loop->recursive++) {
abort(); // Should not re-enter uv_run
}
@@ -55,10 +52,19 @@ void loop_poll_events(Loop *loop, int ms)
uv_timer_stop(&loop->poll_timer);
}
- recursive--; // Can re-enter uv_run now
+ loop->recursive--; // Can re-enter uv_run now
queue_process_events(loop->fast_events);
}
+// Schedule an event from another thread
+void loop_schedule(Loop *loop, Event event)
+{
+ uv_mutex_lock(&loop->mutex);
+ queue_put_event(loop->thread_events, event);
+ uv_async_send(&loop->async);
+ uv_mutex_unlock(&loop->mutex);
+}
+
void loop_on_put(Queue *queue, void *data)
{
Loop *loop = data;
@@ -72,14 +78,32 @@ void loop_on_put(Queue *queue, void *data)
void loop_close(Loop *loop)
{
+ uv_mutex_destroy(&loop->mutex);
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);
+ uv_close((uv_handle_t *)&loop->async, NULL);
do {
uv_run(&loop->uv, UV_RUN_DEFAULT);
} while (uv_loop_close(&loop->uv));
+ queue_free(loop->events);
+ queue_free(loop->fast_events);
+ queue_free(loop->thread_events);
+ kl_destroy(WatcherPtr, loop->children);
+}
+
+static void async_cb(uv_async_t *handle)
+{
+ Loop *l = handle->loop->data;
+ uv_mutex_lock(&l->mutex);
+ while (!queue_empty(l->thread_events)) {
+ Event ev = queue_get(l->thread_events);
+ queue_put_event(l->fast_events, ev);
+ }
+ uv_mutex_unlock(&l->mutex);
}
static void timer_cb(uv_timer_t *handle)
{
}
+
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index 9212a45aa4..0c1fcb5ed9 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -16,11 +16,14 @@ KLIST_INIT(WatcherPtr, WatcherPtr, _noop)
typedef struct loop {
uv_loop_t uv;
- Queue *events, *fast_events;
+ Queue *events, *fast_events, *thread_events;
klist_t(WatcherPtr) *children;
uv_signal_t children_watcher;
uv_timer_t children_kill_timer, poll_timer;
size_t children_stop_requests;
+ uv_async_t async;
+ uv_mutex_t mutex;
+ int recursive;
} Loop;
#define CREATE_EVENT(queue, handler, argc, ...) \
diff --git a/src/nvim/event/queue.c b/src/nvim/event/queue.c
index 19eca14144..c5ef22d426 100644
--- a/src/nvim/event/queue.c
+++ b/src/nvim/event/queue.c
@@ -105,16 +105,15 @@ static Queue *queue_new(Queue *parent, put_callback put_cb, void *data)
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);
+ while (!QUEUE_EMPTY(&queue->headtail)) {
+ QUEUE *q = QUEUE_HEAD(&queue->headtail);
+ QueueItem *item = queue_node_data(q);
+ if (queue->parent) {
QUEUE_REMOVE(&item->data.item.parent->node);
xfree(item->data.item.parent);
- QUEUE_REMOVE(q);
- xfree(item);
}
+ QUEUE_REMOVE(q);
+ xfree(item);
}
xfree(queue);
@@ -128,9 +127,8 @@ Event queue_get(Queue *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) {
+ if (queue->parent && queue->parent->put_cb) {
queue->parent->put_cb(queue->parent, queue->parent->data);
}
}
@@ -177,11 +175,11 @@ static Event queue_remove(Queue *queue)
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);
+ if (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;
}
@@ -195,11 +193,13 @@ static void queue_push(Queue *queue, Event event)
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);
+ if (queue->parent) {
+ // 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)
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 6caad6fdcc..376eb9fce7 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -56,7 +56,6 @@ 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;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index e5a993aa5a..183e8fcff2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -633,6 +633,10 @@ EXTERN int silent_mode INIT(= FALSE);
/* set to TRUE when "-s" commandline argument
* used for ex */
+// Set to true when sourcing of startup scripts (nvimrc) is done.
+// Used for options that cannot be changed after startup scripts.
+EXTERN bool did_source_startup_scripts INIT(= false);
+
EXTERN pos_T VIsual; /* start position of active Visual selection */
EXTERN int VIsual_active INIT(= FALSE);
/* whether Visual mode is active */
@@ -1078,6 +1082,8 @@ EXTERN garray_T error_ga
* Excluded are errors that are only used once and debugging messages.
*/
EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted"));
+EXTERN char_u e_afterinit[] INIT(= N_(
+ "E905: Cannot set this option after startup"));
EXTERN char_u e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
EXTERN char_u e_argreq[] INIT(= N_("E471: Argument required"));
EXTERN char_u e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 3575f49e78..08b6d0483e 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -16,29 +16,49 @@
#define USR_LOG_FILE "$HOME/.nvimlog"
+static uv_mutex_t mutex;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "log.c.generated.h"
#endif
+void log_init(void)
+{
+ uv_mutex_init(&mutex);
+}
+
+void log_lock(void)
+{
+ uv_mutex_lock(&mutex);
+}
+
+void log_unlock(void)
+{
+ uv_mutex_unlock(&mutex);
+}
+
bool do_log(int log_level, const char *func_name, int line_num, bool eol,
const char* fmt, ...) FUNC_ATTR_UNUSED
{
+ log_lock();
+ bool ret = false;
FILE *log_file = open_log_file();
if (log_file == NULL) {
- return false;
+ goto end;
}
va_list args;
va_start(args, fmt);
- bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol,
+ ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol,
fmt, args);
va_end(args);
if (log_file != stderr && log_file != stdout) {
fclose(log_file);
}
+end:
+ log_unlock();
return ret;
}
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 3e096ee190..27f8340ec7 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -177,6 +177,7 @@ void event_teardown(void)
/// Needed for unit tests. Must be called after `time_init()`.
void early_init(void)
{
+ log_init();
fs_init();
handle_init();
@@ -1910,6 +1911,7 @@ static void source_startup_scripts(mparm_T *parmp)
need_wait_return = TRUE;
secure = 0;
}
+ did_source_startup_scripts = true;
TIME_MSG("sourcing vimrc file(s)");
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 2a1f722e95..3831004703 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -1317,7 +1317,9 @@ void scroll_cursor_top(int min_scroll, int always)
* set new_topline and advance "top" and "bot" to include more lines.
*/
while (top > 0) {
- int i = (hasFolding(top, &top, NULL)) ? 1 : plines(top);
+ int i = hasFolding(top, &top, NULL)
+ ? 1 // count one logical line for a sequence of folded lines
+ : plines(top);
used += i;
if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) {
if (hasFolding(bot, NULL, &bot))
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 45e78d6e79..34ff7c6374 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -825,6 +825,7 @@ static void log_server_msg(uint64_t channel_id,
msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id);
+ log_lock();
FILE *f = open_log_file();
fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
log_msg_close(f, unpacked.data);
@@ -836,6 +837,7 @@ static void log_client_msg(uint64_t channel_id,
msgpack_object msg)
{
DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id);
+ log_lock();
FILE *f = open_log_file();
fprintf(f, is_request ? REQ : RES);
log_msg_close(f, msg);
@@ -847,6 +849,7 @@ static void log_msg_close(FILE *f, msgpack_object msg)
fputc('\n', f);
fflush(f);
fclose(f);
+ log_unlock();
}
#endif
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 6e82c45edf..c47616620c 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -575,6 +575,12 @@ void set_init_1(void)
mb_init();
}
+ // Don't change &encoding when resetting to defaults with ":set all&".
+ opt_idx = findoption((char_u *)"encoding");
+ if (opt_idx >= 0) {
+ options[opt_idx].flags |= P_NODEFAULT;
+ }
+
/* Set the default for 'helplang'. */
set_helplang_default(get_mess_lang());
}
@@ -2271,10 +2277,11 @@ did_set_string_option (
else if (varp == &p_ei) {
if (check_ei() == FAIL)
errmsg = e_invarg;
- }
/* 'encoding' and 'fileencoding' */
- else if (varp == &p_enc || gvarp == &p_fenc) {
- if (gvarp == &p_fenc) {
+ } else if (varp == &p_enc || gvarp == &p_fenc) {
+ if (varp == &p_enc && did_source_startup_scripts) {
+ errmsg = e_afterinit;
+ } else if (gvarp == &p_fenc) {
if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL)
errmsg = e_modifiable;
else if (vim_strchr(*varp, ',') != NULL)
@@ -2305,10 +2312,6 @@ did_set_string_option (
* (with another encoding). */
if (varp == &p_enc && *curbuf->b_p_keymap != NUL)
(void)keymap_init();
-
- if (varp == &p_enc) {
- ui_update_encoding();
- }
}
} else if (varp == &p_penc) {
/* Canonize printencoding if VIM standard one */
diff --git a/src/nvim/testdir/test10.in b/src/nvim/testdir/test10.in
index 2d0d546606..769d690acb 100644
--- a/src/nvim/testdir/test10.in
+++ b/src/nvim/testdir/test10.in
@@ -4,7 +4,6 @@ STARTTEST
:so small.vim
:" Also test a BOM is ignored.
:so mbyte.vim
-:set encoding=utf-8
:7/start of errorfile/,/end of errorfile/w! Xerrorfile1
:7/start of errorfile/,/end of errorfile/-1w! Xerrorfile2
:/start of testfile/,/end of testfile/w! Xtestfile
diff --git a/src/nvim/testdir/test39.in b/src/nvim/testdir/test39.in
index c4e46fff26..ebbcbd6d0d 100644
--- a/src/nvim/testdir/test39.in
+++ b/src/nvim/testdir/test39.in
@@ -5,8 +5,6 @@ And test "U" in Visual mode, also on German sharp S.
STARTTEST
:so small.vim
:so mbyte.vim
-:" This only works when 'encoding' is "latin1", don't depend on the environment
-:set enc=latin1
/^abcde
:" Test shift-right of a block
jlllljj>wlljlll>
@@ -39,18 +37,18 @@ G$khhhhhkkcmno
:exe ":norm! l\<C-V>j$hhAab\<Esc>"
:.,/^$/w >> test.out
:" Test for Visual block insert when virtualedit=all and utf-8 encoding
-:set ve=all enc=utf-8
+:set ve=all
:/\t\tline
:exe ":norm! 07l\<C-V>jjIx\<Esc>"
:.,/^$/w >> test.out
:" Test for Visual block append when virtualedit=all
:exe ":norm! 012l\<C-v>jjAx\<Esc>"
-:set ve= enc=latin1
+:set ve=
:.,/^$/w >> test.out
-:" gUe must uppercase a whole word, also when ß changes to SS
-Gothe youtußeuu endYpk0wgUe
+:" gUe must uppercase a whole word, also when ß changes to SS
+Gothe youtußeuu endYpk0wgUe
:" gUfx must uppercase until x, inclusive.
-O- youßtußexu -0fogUfx
+O- youßtußexu -0fogUfx
:" VU must uppercase a whole line
YpkVU
:" same, when it's the last line in the buffer
@@ -89,7 +87,7 @@ cccc
dddd
yaaa
-¿¿¿
+¿¿¿
bbb
A23
diff --git a/src/nvim/testdir/test39.ok b/src/nvim/testdir/test39.ok
index 5c517e2223..198e5b14dc 100644
--- a/src/nvim/testdir/test39.ok
+++ b/src/nvim/testdir/test39.ok
Binary files differ
diff --git a/src/nvim/testdir/test69.in b/src/nvim/testdir/test69.in
index 26f41e8a29..f583947dfb 100644
--- a/src/nvim/testdir/test69.in
+++ b/src/nvim/testdir/test69.in
@@ -5,7 +5,6 @@ Also test byteidx() and byteidxcomp()
STARTTEST
:so mbyte.vim
-:set encoding=utf-8
ENDTEST
Results of test69:
diff --git a/src/nvim/testdir/test83.in b/src/nvim/testdir/test83.in
index 297d560d2f..d54b1bcddd 100644
--- a/src/nvim/testdir/test83.in
+++ b/src/nvim/testdir/test83.in
@@ -2,7 +2,6 @@ Tests for tag search with !_TAG_FILE_ENCODING.
STARTTEST
:so mbyte.vim
-:set enc=utf8
:if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21"
: e! test.ok
: w! test.out
diff --git a/src/nvim/testdir/test_eval.in b/src/nvim/testdir/test_eval.in
index 328ee2e127..b2b982a434 100644
--- a/src/nvim/testdir/test_eval.in
+++ b/src/nvim/testdir/test_eval.in
@@ -4,7 +4,6 @@ Note: system clipboard is saved, changed and restored.
STARTTEST
:so small.vim
-:set encoding=latin1
:set noswapfile
:lang C
:fun AppendRegContents(reg)
diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim
index f766e74c30..aa1f6a92bc 100644
--- a/src/nvim/testdir/unix.vim
+++ b/src/nvim/testdir/unix.vim
@@ -1,3 +1,6 @@
" Settings for test script execution
" Always use "sh", don't use the value of "$SHELL".
set shell=sh
+
+" Don't depend on system locale, always use utf-8
+set encoding=utf-8
diff --git a/src/nvim/tui/term_input.inl b/src/nvim/tui/input.c
index c396557160..6c362540d0 100644
--- a/src/nvim/tui/term_input.inl
+++ b/src/nvim/tui/input.c
@@ -1,21 +1,81 @@
-#include <termkey.h>
+#include "nvim/tui/input.h"
+#include "nvim/vim.h"
+#include "nvim/api/vim.h"
+#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/misc2.h"
#include "nvim/os/os.h"
#include "nvim/os/input.h"
#include "nvim/event/rstream.h"
-#include "nvim/event/time.h"
#define PASTETOGGLE_KEY "<f37>"
-struct term_input {
- int in_fd;
- bool paste_enabled;
- TermKey *tk;
- TimeWatcher timer_handle;
- Stream read_stream;
-};
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/input.c.generated.h"
+#endif
+
+void term_input_init(TermInput *input, Loop *loop)
+{
+ input->loop = loop;
+ input->paste_enabled = false;
+ input->in_fd = 0;
+
+ const char *term = os_getenv("TERM");
+ if (!term) {
+ term = ""; // termkey_new_abstract assumes non-null (#2745)
+ }
+ int enc_flag = enc_utf8 ? TERMKEY_FLAG_UTF8 : TERMKEY_FLAG_RAW;
+ input->tk = termkey_new_abstract(term, enc_flag);
+
+ int curflags = termkey_get_canonflags(input->tk);
+ termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
+ // setup input handle
+ rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff, input);
+ // initialize a timer handle for handling ESC with libtermkey
+ time_watcher_init(loop, &input->timer_handle, input);
+ // Set the pastetoggle option to a special key that will be sent when
+ // \e[20{0,1}~/ are received
+ Error err = ERROR_INIT;
+ vim_set_option(cstr_as_string("pastetoggle"),
+ STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
+}
+
+void term_input_destroy(TermInput *input)
+{
+ time_watcher_close(&input->timer_handle, NULL);
+ stream_close(&input->read_stream, NULL);
+ termkey_destroy(input->tk);
+}
+
+void term_input_start(TermInput *input)
+{
+ rstream_start(&input->read_stream, read_cb);
+}
+
+void term_input_stop(TermInput *input)
+{
+ rstream_stop(&input->read_stream);
+ time_watcher_stop(&input->timer_handle);
+}
+
+static void input_enqueue_event(void **argv)
+{
+ char *buf = argv[0];
+ input_enqueue(cstr_as_string(buf));
+ xfree(buf);
+}
+
+static void input_done_event(void **argv)
+{
+ input_done();
+}
+
+static void enqueue_input(char *buf, size_t size)
+{
+ loop_schedule(&loop, event_create(1, input_enqueue_event, 1,
+ xstrndup(buf, size)));
+}
static void forward_simple_utf8(TermKeyKey *key)
{
@@ -33,7 +93,7 @@ static void forward_simple_utf8(TermKeyKey *key)
}
buf[len] = 0;
- input_enqueue((String){.data = buf, .size = len});
+ enqueue_input(buf, len);
}
static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
@@ -48,7 +108,7 @@ static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
}
- input_enqueue((String){.data = buf, .size = len});
+ enqueue_input(buf, len);
}
static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
@@ -99,7 +159,7 @@ static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
}
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
- input_enqueue((String){.data = buf, .size = len});
+ enqueue_input(buf, len);
}
static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
@@ -175,16 +235,16 @@ static bool handle_bracketed_paste(TermInput *input)
int state = get_real_state();
if (state & NORMAL) {
// Enter insert mode
- input_enqueue(cstr_as_string("i"));
+ enqueue_input("i", 1);
} else if (state & VISUAL) {
// Remove the selected text and enter insert mode
- input_enqueue(cstr_as_string("c"));
+ enqueue_input("c", 1);
} else if (!(state & INSERT)) {
// Don't mess with the paste option
return true;
}
}
- input_enqueue(cstr_as_string(PASTETOGGLE_KEY));
+ enqueue_input(PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1);
input->paste_enabled = enable;
return true;
}
@@ -227,9 +287,9 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
// ls *.md | xargs nvim
input->in_fd = 2;
stream_close(&input->read_stream, NULL);
- queue_put(loop.fast_events, restart_reading, 1, input);
+ queue_put(input->loop->fast_events, restart_reading, 1, input);
} else {
- input_done();
+ loop_schedule(&loop, event_create(1, input_done_event, 0));
}
return;
}
@@ -275,51 +335,6 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
static void restart_reading(void **argv)
{
TermInput *input = argv[0];
- rstream_init_fd(&loop, &input->read_stream, input->in_fd, 0xfff, input);
+ rstream_init_fd(input->loop, &input->read_stream, input->in_fd, 0xfff, input);
rstream_start(&input->read_stream, read_cb);
}
-
-static TermInput *term_input_new(void)
-{
- TermInput *rv = xmalloc(sizeof(TermInput));
- rv->paste_enabled = false;
- rv->in_fd = 0;
-
- const char *term = os_getenv("TERM");
- if (!term) {
- term = ""; // termkey_new_abstract assumes non-null (#2745)
- }
- rv->tk = termkey_new_abstract(term, 0);
- int curflags = termkey_get_canonflags(rv->tk);
- termkey_set_canonflags(rv->tk, curflags | TERMKEY_CANON_DELBS);
- // setup input handle
- rstream_init_fd(&loop, &rv->read_stream, rv->in_fd, 0xfff, rv);
- rstream_start(&rv->read_stream, read_cb);
- // initialize a timer handle for handling ESC with libtermkey
- time_watcher_init(&loop, &rv->timer_handle, rv);
- // Set the pastetoggle option to a special key that will be sent when
- // \e[20{0,1}~/ are received
- Error err = ERROR_INIT;
- vim_set_option(cstr_as_string("pastetoggle"),
- STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
- return rv;
-}
-
-static void term_input_destroy(TermInput *input)
-{
- time_watcher_stop(&input->timer_handle);
- time_watcher_close(&input->timer_handle, NULL);
- rstream_stop(&input->read_stream);
- stream_close(&input->read_stream, NULL);
- termkey_destroy(input->tk);
- // Run once to remove references to input/timer handles
- loop_poll_events(&loop, 0);
- xfree(input);
-}
-
-static void term_input_set_encoding(TermInput *input, char* enc)
-{
- int enc_flag = strcmp(enc, "utf-8") == 0 ? TERMKEY_FLAG_UTF8
- : TERMKEY_FLAG_RAW;
- termkey_set_flags(input->tk, enc_flag);
-}
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
new file mode 100644
index 0000000000..033f53b4e2
--- /dev/null
+++ b/src/nvim/tui/input.h
@@ -0,0 +1,23 @@
+#ifndef NVIM_TUI_INPUT_H
+#define NVIM_TUI_INPUT_H
+
+#include <stdbool.h>
+
+#include <termkey.h>
+#include "nvim/event/stream.h"
+#include "nvim/event/time.h"
+
+typedef struct term_input {
+ int in_fd;
+ bool paste_enabled;
+ TermKey *tk;
+ TimeWatcher timer_handle;
+ Loop *loop;
+ Stream read_stream;
+} TermInput;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/input.h.generated.h"
+#endif
+
+#endif // NVIM_TUI_INPUT_H
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index acc2ccc682..4c8e88c383 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -17,46 +17,45 @@
#include "nvim/event/loop.h"
#include "nvim/event/signal.h"
#include "nvim/tui/tui.h"
+#include "nvim/tui/input.h"
+#include "nvim/os/input.h"
+#include "nvim/os/os.h"
#include "nvim/strings.h"
+#include "nvim/ugrid.h"
+#include "nvim/ui_bridge.h"
// Space reserved in the output buffer to restore the cursor to normal when
// flushing. No existing terminal will require 32 bytes to do that.
#define CNORM_COMMAND_MAX_SIZE 32
#define OUTBUF_SIZE 0xffff
-typedef struct term_input TermInput;
-
-#include "term_input.inl"
-
typedef struct {
int top, bot, left, right;
} Rect;
typedef struct {
- char data[7];
- HlAttrs attrs;
-} Cell;
-
-typedef struct {
+ UIBridgeData *bridge;
+ Loop *loop;
+ bool stop;
unibi_var_t params[9];
char buf[OUTBUF_SIZE];
size_t bufpos, bufsize;
- TermInput *input;
- uv_loop_t *write_loop;
+ TermInput input;
+ uv_loop_t write_loop;
unibi_term *ut;
uv_tty_t output_handle;
- SignalWatcher winch_handle;
- Rect scroll_region;
+ SignalWatcher winch_handle, cont_handle;
+ bool cont_received;
+ // Event scheduled by the ui bridge. Since the main thread suspends until
+ // the event is handled, it is fine to use a single field instead of a queue
+ Event scheduled_event;
+ UGrid grid;
kvec_t(Rect) invalid_regions;
- int row, col;
- int bg, fg;
int out_fd;
- int old_height;
bool can_use_terminal_scroll;
bool mouse_enabled;
bool busy;
- HlAttrs attrs, print_attrs;
- Cell **screen;
+ HlAttrs print_attrs;
int showing_mode;
struct {
int enable_mouse, disable_mouse;
@@ -72,32 +71,40 @@ static bool volatile got_winch = false;
# include "tui/tui.c.generated.h"
#endif
-#define EMPTY_ATTRS ((HlAttrs){false, false, false, false, false, -1, -1})
-
-#define FOREACH_CELL(ui, top, bot, left, right, go, code) \
- do { \
- TUIData *data = ui->data; \
- for (int row = top; row <= bot; ++row) { \
- Cell *cells = data->screen[row]; \
- if (go) { \
- unibi_goto(ui, row, left); \
- } \
- for (int col = left; col <= right; ++col) { \
- Cell *cell = cells + col; \
- (void)(cell); \
- code; \
- } \
- } \
- } while (0)
-
UI *tui_start(void)
{
- TUIData *data = xcalloc(1, sizeof(TUIData));
UI *ui = xcalloc(1, sizeof(UI));
- ui->data = data;
- data->attrs = data->print_attrs = EMPTY_ATTRS;
- data->fg = data->bg = -1;
+ ui->stop = tui_stop;
+ ui->rgb = os_getenv("NVIM_TUI_ENABLE_TRUE_COLOR") != NULL;
+ ui->resize = tui_resize;
+ ui->clear = tui_clear;
+ ui->eol_clear = tui_eol_clear;
+ ui->cursor_goto = tui_cursor_goto;
+ ui->update_menu = tui_update_menu;
+ ui->busy_start = tui_busy_start;
+ ui->busy_stop = tui_busy_stop;
+ ui->mouse_on = tui_mouse_on;
+ ui->mouse_off = tui_mouse_off;
+ ui->mode_change = tui_mode_change;
+ ui->set_scroll_region = tui_set_scroll_region;
+ ui->scroll = tui_scroll;
+ ui->highlight_set = tui_highlight_set;
+ ui->put = tui_put;
+ ui->bell = tui_bell;
+ ui->visual_bell = tui_visual_bell;
+ ui->update_fg = tui_update_fg;
+ ui->update_bg = tui_update_bg;
+ ui->flush = tui_flush;
+ ui->suspend = tui_suspend;
+ ui->set_title = tui_set_title;
+ ui->set_icon = tui_set_icon;
+ return ui_bridge_attach(ui, tui_main, tui_scheduler);
+}
+
+static void terminfo_start(UI *ui)
+{
+ TUIData *data = ui->data;
data->can_use_terminal_scroll = true;
data->bufpos = 0;
data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE;
@@ -109,12 +116,8 @@ UI *tui_start(void)
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
data->out_fd = os_isatty(1) ? 1 : (os_isatty(2) ? 2 : 1);
- kv_init(data->invalid_regions);
- // setup term input
- data->input = term_input_new();
// setup unibilium
data->ut = unibi_from_env();
if (!data->ut) {
@@ -128,62 +131,14 @@ UI *tui_start(void)
unibi_out(ui, unibi_clear_screen);
// Enable bracketed paste
unibi_out(ui, data->unibi_ext.enable_bracketed_paste);
- // setup output handle in a separate event loop(we wanna do synchronous
- // write to the tty)
- data->write_loop = xmalloc(sizeof(uv_loop_t));
- uv_loop_init(data->write_loop);
- uv_tty_init(data->write_loop, &data->output_handle, data->out_fd, 0);
+ uv_loop_init(&data->write_loop);
+ uv_tty_init(&data->write_loop, &data->output_handle, data->out_fd, 0);
uv_tty_set_mode(&data->output_handle, UV_TTY_MODE_RAW);
-
- // Obtain screen dimensions
- update_size(ui);
-
- // listen for SIGWINCH
- signal_watcher_init(&loop, &data->winch_handle, ui);
- data->winch_handle.events = queue_new_child(loop.events);
- signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
-
- ui->stop = tui_stop;
- ui->rgb = os_getenv("NVIM_TUI_ENABLE_TRUE_COLOR") != NULL;
- ui->data = data;
- ui->resize = tui_resize;
- ui->clear = tui_clear;
- ui->eol_clear = tui_eol_clear;
- ui->cursor_goto = tui_cursor_goto;
- ui->update_menu = tui_update_menu;
- ui->busy_start = tui_busy_start;
- ui->busy_stop = tui_busy_stop;
- ui->mouse_on = tui_mouse_on;
- ui->mouse_off = tui_mouse_off;
- ui->mode_change = tui_mode_change;
- ui->set_scroll_region = tui_set_scroll_region;
- ui->scroll = tui_scroll;
- ui->highlight_set = tui_highlight_set;
- ui->put = tui_put;
- ui->bell = tui_bell;
- ui->visual_bell = tui_visual_bell;
- ui->update_fg = tui_update_fg;
- ui->update_bg = tui_update_bg;
- ui->flush = tui_flush;
- ui->suspend = tui_suspend;
- ui->set_title = tui_set_title;
- ui->set_icon = tui_set_icon;
- ui->set_encoding = tui_set_encoding;
- // Attach
- ui_attach(ui);
- return ui;
}
-static void tui_stop(UI *ui)
+static void terminfo_stop(UI *ui)
{
TUIData *data = ui->data;
- // Destroy common stuff
- kv_destroy(data->invalid_regions);
- signal_watcher_stop(&data->winch_handle);
- queue_free(data->winch_handle.events);
- signal_watcher_close(&data->winch_handle, NULL);
- // Destroy input stuff
- term_input_destroy(data->input);
// Destroy output stuff
tui_mode_change(ui, NORMAL);
tui_mouse_off(ui);
@@ -196,24 +151,99 @@ static void tui_stop(UI *ui)
flush_buf(ui);
uv_tty_reset_mode();
uv_close((uv_handle_t *)&data->output_handle, NULL);
- uv_run(data->write_loop, UV_RUN_DEFAULT);
- if (uv_loop_close(data->write_loop)) {
+ uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ if (uv_loop_close(&data->write_loop)) {
abort();
}
- xfree(data->write_loop);
unibi_destroy(data->ut);
- destroy_screen(data);
+}
+
+static void tui_terminal_start(UI *ui)
+{
+ TUIData *data = ui->data;
+ data->print_attrs = EMPTY_ATTRS;
+ ugrid_init(&data->grid);
+ terminfo_start(ui);
+ update_size(ui);
+ signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
+ term_input_start(&data->input);
+}
+
+static void tui_terminal_stop(UI *ui)
+{
+ TUIData *data = ui->data;
+ term_input_stop(&data->input);
+ signal_watcher_stop(&data->winch_handle);
+ terminfo_stop(ui);
+ ugrid_free(&data->grid);
+}
+
+static void tui_stop(UI *ui)
+{
+ tui_terminal_stop(ui);
+ TUIData *data = ui->data;
+ data->stop = true;
+}
+
+// Main function of the TUI thread
+static void tui_main(UIBridgeData *bridge, UI *ui)
+{
+ Loop tui_loop;
+ loop_init(&tui_loop, NULL);
+ TUIData *data = xcalloc(1, sizeof(TUIData));
+ ui->data = data;
+ data->bridge = bridge;
+ data->loop = &tui_loop;
+ kv_init(data->invalid_regions);
+ signal_watcher_init(data->loop, &data->winch_handle, ui);
+ signal_watcher_init(data->loop, &data->cont_handle, data);
+ signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);
+ // initialize input reading structures
+ term_input_init(&data->input, &tui_loop);
+ tui_terminal_start(ui);
+ data->stop = false;
+ // allow the main thread to continue, we are ready to start handling UI
+ // callbacks
+ CONTINUE(bridge);
+
+ while (!data->stop) {
+ loop_poll_events(&tui_loop, -1);
+ }
+
+ term_input_destroy(&data->input);
+ signal_watcher_stop(&data->cont_handle);
+ signal_watcher_close(&data->cont_handle, NULL);
+ signal_watcher_close(&data->winch_handle, NULL);
+ loop_close(&tui_loop);
+ kv_destroy(data->invalid_regions);
xfree(data);
- ui_detach(ui);
xfree(ui);
}
+static void tui_scheduler(Event event, void *d)
+{
+ UI *ui = d;
+ TUIData *data = ui->data;
+ loop_schedule(data->loop, event);
+}
+
+static void refresh_event(void **argv)
+{
+ ui_refresh();
+}
+
+static void sigcont_cb(SignalWatcher *watcher, int signum, void *data)
+{
+ ((TUIData *)data)->cont_received = true;
+}
+
static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
{
got_winch = true;
UI *ui = data;
update_size(ui);
- ui_refresh();
+ // run refresh_event in nvim main loop
+ loop_schedule(&loop, event_create(1, refresh_event, 0));
}
static bool attrs_differ(HlAttrs a1, HlAttrs a2)
@@ -234,9 +264,10 @@ static void update_attrs(UI *ui, HlAttrs attrs)
data->print_attrs = attrs;
unibi_out(ui, unibi_exit_attribute_mode);
+ UGrid *grid = &data->grid;
- int fg = attrs.foreground != -1 ? attrs.foreground : data->fg;
- int bg = attrs.background != -1 ? attrs.background : data->bg;
+ int fg = attrs.foreground != -1 ? attrs.foreground : grid->fg;
+ int bg = attrs.background != -1 ? attrs.background : grid->bg;
if (ui->rgb) {
if (fg != -1) {
@@ -278,25 +309,25 @@ static void update_attrs(UI *ui, HlAttrs attrs)
}
}
-static void print_cell(UI *ui, Cell *ptr)
+static void print_cell(UI *ui, UCell *ptr)
{
update_attrs(ui, ptr->attrs);
out(ui, ptr->data, strlen(ptr->data));
}
-static void clear_region(UI *ui, int top, int bot, int left, int right,
- bool refresh)
+static void clear_region(UI *ui, int top, int bot, int left, int right)
{
TUIData *data = ui->data;
- HlAttrs clear_attrs = EMPTY_ATTRS;
- clear_attrs.foreground = data->fg;
- clear_attrs.background = data->bg;
- update_attrs(ui, clear_attrs);
+ UGrid *grid = &data->grid;
bool cleared = false;
- if (refresh && data->bg == -1 && right == ui->width -1) {
+ if (grid->bg == -1 && right == ui->width -1) {
// Background is set to the default color and the right edge matches the
// screen end, try to use terminal codes for clearing the requested area.
+ HlAttrs clear_attrs = EMPTY_ATTRS;
+ clear_attrs.foreground = grid->fg;
+ clear_attrs.background = grid->bg;
+ update_attrs(ui, clear_attrs);
if (left == 0) {
if (bot == ui->height - 1) {
if (top == 0) {
@@ -319,36 +350,26 @@ static void clear_region(UI *ui, int top, int bot, int left, int right,
}
}
- bool clear = refresh && !cleared;
- FOREACH_CELL(ui, top, bot, left, right, clear, {
- cell->data[0] = ' ';
- cell->data[1] = 0;
- cell->attrs = clear_attrs;
- if (clear) {
+ if (!cleared) {
+ // could not clear using faster terminal codes, refresh the whole region
+ int currow = -1;
+ UGRID_FOREACH_CELL(grid, top, bot, left, right, {
+ if (currow != row) {
+ unibi_goto(ui, row, col);
+ currow = row;
+ }
print_cell(ui, cell);
- }
- });
+ });
+ }
// restore cursor
- unibi_goto(ui, data->row, data->col);
+ unibi_goto(ui, grid->row, grid->col);
}
static void tui_resize(UI *ui, int width, int height)
{
TUIData *data = ui->data;
- destroy_screen(data);
-
- data->screen = xmalloc((size_t)height * sizeof(Cell *));
- for (int i = 0; i < height; i++) {
- data->screen[i] = xcalloc((size_t)width, sizeof(Cell));
- }
-
- data->old_height = height;
- data->scroll_region.top = 0;
- data->scroll_region.bot = height - 1;
- data->scroll_region.left = 0;
- data->scroll_region.right = width - 1;
- data->row = data->col = 0;
+ ugrid_resize(&data->grid, width, height);
if (!got_winch) { // Try to resize the terminal window.
char r[16]; // enough for 9999x9999
@@ -362,22 +383,23 @@ static void tui_resize(UI *ui, int width, int height)
static void tui_clear(UI *ui)
{
TUIData *data = ui->data;
- clear_region(ui, data->scroll_region.top, data->scroll_region.bot,
- data->scroll_region.left, data->scroll_region.right, true);
+ UGrid *grid = &data->grid;
+ ugrid_clear(grid);
+ clear_region(ui, grid->top, grid->bot, grid->left, grid->right);
}
static void tui_eol_clear(UI *ui)
{
TUIData *data = ui->data;
- clear_region(ui, data->row, data->row, data->col,
- data->scroll_region.right, true);
+ UGrid *grid = &data->grid;
+ ugrid_eol_clear(grid);
+ clear_region(ui, grid->row, grid->row, grid->col, grid->right);
}
static void tui_cursor_goto(UI *ui, int row, int col)
{
TUIData *data = ui->data;
- data->row = row;
- data->col = col;
+ ugrid_goto(&data->grid, row, col);
unibi_goto(ui, row, col);
}
@@ -435,11 +457,7 @@ static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
int right)
{
TUIData *data = ui->data;
- data->scroll_region.top = top;
- data->scroll_region.bot = bot;
- data->scroll_region.left = left;
- data->scroll_region.right = right;
-
+ ugrid_set_scroll_region(&data->grid, top, bot, left, right);
data->can_use_terminal_scroll =
left == 0 && right == ui->width - 1
&& ((top == 0 && bot == ui->height - 1)
@@ -449,31 +467,24 @@ static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
static void tui_scroll(UI *ui, int count)
{
TUIData *data = ui->data;
- int top = data->scroll_region.top;
- int bot = data->scroll_region.bot;
- int left = data->scroll_region.left;
- int right = data->scroll_region.right;
+ UGrid *grid = &data->grid;
+ int clear_top, clear_bot;
+ ugrid_scroll(grid, count, &clear_top, &clear_bot);
if (data->can_use_terminal_scroll) {
// Change terminal scroll region and move cursor to the top
- data->params[0].i = top;
- data->params[1].i = bot;
+ data->params[0].i = grid->top;
+ data->params[1].i = grid->bot;
unibi_out(ui, unibi_change_scroll_region);
- unibi_goto(ui, top, left);
+ unibi_goto(ui, grid->top, grid->left);
// also set default color attributes or some terminals can become funny
HlAttrs clear_attrs = EMPTY_ATTRS;
- clear_attrs.foreground = data->fg;
- clear_attrs.background = data->bg;
+ clear_attrs.foreground = grid->fg;
+ clear_attrs.background = grid->bg;
update_attrs(ui, clear_attrs);
}
- // Compute start/stop/step for the loop below, also use terminal scroll
- // if possible
- int start, stop, step;
if (count > 0) {
- start = top;
- stop = bot - count + 1;
- step = 1;
if (data->can_use_terminal_scroll) {
if (count == 1) {
unibi_out(ui, unibi_delete_line);
@@ -484,9 +495,6 @@ static void tui_scroll(UI *ui, int count)
}
} else {
- start = bot;
- stop = top - count - 1;
- step = -1;
if (data->can_use_terminal_scroll) {
if (count == -1) {
unibi_out(ui, unibi_insert_line);
@@ -502,52 +510,30 @@ static void tui_scroll(UI *ui, int count)
data->params[0].i = 0;
data->params[1].i = ui->height - 1;
unibi_out(ui, unibi_change_scroll_region);
- unibi_goto(ui, data->row, data->col);
- }
-
- int i;
- // Scroll internal screen
- for (i = start; i != stop; i += step) {
- Cell *target_row = data->screen[i] + left;
- Cell *source_row = data->screen[i + count] + left;
- memcpy(target_row, source_row, sizeof(Cell) * (size_t)(right - left + 1));
- }
-
- // clear emptied region, updating the terminal if its builtin scrolling
- // facility was used. This is done when the background color is not the
- // default, since scrolling may leave wrong background in the cleared area.
- bool update_clear = data->bg != -1 && data->can_use_terminal_scroll;
- if (count > 0) {
- clear_region(ui, stop, stop + count - 1, left, right, update_clear);
+ unibi_goto(ui, grid->row, grid->col);
+
+ if (grid->bg != -1) {
+ // Update the cleared area of the terminal if its builtin scrolling
+ // facility was used and the background color is not the default. This is
+ // required because scrolling may leave wrong background in the cleared
+ // area.
+ clear_region(ui, clear_top, clear_bot, grid->left, grid->right);
+ }
} else {
- clear_region(ui, stop + count + 1, stop, left, right, update_clear);
- }
-
- if (!data->can_use_terminal_scroll) {
// Mark the entire scroll region as invalid for redrawing later
- invalidate(ui, data->scroll_region.top, data->scroll_region.bot,
- data->scroll_region.left, data->scroll_region.right);
+ invalidate(ui, grid->top, grid->bot, grid->left, grid->right);
}
}
static void tui_highlight_set(UI *ui, HlAttrs attrs)
{
- ((TUIData *)ui->data)->attrs = attrs;
+ ((TUIData *)ui->data)->grid.attrs = attrs;
}
static void tui_put(UI *ui, uint8_t *text, size_t size)
{
TUIData *data = ui->data;
- Cell *cell = data->screen[data->row] + data->col;
- cell->data[size] = 0;
- cell->attrs = data->attrs;
-
- if (text) {
- memcpy(cell->data, text, size);
- }
-
- print_cell(ui, cell);
- data->col += 1;
+ print_cell(ui, ugrid_put(&data->grid, text, size));
}
static void tui_bell(UI *ui)
@@ -562,40 +548,64 @@ static void tui_visual_bell(UI *ui)
static void tui_update_fg(UI *ui, int fg)
{
- ((TUIData *)ui->data)->fg = fg;
+ ((TUIData *)ui->data)->grid.fg = fg;
}
static void tui_update_bg(UI *ui, int bg)
{
- ((TUIData *)ui->data)->bg = bg;
+ ((TUIData *)ui->data)->grid.bg = bg;
}
static void tui_flush(UI *ui)
{
TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
while (kv_size(data->invalid_regions)) {
Rect r = kv_pop(data->invalid_regions);
- FOREACH_CELL(ui, r.top, r.bot, r.left, r.right, true, {
+ int currow = -1;
+ UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, {
+ if (currow != row) {
+ unibi_goto(ui, row, col);
+ currow = row;
+ }
print_cell(ui, cell);
});
}
- unibi_goto(ui, data->row, data->col);
+ unibi_goto(ui, grid->row, grid->col);
flush_buf(ui);
}
-static void tui_suspend(UI *ui)
+static void suspend_event(void **argv)
{
+ UI *ui = argv[0];
TUIData *data = ui->data;
bool enable_mouse = data->mouse_enabled;
- tui_stop(ui);
+ tui_terminal_stop(ui);
+ data->cont_received = false;
kill(0, SIGTSTP);
- ui = tui_start();
+ while (!data->cont_received) {
+ // poll the event loop until SIGCONT is received
+ loop_poll_events(data->loop, -1);
+ }
+ tui_terminal_start(ui);
if (enable_mouse) {
tui_mouse_on(ui);
}
+ // resume the main thread
+ CONTINUE(data->bridge);
+}
+
+static void tui_suspend(UI *ui)
+{
+ TUIData *data = ui->data;
+ // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
+ // before continuing. This is done in another callback to avoid
+ // loop_poll_events recursion
+ queue_put_event(data->loop->fast_events,
+ event_create(1, suspend_event, 1, ui));
}
static void tui_set_title(UI *ui, char *title)
@@ -614,12 +624,6 @@ static void tui_set_icon(UI *ui, char *icon)
{
}
-static void tui_set_encoding(UI *ui, char* enc)
-{
- TUIData *data = ui->data;
- term_input_set_encoding(data->input, enc);
-}
-
static void invalidate(UI *ui, int top, int bot, int left, int right)
{
TUIData *data = ui->data;
@@ -698,8 +702,8 @@ end:
height = DFLT_ROWS;
}
- ui->width = width;
- ui->height = height;
+ data->bridge->bridge.width = ui->width = width;
+ data->bridge->bridge.height = ui->height = height;
}
static void unibi_goto(UI *ui, int row, int col)
@@ -882,7 +886,7 @@ static void flush_buf(UI *ui)
buf.base = data->buf;
buf.len = data->bufpos;
uv_write(&req, (uv_stream_t *)&data->output_handle, &buf, 1, NULL);
- uv_run(data->write_loop, UV_RUN_DEFAULT);
+ uv_run(&data->write_loop, UV_RUN_DEFAULT);
data->bufpos = 0;
if (!data->busy) {
@@ -891,13 +895,3 @@ static void flush_buf(UI *ui)
unibi_out(ui, unibi_cursor_invisible);
}
}
-
-static void destroy_screen(TUIData *data)
-{
- if (data->screen) {
- for (int i = 0; i < data->old_height; i++) {
- xfree(data->screen[i]);
- }
- xfree(data->screen);
- }
-}
diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c
new file mode 100644
index 0000000000..127b18feb6
--- /dev/null
+++ b/src/nvim/ugrid.c
@@ -0,0 +1,137 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "nvim/vim.h"
+#include "nvim/ui.h"
+#include "nvim/ugrid.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ugrid.c.generated.h"
+#endif
+
+void ugrid_init(UGrid *grid)
+{
+ grid->attrs = EMPTY_ATTRS;
+ grid->fg = grid->bg = -1;
+ grid->cells = NULL;
+}
+
+void ugrid_free(UGrid *grid)
+{
+ destroy_cells(grid);
+}
+
+void ugrid_resize(UGrid *grid, int width, int height)
+{
+ destroy_cells(grid);
+ grid->cells = xmalloc((size_t)height * sizeof(UCell *));
+ for (int i = 0; i < height; i++) {
+ grid->cells[i] = xcalloc((size_t)width, sizeof(UCell));
+ }
+
+ grid->top = 0;
+ grid->bot = height - 1;
+ grid->left = 0;
+ grid->right = width - 1;
+ grid->row = grid->col = 0;
+ grid->width = width;
+ grid->height = height;
+}
+
+void ugrid_clear(UGrid *grid)
+{
+ clear_region(grid, grid->top, grid->bot, grid->left, grid->right);
+}
+
+void ugrid_eol_clear(UGrid *grid)
+{
+ clear_region(grid, grid->row, grid->row, grid->col, grid->right);
+}
+
+void ugrid_goto(UGrid *grid, int row, int col)
+{
+ grid->row = row;
+ grid->col = col;
+}
+
+void ugrid_set_scroll_region(UGrid *grid, int top, int bot, int left, int right)
+{
+ grid->top = top;
+ grid->bot = bot;
+ grid->left = left;
+ grid->right = right;
+}
+
+void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot)
+{
+ // Compute start/stop/step for the loop below
+ int start, stop, step;
+ if (count > 0) {
+ start = grid->top;
+ stop = grid->bot - count + 1;
+ step = 1;
+ } else {
+ start = grid->bot;
+ stop = grid->top - count - 1;
+ step = -1;
+ }
+
+ int i;
+
+ // Copy cell data
+ for (i = start; i != stop; i += step) {
+ UCell *target_row = grid->cells[i] + grid->left;
+ UCell *source_row = grid->cells[i + count] + grid->left;
+ memcpy(target_row, source_row,
+ sizeof(UCell) * (size_t)(grid->right - grid->left + 1));
+ }
+
+ // clear cells in the emptied region,
+ if (count > 0) {
+ *clear_top = stop;
+ *clear_bot = stop + count - 1;
+ } else {
+ *clear_bot = stop;
+ *clear_top = stop + count + 1;
+ }
+ clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right);
+}
+
+UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
+{
+ UCell *cell = grid->cells[grid->row] + grid->col;
+ cell->data[size] = 0;
+ cell->attrs = grid->attrs;
+
+ if (text) {
+ memcpy(cell->data, text, size);
+ }
+
+ grid->col += 1;
+ return cell;
+}
+
+static void clear_region(UGrid *grid, int top, int bot, int left, int right)
+{
+ HlAttrs clear_attrs = EMPTY_ATTRS;
+ clear_attrs.foreground = grid->fg;
+ clear_attrs.background = grid->bg;
+ UGRID_FOREACH_CELL(grid, top, bot, left, right, {
+ cell->data[0] = ' ';
+ cell->data[1] = 0;
+ cell->attrs = clear_attrs;
+ });
+}
+
+static void destroy_cells(UGrid *grid)
+{
+ if (grid->cells) {
+ for (int i = 0; i < grid->height; i++) {
+ xfree(grid->cells[i]);
+ }
+ xfree(grid->cells);
+ }
+}
+
diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h
new file mode 100644
index 0000000000..e41461fa16
--- /dev/null
+++ b/src/nvim/ugrid.h
@@ -0,0 +1,40 @@
+#ifndef NVIM_UGRID_H
+#define NVIM_UGRID_H
+
+#include "nvim/ui.h"
+
+typedef struct ucell UCell;
+typedef struct ugrid UGrid;
+
+struct ucell {
+ char data[7];
+ HlAttrs attrs;
+};
+
+struct ugrid {
+ int top, bot, left, right;
+ int row, col;
+ int bg, fg;
+ int width, height;
+ HlAttrs attrs;
+ UCell **cells;
+};
+
+#define EMPTY_ATTRS ((HlAttrs){false, false, false, false, false, -1, -1})
+
+#define UGRID_FOREACH_CELL(grid, top, bot, left, right, code) \
+ do { \
+ for (int row = top; row <= bot; ++row) { \
+ UCell *row_cells = (grid)->cells[row]; \
+ for (int col = left; col <= right; ++col) { \
+ UCell *cell = row_cells + col; \
+ (void)(cell); \
+ code; \
+ } \
+ } \
+ } while (0)
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ugrid.h.generated.h"
+#endif
+#endif // NVIM_UGRID_H
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index e1bbcdb193..786f6026de 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -113,11 +113,6 @@ void ui_set_icon(char *icon)
UI_CALL(flush);
}
-void ui_update_encoding(void)
-{
- UI_CALL(set_encoding, (char*)p_enc);
-}
-
// May update the shape of the cursor.
void ui_cursor_shape(void)
{
@@ -188,7 +183,6 @@ void ui_attach(UI *ui)
}
uis[ui_count++] = ui;
- ui_update_encoding();
ui_refresh();
}
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index c87d7f0c55..4c051fcfbf 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -38,7 +38,6 @@ struct ui_t {
void (*suspend)(UI *ui);
void (*set_title)(UI *ui, char *title);
void (*set_icon)(UI *ui, char *icon);
- void (*set_encoding)(UI *ui, char *enc);
void (*stop)(UI *ui);
};
diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c
new file mode 100644
index 0000000000..2ec31de5e1
--- /dev/null
+++ b/src/nvim/ui_bridge.c
@@ -0,0 +1,335 @@
+// FIXME(tarruda): This module is very repetitive. It might be a good idea to
+// automatically generate it with a lua script during build
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "nvim/vim.h"
+#include "nvim/ui.h"
+#include "nvim/memory.h"
+#include "nvim/ui_bridge.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui_bridge.c.generated.h"
+#endif
+
+#define UI(b) (((UIBridgeData *)b)->ui)
+
+// Call a function in the UI thread
+#define UI_CALL(ui, name, argc, ...) \
+ ((UIBridgeData *)ui)->scheduler( \
+ event_create(1, ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui))
+
+#define INT2PTR(i) ((void *)(uintptr_t)i)
+#define PTR2INT(p) ((int)(uintptr_t)p)
+
+UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
+{
+ UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData));
+ rv->ui = ui;
+ rv->bridge.rgb = ui->rgb;
+ rv->bridge.stop = ui_bridge_stop;
+ rv->bridge.resize = ui_bridge_resize;
+ rv->bridge.clear = ui_bridge_clear;
+ rv->bridge.eol_clear = ui_bridge_eol_clear;
+ rv->bridge.cursor_goto = ui_bridge_cursor_goto;
+ rv->bridge.update_menu = ui_bridge_update_menu;
+ rv->bridge.busy_start = ui_bridge_busy_start;
+ rv->bridge.busy_stop = ui_bridge_busy_stop;
+ rv->bridge.mouse_on = ui_bridge_mouse_on;
+ rv->bridge.mouse_off = ui_bridge_mouse_off;
+ rv->bridge.mode_change = ui_bridge_mode_change;
+ rv->bridge.set_scroll_region = ui_bridge_set_scroll_region;
+ rv->bridge.scroll = ui_bridge_scroll;
+ rv->bridge.highlight_set = ui_bridge_highlight_set;
+ rv->bridge.put = ui_bridge_put;
+ rv->bridge.bell = ui_bridge_bell;
+ rv->bridge.visual_bell = ui_bridge_visual_bell;
+ rv->bridge.update_fg = ui_bridge_update_fg;
+ rv->bridge.update_bg = ui_bridge_update_bg;
+ rv->bridge.flush = ui_bridge_flush;
+ rv->bridge.suspend = ui_bridge_suspend;
+ rv->bridge.set_title = ui_bridge_set_title;
+ rv->bridge.set_icon = ui_bridge_set_icon;
+ rv->scheduler = scheduler;
+
+ rv->ui_main = ui_main;
+ uv_mutex_init(&rv->mutex);
+ uv_cond_init(&rv->cond);
+ uv_mutex_lock(&rv->mutex);
+ rv->ready = false;
+
+ if (uv_thread_create(&rv->ui_thread, ui_thread_run, rv)) {
+ abort();
+ }
+
+ while (!rv->ready) {
+ uv_cond_wait(&rv->cond, &rv->mutex);
+ }
+ uv_mutex_unlock(&rv->mutex);
+
+ ui_attach(&rv->bridge);
+ return &rv->bridge;
+}
+
+static void ui_thread_run(void *data)
+{
+ UIBridgeData *bridge = data;
+ bridge->ui_main(bridge, bridge->ui);
+}
+
+static void ui_bridge_stop(UI *b)
+{
+ UI_CALL(b, stop, 1, b);
+ UIBridgeData *bridge = (UIBridgeData *)b;
+ uv_thread_join(&bridge->ui_thread);
+ uv_mutex_destroy(&bridge->mutex);
+ uv_cond_destroy(&bridge->cond);
+ ui_detach(b);
+ xfree(b);
+}
+static void ui_bridge_stop_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->stop(ui);
+}
+
+static void ui_bridge_resize(UI *b, int width, int height)
+{
+ UI_CALL(b, resize, 3, b, INT2PTR(width), INT2PTR(height));
+}
+static void ui_bridge_resize_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->resize(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
+}
+
+static void ui_bridge_clear(UI *b)
+{
+ UI_CALL(b, clear, 1, b);
+}
+static void ui_bridge_clear_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->clear(ui);
+}
+
+static void ui_bridge_eol_clear(UI *b)
+{
+ UI_CALL(b, eol_clear, 1, b);
+}
+static void ui_bridge_eol_clear_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->eol_clear(ui);
+}
+
+static void ui_bridge_cursor_goto(UI *b, int row, int col)
+{
+ UI_CALL(b, cursor_goto, 3, b, INT2PTR(row), INT2PTR(col));
+}
+static void ui_bridge_cursor_goto_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
+}
+
+static void ui_bridge_update_menu(UI *b)
+{
+ UI_CALL(b, update_menu, 1, b);
+}
+static void ui_bridge_update_menu_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->update_menu(ui);
+}
+
+static void ui_bridge_busy_start(UI *b)
+{
+ UI_CALL(b, busy_start, 1, b);
+}
+static void ui_bridge_busy_start_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->busy_start(ui);
+}
+
+static void ui_bridge_busy_stop(UI *b)
+{
+ UI_CALL(b, busy_stop, 1, b);
+}
+static void ui_bridge_busy_stop_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->busy_stop(ui);
+}
+
+static void ui_bridge_mouse_on(UI *b)
+{
+ UI_CALL(b, mouse_on, 1, b);
+}
+static void ui_bridge_mouse_on_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->mouse_on(ui);
+}
+
+static void ui_bridge_mouse_off(UI *b)
+{
+ UI_CALL(b, mouse_off, 1, b);
+}
+static void ui_bridge_mouse_off_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->mouse_off(ui);
+}
+
+static void ui_bridge_mode_change(UI *b, int mode)
+{
+ UI_CALL(b, mode_change, 2, b, INT2PTR(mode));
+}
+static void ui_bridge_mode_change_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->mode_change(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_set_scroll_region(UI *b, int top, int bot, int left,
+ int right)
+{
+ UI_CALL(b, set_scroll_region, 5, b, INT2PTR(top), INT2PTR(bot),
+ INT2PTR(left), INT2PTR(right));
+}
+static void ui_bridge_set_scroll_region_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->set_scroll_region(ui, PTR2INT(argv[1]), PTR2INT(argv[2]),
+ PTR2INT(argv[3]), PTR2INT(argv[4]));
+}
+
+static void ui_bridge_scroll(UI *b, int count)
+{
+ UI_CALL(b, scroll, 2, b, INT2PTR(count));
+}
+static void ui_bridge_scroll_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->scroll(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_highlight_set(UI *b, HlAttrs attrs)
+{
+ HlAttrs *a = xmalloc(sizeof(HlAttrs));
+ *a = attrs;
+ UI_CALL(b, highlight_set, 2, b, a);
+}
+static void ui_bridge_highlight_set_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->highlight_set(ui, *((HlAttrs *)argv[1]));
+ xfree(argv[1]);
+}
+
+static void ui_bridge_put(UI *b, uint8_t *text, size_t size)
+{
+ uint8_t *t = xmalloc(8);
+ memcpy(t, text, size);
+ UI_CALL(b, put, 3, b, t, INT2PTR(size));
+}
+static void ui_bridge_put_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->put(ui, (uint8_t *)argv[1], (size_t)(uintptr_t)argv[2]);
+ xfree(argv[1]);
+}
+
+static void ui_bridge_bell(UI *b)
+{
+ UI_CALL(b, bell, 1, b);
+}
+static void ui_bridge_bell_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->bell(ui);
+}
+
+static void ui_bridge_visual_bell(UI *b)
+{
+ UI_CALL(b, visual_bell, 1, b);
+}
+static void ui_bridge_visual_bell_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->visual_bell(ui);
+}
+
+static void ui_bridge_update_fg(UI *b, int fg)
+{
+ UI_CALL(b, update_fg, 2, b, INT2PTR(fg));
+}
+static void ui_bridge_update_fg_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->update_fg(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_update_bg(UI *b, int bg)
+{
+ UI_CALL(b, update_bg, 2, b, INT2PTR(bg));
+}
+static void ui_bridge_update_bg_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->update_bg(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_flush(UI *b)
+{
+ UI_CALL(b, flush, 1, b);
+}
+static void ui_bridge_flush_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->flush(ui);
+}
+
+static void ui_bridge_suspend(UI *b)
+{
+ UIBridgeData *data = (UIBridgeData *)b;
+ uv_mutex_lock(&data->mutex);
+ UI_CALL(b, suspend, 1, b);
+ data->ready = false;
+ // suspend the main thread until CONTINUE is called by the UI thread
+ while (!data->ready) {
+ uv_cond_wait(&data->cond, &data->mutex);
+ }
+ uv_mutex_unlock(&data->mutex);
+}
+static void ui_bridge_suspend_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->suspend(ui);
+}
+
+static void ui_bridge_set_title(UI *b, char *title)
+{
+ UI_CALL(b, set_title, 2, b, title ? xstrdup(title) : NULL);
+}
+static void ui_bridge_set_title_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->set_title(ui, argv[1]);
+ xfree(argv[1]);
+}
+
+static void ui_bridge_set_icon(UI *b, char *icon)
+{
+ UI_CALL(b, set_icon, 2, b, icon ? xstrdup(icon) : NULL);
+}
+static void ui_bridge_set_icon_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->set_icon(ui, argv[1]);
+ xfree(argv[1]);
+}
diff --git a/src/nvim/ui_bridge.h b/src/nvim/ui_bridge.h
new file mode 100644
index 0000000000..76e9e27989
--- /dev/null
+++ b/src/nvim/ui_bridge.h
@@ -0,0 +1,40 @@
+// Bridge used for communication between a builtin UI thread and nvim core
+#ifndef NVIM_UI_BRIDGE_H
+#define NVIM_UI_BRIDGE_H
+
+#include <uv.h>
+
+#include "nvim/ui.h"
+#include "nvim/event/defs.h"
+
+typedef struct ui_bridge_data UIBridgeData;
+typedef void(*ui_main_fn)(UIBridgeData *bridge, UI *ui);
+struct ui_bridge_data {
+ UI bridge; // actual UI passed to ui_attach
+ UI *ui; // UI pointer that will have it's callback called in
+ // another thread
+ event_scheduler scheduler;
+ uv_thread_t ui_thread;
+ ui_main_fn ui_main;
+ uv_mutex_t mutex;
+ uv_cond_t cond;
+ // When the UI thread is called, the main thread will suspend until
+ // the call returns. This flag is used as a condition for the main
+ // thread to continue.
+ bool ready;
+};
+
+#define CONTINUE(b) \
+ do { \
+ UIBridgeData *d = (UIBridgeData *)b; \
+ uv_mutex_lock(&d->mutex); \
+ d->ready = true; \
+ uv_cond_signal(&d->cond); \
+ uv_mutex_unlock(&d->mutex); \
+ } while (0)
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui_bridge.h.generated.h"
+#endif
+#endif // NVIM_UI_BRIDGE_H
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 8f0e6ccfff..072084de40 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -312,7 +312,7 @@ static int included_patches[] = {
// 612,
// 611 NA
// 610 NA
- // 609,
+ 609,
// 608,
// 607,
606,