diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
commit | 931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch) | |
tree | d8c1843a95da5ea0bb4acc09f7e37843d9995c86 /src/nvim/tui/tui.c | |
parent | 142d9041391780ac15b89886a54015fdc5c73995 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-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.c | 482 |
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)); } |