Skip to content

Commit 4bf8749

Browse files
review
1 parent 1aad3e1 commit 4bf8749

File tree

2 files changed

+37
-22
lines changed

2 files changed

+37
-22
lines changed

packages/flutter/lib/src/rendering/paragraph.dart

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ typedef EditableTextContextMenuBuilder = Widget Function(
6969
EditableTextState editableTextState,
7070
);
7171

72+
// Signature for a function that determines the target location of the given
73+
// [TextPosition] after applying the given [TextBoundary].
74+
typedef _ApplyTextBoundary = TextPosition Function(TextPosition, bool, TextBoundary);
75+
7276
// The time it takes for the cursor to fade from fully opaque to fully
7377
// transparent and vice versa. A full cursor blink, from transparent to opaque
7478
// to transparent, is twice this duration.
@@ -4716,11 +4720,11 @@ class _CodeUnitBoundary extends TextBoundary {
47164720

47174721
// ------------------------------- Text Actions -------------------------------
47184722
class _DeleteTextAction<T extends DirectionalTextEditingIntent> extends ContextAction<T> {
4719-
_DeleteTextAction(this.state, this.textBoundary, this._applyTextBoundary);
4723+
_DeleteTextAction(this.state, this.getTextBoundary, this._applyTextBoundary);
47204724

47214725
final EditableTextState state;
4722-
final TextBoundary Function() textBoundary;
4723-
final TextPosition Function(TextPosition, bool, TextBoundary) _applyTextBoundary;
4726+
final TextBoundary Function() getTextBoundary;
4727+
final _ApplyTextBoundary _applyTextBoundary;
47244728

47254729
@override
47264730
Object? invoke(T intent, [BuildContext? context]) {
@@ -4743,7 +4747,7 @@ class _DeleteTextAction<T extends DirectionalTextEditingIntent> extends ContextA
47434747
);
47444748
}
47454749

4746-
final int target = _applyTextBoundary(selection.base, intent.forward, textBoundary()).offset;
4750+
final int target = _applyTextBoundary(selection.base, intent.forward, getTextBoundary()).offset;
47474751

47484752
final TextRange rangeToDelete = TextSelection(
47494753
baseOffset: intent.forward
@@ -4764,7 +4768,7 @@ class _DeleteTextAction<T extends DirectionalTextEditingIntent> extends ContextA
47644768
class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> extends ContextAction<T> {
47654769
_UpdateTextSelectionAction(
47664770
this.state,
4767-
this.textBoundary,
4771+
this.getTextBoundary,
47684772
this.applyTextBoundary, {
47694773
required this.ignoreNonCollapsedSelection,
47704774
this.isExpand = false,
@@ -4775,8 +4779,8 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> exten
47754779
final bool ignoreNonCollapsedSelection;
47764780
final bool isExpand;
47774781
final bool extentAtIndex;
4778-
final TextBoundary Function() textBoundary;
4779-
final TextPosition Function(TextPosition, bool, TextBoundary) applyTextBoundary;
4782+
final TextBoundary Function() getTextBoundary;
4783+
final _ApplyTextBoundary applyTextBoundary;
47804784

47814785
static const int NEWLINE_CODE_UNIT = 10;
47824786

@@ -4832,7 +4836,7 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> exten
48324836
}
48334837

48344838
final bool shouldTargetBase = isExpand && (intent.forward ? selection.baseOffset > selection.extentOffset : selection.baseOffset < selection.extentOffset);
4835-
final TextPosition newExtent = applyTextBoundary(shouldTargetBase ? selection.base : extent, intent.forward, textBoundary());
4839+
final TextPosition newExtent = applyTextBoundary(shouldTargetBase ? selection.base : extent, intent.forward, getTextBoundary());
48364840
final TextSelection newSelection = collapseSelection || (!isExpand && newExtent.offset == selection.baseOffset)
48374841
? TextSelection.fromPosition(newExtent)
48384842
: isExpand ? selection.expandTo(newExtent, extentAtIndex || selection.isCollapsed) : selection.extendTo(newExtent);

0 commit comments

Comments
 (0)