diff options
-rw-r--r-- | src/nvim/tui/input.c | 72 | ||||
-rw-r--r-- | src/nvim/tui/input.h | 1 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 224 |
3 files changed, 198 insertions, 99 deletions
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index dd62df964b..b680e885df 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -15,42 +15,46 @@ # include "tui/input.c.generated.h" #endif -TermInput *term_input_new(void) +void term_input_init(TermInput *input, Loop *loop) { - TermInput *rv = xmalloc(sizeof(TermInput)); - rv->paste_enabled = false; - rv->in_fd = 0; + 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) } - rv->tk = termkey_new_abstract(term, 0); - int curflags = termkey_get_canonflags(rv->tk); - termkey_set_canonflags(rv->tk, curflags | TERMKEY_CANON_DELBS); + 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, &rv->read_stream, rv->in_fd, 0xfff, rv); - rstream_start(&rv->read_stream, read_cb); + 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, &rv->timer_handle, rv); + 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); - return rv; } 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); +} + +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) @@ -60,6 +64,24 @@ void term_input_set_encoding(TermInput *input, char* enc) 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) { size_t len = 0; @@ -76,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) @@ -91,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) @@ -142,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) @@ -218,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; } @@ -270,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; } @@ -318,6 +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); } diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 4886f14001..033f53b4e2 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -12,6 +12,7 @@ typedef struct term_input { bool paste_enabled; TermKey *tk; TimeWatcher timer_handle; + Loop *loop; Stream read_stream; } TermInput; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index a5a6783927..b2bb80a092 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -22,6 +22,7 @@ #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. @@ -33,14 +34,21 @@ typedef struct { } Rect; 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; + 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 out_fd; @@ -66,11 +74,38 @@ static bool volatile got_winch = false; UI *tui_start(void) { - TUIData *data = xcalloc(1, sizeof(TUIData)); UI *ui = xcalloc(1, sizeof(UI)); - ui->data = data; - data->print_attrs = EMPTY_ATTRS; - ugrid_init(&data->grid); + 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; @@ -82,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) { @@ -101,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); @@ -169,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); +} + +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) @@ -521,16 +579,34 @@ static void tui_flush(UI *ui) 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) @@ -552,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) @@ -633,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) @@ -817,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) { |