diff options
Diffstat (limited to 'src/nvim/tui/term_input.inl')
-rw-r--r-- | src/nvim/tui/term_input.inl | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/src/nvim/tui/term_input.inl b/src/nvim/tui/term_input.inl new file mode 100644 index 0000000000..0c0e6c07c9 --- /dev/null +++ b/src/nvim/tui/term_input.inl @@ -0,0 +1,246 @@ +#include <termkey.h> + +#include "nvim/ascii.h" +#include "nvim/os/os.h" +#include "nvim/os/input.h" +#include "nvim/os/rstream.h" + + +struct term_input { + int in_fd; + TermKey *tk; + uv_tty_t input_handle; + uv_timer_t timer_handle; + RBuffer *read_buffer; + RStream *read_stream; +}; + +static void forward_simple_utf8(TermKeyKey *key) +{ + size_t len = 0; + char buf[64]; + char *ptr = key->utf8; + + while (*ptr) { + if (*ptr == '<') { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>"); + } else { + buf[len++] = *ptr; + } + ptr++; + } + + buf[len] = 0; + input_enqueue((String){.data = buf, .size = len}); +} + +static void forward_modified_utf8(TermKey *tk, TermKeyKey *key) +{ + size_t len; + char buf[64]; + + if (key->type == TERMKEY_TYPE_KEYSYM + && key->code.sym == TERMKEY_SYM_ESCAPE) { + len = (size_t)snprintf(buf, sizeof(buf), "<Esc>"); + } else { + len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); + } + + input_enqueue((String){.data = buf, .size = len}); +} + +static void forward_mouse_event(TermKey *tk, TermKeyKey *key) +{ + char buf[64]; + size_t len = 0; + int button, row, col; + TermKeyMouseEvent ev; + termkey_interpret_mouse(tk, key, &ev, &button, &row, &col); + + if (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG) { + return; + } + + row--; col--; // Termkey uses 1-based coordinates + buf[len++] = '<'; + + if (key->modifiers & TERMKEY_KEYMOD_SHIFT) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-"); + } + + if (key->modifiers & TERMKEY_KEYMOD_CTRL) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-"); + } + + if (key->modifiers & TERMKEY_KEYMOD_ALT) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-"); + } + + if (button == 1) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Left"); + } else if (button == 2) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Middle"); + } else if (button == 3) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Right"); + } + + if (ev == TERMKEY_MOUSE_PRESS) { + if (button == 4) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp"); + } else if (button == 5) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown"); + } else { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); + } + } else if (ev == TERMKEY_MOUSE_DRAG) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Drag"); + } + + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row); + input_enqueue((String){.data = buf, .size = len}); +} + +static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) +{ + return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key); +} + +static void timer_cb(uv_timer_t *handle); + +static int get_key_code_timeout(void) +{ + Integer ms = 0; + bool timeout = false; + // Check 'timeout' and 'ttimeout' to determine if we should send ESC + // after 'ttimeoutlen'. See :help 'ttimeout' for more information + Error err; + timeout = vim_get_option(cstr_as_string("timeout"), &err).data.boolean; + if (!timeout) { + timeout = vim_get_option(cstr_as_string("ttimeout"), &err).data.boolean; + } + + if (timeout) { + ms = vim_get_option(cstr_as_string("ttimeoutlen"), &err).data.integer; + } + + return (int)ms; +} + +static void tk_getkeys(TermInput *input, bool force) +{ + TermKeyKey key; + TermKeyResult result; + + while ((result = tk_getkey(input->tk, &key, force)) == TERMKEY_RES_KEY) { + if (key.type == TERMKEY_TYPE_UNICODE && !key.modifiers) { + forward_simple_utf8(&key); + } else if (key.type == TERMKEY_TYPE_UNICODE || + key.type == TERMKEY_TYPE_FUNCTION || + key.type == TERMKEY_TYPE_KEYSYM) { + forward_modified_utf8(input->tk, &key); + } else if (key.type == TERMKEY_TYPE_MOUSE) { + forward_mouse_event(input->tk, &key); + } + } + + if (result != TERMKEY_RES_AGAIN) { + return; + } + + int ms = get_key_code_timeout(); + + if (ms > 0) { + // Stop the current timer if already running + uv_timer_stop(&input->timer_handle); + uv_timer_start(&input->timer_handle, timer_cb, (uint32_t)ms, 0); + } else { + tk_getkeys(input, true); + } +} + + +static void timer_cb(uv_timer_t *handle) +{ + tk_getkeys(handle->data, true); +} + +static void read_cb(RStream *rstream, void *rstream_data, bool eof) +{ + if (eof) { + input_done(); + return; + } + + TermInput *input = rstream_data; + + do { + char *ptr = rbuffer_read_ptr(input->read_buffer); + size_t len = rbuffer_pending(input->read_buffer); + if (len > 1 && ptr[0] == ESC && ptr[1] == NUL) { + // skip the ESC and NUL and push one <esc> to the input buffer + termkey_push_bytes(input->tk, ptr, 1); + rbuffer_consumed(input->read_buffer, 2); + tk_getkeys(input, true); + continue; + } + // Find the next 'esc' and push everything up to it(excluding) + size_t i; + for (i = ptr[0] == ESC ? 1 : 0; i < len; i++) { + if (ptr[i] == '\x1b') { + break; + } + } + size_t consumed = termkey_push_bytes(input->tk, ptr, i); + rbuffer_consumed(input->read_buffer, consumed); + tk_getkeys(input, false); + } while (rbuffer_pending(input->read_buffer)); +} + +static TermInput *term_input_new(void) +{ + TermInput *rv = xmalloc(sizeof(TermInput)); + // read input from stderr if stdin is not a tty + rv->in_fd = os_isatty(0) ? 0 : (os_isatty(2) ? 2 : 0); + + // Set terminal encoding based on environment(taken from libtermkey source + // code) + const char *e; + int flags = 0; + if (((e = os_getenv("LANG")) || (e = os_getenv("LC_MESSAGES")) + || (e = os_getenv("LC_ALL"))) && (e = strchr(e, '.')) && e++ && + (strcasecmp(e, "UTF-8") == 0 || strcasecmp(e, "UTF8") == 0)) { + flags |= TERMKEY_FLAG_UTF8; + } else { + flags |= TERMKEY_FLAG_RAW; + } + + rv->tk = termkey_new_abstract(os_getenv("TERM"), flags); + int curflags = termkey_get_canonflags(rv->tk); + termkey_set_canonflags(rv->tk, curflags | TERMKEY_CANON_DELBS); + // setup input handle + uv_tty_init(uv_default_loop(), &rv->input_handle, rv->in_fd, 1); + uv_tty_set_mode(&rv->input_handle, UV_TTY_MODE_RAW); + rv->input_handle.data = NULL; + rv->read_buffer = rbuffer_new(0xfff); + rv->read_stream = rstream_new(read_cb, rv->read_buffer, rv); + rstream_set_stream(rv->read_stream, (uv_stream_t *)&rv->input_handle); + rstream_start(rv->read_stream); + // initialize a timer handle for handling ESC with libtermkey + uv_timer_init(uv_default_loop(), &rv->timer_handle); + rv->timer_handle.data = rv; + return rv; +} + +static void term_input_destroy(TermInput *input) +{ + uv_tty_reset_mode(); + uv_timer_stop(&input->timer_handle); + rstream_stop(input->read_stream); + rstream_free(input->read_stream); + uv_close((uv_handle_t *)&input->input_handle, NULL); + uv_close((uv_handle_t *)&input->timer_handle, NULL); + termkey_destroy(input->tk); + event_poll(0); // Run once to remove references to input/timer handles + free(input->input_handle.data); + free(input); +} |