From 7d83ff7c8fffa7ffc232a16e3b4034443e174bf9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 21 Aug 2026 07:37:16 -0700 Subject: [PATCH] improve(terminal): speed up fitting table rendering (#127221) * test(terminal): cover fitting table cell rendering Amp-Thread-ID: https://ampcode.com/threads/T-01a0242a-0302-708f-a777-ec7c0a5c9c60 * perf(terminal): skip wrapping work for fitting ASCII cells Amp-Thread-ID: https://ampcode.com/threads/T-01a021f5-984a-7628-a30c-491c166ff247 --------- Co-authored-by: Amp --- packages/terminal-core/src/table.test.ts | 15 +++++++++++++++ packages/terminal-core/src/table.ts | 10 ++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/terminal-core/src/table.test.ts b/packages/terminal-core/src/table.test.ts index 2f44ecc55318..488208b72240 100644 --- a/packages/terminal-core/src/table.test.ts +++ b/packages/terminal-core/src/table.test.ts @@ -70,6 +70,21 @@ describe("renderTable", () => { vi.restoreAllMocks(); }); + it("renders fitting ASCII cells without grapheme segmentation", () => { + const segment = vi.spyOn(Intl.Segmenter.prototype, "segment"); + + const out = renderTable({ + border: "ascii", + columns: [{ key: "Name", header: "Name" }], + rows: [{ Name: "alpha" }, { Name: "beta" }], + }); + + expect(out).toBe( + ["+-------+", "| Name |", "+-------+", "| alpha |", "| beta |", "+-------+", ""].join("\n"), + ); + expect(segment).not.toHaveBeenCalled(); + }); + it.each(["$&", "$`", "$'", "$$"])( "displays a literal %s home path in terminal tables", (pattern) => { diff --git a/packages/terminal-core/src/table.ts b/packages/terminal-core/src/table.ts index 7ac971acccb7..1f0bd1112365 100644 --- a/packages/terminal-core/src/table.ts +++ b/packages/terminal-core/src/table.ts @@ -55,8 +55,9 @@ function padCell(text: string, width: number, align: Align): string { // A single grapheme wider than the cell (e.g. a width-2 CJK/emoji glyph in a // width-1 column) survives wrapLine intact, so clamp here to keep every cell // exactly `width` columns and preserve the border-alignment invariant. - const content = visibleWidth(text) > width ? truncateToVisibleWidth(text, width) : text; - const w = visibleWidth(content); + const textWidth = visibleWidth(text); + const content = textWidth > width ? truncateToVisibleWidth(text, width) : text; + const w = content === text ? textWidth : visibleWidth(content); if (w >= width) { return content; } @@ -366,6 +367,11 @@ function wrapLine(text: string, width: number): string[] { if (width <= 0) { return [text]; } + // Fitting edge-trimmed ASCII is one column per code unit and needs no ANSI/grapheme scan. + // Keep edge whitespace on the full path, where wrapping preserves its trimming semantics. + if (text.length <= width && /^[!-~](?:[ -~]*[!-~])?$/u.test(text)) { + return [text]; + } // ANSI-aware wrapping: never split inside ANSI SGR/OSC-8 sequences. // Table cells are padded and bordered per physical line, so wrapped lines