diff options
Diffstat (limited to 'src/nvim/tui/tui.c')
-rw-r--r-- | src/nvim/tui/tui.c | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 8aab4d836c..aff7b100d8 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -104,6 +104,7 @@ struct TUIData { 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; @@ -137,6 +138,7 @@ struct TUIData { int set_underline_color; int enable_extended_keys, disable_extended_keys; int get_extkeys; + int sync; } unibi_ext; char *space_buf; bool stopped; @@ -217,6 +219,41 @@ static size_t unibi_pre_fmt_str(TUIData *tui, unsigned unibi_index, char *buf, s return unibi_run(str, tui->params, buf, len); } +/// Request the terminal's DEC mode (DECRQM). +/// +/// @see handle_modereport +static void tui_dec_request_mode(TUIData *tui, TerminalDecMode mode) +{ + // 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); +} + +/// Handle a DECRPM response from the terminal. +void tui_dec_report_mode(TUIData *tui, TerminalDecMode mode, TerminalModeState state) +{ + assert(tui); + switch (state) { + case kTerminalModeNotRecognized: + case kTerminalModePermanentlySet: + case kTerminalModePermanentlyReset: + // 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 kTerminalModeSet: + case kTerminalModeReset: + // The terminal supports changing the given mode + switch (mode) { + case kDecModeSynchronizedOutput: + // 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%;"); + } + } +} + static void terminfo_start(TUIData *tui) { tui->scroll_region_is_full_screen = true; @@ -253,6 +290,7 @@ static void terminfo_start(TUIData *tui) 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; @@ -329,6 +367,11 @@ static void terminfo_start(TUIData *tui) // Enable bracketed paste unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste); + // 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_dec_request_mode(tui, kDecModeSynchronizedOutput); + // 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); @@ -395,6 +438,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); @@ -1257,6 +1305,20 @@ void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Intege invalidate(tui, 0, tui->grid.height, 0, tui->grid.width); } +/// Enable synchronized output. When enabled, the terminal emulator will preserve the last rendered +/// state on subsequent re-renders. It will continue to process incoming events. When synchronized +/// mode is disabled again the emulator renders using the most recent state. This avoids tearing +/// when the terminal updates the screen faster than Nvim can redraw it. +static void tui_sync_output(TUIData *tui, bool enable) +{ + if (!tui->sync_output) { + return; + } + + UNIBI_SET_NUM_VAR(tui->params[0], enable ? 1 : 0); + unibi_out_ext(tui, tui->unibi_ext.sync); +} + void tui_flush(TUIData *tui) { UGrid *grid = &tui->grid; @@ -1273,6 +1335,8 @@ void tui_flush(TUIData *tui) tui_busy_stop(tui); // avoid hidden cursor } + tui_sync_output(tui, true); + while (kv_size(tui->invalid_regions)) { Rect r = kv_pop(tui->invalid_regions); assert(r.bot <= grid->height && r.right <= grid->width); @@ -1300,6 +1364,8 @@ void tui_flush(TUIData *tui) cursor_goto(tui, tui->row, tui->col); + tui_sync_output(tui, false); + flush_buf(tui); } @@ -1449,6 +1515,8 @@ void tui_option_set(TUIData *tui, String name, Object value) 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; } } |