fix: fall back to caret offset when a text line yields no glyph boxes (selection endpoints & line boundary) - #2751
Open
mtallenca wants to merge 3 commits into
Open
Conversation
RenderEditableTextLine._getEndpointForSelection assumed getBoxesForSelection always returns at least one box for a non-collapsed selection. A selection edge that produces no glyph boxes (e.g. an offset inside a grapheme cluster such as an emoji surrogate pair) made boxes.first/boxes.last throw during RenderEditor.paint — an assert in debug and a fatal RangeError in release. Fall back to the caret offset for that edge, mirroring the existing isCollapsed branch.
…glyph boxes An empty line (Line.length == 1) makes the [0, line.length - 1] == [0, 0] box selection collapsed, so getBoxesForSelection returns no boxes and lineBoxes ends up empty. getLineBoundary then threw 'Bad state: No element' on lineBoxes.first/last during line-boundary navigation (Home/End/Shift+Home on a hardware keyboard) with the caret on an empty line. Mirror the existing empty-boxes guard in _getEndpointForSelection: fall back to the caret position.
mtallenca
force-pushed
the
fix/empty-selection-boxes
branch
from
July 21, 2026 20:37
5a06bf9 to
0581d45
Compare
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.
This PR contains two closely-related crash fixes in
text_line.dart. Both stem from the same root cause — code that reads glyph-box geometry and assumesgetBoxesForSelectionreturns at least one box — and both apply the same remedy: fall back to the caret offset when there are no boxes.Fix 1 — selection endpoints (
_getEndpointForSelection)RenderEditableTextLine._getEndpointForSelectionassumesgetBoxesForSelectionalways returns at least one box for a non-collapsed selection:That assumption doesn't hold when a selection edge lands on a position that produces no glyph boxes — for example an offset inside a grapheme cluster (emoji surrogate pair). When that happens,
boxes.first/boxes.lastthrows duringRenderEditor.paint→getEndpointsForSelection: an assert failure in debug and a fatalRangeErrorin release.Production crash (Crashlytics, flutter_quill 11.5.1, iOS):
Fix: when
boxesis empty, fall back to the caret offset for that selection edge — the same geometry the method already uses for collapsed selections — instead of throwing.Fix 2 — line boundary (
getLineBoundary)getLineBoundaryderives the line's left/right edges fromlineBoxes.first/lineBoxes.last. An empty line (Line.length == 1) makes the[0, line.length - 1]==[0, 0]box selection collapsed, sogetBoxesForSelectionreturns no boxes andlineBoxesends up empty. It then threwBad state: No elementduring line-boundary navigation (Home / End / Shift+Home on a hardware keyboard) with the caret on an empty line.Fix: mirror the empty-boxes guard from Fix 1 — when
lineBoxesis empty, fall back to the caret position (TextRange(start: position.offset, end: position.offset)) instead of reading.first/.last.Tests
Added regression tests to
test/bug_fix_test.dart:RenderEditor.getEndpointsForSelectionwith a selection edge inside an emoji surrogate pair — fails theboxes.isNotEmptyassert without the fix, passes with it.getLineBoundaryon an empty line — throwsBad state: No elementwithout the fix, passes with it.