Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions packages/tui/src/tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,7 @@ export class TUI extends Container {
const firstInsertedScreenRow = regionBottom - plan.insertedRows.length + 1;
for (let index = 0; index < plan.insertedRows.length; index++) {
const screenRow = firstInsertedScreenRow + index;
buffer += `\x1b[${screenRow + 1};1H\x1b[2K`;
buffer += `\x1b[${screenRow + 1};1H\x1b[2K${TUI.SEGMENT_RESET}`;
buffer += plan.insertedRows[index] ?? "";
}

Expand Down Expand Up @@ -1345,7 +1345,7 @@ export class TUI extends Container {
const bufferLength = Math.max(height, newLines.length);
for (let row = 0; row < bufferLength; row++) {
if (row > 0) buffer += "\r\n";
buffer += "\r\x1b[2K";
buffer += `\r\x1b[2K${TUI.SEGMENT_RESET}`;
buffer += newLines[row] ?? "";
}

Expand Down Expand Up @@ -1627,7 +1627,7 @@ export class TUI extends Container {
buffer += `\x1b[${clearStartOffset}B`;
}
for (let i = 0; i < extraLines; i++) {
buffer += "\r\x1b[2K";
buffer += `\r\x1b[2K${TUI.SEGMENT_RESET}`;
if (i < extraLines - 1) buffer += "\x1b[1B";
}
const moveBack = Math.max(0, extraLines - 1 + clearStartOffset);
Expand Down Expand Up @@ -1699,7 +1699,7 @@ export class TUI extends Container {

for (let row = 0; row < height; row++) {
if (row > 0) buffer += "\r\n";
buffer += "\r\x1b[2K";
buffer += `\r\x1b[2K${TUI.SEGMENT_RESET}`;
buffer += newLines[viewportTop + row] ?? "";
}

Expand Down Expand Up @@ -1769,9 +1769,9 @@ export class TUI extends Container {
return;
}

buffer += "\x1b[2K";
buffer += `\x1b[2K${TUI.SEGMENT_RESET}`;
for (let row = 1; row < imageReservedRows; row++) {
buffer += "\r\n\x1b[2K";
buffer += `\r\n\x1b[2K${TUI.SEGMENT_RESET}`;
}
buffer += `\x1b[${imageReservedRows - 1}A`;
buffer += line;
Expand All @@ -1780,7 +1780,7 @@ export class TUI extends Container {
continue;
}

buffer += "\x1b[2K"; // Clear current line
buffer += `\x1b[2K${TUI.SEGMENT_RESET}`; // Clear current line
if (!isImage && visibleWidth(line) > width) {
// Log all lines to crash file for debugging
const crashLogPath = path.join(os.homedir(), ".senpi", "agent", "senpi-crash.log");
Expand Down Expand Up @@ -1825,7 +1825,7 @@ export class TUI extends Container {
}
const extraLines = this.previousLines.length - newLines.length;
for (let i = newLines.length; i < this.previousLines.length; i++) {
buffer += "\r\n\x1b[2K";
buffer += `\r\n\x1b[2K${TUI.SEGMENT_RESET}`;
}
// Move cursor back to end of new content
buffer += `\x1b[${extraLines}A`;
Expand Down
61 changes: 61 additions & 0 deletions packages/tui/test/tui-render-sgr-reset.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import assert from "node:assert";
import { describe, it } from "node:test";
import { type Component, TUI } from "../src/tui.ts";
import { VirtualTerminal } from "./virtual-terminal.ts";

class TestComponent implements Component {
lines: string[] = [];

render(_width: number): string[] {
return this.lines;
}

invalidate(): void {}
}

class LoggingVirtualTerminal extends VirtualTerminal {
private writes: string[] = [];

override write(data: string): void {
this.writes.push(data);
super.write(data);
}

getWrites(): string {
return this.writes.join("");
}

clearWrites(): void {
this.writes = [];
}
}

describe("TUI changed-row SGR repaint", () => {
it("resets SGR state before clearing and repainting changed rows", async () => {
const terminal = new LoggingVirtualTerminal(72, 6);
const tui = new TUI(terminal);
const component = new TestComponent();
tui.addChild(component);

component.lines = ["header", "\x1b[38;2;9;131;232mWorking\x1b[0m", "footer", "input"];
tui.start();
await terminal.waitForRender();
terminal.clearWrites();

component.lines = ["header", "\x1b[38;2;9;131;232mWorking harder\x1b[0m", "footer", "input"];
tui.requestRender();
await terminal.waitForRender();

const writes = terminal.getWrites();
assert.ok(
writes.includes("\x1b[2K\x1b[0m\x1b]8;;\x07\x1b[38;2;9;131;232mWorking harder"),
"changed-row repaint should reset terminal style state immediately after clearing the row",
);
assert.ok(
!writes.includes("\x1b[2K\x1b[38;2;9;131;232m"),
"repaint should not write truecolor text directly after CSI 2K",
);

tui.stop();
});
});
2 changes: 1 addition & 1 deletion packages/tui/test/tui-render.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ describe("TUI Kitty image cleanup", () => {

const writes = terminal.getWrites();
assert.ok(
writes.includes(`\x1b[2K\r\n\x1b[2K\x1b[1A${imageSequence}\x1b[1B`),
writes.includes(`\x1b[2K\x1b[0m\x1b]8;;\x07\r\n\x1b[2K\x1b[0m\x1b]8;;\x07\x1b[1A${imageSequence}\x1b[1B`),
"reserved rows should be cleared before the image placement is drawn",
);
assert.ok(
Expand Down