diff options
Diffstat (limited to 'src/nvim/tui')
-rw-r--r-- | src/nvim/tui/input.c | 386 | ||||
-rw-r--r-- | src/nvim/tui/input.h | 32 | ||||
-rw-r--r-- | src/nvim/tui/input_defs.h | 5 | ||||
-rw-r--r-- | src/nvim/tui/terminfo.c | 5 | ||||
-rw-r--r-- | src/nvim/tui/terminfo.h | 9 | ||||
-rw-r--r-- | src/nvim/tui/terminfo_defs.h | 11 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 483 | ||||
-rw-r--r-- | src/nvim/tui/tui.h | 21 |
8 files changed, 481 insertions, 471 deletions
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 733aa25f03..bdbb5e4872 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -1,28 +1,23 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" -#include "nvim/charset.h" #include "nvim/event/defs.h" -#include "nvim/log.h" -#include "nvim/macros.h" +#include "nvim/func_attr.h" +#include "nvim/macros_defs.h" #include "nvim/main.h" -#include "nvim/map.h" +#include "nvim/map_defs.h" #include "nvim/memory.h" -#include "nvim/option.h" -#include "nvim/os/input.h" +#include "nvim/option_vars.h" #include "nvim/os/os.h" +#include "nvim/strings.h" #include "nvim/tui/input.h" #include "nvim/tui/input_defs.h" #include "nvim/tui/tui.h" -#include "nvim/types.h" #include "nvim/ui_client.h" #ifdef MSWIN # include "nvim/os/os_win_console.h" @@ -30,10 +25,11 @@ #include "nvim/event/rstream.h" #include "nvim/msgpack_rpc/channel.h" +#define READ_STREAM_SIZE 0xfff #define KEY_BUFFER_SIZE 0xfff static const struct kitty_key_map_entry { - KittyKey key; + int key; const char *name; } kitty_key_map_entry[] = { { KITTY_KEY_ESCAPE, "Esc" }, @@ -115,7 +111,7 @@ static const struct kitty_key_map_entry { { KITTY_KEY_KP_BEGIN, "kOrigin" }, }; -static Map(KittyKey, cstr_t) kitty_key_map = MAP_INIT; +static PMap(int) kitty_key_map = MAP_INIT; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/input.c.generated.h" @@ -126,19 +122,13 @@ void tinput_init(TermInput *input, Loop *loop) input->loop = loop; input->paste = 0; input->in_fd = STDIN_FILENO; - input->waiting_for_bg_response = 0; - input->extkeys_type = kExtkeysNone; - // The main thread is waiting for the UI thread to call CONTINUE, so it can - // safely access global variables. + input->key_encoding = kKeyEncodingLegacy; input->ttimeout = (bool)p_ttimeout; input->ttimeoutlen = p_ttm; input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); - uv_mutex_init(&input->key_buffer_mutex); - uv_cond_init(&input->key_buffer_cond); for (size_t i = 0; i < ARRAY_SIZE(kitty_key_map_entry); i++) { - map_put(KittyKey, cstr_t)(&kitty_key_map, kitty_key_map_entry[i].key, - kitty_key_map_entry[i].name); + pmap_put(int)(&kitty_key_map, kitty_key_map_entry[i].key, (ptr_t)kitty_key_map_entry[i].name); } input->in_fd = STDIN_FILENO; @@ -157,17 +147,17 @@ void tinput_init(TermInput *input, Loop *loop) termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); // setup input handle - rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff); + rstream_init_fd(loop, &input->read_stream, input->in_fd, READ_STREAM_SIZE); + termkey_set_buffer_size(input->tk, rbuffer_capacity(input->read_stream.buffer)); + // initialize a timer handle for handling ESC with libtermkey time_watcher_init(loop, &input->timer_handle, input); } void tinput_destroy(TermInput *input) { - map_destroy(KittyKey, cstr_t)(&kitty_key_map); + map_destroy(int, &kitty_key_map); rbuffer_free(input->key_buffer); - uv_mutex_destroy(&input->key_buffer_mutex); - uv_cond_destroy(&input->key_buffer_cond); time_watcher_close(&input->timer_handle, NULL); stream_close(&input->read_stream, NULL, NULL); termkey_destroy(input->tk); @@ -185,13 +175,14 @@ void tinput_stop(TermInput *input) } static void tinput_done_event(void **argv) + FUNC_ATTR_NORETURN { - input_done(); + os_exit(1); } -static void tinput_wait_enqueue(void **argv) +/// Send all pending input in key buffer to Nvim server. +static void tinput_flush(TermInput *input) { - TermInput *input = argv[0]; if (input->paste) { // produce exactly one paste event const size_t len = rbuffer_size(input->key_buffer); String keys = { .data = xmallocz(len), .size = len }; @@ -207,7 +198,7 @@ static void tinput_wait_enqueue(void **argv) input->paste = 2; } rbuffer_reset(input->key_buffer); - } else { // enqueue input for the main thread or Nvim server + } else { // enqueue input RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { const String keys = { .data = buf, .size = len }; MAXSIZE_TEMP_ARRAY(args, 1); @@ -221,42 +212,66 @@ static void tinput_wait_enqueue(void **argv) } } -static void tinput_flush(TermInput *input, bool wait_until_empty) -{ - size_t drain_boundary = wait_until_empty ? 0 : 0xff; - do { - tinput_wait_enqueue((void **)&input); - } while (rbuffer_size(input->key_buffer) > drain_boundary); -} - static void tinput_enqueue(TermInput *input, char *buf, size_t size) { if (rbuffer_size(input->key_buffer) > rbuffer_capacity(input->key_buffer) - 0xff) { // don't ever let the buffer get too full or we risk putting incomplete keys // into it - tinput_flush(input, false); + tinput_flush(input); } rbuffer_write(input->key_buffer, buf, size); } +/// Handle TERMKEY_KEYMOD_* modifiers, i.e. Shift, Alt and Ctrl. +/// +/// @return The number of bytes written into "buf", excluding the final NUL. +static size_t handle_termkey_modifiers(TermKeyKey *key, char *buf, size_t buflen) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t len = 0; + if (key->modifiers & TERMKEY_KEYMOD_SHIFT) { // Shift + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-"); + } + if (key->modifiers & TERMKEY_KEYMOD_ALT) { // Alt + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-"); + } + if (key->modifiers & TERMKEY_KEYMOD_CTRL) { // Ctrl + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-"); + } + assert(len < buflen); + return len; +} + +/// Handle modifiers not handled by libtermkey. +/// Currently only Super ("D-") and Meta ("T-") are supported in Nvim. +/// +/// @return The number of bytes written into "buf", excluding the final NUL. +static size_t handle_more_modifiers(TermKeyKey *key, char *buf, size_t buflen) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t len = 0; + if (key->modifiers & 8) { // Super + len += (size_t)snprintf(buf + len, buflen - len, "D-"); + } + if (key->modifiers & 32) { // Meta + len += (size_t)snprintf(buf + len, buflen - len, "T-"); + } + assert(len < buflen); + return len; +} + static void handle_kitty_key_protocol(TermInput *input, TermKeyKey *key) { - const char *name = map_get(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint); + const char *name = pmap_get(int)(&kitty_key_map, (int)key->code.codepoint); if (name) { char buf[64]; size_t len = 0; buf[len++] = '<'; - if (key->modifiers & TERMKEY_KEYMOD_SHIFT) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-"); - } - if (key->modifiers & TERMKEY_KEYMOD_ALT) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-"); - } - if (key->modifiers & TERMKEY_KEYMOD_CTRL) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-"); - } + len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len); + len += handle_more_modifiers(key, buf + len, sizeof(buf) - len); len += (size_t)snprintf(buf + len, sizeof(buf) - len, "%s>", name); + assert(len < sizeof(buf)); tinput_enqueue(input, buf, len); } } @@ -268,7 +283,7 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key) char *ptr = key->utf8; if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF - && map_has(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint)) { + && map_has(int, &kitty_key_map, (int)key->code.codepoint)) { handle_kitty_key_protocol(input, key); return; } @@ -278,6 +293,7 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key) } else { buf[len++] = *ptr; } + assert(len < sizeof(buf)); ptr++; } @@ -297,8 +313,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) } else { assert(key->modifiers); if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF - && map_has(KittyKey, cstr_t)(&kitty_key_map, - (KittyKey)key->code.codepoint)) { + && map_has(int, &kitty_key_map, (int)key->code.codepoint)) { handle_kitty_key_protocol(input, key); return; } @@ -309,7 +324,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) if ((key->modifiers & TERMKEY_KEYMOD_CTRL) && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) && ASCII_ISUPPER(key->code.codepoint)) { - assert(len <= 62); + assert(len + 2 < sizeof(buf)); // Make room for the S- memmove(buf + 3, buf + 1, len - 1); buf[1] = 'S'; @@ -318,6 +333,16 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) } } + char more_buf[25]; + size_t more_len = handle_more_modifiers(key, more_buf, sizeof(more_buf)); + if (more_len > 0) { + assert(len + more_len < sizeof(buf)); + memmove(buf + 1 + more_len, buf + 1, len - 1); + memcpy(buf + 1, more_buf, more_len); + len += more_len; + } + + assert(len < sizeof(buf)); tinput_enqueue(input, buf, len); } @@ -341,9 +366,10 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) if (ev == TERMKEY_MOUSE_UNKNOWN && !(key->code.mouse[0] & 0x20)) { int code = key->code.mouse[0] & ~0x3c; + // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Other-buttons if (code == 66 || code == 67) { ev = TERMKEY_MOUSE_PRESS; - button = code - 60; + button = code + 4 - 64; } } @@ -355,17 +381,9 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) 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-"); - } + len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len); + // Doesn't actually work because there are only 3 bits (0x1c) for modifiers. + // len += handle_more_modifiers(key, buf + len, sizeof(buf) - len); if (button == 1) { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Left"); @@ -402,6 +420,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) } len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row); + assert(len < sizeof(buf)); tinput_enqueue(input, buf, len); } @@ -425,38 +444,11 @@ static void tk_getkeys(TermInput *input, bool force) } else if (key.type == TERMKEY_TYPE_MOUSE) { forward_mouse_event(input, &key); } else if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) { - // There is no specified limit on the number of parameters a CSI sequence can contain, so just - // allocate enough space for a large upper bound - long args[16]; - size_t nargs = 16; - unsigned long cmd; - if (termkey_interpret_csi(input->tk, &key, args, &nargs, &cmd) == TERMKEY_RES_KEY) { - uint8_t intermediate = (cmd >> 16) & 0xFF; - uint8_t initial = (cmd >> 8) & 0xFF; - uint8_t command = cmd & 0xFF; - - // Currently unused - (void)intermediate; - - if (input->waiting_for_csiu_response > 0) { - if (initial == '?' && command == 'u') { - // The first (and only) argument contains the current progressive - // enhancement flags. Only enable CSI u mode if the first bit - // (disambiguate escape codes) is not already set - if (nargs > 0 && (args[0] & 0x1) == 0) { - input->extkeys_type = kExtkeysCSIu; - } else { - input->extkeys_type = kExtkeysNone; - } - } else if (initial == '?' && command == 'c') { - // Received Primary Device Attributes response - input->waiting_for_csiu_response = 0; - tui_enable_extkeys(input->tui_data); - } else { - input->waiting_for_csiu_response--; - } - } - } + handle_unknown_csi(input, &key); + } else if (key.type == TERMKEY_TYPE_OSC || key.type == TERMKEY_TYPE_DCS) { + handle_term_response(input, &key); + } else if (key.type == TERMKEY_TYPE_MODEREPORT) { + handle_modereport(input, &key); } } @@ -486,7 +478,7 @@ static void tinput_timer_cb(TimeWatcher *watcher, void *data) handle_raw_buffer(input, true); } tk_getkeys(input, true); - tinput_flush(input, true); + tinput_flush(input); } /// Handle focus events. @@ -534,13 +526,13 @@ static HandleState handle_bracketed_paste(TermInput *input) if (enable) { // Flush before starting paste. - tinput_flush(input, true); + tinput_flush(input); // Paste phase: "first-chunk". input->paste = 1; } else if (input->paste) { // Paste phase: "last-chunk". input->paste = input->paste == 2 ? 3 : -1; - tinput_flush(input, true); + tinput_flush(input); // Paste phase: "disabled". input->paste = 0; } @@ -555,124 +547,116 @@ static HandleState handle_bracketed_paste(TermInput *input) return kNotApplicable; } -static void set_bg(char *bgvalue) +/// Handle an OSC or DCS response sequence from the terminal. +static void handle_term_response(TermInput *input, const TermKeyKey *key) + FUNC_ATTR_NONNULL_ALL { - if (ui_client_attached) { + const char *str = NULL; + if (termkey_interpret_string(input->tk, key, &str) == TERMKEY_RES_KEY) { + assert(str != NULL); + + // Send an event to nvim core. This will update the v:termresponse variable + // and fire the TermResponse event MAXSIZE_TEMP_ARRAY(args, 2); - ADD_C(args, STRING_OBJ(cstr_as_string("term_background"))); - ADD_C(args, STRING_OBJ(cstr_as_string(bgvalue))); - rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); + ADD_C(args, STATIC_CSTR_AS_OBJ("termresponse")); + + // libtermkey strips the OSC/DCS bytes from the response. We add it back in + // so that downstream consumers of v:termresponse can differentiate between + // the two. + StringBuilder response = KV_INITIAL_VALUE; + switch (key->type) { + case TERMKEY_TYPE_OSC: + kv_printf(response, "\x1b]%s", str); + break; + case TERMKEY_TYPE_DCS: + kv_printf(response, "\x1bP%s", str); + break; + default: + // Key type already checked for OSC/DCS in termkey_interpret_string + UNREACHABLE; + } + + ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size))); + rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args); + kv_destroy(response); } } -// During startup, tui.c requests the background color (see `ext.get_bg`). -// -// Here in input.c, we watch for the terminal response `\e]11;COLOR\a`. If -// COLOR matches `rgb:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, -// then compute the luminance[1] of the RGB color and classify it as light/dark -// accordingly. Note that the color components may have anywhere from one to -// four hex digits, and require scaling accordingly as values out of 4, 8, 12, -// or 16 bits. Also note the A(lpha) component is optional, and is parsed but -// ignored in the calculations. -// -// [1] https://en.wikipedia.org/wiki/Luma_%28video%29 -HandleState handle_background_color(TermInput *input) +/// Handle a mode report (DECRPM) sequence from the terminal. +static void handle_modereport(TermInput *input, const TermKeyKey *key) + FUNC_ATTR_NONNULL_ALL { - if (input->waiting_for_bg_response <= 0) { - return kNotApplicable; + int initial; + int mode; + int value; + if (termkey_interpret_modereport(input->tk, key, &initial, &mode, &value) == TERMKEY_RES_KEY) { + (void)initial; // Unused + tui_handle_term_mode(input->tui_data, (TermMode)mode, (TermModeState)value); } - size_t count = 0; - size_t component = 0; - size_t header_size = 0; - size_t num_components = 0; - size_t buf_size = rbuffer_size(input->read_stream.buffer); - uint16_t rgb[] = { 0, 0, 0 }; - uint16_t rgb_max[] = { 0, 0, 0 }; - bool eat_backslash = false; - bool done = false; - bool bad = false; - if (buf_size >= 9 - && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgb:", 9)) { - header_size = 9; - num_components = 3; - } else if (buf_size >= 10 - && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgba:", 10)) { - header_size = 10; - num_components = 4; - } else if (buf_size < 10 - && !rbuffer_cmp(input->read_stream.buffer, - "\x1b]11;rgba", buf_size)) { - // An incomplete sequence was found, waiting for the next input. - return kIncomplete; - } else { - input->waiting_for_bg_response--; - if (input->waiting_for_bg_response == 0) { - DLOG("did not get a response for terminal background query"); - } - return kNotApplicable; +} + +/// Handle a CSI sequence from the terminal that is unrecognized by libtermkey. +static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) + FUNC_ATTR_NONNULL_ALL +{ + // There is no specified limit on the number of parameters a CSI sequence can + // contain, so just allocate enough space for a large upper bound + long args[16]; + size_t nargs = 16; + unsigned long cmd; + if (termkey_interpret_csi(input->tk, key, args, &nargs, &cmd) != TERMKEY_RES_KEY) { + return; } - RBUFFER_EACH(input->read_stream.buffer, c, i) { - count = i + 1; - // Skip the header. - if (i < header_size) { - continue; - } - if (eat_backslash) { - done = true; - break; - } else if (c == '\x07') { - done = true; + + uint8_t intermediate = (cmd >> 16) & 0xFF; + uint8_t initial = (cmd >> 8) & 0xFF; + uint8_t command = cmd & 0xFF; + + // Currently unused + (void)intermediate; + + switch (command) { + case 'u': + switch (initial) { + case '?': + // Kitty keyboard protocol query response. + if (input->waiting_for_kkp_response) { + input->waiting_for_kkp_response = false; + input->key_encoding = kKeyEncodingKitty; + tui_set_key_encoding(input->tui_data); + } + break; - } else if (c == '\x1b') { - eat_backslash = true; - } else if (bad) { - // ignore - } else if ((c == '/') && (++component < num_components)) { - // work done in condition - } else if (ascii_isxdigit(c)) { - if (component < 3 && rgb_max[component] != 0xffff) { - rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf); - rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c)); + } + break; + case 'c': + switch (initial) { + case '?': + // Primary Device Attributes response + if (input->waiting_for_kkp_response) { + input->waiting_for_kkp_response = false; + + // Enable the fallback key encoding (if any) + tui_set_key_encoding(input->tui_data); } - } else { - bad = true; + + break; } + break; + default: + break; } - if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) { - rbuffer_consumed(input->read_stream.buffer, count); - double r = (double)rgb[0] / (double)rgb_max[0]; - double g = (double)rgb[1] / (double)rgb_max[1]; - double b = (double)rgb[2] / (double)rgb_max[2]; - double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 - bool is_dark = luminance < 0.5; - char *bgvalue = is_dark ? "dark" : "light"; - DLOG("bg response: %s", bgvalue); - ui_client_bg_response = is_dark ? kTrue : kFalse; - set_bg(bgvalue); - input->waiting_for_bg_response = 0; - } else if (!done && !bad) { - // An incomplete sequence was found, waiting for the next input. - return kIncomplete; - } else { - input->waiting_for_bg_response = 0; - rbuffer_consumed(input->read_stream.buffer, count); - DLOG("failed to parse bg response"); - return kNotApplicable; - } - return kComplete; } static void handle_raw_buffer(TermInput *input, bool force) { HandleState is_paste = kNotApplicable; - HandleState is_bc = kNotApplicable; do { if (!force && (handle_focus_event(input) - || (is_paste = handle_bracketed_paste(input)) != kNotApplicable - || (is_bc = handle_background_color(input)) != kNotApplicable)) { - if (is_paste == kIncomplete || is_bc == kIncomplete) { + || (is_paste = handle_bracketed_paste(input)) != kNotApplicable)) { + if (is_paste == kIncomplete) { // Wait for the next input, leaving it in the raw buffer due to an // incomplete sequence. return; @@ -710,9 +694,9 @@ static void handle_raw_buffer(TermInput *input, bool force) RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) { size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len)); // termkey_push_bytes can return (size_t)-1, so it is possible that - // `consumed > input->read_stream.buffer->size`, but since tk_getkeys is + // `consumed > rbuffer_size(input->read_stream.buffer)`, but since tk_getkeys is // called soon, it shouldn't happen. - assert(consumed <= input->read_stream.buffer->size); + assert(consumed <= rbuffer_size(input->read_stream.buffer)); rbuffer_consumed(input->read_stream.buffer, consumed); // Process the keys now: there is no guarantee `count` will // fit into libtermkey's input buffer. @@ -734,15 +718,15 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, void *da } handle_raw_buffer(input, false); - tinput_flush(input, true); + tinput_flush(input); // An incomplete sequence was found. Leave it in the raw buffer and wait for // the next input. if (rbuffer_size(input->read_stream.buffer)) { // If 'ttimeout' is not set, start the timer with a timeout of 0 to process // the next input. - long ms = input->ttimeout ? - (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0; + int64_t ms = input->ttimeout + ? (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0; // Stop the current timer if already running time_watcher_stop(&input->timer_handle); time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0); diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index d33cea6383..2743a5e286 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -1,5 +1,4 @@ -#ifndef NVIM_TUI_INPUT_H -#define NVIM_TUI_INPUT_H +#pragma once #include <stdbool.h> #include <stdint.h> @@ -10,33 +9,34 @@ #include "nvim/event/stream.h" #include "nvim/event/time.h" #include "nvim/rbuffer.h" -#include "nvim/tui/input_defs.h" +#include "nvim/tui/input_defs.h" // IWYU pragma: export #include "nvim/tui/tui.h" +#include "nvim/types_defs.h" typedef enum { - kExtkeysNone, - kExtkeysCSIu, - kExtkeysXterm, -} ExtkeysType; + kKeyEncodingLegacy, ///< Legacy key encoding + kKeyEncodingKitty, ///< Kitty keyboard protocol encoding + kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS) +} KeyEncoding; -typedef struct term_input { +typedef struct { int in_fd; // Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk int8_t paste; - bool waiting; bool ttimeout; - int8_t waiting_for_bg_response; - int8_t waiting_for_csiu_response; - ExtkeysType extkeys_type; - long ttimeoutlen; + + bool waiting_for_kkp_response; ///< True if we are expecting to receive a response to a query for + ///< Kitty keyboard protocol support + + KeyEncoding key_encoding; ///< The key encoding used by the terminal emulator + + OptInt ttimeoutlen; TermKey *tk; TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook TimeWatcher timer_handle; Loop *loop; Stream read_stream; RBuffer *key_buffer; - uv_mutex_t key_buffer_mutex; - uv_cond_t key_buffer_cond; TUIData *tui_data; } TermInput; @@ -49,5 +49,3 @@ typedef enum { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/input.h.generated.h" #endif - -#endif // NVIM_TUI_INPUT_H diff --git a/src/nvim/tui/input_defs.h b/src/nvim/tui/input_defs.h index 846cf45350..9aea8460c8 100644 --- a/src/nvim/tui/input_defs.h +++ b/src/nvim/tui/input_defs.h @@ -1,5 +1,4 @@ -#ifndef NVIM_TUI_INPUT_DEFS_H -#define NVIM_TUI_INPUT_DEFS_H +#pragma once typedef enum { KITTY_KEY_ESCAPE = 57344, @@ -114,5 +113,3 @@ typedef enum { KITTY_KEY_ISO_LEVEL3_SHIFT = 57453, KITTY_KEY_ISO_LEVEL5_SHIFT = 57454, } KittyKey; - -#endif // NVIM_TUI_INPUT_DEFS_H diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index d630a34ce2..3cf9650428 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // Built-in fallback terminfo entries. #include <stdbool.h> @@ -10,7 +7,7 @@ #include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/charset.h" #include "nvim/memory.h" #include "nvim/strings.h" diff --git a/src/nvim/tui/terminfo.h b/src/nvim/tui/terminfo.h index 178d384457..039c722d25 100644 --- a/src/nvim/tui/terminfo.h +++ b/src/nvim/tui/terminfo.h @@ -1,12 +1,9 @@ -#ifndef NVIM_TUI_TERMINFO_H -#define NVIM_TUI_TERMINFO_H +#pragma once -#include <unibilium.h> +#include <unibilium.h> // IWYU pragma: keep -#include "nvim/api/private/defs.h" +#include "nvim/api/private/defs.h" // IWYU pragma: keep #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/terminfo.h.generated.h" #endif - -#endif // NVIM_TUI_TERMINFO_H diff --git a/src/nvim/tui/terminfo_defs.h b/src/nvim/tui/terminfo_defs.h index 0bc4972dd3..e221e80d1e 100644 --- a/src/nvim/tui/terminfo_defs.h +++ b/src/nvim/tui/terminfo_defs.h @@ -1,14 +1,8 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // uncrustify:off -// -// Generated by scripts/update_terminfo.sh and ncurses 6.3.20211021 -// +// Generated by scripts/update_terminfo.sh and ncurses 6.4.20221231 -#ifndef NVIM_TUI_TERMINFO_DEFS_H -#define NVIM_TUI_TERMINFO_DEFS_H +#pragma once #include <stdint.h> @@ -2309,4 +2303,3 @@ static const int8_t win32con_terminfo[] = { static const int8_t xterm_256colour_terminfo[] = { 30,2,37,0,38,0,15,0,-99,1,51,6,120,116,101,114,109,45,50,53,54,99,111,108,111,114,124,120,116,101,114,109,32,119,105,116,104,32,50,53,54,32,99,111,108,111,114,115,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1,0,80,0,0,0,8,0,0,0,24,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,0,0,0,0,1,0,0,0,4,0,6,0,8,0,25,0,30,0,38,0,42,0,46,0,-1,-1,57,0,74,0,76,0,80,0,87,0,-1,-1,89,0,102,0,-1,-1,106,0,110,0,120,0,124,0,-1,-1,-1,-1,-128,0,-124,0,-119,0,-114,0,-1,-1,-96,0,-91,0,-86,0,-1,-1,-81,0,-76,0,-71,0,-66,0,-57,0,-53,0,-46,0,-1,-1,-28,0,-23,0,-17,0,-11,0,-1,-1,-1,-1,-1,-1,7,1,-1,-1,-1,-1,-1,-1,25,1,-1,-1,29,1,-1,-1,-1,-1,-1,-1,31,1,-1,-1,36,1,-1,-1,-1,-1,-1,-1,-1,-1,40,1,44,1,50,1,54,1,58,1,62,1,68,1,74,1,80,1,86,1,92,1,96,1,-1,-1,101,1,-1,-1,105,1,110,1,115,1,119,1,126,1,-1,-1,-123,1,-119,1,-111,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-103,1,-94,1,-85,1,-1,-1,-82,1,-73,1,-64,1,-55,1,-46,1,-37,1,-28,1,-19,1,-10,1,-1,1,-1,-1,-1,-1,-1,-1,8,2,12,2,17,2,22,2,42,2,51,2,-1,-1,-1,-1,69,2,72,2,83,2,86,2,88,2,91,2,-72,2,-1,-1,-69,2,-1,-1,-1,-1,-1,-1,-1,-1,-67,2,-63,2,-59,2,-55,2,-51,2,-1,-1,-1,-1,-47,2,-1,-1,6,3,-1,-1,-1,-1,10,3,16,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,22,3,26,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,30,3,-1,-1,-1,-1,37,3,-1,-1,-1,-1,-1,-1,-1,-1,44,3,51,3,58,3,-1,-1,-1,-1,65,3,-1,-1,72,3,-1,-1,-1,-1,-1,-1,79,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,3,92,3,98,3,105,3,112,3,119,3,126,3,-122,3,-114,3,-106,3,-98,3,-90,3,-82,3,-74,3,-66,3,-59,3,-52,3,-45,3,-38,3,-30,3,-22,3,-14,3,-6,3,2,4,10,4,18,4,26,4,33,4,40,4,47,4,54,4,62,4,70,4,78,4,86,4,94,4,102,4,110,4,118,4,125,4,-124,4,-117,4,-110,4,-102,4,-94,4,-86,4,-78,4,-70,4,-62,4,-54,4,-46,4,-39,4,-32,4,-25,4,-20,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-13,4,-2,4,3,5,22,5,26,5,35,5,42,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-120,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-115,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-109,5,-1,-1,-1,-1,-1,-1,-105,5,-42,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,22,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,45,6,48,6,27,91,90,0,7,0,13,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,114,0,27,91,51,103,0,27,91,72,27,91,50,74,0,27,91,75,0,27,91,74,0,27,91,37,105,37,112,49,37,100,71,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,72,0,10,0,27,91,72,0,27,91,63,50,53,108,0,8,0,27,91,63,49,50,108,27,91,63,50,53,104,0,27,91,67,0,27,91,65,0,27,91,63,49,50,59,50,53,104,0,27,91,80,0,27,91,77,0,27,40,48,0,27,91,53,109,0,27,91,49,109,0,27,91,63,49,48,52,57,104,27,91,50,50,59,48,59,48,116,0,27,91,50,109,0,27,91,52,104,0,27,91,56,109,0,27,91,55,109,0,27,91,55,109,0,27,91,52,109,0,27,91,37,112,49,37,100,88,0,27,40,66,0,27,40,66,27,91,109,0,27,91,63,49,48,52,57,108,27,91,50,51,59,48,59,48,116,0,27,91,52,108,0,27,91,50,55,109,0,27,91,50,52,109,0,27,91,63,53,104,36,60,49,48,48,47,62,27,91,63,53,108,0,27,91,33,112,27,91,63,51,59,52,108,27,91,52,108,27,62,0,27,91,76,0,8,0,27,91,51,126,0,27,79,66,0,27,79,80,0,27,91,50,49,126,0,27,79,81,0,27,79,82,0,27,79,83,0,27,91,49,53,126,0,27,91,49,55,126,0,27,91,49,56,126,0,27,91,49,57,126,0,27,91,50,48,126,0,27,79,72,0,27,91,50,126,0,27,79,68,0,27,91,54,126,0,27,91,53,126,0,27,79,67,0,27,91,49,59,50,66,0,27,91,49,59,50,65,0,27,79,65,0,27,91,63,49,108,27,62,0,27,91,63,49,104,27,61,0,27,91,63,49,48,51,52,108,0,27,91,63,49,48,51,52,104,0,27,69,0,27,91,37,112,49,37,100,80,0,27,91,37,112,49,37,100,77,0,27,91,37,112,49,37,100,66,0,27,91,37,112,49,37,100,64,0,27,91,37,112,49,37,100,83,0,27,91,37,112,49,37,100,76,0,27,91,37,112,49,37,100,68,0,27,91,37,112,49,37,100,67,0,27,91,37,112,49,37,100,84,0,27,91,37,112,49,37,100,65,0,27,91,105,0,27,91,52,105,0,27,91,53,105,0,37,112,49,37,99,27,91,37,112,50,37,123,49,125,37,45,37,100,98,0,27,99,27,93,49,48,52,7,0,27,91,33,112,27,91,63,51,59,52,108,27,91,52,108,27,62,0,27,56,0,27,91,37,105,37,112,49,37,100,100,0,27,55,0,10,0,27,77,0,37,63,37,112,57,37,116,27,40,48,37,101,27,40,66,37,59,27,91,48,37,63,37,112,54,37,116,59,49,37,59,37,63,37,112,53,37,116,59,50,37,59,37,63,37,112,50,37,116,59,52,37,59,37,63,37,112,49,37,112,51,37,124,37,116,59,55,37,59,37,63,37,112,52,37,116,59,53,37,59,37,63,37,112,55,37,116,59,56,37,59,109,0,27,72,0,9,0,27,79,119,0,27,79,121,0,27,79,117,0,27,79,113,0,27,79,115,0,96,96,97,97,102,102,103,103,105,105,106,106,107,107,108,108,109,109,110,110,111,111,112,112,113,113,114,114,115,115,116,116,117,117,118,118,119,119,120,120,121,121,122,122,123,123,124,124,125,125,126,126,0,27,91,90,0,27,91,63,55,104,0,27,91,63,55,108,0,27,79,70,0,27,79,77,0,27,91,51,59,50,126,0,27,91,49,59,50,70,0,27,91,49,59,50,72,0,27,91,50,59,50,126,0,27,91,49,59,50,68,0,27,91,54,59,50,126,0,27,91,53,59,50,126,0,27,91,49,59,50,67,0,27,91,50,51,126,0,27,91,50,52,126,0,27,91,49,59,50,80,0,27,91,49,59,50,81,0,27,91,49,59,50,82,0,27,91,49,59,50,83,0,27,91,49,53,59,50,126,0,27,91,49,55,59,50,126,0,27,91,49,56,59,50,126,0,27,91,49,57,59,50,126,0,27,91,50,48,59,50,126,0,27,91,50,49,59,50,126,0,27,91,50,51,59,50,126,0,27,91,50,52,59,50,126,0,27,91,49,59,53,80,0,27,91,49,59,53,81,0,27,91,49,59,53,82,0,27,91,49,59,53,83,0,27,91,49,53,59,53,126,0,27,91,49,55,59,53,126,0,27,91,49,56,59,53,126,0,27,91,49,57,59,53,126,0,27,91,50,48,59,53,126,0,27,91,50,49,59,53,126,0,27,91,50,51,59,53,126,0,27,91,50,52,59,53,126,0,27,91,49,59,54,80,0,27,91,49,59,54,81,0,27,91,49,59,54,82,0,27,91,49,59,54,83,0,27,91,49,53,59,54,126,0,27,91,49,55,59,54,126,0,27,91,49,56,59,54,126,0,27,91,49,57,59,54,126,0,27,91,50,48,59,54,126,0,27,91,50,49,59,54,126,0,27,91,50,51,59,54,126,0,27,91,50,52,59,54,126,0,27,91,49,59,51,80,0,27,91,49,59,51,81,0,27,91,49,59,51,82,0,27,91,49,59,51,83,0,27,91,49,53,59,51,126,0,27,91,49,55,59,51,126,0,27,91,49,56,59,51,126,0,27,91,49,57,59,51,126,0,27,91,50,48,59,51,126,0,27,91,50,49,59,51,126,0,27,91,50,51,59,51,126,0,27,91,50,52,59,51,126,0,27,91,49,59,52,80,0,27,91,49,59,52,81,0,27,91,49,59,52,82,0,27,91,49,75,0,27,91,63,54,57,108,0,27,91,37,105,37,100,59,37,100,82,0,27,91,54,110,0,27,91,63,37,91,59,48,49,50,51,52,53,54,55,56,57,93,99,0,27,91,99,0,27,91,51,57,59,52,57,109,0,27,93,49,48,52,7,0,27,93,52,59,37,112,49,37,100,59,114,103,98,58,37,112,50,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,51,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,52,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,27,92,0,27,91,51,109,0,27,91,50,51,109,0,27,91,60,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,51,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,57,37,112,49,37,123,56,125,37,45,37,100,37,101,51,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,52,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,49,48,37,112,49,37,123,56,125,37,45,37,100,37,101,52,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,63,54,57,104,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,115,0,27,108,0,27,109,0,0,2,0,0,0,74,0,-106,0,-84,3,1,1,0,0,7,0,19,0,24,0,42,0,48,0,58,0,90,0,97,0,104,0,111,0,118,0,125,0,-124,0,-117,0,-110,0,-103,0,-96,0,-89,0,-82,0,-75,0,-68,0,-61,0,-54,0,-47,0,-40,0,-33,0,-26,0,-19,0,-12,0,-5,0,2,1,9,1,16,1,23,1,30,1,37,1,44,1,51,1,58,1,65,1,72,1,79,1,86,1,93,1,100,1,107,1,114,1,121,1,-128,1,-121,1,-114,1,-107,1,-100,1,-93,1,-86,1,-79,1,-72,1,-65,1,-58,1,-54,1,-50,1,-46,1,-42,1,-38,1,-34,1,-30,1,-26,1,-22,1,-18,1,-14,1,-10,1,-4,1,1,2,0,0,3,0,6,0,9,0,12,0,15,0,18,0,21,0,24,0,27,0,32,0,37,0,42,0,47,0,52,0,56,0,61,0,66,0,71,0,76,0,81,0,87,0,93,0,99,0,105,0,111,0,117,0,123,0,-127,0,-121,0,-115,0,-110,0,-105,0,-100,0,-95,0,-90,0,-84,0,-78,0,-72,0,-66,0,-60,0,-54,0,-48,0,-42,0,-36,0,-30,0,-24,0,-18,0,-12,0,-6,0,0,1,6,1,12,1,18,1,24,1,30,1,34,1,39,1,44,1,49,1,54,1,59,1,63,1,67,1,71,1,75,1,79,1,85,1,91,1,97,1,103,1,109,1,115,1,121,1,126,1,-125,1,27,93,49,49,50,7,0,27,93,49,50,59,37,112,49,37,115,7,0,27,91,51,74,0,27,93,53,50,59,37,112,49,37,115,59,37,112,50,37,115,7,0,27,91,50,32,113,0,27,91,37,112,49,37,100,32,113,0,27,91,63,49,48,48,54,59,49,48,48,48,37,63,37,112,49,37,123,49,125,37,61,37,116,104,37,101,108,37,59,0,27,91,51,59,51,126,0,27,91,51,59,52,126,0,27,91,51,59,53,126,0,27,91,51,59,54,126,0,27,91,51,59,55,126,0,27,91,49,59,50,66,0,27,91,49,59,51,66,0,27,91,49,59,52,66,0,27,91,49,59,53,66,0,27,91,49,59,54,66,0,27,91,49,59,55,66,0,27,91,49,59,51,70,0,27,91,49,59,52,70,0,27,91,49,59,53,70,0,27,91,49,59,54,70,0,27,91,49,59,55,70,0,27,91,49,59,51,72,0,27,91,49,59,52,72,0,27,91,49,59,53,72,0,27,91,49,59,54,72,0,27,91,49,59,55,72,0,27,91,50,59,51,126,0,27,91,50,59,52,126,0,27,91,50,59,53,126,0,27,91,50,59,54,126,0,27,91,50,59,55,126,0,27,91,49,59,51,68,0,27,91,49,59,52,68,0,27,91,49,59,53,68,0,27,91,49,59,54,68,0,27,91,49,59,55,68,0,27,91,54,59,51,126,0,27,91,54,59,52,126,0,27,91,54,59,53,126,0,27,91,54,59,54,126,0,27,91,54,59,55,126,0,27,91,53,59,51,126,0,27,91,53,59,52,126,0,27,91,53,59,53,126,0,27,91,53,59,54,126,0,27,91,53,59,55,126,0,27,91,49,59,51,67,0,27,91,49,59,52,67,0,27,91,49,59,53,67,0,27,91,49,59,54,67,0,27,91,49,59,55,67,0,27,91,49,59,50,65,0,27,91,49,59,51,65,0,27,91,49,59,52,65,0,27,91,49,59,53,65,0,27,91,49,59,54,65,0,27,91,49,59,55,65,0,27,79,120,0,27,79,116,0,27,79,118,0,27,79,114,0,27,79,69,0,27,79,107,0,27,79,108,0,27,79,111,0,27,79,110,0,27,79,106,0,27,79,109,0,27,79,112,0,27,91,50,57,109,0,27,91,57,109,0,27,91,60,37,105,37,112,51,37,100,59,37,112,49,37,100,59,37,112,50,37,100,59,37,63,37,112,52,37,116,77,37,101,109,37,59,0,65,88,0,88,84,0,67,114,0,67,115,0,69,51,0,77,115,0,83,101,0,83,115,0,88,77,0,107,68,67,51,0,107,68,67,52,0,107,68,67,53,0,107,68,67,54,0,107,68,67,55,0,107,68,78,0,107,68,78,51,0,107,68,78,52,0,107,68,78,53,0,107,68,78,54,0,107,68,78,55,0,107,69,78,68,51,0,107,69,78,68,52,0,107,69,78,68,53,0,107,69,78,68,54,0,107,69,78,68,55,0,107,72,79,77,51,0,107,72,79,77,52,0,107,72,79,77,53,0,107,72,79,77,54,0,107,72,79,77,55,0,107,73,67,51,0,107,73,67,52,0,107,73,67,53,0,107,73,67,54,0,107,73,67,55,0,107,76,70,84,51,0,107,76,70,84,52,0,107,76,70,84,53,0,107,76,70,84,54,0,107,76,70,84,55,0,107,78,88,84,51,0,107,78,88,84,52,0,107,78,88,84,53,0,107,78,88,84,54,0,107,78,88,84,55,0,107,80,82,86,51,0,107,80,82,86,52,0,107,80,82,86,53,0,107,80,82,86,54,0,107,80,82,86,55,0,107,82,73,84,51,0,107,82,73,84,52,0,107,82,73,84,53,0,107,82,73,84,54,0,107,82,73,84,55,0,107,85,80,0,107,85,80,51,0,107,85,80,52,0,107,85,80,53,0,107,85,80,54,0,107,85,80,55,0,107,97,50,0,107,98,49,0,107,98,51,0,107,99,50,0,107,112,53,0,107,112,65,68,68,0,107,112,67,77,65,0,107,112,68,73,86,0,107,112,68,79,84,0,107,112,77,85,76,0,107,112,83,85,66,0,107,112,90,82,79,0,114,109,120,120,0,115,109,120,120,0,120,109,0 }; -#endif // NVIM_TUI_TERMINFO_DEFS_H diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f760e99262..197bbcabb5 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // Terminal UI functions. Invoked (by ui_client.c) on the UI process. #include <assert.h> @@ -16,33 +13,35 @@ #include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/cursor_shape.h" -#include "nvim/event/defs.h" #include "nvim/event/loop.h" -#include "nvim/event/multiqueue.h" #include "nvim/event/signal.h" #include "nvim/event/stream.h" +#include "nvim/func_attr.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" +#include "nvim/grid.h" #include "nvim/highlight_defs.h" #include "nvim/log.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/ui_client.h" -#ifdef MSWIN -# include "nvim/os/os_win_console.h" -#endif #include "nvim/tui/input.h" #include "nvim/tui/terminfo.h" #include "nvim/tui/tui.h" +#include "nvim/types_defs.h" #include "nvim/ugrid.h" #include "nvim/ui.h" +#include "nvim/ui_client.h" + +#ifdef MSWIN +# include "nvim/os/os_win_console.h" +# include "nvim/os/tty.h" +#endif // Space reserved in two output buffers to make the cursor normal or invisible // when flushing. No existing terminal will require 32 bytes to do that. @@ -58,27 +57,14 @@ #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" -#ifdef NVIM_UNIBI_HAS_VAR_FROM -# define UNIBI_SET_NUM_VAR(var, num) \ +#define UNIBI_SET_NUM_VAR(var, num) \ do { \ (var) = unibi_var_from_num((num)); \ } while (0) -# define UNIBI_SET_STR_VAR(var, str) \ +#define UNIBI_SET_STR_VAR(var, str) \ do { \ (var) = unibi_var_from_str((str)); \ } while (0) -#else -# define UNIBI_SET_NUM_VAR(var, num) \ - do { \ - (var).p = NULL; \ - (var).i = (num); \ - } while (0) -# define UNIBI_SET_STR_VAR(var, str) \ - do { \ - (var).i = INT_MIN; \ - (var).p = str; \ - } while (0) -#endif typedef struct { int top, bot, left, right; @@ -89,9 +75,6 @@ struct TUIData { unibi_var_t params[9]; char buf[OUTBUF_SIZE]; size_t bufpos; - char norm[CNORM_COMMAND_MAX_SIZE]; - char invis[CNORM_COMMAND_MAX_SIZE]; - size_t normlen, invislen; TermInput input; uv_loop_t write_loop; unibi_term *ut; @@ -117,6 +100,8 @@ struct TUIData { bool bce; bool mouse_enabled; bool mouse_move_enabled; + bool title_enabled; + bool sync_output; bool busy, is_invisible, want_invisible; bool cork, overflow; bool set_cursor_color_as_str; @@ -146,14 +131,13 @@ struct TUIData { int reset_scroll_region; int set_cursor_style, reset_cursor_style; int save_title, restore_title; - int get_bg; int set_underline_style; int set_underline_color; - int enable_extended_keys, disable_extended_keys; - int get_extkeys; + int sync; } unibi_ext; char *space_buf; bool stopped; + int seen_error_exit; int width; int height; bool rgb; @@ -165,12 +149,13 @@ static bool cursor_style_enabled = false; # include "tui/tui.c.generated.h" #endif -TUIData *tui_start(int *width, int *height, char **term) +void tui_start(TUIData **tui_p, int *width, int *height, char **term) { TUIData *tui = xcalloc(1, sizeof(TUIData)); tui->is_starting = true; tui->screenshot = NULL; tui->stopped = false; + tui->seen_error_exit = 0; tui->loop = &main_loop; kv_init(tui->invalid_regions); signal_watcher_init(tui->loop, &tui->winch_handle, tui); @@ -188,45 +173,92 @@ TUIData *tui_start(int *width, int *height, char **term) uv_timer_start(&tui->startup_delay_timer, after_startup_cb, 100, 0); + *tui_p = tui; loop_poll_events(&main_loop, 1); *width = tui->width; *height = tui->height; *term = tui->term; - return tui; } -void tui_enable_extkeys(TUIData *tui) +void tui_set_key_encoding(TUIData *tui) + FUNC_ATTR_NONNULL_ALL { - TermInput input = tui->input; - unibi_term *ut = tui->ut; + switch (tui->input.key_encoding) { + case kKeyEncodingKitty: + out(tui, S_LEN("\x1b[>1u")); + break; + case kKeyEncodingXterm: + out(tui, S_LEN("\x1b[>4;2m")); + break; + case kKeyEncodingLegacy: + break; + } +} - switch (input.extkeys_type) { - case kExtkeysCSIu: - tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", - "\x1b[>1u"); - tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", - "\x1b[<1u"); +static void tui_reset_key_encoding(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ + switch (tui->input.key_encoding) { + case kKeyEncodingKitty: + out(tui, S_LEN("\x1b[<1u")); break; - case kExtkeysXterm: - tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", - "\x1b[>4;2m"); - tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", - "\x1b[>4;0m"); + case kKeyEncodingXterm: + out(tui, S_LEN("\x1b[>4;0m")); break; - default: + case kKeyEncodingLegacy: break; } +} - unibi_out_ext(tui, tui->unibi_ext.enable_extended_keys); +/// Request the terminal's mode (DECRQM). +/// +/// @see handle_modereport +static void tui_request_term_mode(TUIData *tui, TermMode mode) + FUNC_ATTR_NONNULL_ALL +{ + // 5 bytes for \x1b[?$p, 1 byte for null terminator, 6 bytes for mode digits (more than enough) + char buf[12]; + int len = snprintf(buf, sizeof(buf), "\x1b[?%d$p", (int)mode); + assert((len > 0) && (len < (int)sizeof(buf))); + out(tui, buf, (size_t)len); } -static size_t unibi_pre_fmt_str(TUIData *tui, unsigned int unibi_index, char *buf, size_t len) +/// Handle a mode report (DECRPM) from the terminal. +void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state) + FUNC_ATTR_NONNULL_ALL { - const char *str = unibi_get_str(tui->ut, unibi_index); - if (!str) { - return 0U; + switch (state) { + case kTermModeNotRecognized: + case kTermModePermanentlySet: + case kTermModePermanentlyReset: + // If the mode is not recognized, or if the terminal emulator does not allow it to be changed, + // then there is nothing to do + break; + case kTermModeSet: + case kTermModeReset: + // The terminal supports changing the given mode + switch (mode) { + case kTermModeSynchronizedOutput: + // Ref: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 + tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync", + "\x1b[?2026%?%p1%{1}%-%tl%eh%;"); + } } - return unibi_run(str, tui->params, buf, len); +} + +/// Query the terminal emulator to see if it supports Kitty's keyboard protocol. +/// +/// Write CSI ? u followed by a primary device attributes request (CSI c). If +/// a primary device attributes response is received without first receiving an +/// answer to the progressive enhancement query (CSI u), then the terminal does +/// not support the Kitty keyboard protocol. +/// +/// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol +static void tui_query_kitty_keyboard(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ + tui->input.waiting_for_kkp_response = true; + out(tui, S_LEN("\x1b[?u\x1b[c")); } static void terminfo_start(TUIData *tui) @@ -261,11 +293,8 @@ static void terminfo_start(TUIData *tui) tui->unibi_ext.reset_scroll_region = -1; tui->unibi_ext.set_cursor_style = -1; tui->unibi_ext.reset_cursor_style = -1; - tui->unibi_ext.get_bg = -1; tui->unibi_ext.set_underline_color = -1; - tui->unibi_ext.enable_extended_keys = -1; - tui->unibi_ext.disable_extended_keys = -1; - tui->unibi_ext.get_extkeys = -1; + tui->unibi_ext.sync = -1; tui->out_fd = STDOUT_FILENO; tui->out_isatty = os_isatty(tui->out_fd); tui->input.tui_data = tui; @@ -295,7 +324,7 @@ static void terminfo_start(TUIData *tui) const char *colorterm = os_getenv("COLORTERM"); const char *termprg = os_getenv("TERM_PROGRAM"); const char *vte_version_env = os_getenv("VTE_VERSION"); - long vtev = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0; + int vtev = vte_version_env ? (int)strtol(vte_version_env, NULL, 10) : 0; bool iterm_env = termprg && strstr(termprg, "iTerm.app"); bool nsterm = (termprg && strstr(termprg, "Apple_Terminal")) || terminfo_is_term_family(term, "nsterm"); @@ -303,8 +332,8 @@ static void terminfo_start(TUIData *tui) || os_getenv("KONSOLE_PROFILE_NAME") || os_getenv("KONSOLE_DBUS_SESSION"); const char *konsolev_env = os_getenv("KONSOLE_VERSION"); - long konsolev = konsolev_env ? strtol(konsolev_env, NULL, 10) - : (konsole ? 1 : 0); + int konsolev = konsolev_env ? (int)strtol(konsolev_env, NULL, 10) + : (konsole ? 1 : 0); patch_terminfo_bugs(tui, term, colorterm, vtev, konsolev, iterm_env, nsterm); augment_terminfo(tui, term, vtev, konsolev, iterm_env, nsterm); @@ -327,28 +356,24 @@ static void terminfo_start(TUIData *tui) || terminfo_is_term_family(term, "win32con") || terminfo_is_term_family(term, "interix"); tui->bce = unibi_get_bool(tui->ut, unibi_back_color_erase); - tui->normlen = unibi_pre_fmt_str(tui, unibi_cursor_normal, - tui->norm, sizeof tui->norm); - tui->invislen = unibi_pre_fmt_str(tui, unibi_cursor_invisible, - tui->invis, sizeof tui->invis); // Set 't_Co' from the result of unibilium & fix_terminfo. t_colors = unibi_get_num(tui->ut, unibi_max_colors); // Enter alternate screen, save title, and clear. // NOTE: Do this *before* changing terminal settings. #6433 unibi_out(tui, unibi_enter_ca_mode); - // Save title/icon to the "stack". #4063 - unibi_out_ext(tui, tui->unibi_ext.save_title); unibi_out(tui, unibi_keypad_xmit); unibi_out(tui, unibi_clear_screen); - // Ask the terminal to send us the background color. - tui->input.waiting_for_bg_response = 5; - unibi_out_ext(tui, tui->unibi_ext.get_bg); + // Enable bracketed paste unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste); - // Query the terminal to see if it supports CSI u - tui->input.waiting_for_csiu_response = 5; - unibi_out_ext(tui, tui->unibi_ext.get_extkeys); + // Query support for mode 2026 (Synchronized Output). Some terminals also + // support an older DCS sequence for synchronized output, but we will only use + // mode 2026 + tui_request_term_mode(tui, kTermModeSynchronizedOutput); + + // Query the terminal to see if it supports Kitty's keyboard protocol + tui_query_kitty_keyboard(tui); int ret; uv_loop_init(&tui->write_loop); @@ -357,12 +382,7 @@ static void terminfo_start(TUIData *tui) if (ret) { ELOG("uv_tty_init failed: %s", uv_strerror(ret)); } -#ifdef MSWIN - ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_RAW); - if (ret) { - ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); - } -#else +#ifndef MSWIN int retry_count = 10; // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a // few times. #12322 @@ -396,11 +416,22 @@ static void terminfo_stop(TUIData *tui) // Reset cursor to normal before exiting alternate screen. unibi_out(tui, unibi_cursor_normal); unibi_out(tui, unibi_keypad_local); - // Disable extended keys before exiting alternate screen. - unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys); - unibi_out(tui, unibi_exit_ca_mode); - // Restore title/icon from the "stack". #4063 - unibi_out_ext(tui, tui->unibi_ext.restore_title); + + // Reset the key encoding + tui_reset_key_encoding(tui); + + // May restore old title before exiting alternate screen. + tui_set_title(tui, (String)STRING_INIT); + if (ui_client_exit_status == 0) { + ui_client_exit_status = tui->seen_error_exit; + } + // if nvim exited with nonzero status, without indicated this was an + // intentional exit (like `:1cquit`), it likely was an internal failure. + // Don't clobber the stderr error message in this case. + if (ui_client_exit_status == tui->seen_error_exit) { + // Exit alternate screen. + unibi_out(tui, unibi_exit_ca_mode); + } if (tui->cursor_color_changed) { unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } @@ -408,6 +439,11 @@ static void terminfo_stop(TUIData *tui) unibi_out_ext(tui, tui->unibi_ext.disable_bracketed_paste); // Disable focus reporting unibi_out_ext(tui, tui->unibi_ext.disable_focus_reporting); + + // Disable synchronized output + UNIBI_SET_NUM_VAR(tui->params[0], 0); + unibi_out_ext(tui, tui->unibi_ext.sync); + flush_buf(tui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&tui->output_handle, NULL); @@ -445,7 +481,7 @@ static void tui_terminal_after_startup(TUIData *tui) /// stop the terminal but allow it to restart later (like after suspend) static void tui_terminal_stop(TUIData *tui) { - if (uv_is_closing(STRUCT_CAST(uv_handle_t, &tui->output_handle))) { + if (uv_is_closing((uv_handle_t *)&tui->output_handle)) { // Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075 ELOG("TUI already stopped (race?)"); tui->stopped = true; @@ -453,14 +489,18 @@ static void tui_terminal_stop(TUIData *tui) } tinput_stop(&tui->input); signal_watcher_stop(&tui->winch_handle); + // Position the cursor on the last screen line, below all the text + cursor_goto(tui, tui->height - 1, 0); terminfo_stop(tui); } +void tui_error_exit(TUIData *tui, Integer status) +{ + tui->seen_error_exit = (int)status; +} + void tui_stop(TUIData *tui) { - if (tui->stopped) { - return; - } tui_terminal_stop(tui); stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) tinput_destroy(&tui->input); @@ -470,7 +510,7 @@ void tui_stop(TUIData *tui) } /// Returns true if UI `ui` is stopped. -static bool tui_is_stopped(TUIData *tui) +bool tui_is_stopped(TUIData *tui) { return tui->stopped; } @@ -692,15 +732,15 @@ static void final_column_wrap(TUIData *tui) /// It is undocumented, but in the majority of terminals and terminal emulators /// printing at the right margin does not cause an automatic wrap until the /// next character is printed, holding the cursor in place until then. -static void print_cell(TUIData *tui, UCell *ptr) +static void print_cell(TUIData *tui, char *buf, sattr_T attr) { UGrid *grid = &tui->grid; if (!tui->immediate_wrap_after_last_column) { // Printing the next character finally advances the cursor. final_column_wrap(tui); } - update_attrs(tui, ptr->attr); - out(tui, ptr->data, strlen(ptr->data)); + update_attrs(tui, attr); + out(tui, buf, strlen(buf)); grid->col++; if (tui->immediate_wrap_after_last_column) { // Printing at the right margin immediately advances the cursor. @@ -720,8 +760,8 @@ static bool cheap_to_print(TUIData *tui, int row, int col, int next) return false; } } - if (strlen(cell->data) > 1) { - return false; + if (schar_get_ascii(cell->data) == 0) { + return false; // not ascii } cell++; } @@ -751,11 +791,15 @@ static void cursor_goto(TUIData *tui, int row, int col) if (grid->row == -1) { goto safe_move; } - if (0 == col ? col != grid->col : - row != grid->row ? false : - 1 == col ? 2 < grid->col && cheap_to_print(tui, grid->row, 0, col) : - 2 == col ? 5 < grid->col && cheap_to_print(tui, grid->row, 0, col) : - false) { + if (0 == col + ? col != grid->col + : (row != grid->row + ? false + : (1 == col + ? (2 < grid->col && cheap_to_print(tui, grid->row, 0, col)) + : (2 == col + ? (5 < grid->col && cheap_to_print(tui, grid->row, 0, col)) + : false)))) { // Motion to left margin from anywhere else, or CR + printing chars is // even less expensive than using BSes or CUB. unibi_out(tui, unibi_carriage_return); @@ -848,14 +892,16 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool { UGrid *grid = &tui->grid; - if (grid->row == -1 && cell->data[0] == NUL) { + if (grid->row == -1 && cell->data == NUL) { // If cursor needs to repositioned and there is nothing to print, don't move cursor. return; } cursor_goto(tui, row, col); - bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data)); + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, cell->data); + bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf)); if (is_ambiwidth && is_doublewidth) { // Clear the two screen cells. // If the character is single-width in the host terminal it won't change the second cell. @@ -864,7 +910,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool cursor_goto(tui, row, col); } - print_cell(tui, cell); + print_cell(tui, buf, cell->attr); if (is_ambiwidth) { // Force repositioning cursor after printing an ambiguous-width character. @@ -993,8 +1039,10 @@ void tui_grid_clear(TUIData *tui, Integer g) { UGrid *grid = &tui->grid; ugrid_clear(grid); + // safe to clear cache at this point + schar_cache_clear_if_full(); kv_size(tui->invalid_regions) = 0; - clear_region(tui, 0, grid->height, 0, grid->width, 0); + clear_region(tui, 0, tui->height, 0, tui->width, 0); } void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col) @@ -1004,7 +1052,7 @@ void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col) tui->col = (int)col; } -CursorShape tui_cursor_decode_shape(const char *shape_str) +static CursorShape tui_cursor_decode_shape(const char *shape_str) { CursorShape shape; if (strequal(shape_str, "block")) { @@ -1097,7 +1145,7 @@ void tui_mouse_off(TUIData *tui) } } -void tui_set_mode(TUIData *tui, ModeShape mode) +static void tui_set_mode(TUIData *tui, ModeShape mode) { if (!cursor_style_enabled) { return; @@ -1108,15 +1156,13 @@ void tui_set_mode(TUIData *tui, ModeShape mode) HlAttrs aep = kv_A(tui->attrs, c.id); tui->want_invisible = aep.hl_blend == 100; - if (tui->want_invisible) { - unibi_out(tui, unibi_cursor_invisible); - } else if (aep.rgb_ae_attr & HL_INVERSE) { + if (!tui->want_invisible && aep.rgb_ae_attr & HL_INVERSE) { // We interpret "inverse" as "default" (no termcode for "inverse"...). // Hopefully the user's default cursor color is inverse. unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } else { + char hexbuf[8]; if (tui->set_cursor_color_as_str) { - char hexbuf[8]; snprintf(hexbuf, 7 + 1, "#%06x", aep.rgb_bg_color); UNIBI_SET_STR_VAR(tui->params[0], hexbuf); } else { @@ -1133,8 +1179,6 @@ void tui_set_mode(TUIData *tui, ModeShape mode) int shape; switch (c.shape) { - default: - abort(); break; case SHAPE_BLOCK: shape = 1; break; case SHAPE_HOR: @@ -1153,7 +1197,7 @@ void tui_mode_change(TUIData *tui, String mode, Integer mode_idx) // If stdin is not a TTY, the LHS of pipe may change the state of the TTY // after calling uv_tty_set_mode. So, set the mode of the TTY again here. // #13073 - if (tui->is_starting && tui->input.in_fd == STDERR_FILENO) { + if (tui->is_starting && !stdin_isatty) { int ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_NORMAL); if (ret) { ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); @@ -1174,9 +1218,8 @@ void tui_mode_change(TUIData *tui, String mode, Integer mode_idx) tui->showing_mode = (ModeShape)mode_idx; } -void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, // -V751 - Integer endrow, Integer startcol, Integer endcol, Integer rows, - Integer cols FUNC_ATTR_UNUSED) +void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, Integer endrow, Integer startcol, + Integer endcol, Integer rows, Integer cols FUNC_ATTR_UNUSED) { UGrid *grid = &tui->grid; int top = (int)startrow, bot = (int)endrow - 1; @@ -1265,6 +1308,39 @@ void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Intege invalidate(tui, 0, tui->grid.height, 0, tui->grid.width); } +/// Begin flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates, +/// begin a synchronized update. Otherwise, hide the cursor to avoid cursor jumping. +static void tui_flush_start(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ + if (tui->sync_output && tui->unibi_ext.sync != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 1); + unibi_out_ext(tui, tui->unibi_ext.sync); + } else if (!tui->is_invisible) { + unibi_out(tui, unibi_cursor_invisible); + tui->is_invisible = true; + } +} + +/// Finish flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates, +/// end a synchronized update. Otherwise, make the cursor visible again. +static void tui_flush_end(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ + if (tui->sync_output && tui->unibi_ext.sync != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 0); + unibi_out_ext(tui, tui->unibi_ext.sync); + } + bool should_invisible = tui->busy || tui->want_invisible; + if (tui->is_invisible && !should_invisible) { + unibi_out(tui, unibi_cursor_normal); + tui->is_invisible = false; + } else if (!tui->is_invisible && should_invisible) { + unibi_out(tui, unibi_cursor_invisible); + tui->is_invisible = true; + } +} + void tui_flush(TUIData *tui) { UGrid *grid = &tui->grid; @@ -1281,6 +1357,8 @@ void tui_flush(TUIData *tui) tui_busy_stop(tui); // avoid hidden cursor } + tui_flush_start(tui); + while (kv_size(tui->invalid_regions)) { Rect r = kv_pop(tui->invalid_regions); assert(r.bot <= grid->height && r.right <= grid->width); @@ -1290,7 +1368,7 @@ void tui_flush(TUIData *tui) int clear_col; for (clear_col = r.right; clear_col > 0; clear_col--) { UCell *cell = &grid->cells[row][clear_col - 1]; - if (!(cell->data[0] == ' ' && cell->data[1] == NUL + if (!(cell->data == schar_from_ascii(' ') && cell->attr == clear_attr)) { break; } @@ -1298,7 +1376,7 @@ void tui_flush(TUIData *tui) UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { print_cell_at_pos(tui, row, curcol, cell, - curcol < clear_col - 1 && (cell + 1)->data[0] == NUL); + curcol < clear_col - 1 && (cell + 1)->data == NUL); }); if (clear_col < r.right) { clear_region(tui, row, row + 1, clear_col, r.right, clear_attr); @@ -1308,6 +1386,8 @@ void tui_flush(TUIData *tui) cursor_goto(tui, tui->row, tui->col); + tui_flush_end(tui); + flush_buf(tui); } @@ -1321,16 +1401,16 @@ static void show_verbose_terminfo(TUIData *tui) Array chunks = ARRAY_DICT_INIT; Array title = ARRAY_DICT_INIT; - ADD(title, STRING_OBJ(cstr_to_string("\n\n--- Terminal info --- {{{\n"))); - ADD(title, STRING_OBJ(cstr_to_string("Title"))); + ADD(title, CSTR_TO_OBJ("\n\n--- Terminal info --- {{{\n")); + ADD(title, CSTR_TO_OBJ("Title")); ADD(chunks, ARRAY_OBJ(title)); Array info = ARRAY_DICT_INIT; String str = terminfo_info_msg(ut, tui->term); ADD(info, STRING_OBJ(str)); ADD(chunks, ARRAY_OBJ(info)); Array end_fold = ARRAY_DICT_INIT; - ADD(end_fold, STRING_OBJ(cstr_to_string("}}}\n"))); - ADD(end_fold, STRING_OBJ(cstr_to_string("Title"))); + ADD(end_fold, CSTR_TO_OBJ("}}}\n")); + ADD(end_fold, CSTR_TO_OBJ("Title")); ADD(chunks, ARRAY_OBJ(end_fold)); Array args = ARRAY_DICT_INIT; @@ -1343,10 +1423,10 @@ static void show_verbose_terminfo(TUIData *tui) api_free_array(args); } -#ifdef UNIX -static void suspend_event(void **argv) +void tui_suspend(TUIData *tui) { - TUIData *tui = argv[0]; +// on a non-UNIX system, this is a no-op +#ifdef UNIX ui_client_detach(); bool enable_mouse = tui->mouse_enabled; tui_terminal_stop(tui); @@ -1361,34 +1441,34 @@ static void suspend_event(void **argv) } stream_set_blocking(tui->input.in_fd, false); // libuv expects this ui_client_attach(tui->width, tui->height, tui->term); -} -#endif - -void tui_suspend(TUIData *tui) -{ -// on a non-UNIX system, this is a no-op -#ifdef UNIX - // 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 - multiqueue_put_event(resize_events, - event_create(suspend_event, 1, tui)); #endif } void tui_set_title(TUIData *tui, String title) { - if (!(title.data && unibi_get_str(tui->ut, unibi_to_status_line) + if (!(unibi_get_str(tui->ut, unibi_to_status_line) && unibi_get_str(tui->ut, unibi_from_status_line))) { return; } - unibi_out(tui, unibi_to_status_line); - out(tui, title.data, title.size); - unibi_out(tui, unibi_from_status_line); + if (title.size > 0) { + if (!tui->title_enabled) { + // Save title/icon to the "stack". #4063 + unibi_out_ext(tui, tui->unibi_ext.save_title); + tui->title_enabled = true; + } + unibi_out(tui, unibi_to_status_line); + out(tui, title.data, title.size); + unibi_out(tui, unibi_from_status_line); + } else if (tui->title_enabled) { + // Restore title/icon from the "stack". #4063 + unibi_out_ext(tui, tui->unibi_ext.restore_title); + tui->title_enabled = false; + } } void tui_set_icon(TUIData *tui, String icon) -{} +{ +} void tui_screenshot(TUIData *tui, String path) { @@ -1404,7 +1484,10 @@ void tui_screenshot(TUIData *tui, String path) for (int i = 0; i < grid->height; i++) { cursor_goto(tui, i, 0); for (int j = 0; j < grid->width; j++) { - print_cell(tui, &grid->cells[i][j]); + UCell cell = grid->cells[i][j]; + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, cell.data); + print_cell(tui, buf, cell.attr); } } flush_buf(tui); @@ -1432,16 +1515,18 @@ void tui_option_set(TUIData *tui, String name, Object value) if (ui_client_channel_id) { MAXSIZE_TEMP_ARRAY(args, 2); - ADD_C(args, STRING_OBJ(cstr_as_string("rgb"))); + ADD_C(args, CSTR_AS_OBJ("rgb")); ADD_C(args, BOOLEAN_OBJ(value.data.boolean)); rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); } } else if (strequal(name.data, "ttimeout")) { tui->input.ttimeout = value.data.boolean; } else if (strequal(name.data, "ttimeoutlen")) { - tui->input.ttimeoutlen = (long)value.data.integer; + tui->input.ttimeoutlen = (OptInt)value.data.integer; } else if (strequal(name.data, "verbose")) { tui->verbose = value.data.integer; + } else if (strequal(name.data, "termsync")) { + tui->sync_output = value.data.boolean; } } @@ -1451,13 +1536,13 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In { UGrid *grid = &tui->grid; for (Integer c = startcol; c < endcol; c++) { - memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T)); + grid->cells[linerow][c].data = chunk[c - startcol]; assert((size_t)attrs[c - startcol] < kv_size(tui->attrs)); grid->cells[linerow][c].attr = attrs[c - startcol]; } UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { print_cell_at_pos(tui, (int)linerow, curcol, cell, - curcol < endcol - 1 && (cell + 1)->data[0] == NUL); + curcol < endcol - 1 && (cell + 1)->data == NUL); }); if (clearcol > endcol) { @@ -1474,7 +1559,7 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In if (endcol != grid->width) { // Print the last char of the row, if we haven't already done so. - int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1; + int size = grid->cells[linerow][grid->width - 1].data == NUL ? 2 : 1; print_cell_at_pos(tui, (int)linerow, grid->width - size, &grid->cells[linerow][grid->width - size], size == 2); } @@ -1545,12 +1630,11 @@ void tui_guess_size(TUIData *tui) height = DFLT_ROWS; } - if (tui->width != width || tui->height != height) { - tui->width = width; - tui->height = height; + tui->width = width; + tui->height = height; - ui_client_set_size(width, height); - } + // Redraw on SIGWINCH event if size didn't change. #23411 + ui_client_set_size(width, height); } static void unibi_goto(TUIData *tui, int row, int col) @@ -1635,7 +1719,7 @@ static void pad(void *ctx, size_t delay, int scale FUNC_ATTR_UNUSED, int force) } flush_buf(tui); - uv_sleep((unsigned int)(delay/10)); + uv_sleep((unsigned)(delay/10)); } static void unibi_set_if_empty(unibi_term *ut, enum unibi_string str, const char *val) @@ -1673,13 +1757,10 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name) /// Several entries in terminfo are known to be deficient or outright wrong; /// and several terminal emulators falsely announce incorrect terminal types. static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colorterm, - long vte_version, long konsolev, bool iterm_env, bool nsterm) + int vte_version, int konsolev, bool iterm_env, bool nsterm) { unibi_term *ut = tui->ut; const char *xterm_version = os_getenv("XTERM_VERSION"); -#if 0 // We don't need to identify this specifically, for now. - bool roxterm = !!os_getenv("ROXTERM_ID"); -#endif bool xterm = terminfo_is_term_family(term, "xterm") // Treat Terminal.app as generic xterm-like, for now. || nsterm; @@ -1872,15 +1953,6 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - tui->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); - - // Query the terminal to see if it supports CSI u key encoding by writing CSI - // ? u followed by a request for the primary device attributes (CSI c) - // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol - tui->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys", - "\x1b[?u\x1b[c"); - // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { // See http://fedoraproject.org/wiki/Features/256_Color_Terminals @@ -2003,7 +2075,7 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo /// This adds stuff that is not in standard terminfo as extended unibilium /// capabilities. -static void augment_terminfo(TUIData *tui, const char *term, long vte_version, long konsolev, +static void augment_terminfo(TUIData *tui, const char *term, int vte_version, int konsolev, bool iterm_env, bool nsterm) { unibi_term *ut = tui->ut; @@ -2142,9 +2214,12 @@ static void augment_terminfo(TUIData *tui, const char *term, long vte_version, l "\x1b[?2004l"); // For urxvt send BOTH xterm and old urxvt sequences. #8695 tui->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.enable_focus", - rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); - tui->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.disable_focus", - rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); + rxvt + ? "\x1b[?1004h\x1b]777;focus;on\x7" + : "\x1b[?1004h"); + tui->unibi_ext.disable_focus_reporting = + (int)unibi_add_ext_str(ut, "ext.disable_focus", + rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); tui->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); tui->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse", @@ -2173,71 +2248,29 @@ static void augment_terminfo(TUIData *tui, const char *term, long vte_version, l } if (!kitty && (vte_version == 0 || vte_version >= 5400)) { - // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u - tui->input.extkeys_type = kExtkeysXterm; + // Fallback to Xterm's modifyOtherKeys if terminal does not support the + // Kitty keyboard protocol + tui->input.key_encoding = kKeyEncodingXterm; } } static void flush_buf(TUIData *tui) { uv_write_t req; - uv_buf_t bufs[3]; - uv_buf_t *bufp = &bufs[0]; - - // The content of the output for each condition is shown in the following - // table. Therefore, if tui->bufpos == 0 and N/A or invis + norm, there is - // no need to output it. - // - // | is_invisible | !is_invisible - // ------+-----------------+--------------+--------------- - // busy | want_invisible | N/A | invis - // | !want_invisible | N/A | invis - // ------+-----------------+--------------+--------------- - // !busy | want_invisible | N/A | invis - // | !want_invisible | norm | invis + norm - // ------+-----------------+--------------+--------------- - // - if (tui->bufpos <= 0 - && ((tui->is_invisible && tui->busy) - || (tui->is_invisible && !tui->busy && tui->want_invisible) - || (!tui->is_invisible && !tui->busy && !tui->want_invisible))) { - return; - } - - if (!tui->is_invisible) { - // cursor is visible. Write a "cursor invisible" command before writing the - // buffer. - bufp->base = tui->invis; - bufp->len = UV_BUF_LEN(tui->invislen); - bufp++; - tui->is_invisible = true; - } + uv_buf_t buf; - if (tui->bufpos > 0) { - bufp->base = tui->buf; - bufp->len = UV_BUF_LEN(tui->bufpos); - bufp++; + if (tui->bufpos <= 0) { + return; } - if (!tui->busy) { - assert(tui->is_invisible); - // not busy and the cursor is invisible. Write a "cursor normal" command - // after writing the buffer. - if (!tui->want_invisible) { - bufp->base = tui->norm; - bufp->len = UV_BUF_LEN(tui->normlen); - bufp++; - tui->is_invisible = false; - } - } + buf.base = tui->buf; + buf.len = UV_BUF_LEN(tui->bufpos); if (tui->screenshot) { - for (size_t i = 0; i < (size_t)(bufp - bufs); i++) { - fwrite(bufs[i].base, bufs[i].len, 1, tui->screenshot); - } + fwrite(buf.base, buf.len, 1, tui->screenshot); } else { - int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &tui->output_handle), - bufs, (unsigned)(bufp - bufs), NULL); + int ret + = uv_write(&req, (uv_stream_t *)&tui->output_handle, &buf, 1, NULL); if (ret) { ELOG("uv_write failed: %s", uv_strerror(ret)); } diff --git a/src/nvim/tui/tui.h b/src/nvim/tui/tui.h index 88ea73e99c..8eb4ac9bd8 100644 --- a/src/nvim/tui/tui.h +++ b/src/nvim/tui/tui.h @@ -1,13 +1,24 @@ -#ifndef NVIM_TUI_TUI_H -#define NVIM_TUI_TUI_H +#pragma once -#include "nvim/cursor_shape.h" +#include "nvim/api/private/defs.h" // IWYU pragma: keep +#include "nvim/highlight_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep #include "nvim/ui.h" typedef struct TUIData TUIData; +typedef enum { + kTermModeSynchronizedOutput = 2026, +} TermMode; + +typedef enum { + kTermModeNotRecognized = 0, + kTermModeSet = 1, + kTermModeReset = 2, + kTermModePermanentlySet = 3, + kTermModePermanentlyReset = 4, +} TermModeState; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/tui.h.generated.h" #endif - -#endif // NVIM_TUI_TUI_H |