aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThiago de Arruda <tpadilha84@gmail.com>2015-09-07 07:47:17 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2015-09-07 07:47:17 -0300
commitbb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88 (patch)
tree499d8bcf29973994fbcbdc0b19bd9eb0a67ce726
parentf39ac698241885137e77efa4edeee7be21dd8deb (diff)
parenteb001a4abd2fbc740547c127807b2fc8367cc187 (diff)
downloadrneovim-bb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88.tar.gz
rneovim-bb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88.tar.bz2
rneovim-bb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88.zip
Merge PR #3246 'Run builtin TUI in a another thread'
-rw-r--r--.ci/common/build.sh24
-rwxr-xr-x.ci/run_tests.sh8
-rw-r--r--.travis.yml7
-rw-r--r--contrib/gdb/neovim_gdb.vim (renamed from contrib/neovim_gdb/neovim_gdb.vim)0
-rw-r--r--contrib/gdb/nvim-gdb-pretty-printers.py92
-rw-r--r--src/nvim/CMakeLists.txt3
-rw-r--r--src/nvim/event/defs.h3
-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/log.c24
-rw-r--r--src/nvim/main.c1
-rw-r--r--src/nvim/msgpack_rpc/channel.c3
-rw-r--r--src/nvim/tui/input.c (renamed from src/nvim/tui/term_input.inl)146
-rw-r--r--src/nvim/tui/input.h23
-rw-r--r--src/nvim/tui/tui.c435
-rw-r--r--src/nvim/ugrid.c137
-rw-r--r--src/nvim/ugrid.h40
-rw-r--r--src/nvim/ui_bridge.c347
-rw-r--r--src/nvim/ui_bridge.h40
20 files changed, 1091 insertions, 327 deletions
diff --git a/.ci/common/build.sh b/.ci/common/build.sh
index 0140be3637..34bba42af3 100644
--- a/.ci/common/build.sh
+++ b/.ci/common/build.sh
@@ -61,19 +61,25 @@ build_nvim() {
exit 1
fi
- echo "Building libnvim."
- if ! ${MAKE_CMD} libnvim; then
- exit 1
- fi
+ if [ "$CLANG_SANITIZER" != "TSAN" ]; then
+ echo "Building libnvim."
+ if ! ${MAKE_CMD} libnvim; then
+ exit 1
+ fi
- echo "Building nvim-test."
- if ! ${MAKE_CMD} nvim-test; then
- exit 1
+ echo "Building nvim-test."
+ if ! ${MAKE_CMD} nvim-test; then
+ exit 1
+ fi
fi
# Invoke nvim to trigger *San early.
- bin/nvim --version
- bin/nvim -u NONE -e -c ':qall'
+ if ! (bin/nvim --version && bin/nvim -u NONE -e -c ':qall'); then
+ asan_check "${LOG_DIR}"
+ exit 1
+ fi
+ asan_check "${LOG_DIR}"
+
cd "${TRAVIS_BUILD_DIR}"
}
diff --git a/.ci/run_tests.sh b/.ci/run_tests.sh
index f3a6466c5e..656ea16e37 100755
--- a/.ci/run_tests.sh
+++ b/.ci/run_tests.sh
@@ -17,8 +17,12 @@ source "${CI_DIR}/common/test.sh"
build_nvim
-run_unittests
-run_functionaltests
+if [ "$CLANG_SANITIZER" != "TSAN" ]; then
+ # Additional threads are only created when the builtin UI starts, which
+ # doesn't happen in the unit/functional tests
+ run_unittests
+ run_functionaltests
+fi
run_oldtests
install_nvim
diff --git a/.travis.yml b/.travis.yml
index becc7e8d3d..5f88e209fc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -77,10 +77,9 @@ matrix:
- os: linux
compiler: clang-3.6
env: GCOV=llvm-cov-3.6 CLANG_SANITIZER=MSAN
- # FIXME (tarruda): Uncomment when TSan tests don't hang anymore.
- #- os: linux
- # compiler: clang-3.6
- # env: GCOV=llvm-cov-3.6 CLANG_SANITIZER=TSAN
+ - os: linux
+ compiler: clang-3.6
+ env: CLANG_SANITIZER=TSAN
- os: osx
compiler: clang
env: GCOV=gcov
diff --git a/contrib/neovim_gdb/neovim_gdb.vim b/contrib/gdb/neovim_gdb.vim
index d61e7bc0cc..d61e7bc0cc 100644
--- a/contrib/neovim_gdb/neovim_gdb.vim
+++ b/contrib/gdb/neovim_gdb.vim
diff --git a/contrib/gdb/nvim-gdb-pretty-printers.py b/contrib/gdb/nvim-gdb-pretty-printers.py
new file mode 100644
index 0000000000..609ceeb7ab
--- /dev/null
+++ b/contrib/gdb/nvim-gdb-pretty-printers.py
@@ -0,0 +1,92 @@
+# Register a gdb pretty printer for UGrid instances. Usage:
+#
+# - start gdb
+# - run `source contrib/gdb/nvim-gdb-pretty-printers.py`
+# - when a `UGrid` pointer can be evaluated in the current frame, just print
+# it's value normally: `p *grid` (assuming `grid` is the variable name
+# holding the pointer)
+# - highlighting can be activated by setting the NVIM_GDB_HIGHLIGHT_UGRID
+# environment variable(only xterm-compatible terminals supported). This
+# can be done while gdb is running through the python interface:
+# `python os.environ['NVIM_GDB_HIGHLIGHT_UGRID'] = '1'`
+import os
+import gdb
+import gdb.printing
+
+
+SGR0 = '\x1b(B\x1b[m'
+
+
+def get_color_code(bg, color_num):
+ if color_num < 16:
+ prefix = 3
+ if color_num > 7:
+ prefix = 9
+ if bg:
+ prefix += 1
+ color_num %= 8
+ else:
+ prefix = '48;5;' if bg else '38;5;'
+ return '\x1b[{0}{1}m'.format(prefix, color_num)
+
+
+def highlight(attrs):
+ fg, bg = [int(attrs['foreground']), int(attrs['background'])]
+ rv = [SGR0] # start with sgr0
+ if fg != -1:
+ rv.append(get_color_code(False, fg))
+ if bg != -1:
+ rv.append(get_color_code(True, bg))
+ if bool(attrs['bold']):
+ rv.append('\x1b[1m')
+ if bool(attrs['italic']):
+ rv.append('\x1b[3m')
+ if bool(attrs['undercurl']) or bool(attrs['underline']):
+ rv.append('\x1b[4m')
+ if bool(attrs['reverse']):
+ rv.append('\x1b[7m')
+ return ''.join(rv)
+
+
+class UGridPrinter(object):
+ def __init__(self, val):
+ self.val = val
+
+ def to_string(self):
+ do_hl = (os.getenv('NVIM_GDB_HIGHLIGHT_UGRID') and
+ os.getenv('NVIM_GDB_HIGHLIGHT_UGRID') != '0')
+ grid = self.val
+ height = int(grid['height'])
+ width = int(grid['width'])
+ delimiter = '-' * (width + 2)
+ rows = [delimiter]
+ for row in range(height):
+ cols = []
+ if do_hl:
+ cols.append(SGR0)
+ curhl = None
+ for col in range(width):
+ cell = grid['cells'][row][col]
+ if do_hl:
+ hl = highlight(cell['attrs'])
+ if hl != curhl:
+ cols.append(hl)
+ curhl = hl
+ cols.append(cell['data'].string('utf-8'))
+ if do_hl:
+ cols.append(SGR0)
+ rows.append('|' + ''.join(cols) + '|')
+ rows.append(delimiter)
+ return '\n' + '\n'.join(rows)
+
+ def display_hint(self):
+ return 'hint'
+
+
+def pretty_printers():
+ pp = gdb.printing.RegexpCollectionPrettyPrinter('nvim')
+ pp.add_printer('UGrid', '^ugrid$', UGridPrinter)
+ return pp
+
+
+gdb.printing.register_pretty_printer(gdb, pretty_printers(), replace=True)
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 5e547ed290..ba08ed1c82 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)
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/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/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..dd2b813b1c 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();
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/tui/term_input.inl b/src/nvim/tui/input.c
index c396557160..b680e885df 100644
--- a/src/nvim/tui/term_input.inl
+++ b/src/nvim/tui/input.c
@@ -1,21 +1,86 @@
-#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)
+ }
+ input->tk = termkey_new_abstract(term, 0);
+ 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);
+}
+
+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);
+}
+
+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 +98,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 +113,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 +164,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 +240,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 +292,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 +340,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..b2bb80a092 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,41 @@ 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;
+ ui->set_encoding = tui_set_encoding;
+ 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 +117,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 +132,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, da