From bc5e61615bb007df858ef1f801b90a7ebc31d75a Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Fri, 29 May 2026 16:18:45 +0200 Subject: [PATCH] refactor(ui): simplify truncPad via runewidth Truncate/FillRight Replace truncPad's hand-rolled width-aware truncate-and-pad loop with runewidth's built-in Truncate/FillRight helpers. go-runewidth is already a direct dependency, so this adds no modules while removing the manual width bookkeeping (ellipsis-cell reservation, wide-rune boundary handling, padding). Output is byte-for-byte identical: verified against the side-by-side golden fixture, the unicode/wide-rune tests, and a differential check over 21 inputs x 28 widths (incl. negatives, zero, CJK, tabs, emoji, accents, and the wide-rune-straddling case). This is also the resolution of the side-by-side library investigation: the lightest alternative to lipgloss/x-cellbuf/reflow is no new library at all, since color is applied after truncPad and ANSI-aware truncation buys us nothing. Closes #93 Co-Authored-By: Claude Opus 4.8 --- internal/ui/printer.go | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/internal/ui/printer.go b/internal/ui/printer.go index 84761a1..ea1ee96 100644 --- a/internal/ui/printer.go +++ b/internal/ui/printer.go @@ -380,28 +380,11 @@ func truncPad(s string, w int) string { } // expand tabs to 4 spaces for stable columns s = strings.ReplaceAll(s, "\t", " ") - runes := []rune(s) - if total := runewidth.StringWidth(s); total <= w { - return s + strings.Repeat(" ", w-total) - } - if w == 1 { - return "…" - } - // Keep the widest prefix that still leaves one cell for the ellipsis, then - // pad: a wide rune straddling the boundary can leave the prefix a cell short. - width := 0 - var b strings.Builder - for _, r := range runes { - rw := runewidth.RuneWidth(r) - if width+rw+1 > w { // reserve one cell for "…" - break - } - b.WriteRune(r) - width += rw - } - b.WriteString("…") - width++ - return b.String() + strings.Repeat(" ", w-width) + // runewidth.Truncate fits s into w cells, appending "…" (and reserving its + // cell) when it has to cut — including the wide-rune-straddling-the-boundary + // case. FillRight then right-pads the result to exactly w cells. Both measure + // in terminal cells, so CJK and other wide runes count as two. + return runewidth.FillRight(runewidth.Truncate(s, w, "…"), w) } // paint renders s in role's style when color is enabled, else returns s plain.