Skip to content

fix: stop TUI truncation helpers from splitting multi-byte UTF-8 runes - #1160

Open
STiFLeR7 wants to merge 1 commit into
usestrix:mainfrom
STiFLeR7:fix/tui-truncation-utf8-mojibake
Open

fix: stop TUI truncation helpers from splitting multi-byte UTF-8 runes#1160
STiFLeR7 wants to merge 1 commit into
usestrix:mainfrom
STiFLeR7:fix/tui-truncation-utf8-mojibake

Conversation

@STiFLeR7

Copy link
Copy Markdown

Summary

truncStr, firstN, lastN (internal/render/helpers.go), ptrunc (proxy.go), and truncateShellLine (terminal.go) all slice on raw byte offsets. When a cut lands mid-rune, the output is invalid UTF-8 and the terminal shows . Same root cause cuts CJK/emoji lines short, since those runes spend 2-4 bytes but only 1-2 display columns of the truncation budget.

Fix

Switch all five to github.com/charmbracelet/x/ansi (Truncate/TruncateLeft), already a direct dependency and already the established pattern for this exact problem in internal/app (view.go). Both are grapheme-aware and count display columns, not bytes.

  • truncStr/firstN ("keep the start"): ansi.Truncate(s, n, "")
  • lastN ("keep the end"): reversed via ansi.TruncateLeft(s, w-n, "") where w is the string's total display width — ansi.TruncateLeft removes N columns from the start, so keeping the last n columns means removing w - n.
  • ptrunc/truncateShellLine (truncate-with-ellipsis): ansi.Truncate already reserves the tail string's width internally, so this is a direct swap of the same max/maxLineLength budget — no manual -3 arithmetic needed anymore.

Fixes #1152.

Testing

  • Added helpers_test.go with the issue's own repro (TestTruncSplitsRunes, asserting valid UTF-8 out of all five paths) plus per-function behavior tests (TestFirstNTruncStrKeepStart, TestLastNKeepsEnd, TestPtruncAppendsEllipsisWithinBudget).
  • Verified red-before-green: reverted the fix (kept the new tests), confirmed the exact reported mojibake (e.g. firstN("中文字ABC", 4)"中\xe6", invalid UTF-8); restored the fix, confirmed valid output.
  • go build ./..., go vet ./..., and the full existing internal/render test suite all pass with the fix applied.
  • gofmt -l reports no formatting issues.

truncStr, firstN, and lastN (internal/render/helpers.go), ptrunc
(proxy.go), and truncateShellLine (terminal.go) all sliced strings on
raw byte offsets. When a cut landed mid-rune, the result was invalid
UTF-8 and the terminal rendered mojibake; the same byte-vs-column
mismatch also cut CJK/emoji lines short, since those runes spend 2-4
bytes but only 1-2 display columns of the budget.

Switch all five to github.com/charmbracelet/x/ansi, already a direct
dependency and already the established pattern for exactly this in
internal/app (view.go). Truncate/TruncateLeft are grapheme-aware and
operate on display columns, not bytes, so a cut can no longer split a
rune and CJK/emoji now correctly consume 2 columns instead of 1.

- truncStr/firstN ("keep the start"): ansi.Truncate(s, n, "")
- lastN ("keep the end"): reverse via ansi.TruncateLeft(s, w-n, "")
  where w is the string's total display width
- ptrunc/truncateShellLine (truncate-with-ellipsis): ansi.Truncate
  already reserves the tail's width internally, so this is a direct
  swap of the same max/length budget, no arithmetic needed

Added helpers_test.go with the issue's own repro (asserting valid
UTF-8 out of all five paths) plus per-function behavior tests.

Fixes usestrix#1152.
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR replaces byte-offset TUI truncation with grapheme-aware, display-column-based ANSI helpers to prevent invalid UTF-8 and mojibake.

  • Updates start-preserving, end-preserving, proxy, and shell-output truncation.
  • Adds regression coverage for CJK text, UTF-8 validity, suffix retention, and ellipsis behavior.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issues identified.

The changed helpers consistently use display-column-aware truncation, preserve valid UTF-8, retain existing caller budgets, and are covered by targeted regression tests.

Important Files Changed

Filename Overview
strix/interface/tui/internal/render/helpers.go Replaces byte slicing in shared truncation helpers with display-width-aware truncation while preserving start/end semantics.
strix/interface/tui/internal/render/helpers_test.go Adds focused regression tests for valid UTF-8, CJK display widths, suffix truncation, and ellipsis budgets.
strix/interface/tui/internal/render/proxy.go Uses ANSI-aware truncation for bounded proxy-rendering values without changing reachable caller budgets.
strix/interface/tui/internal/render/terminal.go Makes shell-line truncation grapheme-safe while retaining the existing 200-column limit and ellipsis behavior.

Reviews (1): Last reviewed commit: "fix: stop TUI truncation helpers from sp..." | Re-trigger Greptile

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.

TUI truncation helpers split multi-byte UTF-8 runes (mojibake)

1 participant