From 361f3109a50a7029cf26e10ae283cd809cccb8ed Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 11 Mar 2026 09:11:25 -0400 Subject: [PATCH] Terminal: consume unsupported escape bytes in tables --- src/terminal/table.test.ts | 14 ++++++++++++++ src/terminal/table.ts | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/src/terminal/table.test.ts b/src/terminal/table.test.ts index f6efea976..9c6d53eae 100644 --- a/src/terminal/table.test.ts +++ b/src/terminal/table.test.ts @@ -156,6 +156,20 @@ describe("renderTable", () => { expect(visibleWidth(line)).toBe(width); } }); + + it("consumes unsupported escape sequences without hanging", () => { + const out = renderTable({ + width: 48, + columns: [ + { key: "K", header: "K", minWidth: 6 }, + { key: "V", header: "V", minWidth: 12, flex: true }, + ], + rows: [{ K: "row", V: "before \x1b[2J after" }], + }); + + expect(out).toContain("before"); + expect(out).toContain("after"); + }); }); describe("wrapNoteMessage", () => { diff --git a/src/terminal/table.ts b/src/terminal/table.ts index 2945e4701..a1fbb9f57 100644 --- a/src/terminal/table.ts +++ b/src/terminal/table.ts @@ -98,6 +98,13 @@ function wrapLine(text: string, width: number): string[] { if (nextEsc < 0) { nextEsc = text.length; } + if (nextEsc === i) { + // Consume unsupported escape bytes as plain characters so wrapping + // cannot stall on unknown ANSI/control sequences. + tokens.push({ kind: "char", value: ESC }); + i += ESC.length; + continue; + } const plainChunk = text.slice(i, nextEsc); for (const grapheme of splitGraphemes(plainChunk)) { tokens.push({ kind: "char", value: grapheme });