Skip to content

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
singerdmx:masterfrom
mtallenca:fix/empty-selection-boxes
Open

fix: fall back to caret offset when a text line yields no glyph boxes (selection endpoints & line boundary)#2751
mtallenca wants to merge 3 commits into
singerdmx:masterfrom
mtallenca:fix/empty-selection-boxes

Conversation

@mtallenca

@mtallenca mtallenca commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

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 assumes getBoxesForSelection returns 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._getEndpointForSelection assumes getBoxesForSelection always returns at least one box for a non-collapsed selection:

final boxes = _getBoxes(textSelection);
assert(boxes.isNotEmpty);
final targetBox = first ? boxes.first : boxes.last;

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.last throws during RenderEditor.paintgetEndpointsForSelection: an assert failure in debug and a fatal RangeError in release.

Production crash (Crashlytics, flutter_quill 11.5.1, iOS):

Fatal Exception: FlutterError
0  _Array.last (dart:core)
1  RenderEditableTextLine._getEndpointForSelection (text_line.dart)
2  RenderEditableTextLine.getExtentEndpointForSelection (text_line.dart)
3  RenderEditor.getEndpointsForSelection (editor.dart)
4  RenderEditor.paint (editor.dart)

Fix: when boxes is 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)

getLineBoundary derives the line's left/right edges from lineBoxes.first / lineBoxes.last. 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. It then threw Bad state: No element during 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 lineBoxes is 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:

  • Fix 1 drives RenderEditor.getEndpointsForSelection with a selection edge inside an emoji surrogate pair — fails the boxes.isNotEmpty assert without the fix, passes with it.
  • Fix 2 drives getLineBoundary on an empty line — throws Bad state: No element without the fix, passes with it.

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
mtallenca force-pushed the fix/empty-selection-boxes branch from 5a06bf9 to 0581d45 Compare July 21, 2026 20:37
@mtallenca mtallenca changed the title fix: fall back to caret offset when a selection edge has no glyph boxes fix: fall back to caret offset when a text line yields no glyph boxes (selection endpoints & line boundary) Jul 21, 2026
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