fix(tui): reduce flicker by only re-rendering changed lines (#617)

This commit is contained in:
Ogulcan Celik 2026-01-11 03:02:35 +01:00 committed by Mario Zechner
parent 3bb115a39c
commit df3a220d6b
3 changed files with 118 additions and 5 deletions

View file

@ -368,6 +368,7 @@ export class TUI extends Container {
// Find first and last changed lines
let firstChanged = -1;
let lastChanged = -1;
const maxLines = Math.max(newLines.length, this.previousLines.length);
for (let i = 0; i < maxLines; i++) {
const oldLine = i < this.previousLines.length ? this.previousLines[i] : "";
@ -377,6 +378,7 @@ export class TUI extends Container {
if (firstChanged === -1) {
firstChanged = i;
}
lastChanged = i;
}
}
@ -445,9 +447,10 @@ export class TUI extends Container {
buffer += "\r"; // Move to column 0
// Render from first changed line to end, clearing each line before writing
// This avoids the \x1b[J clear-to-end which can cause flicker in xterm.js
for (let i = firstChanged; i < newLines.length; i++) {
// Only render changed lines (firstChanged to lastChanged), not all lines to end
// This reduces flicker when only a single line changes (e.g., spinner animation)
const renderEnd = Math.min(lastChanged, newLines.length - 1);
for (let i = firstChanged; i <= renderEnd; i++) {
if (i > firstChanged) buffer += "\r\n";
buffer += "\x1b[2K"; // Clear current line
const line = newLines[i];
@ -483,8 +486,17 @@ export class TUI extends Container {
buffer += line;
}
// Track where cursor ended up after rendering
let finalCursorRow = renderEnd;
// If we had more lines before, clear them and move cursor back
if (this.previousLines.length > newLines.length) {
// Move to end of new content first if we stopped before it
if (renderEnd < newLines.length - 1) {
const moveDown = newLines.length - 1 - renderEnd;
buffer += `\x1b[${moveDown}B`;
finalCursorRow = newLines.length - 1;
}
const extraLines = this.previousLines.length - newLines.length;
for (let i = newLines.length; i < this.previousLines.length; i++) {
buffer += "\r\n\x1b[2K";
@ -498,8 +510,8 @@ export class TUI extends Container {
// Write entire buffer at once
this.terminal.write(buffer);
// Cursor is now at end of last line
this.cursorRow = newLines.length - 1;
// Track cursor position for next render
this.cursorRow = finalCursorRow;
this.previousLines = newLines;
this.previousWidth = width;