fix(tui): treated WSL scrollback replay as unsafe

This commit is contained in:
Brit
2026-05-30 00:27:04 +02:00
committed by can1357
parent 03000423ca
commit fd346358c9
2 changed files with 60 additions and 2 deletions
+17 -2
View File
@@ -167,6 +167,15 @@ function isMultiplexerSession(): boolean {
return Boolean(Bun.env.TMUX || Bun.env.STY || Bun.env.ZELLIJ);
}
function requiresNativeViewportProofForReplay(): boolean {
return (
process.platform === "win32" ||
(process.platform === "linux" &&
Boolean(Bun.env.WT_SESSION) &&
Boolean(Bun.env.WSL_DISTRO_NAME || Bun.env.WSL_INTEROP))
);
}
/**
* Options for overlay positioning and sizing.
* Values can be absolute numbers or percentage strings (e.g., "50%").
@@ -1429,7 +1438,10 @@ export class TUI extends Container {
}
#nativeViewportIsScrolled(nativeViewportAtBottom: boolean | undefined): boolean {
return nativeViewportAtBottom === false || (nativeViewportAtBottom === undefined && process.platform === "win32");
return (
nativeViewportAtBottom === false ||
(nativeViewportAtBottom === undefined && requiresNativeViewportProofForReplay())
);
}
#nativeViewportIsAtBottom(nativeViewportAtBottom: boolean | undefined): boolean {
@@ -1437,7 +1449,10 @@ export class TUI extends Container {
}
#canReplayNativeScrollbackAtCheckpoint(nativeViewportAtBottom: boolean | undefined): boolean {
return nativeViewportAtBottom === true || (nativeViewportAtBottom === undefined && process.platform !== "win32");
return (
nativeViewportAtBottom === true ||
(nativeViewportAtBottom === undefined && !requiresNativeViewportProofForReplay())
);
}
#padDeferredShrinkLines(lines: string[], paddedLength: number): string[] {
@@ -995,6 +995,49 @@ describe("TUI terminal-state regressions", () => {
tui.stop();
}
});
it("treats unknown WSL Windows Terminal viewport state as scrolled", async () => {
const originalPlatform = process.platform;
const originalWtSession = Bun.env.WT_SESSION;
const originalWslDistroName = Bun.env.WSL_DISTRO_NAME;
const originalWslInterop = Bun.env.WSL_INTEROP;
Object.defineProperty(process, "platform", { configurable: true, value: "linux" });
Bun.env.WT_SESSION = "wt-test";
Bun.env.WSL_DISTRO_NAME = "Ubuntu";
delete Bun.env.WSL_INTEROP;
const term = new UnknownViewportTerminal(32, 5);
const tui = new TUI(term);
const component = new MutableLinesComponent(rows("line-", 12));
tui.addChild(component);
try {
tui.start();
await settle(term);
term.scrollLines(-2);
const before = term.getBufferPosition();
expect(before.viewportY).toBeGreaterThan(0);
component.setLines(rows("line-", 8));
tui.requestRender();
await settle(term);
const after = term.getBufferPosition();
expect(after.viewportY).toBe(before.viewportY);
expect(visible(term).map(line => line.trim())).toEqual(["line-5", "line-6", "line-7", "", ""]);
expect(tui.refreshNativeScrollbackIfDirty()).toBe(false);
expect(term.getBufferPosition().viewportY).toBe(before.viewportY);
} finally {
Object.defineProperty(process, "platform", { configurable: true, value: originalPlatform });
if (originalWtSession === undefined) delete Bun.env.WT_SESSION;
else Bun.env.WT_SESSION = originalWtSession;
if (originalWslDistroName === undefined) delete Bun.env.WSL_DISTRO_NAME;
else Bun.env.WSL_DISTRO_NAME = originalWslDistroName;
if (originalWslInterop === undefined) delete Bun.env.WSL_INTEROP;
else Bun.env.WSL_INTEROP = originalWslInterop;
tui.stop();
}
});
it("refreshes deferred native scrollback when the native viewport reaches bottom", async () => {
const term = new VirtualTerminal(32, 5);
const tui = new TUI(term);