Skip to content

fix(ansi): reset parser state after grapheme cluster in Truncate#906

Open
sueun-dev wants to merge 1 commit into
charmbracelet:mainfrom
sueun-dev:fix/truncate-obey-width
Open

fix(ansi): reset parser state after grapheme cluster in Truncate#906
sueun-dev wants to merge 1 commit into
charmbracelet:mainfrom
sueun-dev:fix/truncate-obey-width

Conversation

@sueun-dev

Copy link
Copy Markdown

Fixes the Truncate part of #541.

Truncate could return a string wider than the requested length. In the Utf8State branch of truncate(), pstate was reset to GroundState only on the path that writes the cluster; the "ignoring" and "too wide" continue paths left it mid-sequence. When a grapheme cluster interrupts an unterminated escape (e.g. \x1b[3 followed by ), pstate stayed in CSI state, so the next byte was treated as the sequence's final byte and written through the default branch past the truncation point.

A grapheme cluster always returns the parser to the ground state, so this moves the reset to cover all three exit paths.

Before the fix:

Truncate("\x1b[3‘s", 1, "…")          // "\x1b[3…s", width 2
Truncate("7\"(z\x1b[3‘saB'um", 5, "…") // width 6

The fuzz target in the issue failed almost immediately before; with this change StringWidth(Truncate(s, n, tail)) <= n holds across a long fuzz run. Added TestTruncateObeysWidth for the case.

This only touches Truncate (and TruncateWc, which shares truncate()). I left TruncateLeft alone: as noted in the thread it removes n columns from the left, so its result is intentionally total-n wide and the issue's fuzz assertion checks a different invariant for it.

Truncate could return a string wider than the requested length. In the
Utf8State branch of truncate(), pstate was only reset to GroundState on
the path that writes the cluster; the ignoring and too-wide continue
paths left it mid-sequence. When a grapheme cluster interrupts an
unterminated escape (e.g. "\x1b[3" then a multibyte rune), pstate
stayed in CSI state, so the next byte was treated as the sequence final
byte and written through the default branch past the truncation point.

A grapheme cluster always returns the parser to the ground state, so
move the reset to cover all exit paths. Adds TestTruncateObeysWidth.

For charmbracelet#541
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.

1 participant