aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tui/term_input.inl
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/tui/term_input.inl')
-rw-r--r--src/nvim/tui/term_input.inl246
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);
+}