diff options
author | Thiago de Arruda <tpadilha84@gmail.com> | 2015-09-07 07:47:17 -0300 |
---|---|---|
committer | Thiago de Arruda <tpadilha84@gmail.com> | 2015-09-07 07:47:17 -0300 |
commit | bb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88 (patch) | |
tree | 499d8bcf29973994fbcbdc0b19bd9eb0a67ce726 /src | |
parent | f39ac698241885137e77efa4edeee7be21dd8deb (diff) | |
parent | eb001a4abd2fbc740547c127807b2fc8367cc187 (diff) | |
download | rneovim-bb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88.tar.gz rneovim-bb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88.tar.bz2 rneovim-bb46cc2c9ce9a36f19df5c29a403c1feb4dbdf88.zip |
Merge PR #3246 'Run builtin TUI in a another thread'
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/nvim/event/defs.h | 3 | ||||
-rw-r--r-- | src/nvim/event/loop.c | 42 | ||||
-rw-r--r-- | src/nvim/event/loop.h | 5 | ||||
-rw-r--r-- | src/nvim/event/queue.c | 38 | ||||
-rw-r--r-- | src/nvim/log.c | 24 | ||||
-rw-r--r-- | src/nvim/main.c | 1 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/channel.c | 3 | ||||
-rw-r--r-- | src/nvim/tui/input.c (renamed from src/nvim/tui/term_input.inl) | 146 | ||||
-rw-r--r-- | src/nvim/tui/input.h | 23 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 435 | ||||
-rw-r--r-- | src/nvim/ugrid.c | 137 | ||||
-rw-r--r-- | src/nvim/ugrid.h | 40 | ||||
-rw-r--r-- | src/nvim/ui_bridge.c | 347 | ||||
-rw-r--r-- | src/nvim/ui_bridge.h | 40 |
15 files changed, 975 insertions, 312 deletions
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, 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 +152,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 +265,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 +310,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 +351,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 +384,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 +458,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 +468,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 +496,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 +511,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 +549,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) @@ -617,7 +628,7 @@ 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); + term_input_set_encoding(&data->input, enc); } static void invalidate(UI *ui, int top, int bot, int left, int right) @@ -698,8 +709,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 +893,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 +902,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_bridge.c b/src/nvim/ui_bridge.c new file mode 100644 index 0000000000..6e1a27cc9c --- /dev/null +++ b/src/nvim/ui_bridge.c @@ -0,0 +1,347 @@ +// 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->bridge.set_encoding = ui_bridge_set_encoding; + 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]); +} + +static void ui_bridge_set_encoding(UI *b, char* enc) +{ + UI_CALL(b, set_encoding, 2, b, xstrdup(enc)); +} +static void ui_bridge_set_encoding_event(void **argv) +{ + UI *ui = UI(argv[0]); + ui->set_encoding(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 |