fix(pi-tui): park cursor on exit under ledger renderer

This commit is contained in:
liruifengv 2026-06-30 17:36:48 +08:00
parent ade7c0ef94
commit 7418b03506
3 changed files with 50 additions and 1 deletions

View file

@ -688,4 +688,29 @@ export class LedgerTuiEngine {
this.#preparedMeta = [];
this.#preparedValidRows = 0;
}
// Park the hardware cursor just past the last content row so the host's shell
// prompt does not overwrite painted rows on exit. Mirrors TUI.stop()'s legacy
// cursor-parking, but sourced from the ledger's own window/cursor state.
public parkCursorForExit(): void {
const window = this.#previousWindow;
if (window.length === 0) return;
let lastContentRow = -1;
for (let i = window.length - 1; i >= 0; i--) {
if ((window[i] ?? "").length > 0) {
lastContentRow = i;
break;
}
}
const targetRow = lastContentRow + 1;
const height = this.terminal.rows;
const rawCursorRow = this.#hardwareCursorRow - this.#windowTopRow;
const cursorScreenRow = Math.max(0, Math.min(height - 1, rawCursorRow));
let seq = "";
const lineDiff = targetRow - cursorScreenRow;
if (lineDiff > 0) seq += `\x1b[${lineDiff}B`;
else if (lineDiff < 0) seq += `\x1b[${-lineDiff}A`;
seq += "\r\n";
this.terminal.write(seq);
}
}

View file

@ -706,7 +706,9 @@ export class TUI extends Container {
this.terminal.write("\x1b[?2031l");
}
// Move cursor to the end of the content to prevent overwriting/artifacts on exit
if (this.previousLines.length > 0) {
if (TUI.LEDGER_ENABLED && this.ledgerEngine) {
this.ledgerEngine.parkCursorForExit();
} else if (this.previousLines.length > 0) {
const targetRow = this.previousLines.length; // Line after the last content
const lineDiff = targetRow - this.hardwareCursorRow;
if (lineDiff > 0) {

View file

@ -96,4 +96,26 @@ describe("ledger engine golden", () => {
tui.stop();
});
});
it("parks cursor past content on stop()", async () => {
await withLedger(async () => {
const terminal = new LoggingVirtualTerminal(40, 10);
const tui = new TUI(terminal);
const c = new TestComponent();
tui.addChild(c);
c.lines = ["Hello", "World"];
tui.start();
await terminal.waitForRender();
terminal.clearWrites();
tui.stop();
const writes = terminal.getWrites();
// stop() must emit a cursor-parking sequence ending with CRLF so the host
// shell prompt lands on a fresh line below the painted content (not
// overwriting it). This regresses the ledger-path exit artifact.
assert.ok(
writes.includes("\r\n"),
`stop() should park cursor with a trailing CRLF; got: ${JSON.stringify(writes)}`,
);
});
});
});