fix(tui): reset viewport state after shrink
This commit is contained in:
@@ -874,8 +874,11 @@ export class TUI extends Container {
|
||||
if (this.stopped) return;
|
||||
const width = this.terminal.columns;
|
||||
const height = this.terminal.rows;
|
||||
let viewportTop = Math.max(0, this.maxLinesRendered - height);
|
||||
let prevViewportTop = this.previousViewportTop;
|
||||
const widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;
|
||||
const heightChanged = this.previousHeight !== 0 && this.previousHeight !== height;
|
||||
const previousBufferLength = this.previousHeight > 0 ? this.previousViewportTop + this.previousHeight : height;
|
||||
let prevViewportTop = heightChanged ? Math.max(0, previousBufferLength - height) : this.previousViewportTop;
|
||||
let viewportTop = prevViewportTop;
|
||||
let hardwareCursorRow = this.hardwareCursorRow;
|
||||
const computeLineDiff = (targetRow: number): number => {
|
||||
const currentScreenRow = hardwareCursorRow - prevViewportTop;
|
||||
@@ -896,10 +899,6 @@ export class TUI extends Container {
|
||||
|
||||
newLines = this.applyLineResets(newLines);
|
||||
|
||||
// Width or height changed - need full re-render
|
||||
const widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;
|
||||
const heightChanged = this.previousHeight !== 0 && this.previousHeight !== height;
|
||||
|
||||
// Helper to clear scrollback and viewport and render all new lines
|
||||
const fullRender = (clear: boolean): void => {
|
||||
this.fullRedrawCount += 1;
|
||||
@@ -919,7 +918,8 @@ export class TUI extends Container {
|
||||
} else {
|
||||
this.maxLinesRendered = Math.max(this.maxLinesRendered, newLines.length);
|
||||
}
|
||||
this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
|
||||
const bufferLength = Math.max(height, newLines.length);
|
||||
this.previousViewportTop = Math.max(0, bufferLength - height);
|
||||
this.positionHardwareCursor(cursorPos, newLines.length);
|
||||
this.previousLines = newLines;
|
||||
this.previousWidth = width;
|
||||
@@ -993,7 +993,7 @@ export class TUI extends Container {
|
||||
// No changes - but still need to update hardware cursor position if it moved
|
||||
if (firstChanged === -1) {
|
||||
this.positionHardwareCursor(cursorPos, newLines.length);
|
||||
this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
|
||||
this.previousViewportTop = prevViewportTop;
|
||||
this.previousHeight = height;
|
||||
return;
|
||||
}
|
||||
@@ -1004,6 +1004,11 @@ export class TUI extends Container {
|
||||
let buffer = "\x1b[?2026h";
|
||||
// Move to end of new content (clamp to 0 for empty content)
|
||||
const targetRow = Math.max(0, newLines.length - 1);
|
||||
if (targetRow < prevViewportTop) {
|
||||
logRedraw(`deleted lines moved viewport up (${targetRow} < ${prevViewportTop})`);
|
||||
fullRender(true);
|
||||
return;
|
||||
}
|
||||
const lineDiff = computeLineDiff(targetRow);
|
||||
if (lineDiff > 0) buffer += `\x1b[${lineDiff}B`;
|
||||
else if (lineDiff < 0) buffer += `\x1b[${-lineDiff}A`;
|
||||
@@ -1034,16 +1039,14 @@ export class TUI extends Container {
|
||||
this.previousLines = newLines;
|
||||
this.previousWidth = width;
|
||||
this.previousHeight = height;
|
||||
this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
|
||||
this.previousViewportTop = prevViewportTop;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if firstChanged is above what was previously visible
|
||||
// Use previousLines.length (not maxLinesRendered) to avoid false positives after content shrinks
|
||||
const previousContentViewportTop = Math.max(0, this.previousLines.length - height);
|
||||
if (firstChanged < previousContentViewportTop) {
|
||||
// First change is above previous viewport - need full re-render
|
||||
logRedraw(`firstChanged < viewportTop (${firstChanged} < ${previousContentViewportTop})`);
|
||||
// Differential rendering can only touch what was actually visible.
|
||||
// If the first changed line is above the previous viewport, we need a full redraw.
|
||||
if (firstChanged < prevViewportTop) {
|
||||
logRedraw(`firstChanged < viewportTop (${firstChanged} < ${prevViewportTop})`);
|
||||
fullRender(true);
|
||||
return;
|
||||
}
|
||||
@@ -1175,7 +1178,7 @@ export class TUI extends Container {
|
||||
this.hardwareCursorRow = finalCursorRow;
|
||||
// Track terminal's working area (grows but doesn't shrink unless cleared)
|
||||
this.maxLinesRendered = Math.max(this.maxLinesRendered, newLines.length);
|
||||
this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
|
||||
this.previousViewportTop = Math.max(prevViewportTop, finalCursorRow - height + 1);
|
||||
|
||||
// Position hardware cursor for IME
|
||||
this.positionHardwareCursor(cursorPos, newLines.length);
|
||||
|
||||
Reference in New Issue
Block a user