Repository navigation
[low] fix(ansi): never truncate to a line wider than maxWidth - #625
Merged
sirmalloc merged 1 commit intoOct 9, 2026
Merged
Conversation
truncateStyledText measured each run of text between escape sequences separately, while getVisibleWidth measures the escape-stripped text. A grapheme cluster split by an escape (U+2764, SGR, U+FE0F) therefore counted as 1 + 0 columns instead of 2, the loop never overshot, and the function returned the original, over-wide text. Cluster the escape-stripped text, as getVisibleWidth does, and copy escapes that fall inside a cluster. This also drops the rescan of the rest of the visible run for every cluster. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF
truncateStyledTextcan return a string wider thanmaxWidth.getVisibleWidth, which clusters the escape-stripped text. The loop clusters each run between escapes on its own. A grapheme split by an escape (❤SGRU+FE0F, keycap, ZWJ, flag pairs) counts as 1 + 0 columns instead of 2. The loop then never overshoots and the function returns the original, over-wide text. With a flag pair split by an SGR the result is even wider than the input.getVisibleWidthdoes, and copy escapes that fall inside a cluster. This also removes the per-cluster rescan of the rest of the visible run (O(n²)).main(12 columns returned formaxWidth9) and passes with the fix.Details
❤\x1b[31m️\x1b[39mrepeated 6 times is 12 columns.truncateStyledText(text, 9)returns it unchanged. With{ ellipsis: false }, the renderer'smaxWidthpath, the same happens for every width from 1 to 11.preserveColors, or colours that split a cluster at a widget boundary. The line gradient is not affected, because it clusters the whole text.A Lean 4 model of
consumeDisplayCluster/getVisibleWidth/truncateStyledTextmatched the real code on 19,614 generated cases and proves the counterexamples.An exhaustive comparison of old vs new over every string of up to 5 tokens (13-token alphabet,
maxWidth0–8, with and without ellipsis) found:Neither version ever leaves an OSC 8 link open.
Outputs only differ where a cluster straddles an escape: the old code emitted half a cluster, the new code keeps or drops whole clusters.
Checks
bun run lint: clean.bun run build: clean.bun test src/utils/__tests__/renderer* src/utils/__tests__/ansi*: 110 pass.🤖 Generated with Claude Code