@@ -1583,7 +1583,7 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
15831583 switch (granularity) {
15841584 case TextGranularity .character:
15851585 final String text = range.textInside (fullText);
1586- newPosition = _beyondTextBoundary (targetedEdge, forward, CharacterBoundary (text));
1586+ newPosition = _moveBeyondTextBoundaryAtDirection (targetedEdge, forward, CharacterBoundary (text));
15871587 result = SelectionResult .end;
15881588 break ;
15891589 case TextGranularity .word:
@@ -1592,16 +1592,16 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
15921592 return (forward ? offset >= text.length : offset == 0 )
15931593 || ! TextLayoutMetrics .isWhitespace (text.codeUnitAt (forward ? offset - 1 : offset));
15941594 });
1595- newPosition = _beyondTextBoundary (targetedEdge, forward, textBoundary);
1595+ newPosition = _moveBeyondTextBoundaryAtDirection (targetedEdge, forward, textBoundary);
15961596 result = SelectionResult .end;
15971597 break ;
15981598 case TextGranularity .line:
1599- newPosition = _toTextBoundary (targetedEdge, forward, LineBoundary (this ));
1599+ newPosition = _moveToTextBoundaryAtDirection (targetedEdge, forward, LineBoundary (this ));
16001600 result = SelectionResult .end;
16011601 break ;
16021602 case TextGranularity .document:
16031603 final String text = range.textInside (fullText);
1604- newPosition = _beyondTextBoundary (targetedEdge, forward, DocumentBoundary (text));
1604+ newPosition = _moveBeyondTextBoundaryAtDirection (targetedEdge, forward, DocumentBoundary (text));
16051605 if (forward && newPosition.offset == range.end) {
16061606 result = SelectionResult .next;
16071607 } else if (! forward && newPosition.offset == range.start) {
@@ -1620,26 +1620,37 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
16201620 return result;
16211621 }
16221622
1623- TextPosition _beyondTextBoundary (TextPosition extent, bool forward, TextBoundary textBoundary) {
1623+ // Move **beyond** the local boundary of the given type (unless range.start or
1624+ // range.end is reached). Used for most TextGranularity types except for
1625+ // TextGranularity.line, to ensure the selection movement doesn't get stuck at
1626+ // a local fixed point.
1627+ TextPosition _moveBeyondTextBoundaryAtDirection (TextPosition end, bool forward, TextBoundary textBoundary) {
16241628 final int newOffset = forward
1625- ? textBoundary.getTrailingTextBoundaryAt (extent .offset) ?? range.end
1626- : textBoundary.getLeadingTextBoundaryAt (extent .offset - 1 ) ?? range.start;
1629+ ? textBoundary.getTrailingTextBoundaryAt (end .offset) ?? range.end
1630+ : textBoundary.getLeadingTextBoundaryAt (end .offset - 1 ) ?? range.start;
16271631 return TextPosition (offset: newOffset);
16281632 }
16291633
1630- TextPosition _toTextBoundary (TextPosition extent, bool forward, TextBoundary textBoundary) {
1631- assert (extent.offset >= 0 );
1634+ // Move **to** the local boundary of the given type. Typically used for line
1635+ // boundaries, such that performing "move to line start" more than once never
1636+ // moves the selection to the previous line.
1637+ TextPosition _moveToTextBoundaryAtDirection (TextPosition end, bool forward, TextBoundary textBoundary) {
1638+ assert (end.offset >= 0 );
16321639 final int caretOffset;
1633- switch (extent .affinity) {
1640+ switch (end .affinity) {
16341641 case TextAffinity .upstream:
1635- if (extent .offset < 1 && ! forward) {
1636- assert (extent .offset == 0 );
1642+ if (end .offset < 1 && ! forward) {
1643+ assert (end .offset == 0 );
16371644 return const TextPosition (offset: 0 );
16381645 }
1639- caretOffset = math.max (0 , extent.offset - 1 );
1646+ final CharacterBoundary characterBoundary = CharacterBoundary (fullText);
1647+ caretOffset = math.max (
1648+ 0 ,
1649+ characterBoundary.getLeadingTextBoundaryAt (range.start + end.offset) ?? range.start,
1650+ ) - 1 ;
16401651 break ;
16411652 case TextAffinity .downstream:
1642- caretOffset = extent .offset;
1653+ caretOffset = end .offset;
16431654 break ;
16441655 }
16451656 final int offset = forward
0 commit comments