Skip to content

refactor(ui): simplify truncPad via runewidth Truncate/FillRight#94

Merged
peczenyj merged 1 commit into
develfrom
refactor/truncpad-runewidth
May 29, 2026
Merged

refactor(ui): simplify truncPad via runewidth Truncate/FillRight#94
peczenyj merged 1 commit into
develfrom
refactor/truncpad-runewidth

Conversation

@peczenyj

Copy link
Copy Markdown
Owner

What

Replace truncPad's hand-rolled width-aware truncate-and-pad loop with runewidth's built-in Truncate/FillRight helpers:

func truncPad(s string, w int) string {
	if w <= 0 {
		return ""
	}
	s = strings.ReplaceAll(s, "\t", "    ")
	return runewidth.FillRight(runewidth.Truncate(s, w, "…"), w)
}

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).

Why

This is the resolution of the side-by-side rendering library investigation: the lightest alternative to lipgloss / charmbracelet/x/cellbuf / muesli/reflow is no new library at all. Those only buy ANSI-aware truncation, which we don't need — color is applied after truncPad, so cells are always plain text when truncated.

Verification — output is byte-for-byte identical

  • ✅ Side-by-side golden fixture unchanged
  • TestTruncPadUnicode / TestTruncPadWideRunes (incl. 世界世世界…)
  • ✅ Differential check vs the original implementation: identical across 21 inputs × 28 widths (negatives, zero, CJK, tabs, emoji, accents, wide-rune-straddling)
  • task ci green (202 tests, lint 0 issues)

Closes #93

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
@peczenyj peczenyj self-assigned this May 29, 2026
@peczenyj peczenyj merged commit 5ffa49d into devel May 29, 2026
6 checks passed
peczenyj added a commit that referenced this pull request Jun 3, 2026
…101)

A user-supplied -width propagated unclamped into the side-by-side
renderer, where both truncPad (via runewidth.FillRight) and the divider
(strings.Repeat) would attempt a multi-gigabyte allocation and panic
with "makeslice: len out of range":

    structalign -diff=side -width=4611686018427387904 ./pkg

Clamp the per-side width to maxColWidth (1<<16 cells, far wider than
any real terminal) inside truncPad - making the fuzzed function total -
and once in renderSideBySide so the divider shares the bound.

Found by fuzzing during the ClusterFuzzLite integration (#99):
fuzz_trunc_pad crashed within seconds in the OSS-Fuzz container. The
bug predates #94 - the old hand-rolled loop panicked identically in
strings.Repeat. After the fix the target runs 64k+ executions clean.

Closes #100

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Simplify truncPad using runewidth's Truncate/FillRight helpers

1 participant