diff --git a/extensions/codemode-display/render.ts b/extensions/codemode-display/render.ts index 26d640ae7..1dfbb0ea3 100644 --- a/extensions/codemode-display/render.ts +++ b/extensions/codemode-display/render.ts @@ -99,6 +99,14 @@ const ICON_COLOR = { type Tone = "toolPendingBg" | "toolSuccessBg" | "toolErrorBg"; +/** Reapply the block tint after nested foreground styles reset all SGR state. */ +function paintBackground(theme: Theme, color: Tone, row: string) { + return row + .split("\u001b[0m") + .map((segment) => theme.bg(color, segment)) + .join("\u001b[0m"); +} + /** Mirror Pi's default tool shell (padded, tinted block) with a tone derived from all evidence. */ function framed( theme: Theme, @@ -115,15 +123,13 @@ function framed( if (edges.top) rows.unshift(""); if (edges.bottom()) rows.push(""); const bg = tone(); - return rows.map((row) => { - const padded = row + " ".repeat(Math.max(0, width - visibleWidth(row))); - // Pi's truncation/wrapping resets all SGR styles. Reapply the owning - // card background after each reset, including ellipsis and padding. - return padded - .split("\x1b[0m") - .map((segment) => theme.bg(bg, segment)) - .join("\x1b[0m"); - }); + return rows.map((row) => + paintBackground( + theme, + bg, + row + " ".repeat(Math.max(0, width - visibleWidth(row))), + ), + ); }, invalidate() {}, }; diff --git a/tests/extensions/codemode-display/render.test.ts b/tests/extensions/codemode-display/render.test.ts index 9dc769600..78fcc6f66 100644 --- a/tests/extensions/codemode-display/render.test.ts +++ b/tests/extensions/codemode-display/render.test.ts @@ -390,6 +390,24 @@ test("block tint is green only for a clean ledger, neutral for nested issues and assert.equal(tone(result("boom"), true), "toolErrorBg"); }); +test("truncated rows keep the block background after nested foreground resets", () => { + const ansi = { + fg: (_color: string, text: string) => `\u001b[31m${text}\u001b[0m`, + bg: (_color: string, text: string) => `\u001b[44m${text}\u001b[0m`, + bold: (text: string) => text, + } as Theme; + const rows = codemodeRenderers.renderCall!( + { code }, + ansi, + context(false), + ).render(40); + assert.ok(rows.length > 0); + assert.ok( + rows.some((row) => /\u001b\[0m\u001b\[44m/u.test(row)), + "each nested reset must reopen the block background", + ); +}); + test("compact call rows name their first string argument within the row budget", () => { const calls = [ {