mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 09:01:14 +00:00
fix(tui): reset styles per line
This commit is contained in:
parent
741262d89d
commit
6d495348c5
5 changed files with 37 additions and 0 deletions
|
|
@ -24,6 +24,8 @@ interface Component {
|
|||
| `handleInput?(data)` | Receive keyboard input when component has focus. |
|
||||
| `invalidate?()` | Clear cached render state. |
|
||||
|
||||
The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
|
||||
|
||||
## Using Components
|
||||
|
||||
**In hooks** via `ctx.ui.custom()`:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
### Fixed
|
||||
|
||||
- Cursor now moves to end of content on exit, preventing status line from being overwritten ([#629](https://github.com/badlogic/pi-mono/pull/629) by [@tallshort](https://github.com/tallshort))
|
||||
- Reset ANSI styles after each rendered line to prevent style leakage
|
||||
|
||||
## [0.42.5] - 2026-01-11
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ interface Component {
|
|||
| `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
|
||||
| `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
|
||||
|
||||
The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
|
||||
|
||||
## Built-in Components
|
||||
|
||||
### Container
|
||||
|
|
|
|||
|
|
@ -289,6 +289,11 @@ export class TUI extends Container {
|
|||
|
||||
private static readonly SEGMENT_RESET = "\x1b[0m\x1b]8;;\x07";
|
||||
|
||||
private applyLineResets(lines: string[]): string[] {
|
||||
const reset = TUI.SEGMENT_RESET;
|
||||
return lines.map((line) => (this.containsImage(line) ? line : line + reset));
|
||||
}
|
||||
|
||||
/** Splice overlay content into a base line at a specific column. Single-pass optimized. */
|
||||
private compositeLineAt(
|
||||
baseLine: string,
|
||||
|
|
@ -343,6 +348,8 @@ export class TUI extends Container {
|
|||
newLines = this.compositeOverlays(newLines, width, height);
|
||||
}
|
||||
|
||||
newLines = this.applyLineResets(newLines);
|
||||
|
||||
// Width changed - need full re-render
|
||||
const widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import assert from "node:assert";
|
||||
import { describe, it } from "node:test";
|
||||
import type { Terminal as XtermTerminalType } from "@xterm/headless";
|
||||
import { type Component, TUI } from "../src/tui.js";
|
||||
import { VirtualTerminal } from "./virtual-terminal.js";
|
||||
|
||||
|
|
@ -11,6 +12,16 @@ class TestComponent implements Component {
|
|||
invalidate(): void {}
|
||||
}
|
||||
|
||||
function getCellItalic(terminal: VirtualTerminal, row: number, col: number): number {
|
||||
const xterm = (terminal as unknown as { xterm: XtermTerminalType }).xterm;
|
||||
const buffer = xterm.buffer.active;
|
||||
const line = buffer.getLine(buffer.viewportY + row);
|
||||
assert.ok(line, `Missing buffer line at row ${row}`);
|
||||
const cell = line.getCell(col);
|
||||
assert.ok(cell, `Missing cell at row ${row} col ${col}`);
|
||||
return cell.isItalic();
|
||||
}
|
||||
|
||||
describe("TUI differential rendering", () => {
|
||||
it("tracks cursor correctly when content shrinks with unchanged remaining lines", async () => {
|
||||
const terminal = new VirtualTerminal(40, 10);
|
||||
|
|
@ -68,6 +79,20 @@ describe("TUI differential rendering", () => {
|
|||
tui.stop();
|
||||
});
|
||||
|
||||
it("resets styles after each rendered line", async () => {
|
||||
const terminal = new VirtualTerminal(20, 6);
|
||||
const tui = new TUI(terminal);
|
||||
const component = new TestComponent();
|
||||
tui.addChild(component);
|
||||
|
||||
component.lines = ["\x1b[3mItalic", "Plain"];
|
||||
tui.start();
|
||||
await terminal.flush();
|
||||
|
||||
assert.strictEqual(getCellItalic(terminal, 1, 0), 0);
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("renders correctly when first line changes but rest stays same", async () => {
|
||||
const terminal = new VirtualTerminal(40, 10);
|
||||
const tui = new TUI(terminal);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue