aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tui
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/tui')
-rw-r--r--src/nvim/tui/input.c386
-rw-r--r--src/nvim/tui/input.h32
-rw-r--r--src/nvim/tui/input_defs.h5
-rw-r--r--src/nvim/tui/terminfo.c5
-rw-r--r--src/nvim/tui/terminfo.h9
-rw-r--r--src/nvim/tui/terminfo_defs.h11
-rw-r--r--src/nvim/tui/tui.c483
-rw-r--r--src/nvim/tui/tui.h21
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