aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tui/tui.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
commit931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch)
treed8c1843a95da5ea0bb4acc09f7e37843d9995c86 /src/nvim/tui/tui.c
parent142d9041391780ac15b89886a54015fdc5c73995 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-userreg.tar.gz
rneovim-userreg.tar.bz2
rneovim-userreg.zip
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'src/nvim/tui/tui.c')
-rw-r--r--src/nvim/tui/tui.c482
1 files changed, 260 insertions, 222 deletions
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index a50e44f7a3..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,9 +489,16 @@ 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)
{
tui_terminal_stop(tui);
@@ -467,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;
}
@@ -689,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.
@@ -717,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++;
}
@@ -748,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);
@@ -845,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.
@@ -861,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.
@@ -990,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)
@@ -1001,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")) {
@@ -1094,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;
@@ -1105,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 {
@@ -1130,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:
@@ -1150,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));
@@ -1171,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;
@@ -1262,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;
@@ -1278,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);
@@ -1287,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;
}
@@ -1295,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);
@@ -1305,6 +1386,8 @@ void tui_flush(TUIData *tui)
cursor_goto(tui, tui->row, tui->col);
+ tui_flush_end(tui);
+
flush_buf(tui);
}
@@ -1318,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;
@@ -1340,10 +1423,11 @@ 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);
stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598)
@@ -1356,34 +1440,35 @@ static void suspend_event(void **argv)
tui_mouse_on(tui);
}
stream_set_blocking(tui->input.in_fd, false); // libuv expects this
-}
-#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));
+ ui_client_attach(tui->width, tui->height, tui->term);
#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)
{
@@ -1399,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);
@@ -1427,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;
}
}
@@ -1446,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) {
@@ -1469,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);
}
@@ -1540,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)
@@ -1630,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)
@@ -1668,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;
@@ -1867,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
@@ -1998,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;
@@ -2137,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",
@@ -2168,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;
- }
+ uv_buf_t buf;
- 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;
- }
-
- 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));
}