Skip to content

fix(painter): rebuild pairs in style_range instead of splitting in place - #1167

Open
kronberger-droid wants to merge 3 commits into
nushell:mainfrom
kronberger-droid:refactor/styled-text-style-range
Open

fix(painter): rebuild pairs in style_range instead of splitting in place#1167
kronberger-droid wants to merge 3 commits into
nushell:mainfrom
kronberger-droid:refactor/styled-text-style-range

Conversation

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Summary

Started as a lint site (self.buffer[pair_idx] in style_range) and turned into a real crash while probing edge cases:
String::split_off asserts a char boundary,
so a highlighter handing style_range a byte offset that lands inside a multi-byte character panicked the paint path.
Any highlighter computing offsets from chars or graphemes hits it on the first accented character.

The loop is now a rebuild.
Each pair is split into at most three pieces (before, styled, after) with the bounds snapped outward to grapheme boundaries,
non-empty pieces go into a fresh Vec, and the result replaces the buffer.
No insert-during-walk, no index into the buffer, no split_off.
The snapping reuses ensure_grapheme_boundary_prev/next from core_editor::graphemes, now pub(crate).

Two observable changes, both stated in the doc:
a range landing inside a grapheme styles the whole grapheme rather than panicking,
and a range starting or ending on a pair boundary no longer leaves a zero-length pair behind.
Otherwise the segmentation is unchanged; the seven existing tests pass untouched.

Before

style_range(0, 4) on "café"    → panic in split_off
style_range(3, 3) on aaa|bbb|ccc → aaa | "" | bbb | ccc

After

style_range(0, 4) on "café"    → "café" styled whole
style_range(3, 3) on aaa|bbb|ccc → aaa | bbb | ccc

Red on purpose. Three cases show style_range panicking when a byte
offset lands inside a multi-byte char, since split_off asserts the
boundary. Four cases pin that a range starting or ending on a pair
boundary leaves no zero-length pair, which the split-and-insert
version produces today; the rewrite that follows makes them green.
The painter needs the same floor/ceil the rest policy uses. Also pin
that both accept a position inside a multi-byte char, which is the
contract the painter is about to lean on.
String::split_off asserts a char boundary, so a highlighter handing
style_range a byte offset inside a multi-byte char took the paint path
down with it. The bounds now snap outward to grapheme boundaries and
the pairs are rebuilt into a fresh Vec, thus no insert-during-walk, no
index into the buffer, and no zero-length pair left where a range
starts or ends on a boundary.
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