Skip to content

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

Description

@c879873067877881111

Describe the bug

The truncation helpers in the TUI slice on byte offsets instead of rune boundaries, so when a cut lands in the middle of a multi-byte character the output is broken UTF-8 and the terminal shows a . Same root cause also means CJK/emoji lines get cut short, since those runes eat 2-4 bytes of the budget but only take 1-2 columns.

Helpers doing this, all under strix/interface/tui/internal/render/:

  • helpers.go: truncStr, firstN, lastN
  • proxy.go: ptrunc (and psanitize/joinTrunc via it)
  • terminal.go:87

I noticed it in the proxy view, which prints captured hosts/paths/headers/bodies — those carry UTF-8 all the time (JSON APIs, non-ASCII bodies, IDN hosts), so it's easy to hit.

To Reproduce

Helpers are unexported so easiest is a test in package render (commit 1c499c5):

func TestTruncSplitsRunes(t *testing.T) {
	for _, out := range []string{
		firstN("中文字ABC", 4),
		lastN("ABC中文字", 4),
		truncStr("中文字ABC", 4),
		ptrunc("中文字ABCDEF", 5),
	} {
		if !utf8.ValidString(out) {
			t.Errorf("invalid UTF-8: %q", out)
		}
	}
}

All four fail, e.g. firstN("中文字ABC", 4) returns "中\xe6" — the trailing byte is half a rune.

Expected behavior

Truncation shouldn't split a rune, and the width limit should count columns, not bytes.

ansi.Truncate from charmbracelet/x/ansi does exactly this and is already a dependency (used in internal/app), so switching these helpers over would fix it and match how app/ already handles width. ansi.Truncate("中文字ABC", 4, "")"中文".

System Information:

  • OS: macOS 14 arm64 (logic is platform-independent)
  • Commit: 1c499c5
  • Python Version: N/A, this is the Go TUI
  • LLM Used: N/A, pure rendering path

Happy to send a PR switching these to ansi.Truncate with a regression test if that's the direction you want.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions