mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(tui): avoid repainting idle fullscreen sessions on focus loss (#7892)
* fix(tui): avoid idle focus-loss repaint * fix(tui): skip zero-width focus-loss repaint * fix(tui): clarify focus-loss selection check
This commit is contained in:
parent
e3798ca91f
commit
4a879dd759
2 changed files with 76 additions and 4 deletions
|
|
@ -528,6 +528,7 @@ export class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|||
private handleViewportInput(data: string): { consume?: boolean } | undefined {
|
||||
if (data === FOCUS_OUT) {
|
||||
const hadActiveSelection = this.selectionPressActive;
|
||||
const hadNonEmptyActiveSelection = hadActiveSelection && this.getSelectionBounds() !== undefined;
|
||||
this.selectionPressActive = false;
|
||||
this.stopSelectionAutoScroll();
|
||||
this.stopScrollbarHover();
|
||||
|
|
@ -539,9 +540,9 @@ export class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|||
this.selectionFocus = undefined;
|
||||
this.selectionGranularity = "character";
|
||||
this.selectionInitialRange = undefined;
|
||||
if (hadNonEmptyActiveSelection) this.requestRender();
|
||||
}
|
||||
this.lastClick = undefined;
|
||||
this.requestRender();
|
||||
return { consume: true };
|
||||
}
|
||||
if (data === FOCUS_IN) return { consume: true };
|
||||
|
|
|
|||
|
|
@ -1020,16 +1020,23 @@ describe("TuiAltScreen", () => {
|
|||
tui.stop();
|
||||
});
|
||||
|
||||
it("ignores orphan selection events and cancels an active selection on focus loss", async () => {
|
||||
it("does not repaint idle or zero-width selections on focus loss", async () => {
|
||||
const terminal = new RecordingTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
tui.addChild(new Text("alpha\nbeta\ngamma\ndelta", 0, 0));
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
|
||||
const writeCount = () => terminal.events.filter((event) => event.type === "write").length;
|
||||
const clipboardWriteCount = () =>
|
||||
terminal.events.filter((event) => event.type === "write" && event.data.includes("\x1b]52;c;")).length;
|
||||
|
||||
const idleWriteCount = writeCount();
|
||||
terminal.sendInput("\x1b[O");
|
||||
terminal.sendInput("\x1b[I");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(writeCount(), idleWriteCount);
|
||||
|
||||
// A completed click leaves a zero-width anchor, but later orphaned drag/release events must not extend it.
|
||||
terminal.sendInput("\x1b[<0;1;1M");
|
||||
terminal.sendInput("\x1b[<0;1;1m");
|
||||
|
|
@ -1038,10 +1045,14 @@ describe("TuiAltScreen", () => {
|
|||
await terminal.waitForRender();
|
||||
assert.strictEqual(clipboardWriteCount(), 0);
|
||||
|
||||
// Losing focus also cancels a press whose matching release never arrived.
|
||||
terminal.sendInput("\x1b[<0;1;1M");
|
||||
// Losing focus after a press without a drag cancels the press without repainting.
|
||||
terminal.sendInput("\x1b[<0;1;3M");
|
||||
await terminal.waitForRender();
|
||||
const pressedWriteCount = writeCount();
|
||||
terminal.sendInput("\x1b[O");
|
||||
terminal.sendInput("\x1b[I");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(writeCount(), pressedWriteCount);
|
||||
terminal.sendInput("\x1b[<32;4;2M");
|
||||
terminal.sendInput("\x1b[<0;4;2m");
|
||||
await terminal.waitForRender();
|
||||
|
|
@ -1052,6 +1063,66 @@ describe("TuiAltScreen", () => {
|
|||
assert.ok(terminal.events.some((event) => event.type === "write" && event.data.includes("\x1b[?1004l")));
|
||||
});
|
||||
|
||||
it("clears an active visible selection on focus loss and ignores orphan events", async () => {
|
||||
const terminal = new RecordingTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
tui.addChild(new Text("alpha\nbeta\ngamma\ndelta", 0, 0));
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
|
||||
terminal.sendInput("\x1b[<0;1;1M");
|
||||
terminal.sendInput("\x1b[<32;4;2M");
|
||||
await terminal.waitForRender();
|
||||
const focusLossEventCount = terminal.events.length;
|
||||
terminal.sendInput("\x1b[O");
|
||||
terminal.sendInput("\x1b[I");
|
||||
await terminal.waitForRender();
|
||||
const focusLossWrites = terminal.events
|
||||
.slice(focusLossEventCount)
|
||||
.filter((event): event is { type: "write"; data: string } => event.type === "write")
|
||||
.map((event) => event.data)
|
||||
.join("");
|
||||
assert.ok(focusLossWrites.includes("alpha"));
|
||||
assert.ok(focusLossWrites.includes("beta"));
|
||||
assert.ok(!focusLossWrites.includes("\x1b[7m"));
|
||||
|
||||
terminal.sendInput("\x1b[<32;4;2M");
|
||||
terminal.sendInput("\x1b[<0;4;2m");
|
||||
await terminal.waitForRender();
|
||||
assert.ok(terminal.events.every((event) => event.type !== "write" || !event.data.includes("\x1b]52;c;")));
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("retains a completed visible selection across focus changes", async () => {
|
||||
const terminal = new RecordingTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
tui.addChild(new Text("alpha\nbeta\ngamma\ndelta", 0, 0));
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
|
||||
terminal.sendInput("\x1b[<0;1;1M");
|
||||
terminal.sendInput("\x1b[<32;4;2M");
|
||||
terminal.sendInput("\x1b[<0;4;2m");
|
||||
await terminal.waitForRender();
|
||||
const completedWriteCount = terminal.events.filter((event) => event.type === "write").length;
|
||||
terminal.sendInput("\x1b[O");
|
||||
terminal.sendInput("\x1b[I");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(terminal.events.filter((event) => event.type === "write").length, completedWriteCount);
|
||||
|
||||
const redrawEventCount = terminal.events.length;
|
||||
tui.renderNow(true);
|
||||
const redrawWrites = terminal.events
|
||||
.slice(redrawEventCount)
|
||||
.filter((event): event is { type: "write"; data: string } => event.type === "write")
|
||||
.map((event) => event.data)
|
||||
.join("");
|
||||
assert.ok(redrawWrites.includes("alpha"));
|
||||
assert.ok(redrawWrites.includes("beta"));
|
||||
assert.ok(redrawWrites.includes("\x1b[7m"));
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("stacks flash messages and collapses them as they expire", async () => {
|
||||
const terminal = new VirtualTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue