aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tui/tui.c
diff options
context:
space:
mode:
authorGregory Anders <8965202+gpanders@users.noreply.github.com>2023-11-14 08:53:58 -0600
committerGitHub <noreply@github.com>2023-11-14 08:53:58 -0600
commitac8ed77afb359694a716501d9e87b0c9949b2445 (patch)
treeb1345f64f8ecba162278325b750092ce405091ec /src/nvim/tui/tui.c
parentb73a829837bbc05840ae00cbe514fb1786695614 (diff)
downloadrneovim-ac8ed77afb359694a716501d9e87b0c9949b2445.tar.gz
rneovim-ac8ed77afb359694a716501d9e87b0c9949b2445.tar.bz2
rneovim-ac8ed77afb359694a716501d9e87b0c9949b2445.zip
feat(tui): add 'termsync' option (#25871)
The 'termsync' option enables a mode (provided the underlying terminal supports it) where all screen updates during a redraw cycle are buffered and drawn together when the redraw is complete. This eliminates tearing or flickering in cases where Nvim redraws slower than the terminal redraws the screen.
Diffstat (limited to 'src/nvim/tui/tui.c')
-rw-r--r--src/nvim/tui/tui.c68
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;
}
}