Handle invalid selection in TextEditingActionTarget (#90826)
Prevents bugs related to invalid (-1,-1) selection in keyboard shortcuts
This commit is contained in:
parent
2367a177f3
commit
cf09d99372
@ -322,7 +322,7 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// If the selection is not collapsed, deletes the selection.
|
||||
///
|
||||
/// If [readOnly] is true, does nothing.
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@template flutter.widgets.TextEditingActionTarget.cause}
|
||||
/// The given [SelectionChangedCause] indicates the cause of this change and
|
||||
@ -336,6 +336,9 @@ abstract class TextEditingActionTarget {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// `delete` does not depend on the text layout, and the boundary analysis is
|
||||
// done using the `previousCharacter` method instead of ICU, we can keep
|
||||
@ -356,7 +359,7 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// If the selection is not collapsed, deletes the selection.
|
||||
///
|
||||
/// If [readOnly] is true, does nothing.
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// If [obscureText] is true, it treats the whole text content as a single
|
||||
/// word.
|
||||
@ -377,6 +380,9 @@ abstract class TextEditingActionTarget {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (obscureText) {
|
||||
// When the text is obscured, the whole thing is treated as one big line.
|
||||
@ -400,7 +406,7 @@ abstract class TextEditingActionTarget {
|
||||
/// If [obscureText] is true, it treats the whole text content as
|
||||
/// a single word.
|
||||
///
|
||||
/// If [readOnly] is true, does nothing.
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
@ -411,6 +417,9 @@ abstract class TextEditingActionTarget {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When there is a line break, line delete shouldn't do anything
|
||||
final String textBefore = textEditingValue.selection.textBefore(textEditingValue.text);
|
||||
@ -439,7 +448,7 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// If the selection is not collapsed, deletes the selection.
|
||||
///
|
||||
/// If [readOnly] is true, does nothing.
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
@ -450,6 +459,9 @@ abstract class TextEditingActionTarget {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String textAfter = textEditingValue.selection.textAfter(textEditingValue.text);
|
||||
final int characterBoundary = nextCharacter(0, textAfter);
|
||||
@ -462,7 +474,7 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// If the selection is not collapsed, deletes the selection.
|
||||
///
|
||||
/// If [readOnly] is true, does nothing.
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// If [obscureText] is true, it treats the whole text content as
|
||||
/// a single word.
|
||||
@ -479,6 +491,9 @@ abstract class TextEditingActionTarget {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (obscureText) {
|
||||
// When the text is obscured, the whole thing is treated as one big word.
|
||||
@ -498,7 +513,7 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// If the selection is not collapsed, deletes the selection.
|
||||
///
|
||||
/// If [readOnly] is true, does nothing.
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// If [obscureText] is true, it treats the whole text content as
|
||||
/// a single word.
|
||||
@ -512,6 +527,9 @@ abstract class TextEditingActionTarget {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (obscureText) {
|
||||
// When the text is obscured, the whole thing is treated as one big line.
|
||||
@ -539,10 +557,18 @@ abstract class TextEditingActionTarget {
|
||||
/// The given SelectionChangedCause indicates the cause of this change and
|
||||
/// will be passed to setSelection.
|
||||
///
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// See also:
|
||||
/// * [deleteToStart]
|
||||
void deleteToEnd(SelectionChangedCause cause) {
|
||||
assert(textEditingValue.selection.isCollapsed);
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTextEditingValue(_deleteTo(TextPosition(offset: textEditingValue.text.length)), cause);
|
||||
}
|
||||
@ -552,10 +578,18 @@ abstract class TextEditingActionTarget {
|
||||
/// The given SelectionChangedCause indicates the cause of this change and
|
||||
/// will be passed to setSelection.
|
||||
///
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// See also:
|
||||
/// * [deleteToEnd]
|
||||
void deleteToStart(SelectionChangedCause cause) {
|
||||
assert(textEditingValue.selection.isCollapsed);
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTextEditingValue(_deleteTo(const TextPosition(offset: 0)), cause);
|
||||
}
|
||||
@ -569,12 +603,17 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// to the end.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [expandSelectionToStart], which is same but in the opposite direction.
|
||||
void expandSelectionToEnd(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionToEnd(cause);
|
||||
}
|
||||
@ -595,6 +634,8 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// to the start.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -602,6 +643,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [expandSelectionToEnd], which is the same but in the opposite
|
||||
/// direction.
|
||||
void expandSelectionToStart(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionToStart(cause);
|
||||
}
|
||||
@ -623,6 +667,8 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// left by line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -630,6 +676,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [expandSelectionRightByLine], which is the same but in the opposite
|
||||
/// direction.
|
||||
void expandSelectionLeftByLine(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionLeftByLine(cause);
|
||||
}
|
||||
@ -666,6 +715,8 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// right by line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -673,6 +724,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [expandSelectionLeftByLine], which is the same but in the opposite
|
||||
/// direction.
|
||||
void expandSelectionRightByLine(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionRightByLine(cause);
|
||||
}
|
||||
@ -707,12 +761,17 @@ abstract class TextEditingActionTarget {
|
||||
/// If selectionEnabled is false, keeps the selection collapsed and just
|
||||
/// moves it down.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [extendSelectionUp], which is same but in the opposite direction.
|
||||
void extendSelectionDown(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionDown(cause);
|
||||
}
|
||||
@ -747,12 +806,17 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// left.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [extendSelectionRight], which is same but in the opposite direction.
|
||||
void extendSelectionLeft(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionLeft(cause);
|
||||
}
|
||||
@ -782,6 +846,8 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// left by line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -791,6 +857,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [expandSelectionRightByLine], which strictly grows the selection
|
||||
/// regardless of the order.
|
||||
void extendSelectionLeftByLine(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionLeftByLine(cause);
|
||||
}
|
||||
@ -828,12 +897,17 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// right.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [extendSelectionLeft], which is same but in the opposite direction.
|
||||
void extendSelectionRight(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionRight(cause);
|
||||
}
|
||||
@ -860,6 +934,8 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// right by line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -869,6 +945,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [expandSelectionRightByLine], which strictly grows the selection
|
||||
/// regardless of the order.
|
||||
void extendSelectionRightByLine(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionRightByLine(cause);
|
||||
}
|
||||
@ -898,6 +977,8 @@ abstract class TextEditingActionTarget {
|
||||
|
||||
/// Extend the current selection to the previous start of a word.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
|
||||
@ -915,6 +996,9 @@ abstract class TextEditingActionTarget {
|
||||
/// direction.
|
||||
void extendSelectionLeftByWord(SelectionChangedCause cause,
|
||||
[bool includeWhitespace = true, bool stopAtReversal = false]) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// When the text is obscured, the whole thing is treated as one big word.
|
||||
if (obscureText) {
|
||||
return _extendSelectionToStart(cause);
|
||||
@ -946,6 +1030,8 @@ abstract class TextEditingActionTarget {
|
||||
|
||||
/// Extend the current selection to the next end of a word.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
|
||||
@ -958,6 +1044,9 @@ abstract class TextEditingActionTarget {
|
||||
/// direction.
|
||||
void extendSelectionRightByWord(SelectionChangedCause cause,
|
||||
[bool includeWhitespace = true, bool stopAtReversal = false]) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
debugAssertLayoutUpToDate();
|
||||
// When the text is obscured, the whole thing is treated as one big word.
|
||||
if (obscureText) {
|
||||
@ -997,6 +1086,8 @@ abstract class TextEditingActionTarget {
|
||||
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
|
||||
/// up.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -1004,6 +1095,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [extendSelectionDown], which is the same but in the opposite
|
||||
/// direction.
|
||||
void extendSelectionUp(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
if (!selectionEnabled) {
|
||||
return moveSelectionUp(cause);
|
||||
}
|
||||
@ -1043,6 +1137,8 @@ abstract class TextEditingActionTarget {
|
||||
|
||||
/// Move the current selection to the leftmost point of the current line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -1050,6 +1146,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [moveSelectionRightByLine], which is the same but in the opposite
|
||||
/// direction.
|
||||
void moveSelectionLeftByLine(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// If already at the left edge of the line, do nothing.
|
||||
final TextSelection currentLine = textLayoutMetrics.getLineAtOffset(
|
||||
textEditingValue.selection.extent,
|
||||
@ -1079,12 +1178,17 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// Move the current selection to the next line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [moveSelectionUp], which is the same but in the opposite direction.
|
||||
void moveSelectionDown(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// If the selection is collapsed at the end of the field already, then
|
||||
// nothing happens.
|
||||
if (textEditingValue.selection.isCollapsed &&
|
||||
@ -1118,12 +1222,17 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// If it can't be moved left, do nothing.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [moveSelectionRight], which is the same but in the opposite direction.
|
||||
void moveSelectionLeft(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// If the selection is already all the way left, there is nothing to do.
|
||||
if (textEditingValue.selection.isCollapsed && textEditingValue.selection.extentOffset <= 0) {
|
||||
return;
|
||||
@ -1156,6 +1265,8 @@ abstract class TextEditingActionTarget {
|
||||
/// A TextSelection that isn't collapsed will be collapsed and moved from the
|
||||
/// extentOffset.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
|
||||
@ -1166,6 +1277,9 @@ abstract class TextEditingActionTarget {
|
||||
/// direction.
|
||||
void moveSelectionLeftByWord(SelectionChangedCause cause,
|
||||
[bool includeWhitespace = true]) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// When the text is obscured, the whole thing is treated as one big word.
|
||||
if (obscureText) {
|
||||
return moveSelectionToStart(cause);
|
||||
@ -1189,7 +1303,7 @@ abstract class TextEditingActionTarget {
|
||||
|
||||
/// Move the current selection to the right by one character.
|
||||
///
|
||||
/// If it can't be moved right, do nothing.
|
||||
/// If the selection is invalid or it can't be moved right, do nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
@ -1197,6 +1311,9 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// * [moveSelectionLeft], which is the same but in the opposite direction.
|
||||
void moveSelectionRight(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// If the selection is already all the way right, there is nothing to do.
|
||||
if (textEditingValue.selection.isCollapsed &&
|
||||
textEditingValue.selection.extentOffset >= textEditingValue.text.length) {
|
||||
@ -1224,6 +1341,8 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// Move the current selection to the rightmost point of the current line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
@ -1231,6 +1350,9 @@ abstract class TextEditingActionTarget {
|
||||
/// * [moveSelectionLeftByLine], which is the same but in the opposite
|
||||
/// direction.
|
||||
void moveSelectionRightByLine(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// If already at the right edge of the line, do nothing.
|
||||
final TextSelection currentLine = textLayoutMetrics.getLineAtOffset(
|
||||
textEditingValue.selection.extent,
|
||||
@ -1263,6 +1385,8 @@ abstract class TextEditingActionTarget {
|
||||
/// A TextSelection that isn't collapsed will be collapsed and moved from the
|
||||
/// extentOffset.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
|
||||
@ -1273,6 +1397,9 @@ abstract class TextEditingActionTarget {
|
||||
/// direction.
|
||||
void moveSelectionRightByWord(SelectionChangedCause cause,
|
||||
[bool includeWhitespace = true]) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
// When the text is obscured, the whole thing is treated as one big word.
|
||||
if (obscureText) {
|
||||
return moveSelectionToEnd(cause);
|
||||
@ -1328,12 +1455,17 @@ abstract class TextEditingActionTarget {
|
||||
|
||||
/// Move the current selection up by one line.
|
||||
///
|
||||
/// If the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [moveSelectionDown], which is the same but in the opposite direction.
|
||||
void moveSelectionUp(SelectionChangedCause cause) {
|
||||
if (!textEditingValue.selection.isValid) {
|
||||
return;
|
||||
}
|
||||
final int nextIndex =
|
||||
textLayoutMetrics.getTextPositionAbove(textEditingValue.selection.extent).offset;
|
||||
|
||||
@ -1363,12 +1495,14 @@ abstract class TextEditingActionTarget {
|
||||
/// Copy current selection to [Clipboard].
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// If the selection is collapsed or invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
void copySelection(SelectionChangedCause cause) {
|
||||
final TextSelection selection = textEditingValue.selection;
|
||||
final String text = textEditingValue.text;
|
||||
assert(selection != null);
|
||||
if (selection.isCollapsed) {
|
||||
if (selection.isCollapsed || !selection.isValid) {
|
||||
return;
|
||||
}
|
||||
Clipboard.setData(ClipboardData(text: selection.textInside(text)));
|
||||
@ -1378,12 +1512,14 @@ abstract class TextEditingActionTarget {
|
||||
/// Cut current selection to Clipboard.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
void cutSelection(SelectionChangedCause cause) {
|
||||
if (readOnly) {
|
||||
final TextSelection selection = textEditingValue.selection;
|
||||
if (readOnly || !selection.isValid) {
|
||||
return;
|
||||
}
|
||||
final TextSelection selection = textEditingValue.selection;
|
||||
final String text = textEditingValue.text;
|
||||
assert(selection != null);
|
||||
if (selection.isCollapsed) {
|
||||
@ -1408,12 +1544,14 @@ abstract class TextEditingActionTarget {
|
||||
///
|
||||
/// If there is currently a selection, it will be replaced.
|
||||
///
|
||||
/// If [readOnly] is true or the selection is invalid, does nothing.
|
||||
///
|
||||
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
|
||||
Future<void> pasteText(SelectionChangedCause cause) async {
|
||||
if (readOnly) {
|
||||
final TextSelection selection = textEditingValue.selection;
|
||||
if (readOnly || !selection.isValid) {
|
||||
return;
|
||||
}
|
||||
final TextSelection selection = textEditingValue.selection;
|
||||
final String text = textEditingValue.text;
|
||||
assert(selection != null);
|
||||
if (!selection.isValid) {
|
||||
|
@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/rendering_tester.dart';
|
||||
import 'clipboard_utils.dart';
|
||||
|
||||
class _FakeEditableTextState with TextSelectionDelegate, TextEditingActionTarget {
|
||||
_FakeEditableTextState({
|
||||
@ -93,9 +94,24 @@ class _FakeEditableTextState with TextSelectionDelegate, TextEditingActionTarget
|
||||
}
|
||||
|
||||
void main() {
|
||||
final MockClipboard mockClipboard = MockClipboard();
|
||||
// Ensure that all TestRenderingFlutterBinding bindings are initialized.
|
||||
renderer;
|
||||
|
||||
setUp(() async {
|
||||
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
mockClipboard.handleMethodCall,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('moveSelectionLeft/RightByLine stays on the current line', () async {
|
||||
const String text = 'one two three\n\nfour five six';
|
||||
final _FakeEditableTextState editableTextState = _FakeEditableTextState(
|
||||
@ -1712,6 +1728,134 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
test("When a selection is needed but it's invalid, nothing is changed", () async {
|
||||
const String text = 'one two three\n\nfour five six';
|
||||
final _FakeEditableTextState editableTextState = _FakeEditableTextState(
|
||||
textSpan: const TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
|
||||
),
|
||||
),
|
||||
textEditingValue: const TextEditingValue(
|
||||
text: text,
|
||||
selection: TextSelection.collapsed(offset: -1),
|
||||
),
|
||||
);
|
||||
final RenderEditable editable = editableTextState.renderEditable;
|
||||
|
||||
layout(editable);
|
||||
editable.hasFocus = true;
|
||||
|
||||
editableTextState.delete(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.deleteByWord(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.deleteByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.deleteForward(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.deleteForwardByWord(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.deleteForwardByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.deleteToEnd(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.deleteToStart(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
|
||||
editableTextState.expandSelectionToEnd(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.expandSelectionToStart(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.expandSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.expandSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionDown(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionLeft(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionLeft(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionRight(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionLeftByWord(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionRightByWord(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.extendSelectionUp(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionDown(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionLeftByWord(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionRightByWord(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.moveSelectionUp(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
|
||||
editableTextState.copySelection(SelectionChangedCause.keyboard);
|
||||
ClipboardData? clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(clipboardData?.text, null);
|
||||
|
||||
editableTextState.cutSelection(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(clipboardData?.text, null);
|
||||
|
||||
editableTextState.pasteText(SelectionChangedCause.keyboard);
|
||||
expect(editableTextState.textEditingValue.selection.isValid, false);
|
||||
expect(editableTextState.textEditingValue.text, text);
|
||||
});
|
||||
|
||||
group('nextCharacter', () {
|
||||
test('handles normal strings correctly', () {
|
||||
expect(TextEditingActionTarget.nextCharacter(0, '01234567'), 1);
|
||||
|
Loading…
x
Reference in New Issue
Block a user