diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index b5266050..0d5b4612 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed IME hardware cursor positioning in the custom extension editor (`ctx.ui.editor()` / extension editor dialog) by propagating focus to the internal `Editor`, preventing the terminal cursor from getting stuck at the bottom-right during composition. +- Added OSC 133 semantic zone markers around rendered user messages to support terminal navigation between prompts in iTerm2, WezTerm, Kitty, Ghostty, and other compatible terminals ([#1805](https://github.com/badlogic/pi-mono/issues/1805)) ## [0.55.4] - 2026-03-02 diff --git a/packages/coding-agent/src/modes/interactive/components/user-message.ts b/packages/coding-agent/src/modes/interactive/components/user-message.ts index 34cc0a48..104d51b7 100644 --- a/packages/coding-agent/src/modes/interactive/components/user-message.ts +++ b/packages/coding-agent/src/modes/interactive/components/user-message.ts @@ -1,6 +1,9 @@ import { Container, Markdown, type MarkdownTheme, Spacer } from "@mariozechner/pi-tui"; import { getMarkdownTheme, theme } from "../theme/theme.js"; +const OSC133_ZONE_START = "\x1b]133;A\x07"; +const OSC133_ZONE_END = "\x1b]133;B\x07"; + /** * Component that renders a user message */ @@ -15,4 +18,15 @@ export class UserMessageComponent extends Container { }), ); } + + override render(width: number): string[] { + const lines = super.render(width); + if (lines.length === 0) { + return lines; + } + + lines[0] = OSC133_ZONE_START + lines[0]; + lines[lines.length - 1] = lines[lines.length - 1] + OSC133_ZONE_END; + return lines; + } } diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 5722faad..8f56c122 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -7,6 +7,7 @@ - Fixed TUI width calculation for regional indicator symbols (e.g. partial flag sequences like `🇨` during streaming) to prevent wrap drift and stale character artifacts in differential rendering. - Fixed Kitty CSI-u handling to ignore unsupported modifiers so modifier-only events do not insert stray printable characters ([#1807](https://github.com/badlogic/pi-mono/issues/1807)) - Fixed single-line paste performance by inserting pasted text atomically instead of character-by-character, preventing repeated `@` autocomplete scans during paste ([#1812](https://github.com/badlogic/pi-mono/issues/1812)) +- Fixed `visibleWidth()` to ignore generic OSC escape sequences (including OSC 133 semantic prompt markers), preventing width drift when terminals emit semantic zone markers ([#1805](https://github.com/badlogic/pi-mono/issues/1805)) ## [0.55.4] - 2026-03-02 diff --git a/packages/tui/src/utils.ts b/packages/tui/src/utils.ts index 89b436c4..228b2420 100644 --- a/packages/tui/src/utils.ts +++ b/packages/tui/src/utils.ts @@ -115,12 +115,21 @@ export function visibleWidth(str: string): number { clean = clean.replace(/\t/g, " "); } if (clean.includes("\x1b")) { - // Strip SGR codes (\x1b[...m) and cursor codes (\x1b[...G/K/H/J) - clean = clean.replace(/\x1b\[[0-9;]*[mGKHJ]/g, ""); - // Strip OSC 8 hyperlinks: \x1b]8;;URL\x07 and \x1b]8;;\x07 - clean = clean.replace(/\x1b\]8;;[^\x07]*\x07/g, ""); - // Strip APC sequences: \x1b_...\x07 or \x1b_...\x1b\\ (used for cursor marker) - clean = clean.replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, ""); + // Strip supported ANSI/OSC/APC escape sequences in one pass. + // This covers CSI styling/cursor codes, OSC hyperlinks and prompt markers, + // and APC sequences like CURSOR_MARKER. + let stripped = ""; + let i = 0; + while (i < clean.length) { + const ansi = extractAnsiCode(clean, i); + if (ansi) { + i += ansi.length; + continue; + } + stripped += clean[i]; + i++; + } + clean = stripped; } // Calculate width diff --git a/packages/tui/test/wrap-ansi.test.ts b/packages/tui/test/wrap-ansi.test.ts index d74a7f51..9fcc89ce 100644 --- a/packages/tui/test/wrap-ansi.test.ts +++ b/packages/tui/test/wrap-ansi.test.ts @@ -111,6 +111,16 @@ describe("wrapTextWithAnsi", () => { } }); + it("should ignore OSC 133 semantic markers in visible width", () => { + const text = "\x1b]133;A\x07hello\x1b]133;B\x07"; + assert.strictEqual(visibleWidth(text), 5); + }); + + it("should ignore OSC sequences terminated with ST in visible width", () => { + const text = "\x1b]133;A\x1b\\hello\x1b]133;B\x1b\\"; + assert.strictEqual(visibleWidth(text), 5); + }); + it("should treat isolated regional indicators as width 2", () => { assert.strictEqual(visibleWidth("🇨"), 2); assert.strictEqual(visibleWidth("🇨🇳"), 2);