Move text editing Actions to EditableTextState (#90684)

This commit is contained in:
LongCatIsLooong 2021-11-03 11:11:32 -07:00 committed by GitHub
parent 035dfc87c5
commit ffcd32ebb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 3403 additions and 3261 deletions

View File

@ -18,7 +18,7 @@ void main() {
}
// This implements a custom phone number input field that handles the
// [DeleteTextIntent] intent.
// [DeleteCharacterIntent] intent.
class DigitInput extends StatefulWidget {
const DigitInput({
Key? key,
@ -38,9 +38,9 @@ class DigitInput extends StatefulWidget {
}
class DigitInputState extends State<DigitInput> {
late final Action<DeleteTextIntent> _deleteTextAction =
CallbackAction<DeleteTextIntent>(
onInvoke: (DeleteTextIntent intent) {
late final Action<DeleteCharacterIntent> _deleteTextAction =
CallbackAction<DeleteCharacterIntent>(
onInvoke: (DeleteCharacterIntent intent) {
// For simplicity we delete everything in the section.
widget.controller.clear();
},
@ -50,8 +50,8 @@ class DigitInputState extends State<DigitInput> {
Widget build(BuildContext context) {
return Actions(
actions: <Type, Action<Intent>>{
// Make the default `DeleteTextIntent` handler overridable.
DeleteTextIntent: Action<DeleteTextIntent>.overridable(
// Make the default `DeleteCharacterIntent` handler overridable.
DeleteCharacterIntent: Action<DeleteCharacterIntent>.overridable(
defaultAction: _deleteTextAction, context: context),
},
child: TextField(
@ -79,12 +79,12 @@ class SimpleUSPhoneNumberEntry extends StatefulWidget {
_SimpleUSPhoneNumberEntryState();
}
class _DeleteDigit extends Action<DeleteTextIntent> {
class _DeleteDigit extends Action<DeleteCharacterIntent> {
_DeleteDigit(this.state);
final _SimpleUSPhoneNumberEntryState state;
@override
Object? invoke(DeleteTextIntent intent) {
Object? invoke(DeleteCharacterIntent intent) {
assert(callingAction != null);
callingAction?.invoke(intent);
@ -116,7 +116,7 @@ class _SimpleUSPhoneNumberEntryState extends State<SimpleUSPhoneNumberEntry> {
Widget build(BuildContext context) {
return Actions(
actions: <Type, Action<Intent>>{
DeleteTextIntent: _DeleteDigit(this),
DeleteCharacterIntent: _DeleteDigit(this),
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@ -206,6 +206,7 @@ class TextPainter {
/// in framework will automatically invoke this method.
void markNeedsLayout() {
_paragraph = null;
_lineMetricsCache = null;
_previousCaretPosition = null;
_previousCaretPrototype = null;
}
@ -975,6 +976,7 @@ class TextPainter {
return _paragraph!.getLineBoundary(position);
}
List<ui.LineMetrics>? _lineMetricsCache;
/// Returns the full list of [LineMetrics] that describe in detail the various
/// metrics of each laid out line.
///
@ -992,6 +994,6 @@ class TextPainter {
/// should be invalidated upon the next successful [layout].
List<ui.LineMetrics> computeLineMetrics() {
assert(!_debugNeedsLayout);
return _paragraph!.computeLineMetrics();
return _lineMetricsCache ??= _paragraph!.computeLineMetrics();
}
}

View File

@ -4,7 +4,7 @@
import 'dart:collection';
import 'dart:math' as math;
import 'dart:ui' as ui show TextBox, BoxHeightStyle, BoxWidthStyle, PlaceholderAlignment;
import 'dart:ui' as ui show TextBox, BoxHeightStyle, BoxWidthStyle, PlaceholderAlignment, LineMetrics;
import 'package:characters/characters.dart';
import 'package:flutter/foundation.dart';
@ -77,6 +77,154 @@ class TextSelectionPoint {
}
}
/// The consecutive sequence of [TextPosition]s that the caret should move to
/// when the user navigates the paragraph using the upward arrow key or the
/// downward arrow key.
///
/// {@template flutter.rendering.RenderEditable.verticalArrowKeyMovement}
/// When the user presses the upward arrow key or the downward arrow key, on
/// many platforms (macOS for instance), the caret will move to the previous
/// line or the next line, while maintaining its original horizontal location.
/// When it encounters a shorter line, the caret moves to the closest horizontal
/// location within that line, and restores the original horizontal location
/// when a long enough line is encountered.
///
/// Additionally, the caret will move to the beginning of the document if the
/// upward arrow key is pressed and the caret is already on the first line. If
/// the downward arrow key is pressed next, the caret will restore its original
/// horizontal location and move to the second line. Similarly the caret moves
/// to the end of the document if the downward arrow key is pressed when it's
/// already on the last line.
///
/// Consider a left-aligned paragraph:
/// aa|
/// a
/// aaa
/// where the caret was initially placed at the end of the first line. Pressing
/// the downward arrow key once will move the caret to the end of the second
/// line, and twice the arrow key moves to the third line after the second "a"
/// on that line. Pressing the downward arrow key again, the caret will move to
/// the end of the third line (the end of the document). Pressing the upward
/// arrow key in this state will result in the caret moving to the end of the
/// second line.
///
/// Vertical caret runs are typically interrupted when the layout of the text
/// changes (including when the text itself changes), or when the selection is
/// changed by other input events or programmatically (for example, when the
/// user pressed the left arrow key).
/// {@endtemplate}
///
/// The [movePrevious] method moves the caret location (which is
/// [VerticalCaretMovementRun.current]) to the previous line, and in case
/// the caret is already on the first line, the method does nothing and returns
/// false. Similarly the [moveNext] method moves the caret to the next line, and
/// returns false if the caret is already on the last line.
///
/// If the underlying paragraph's layout changes, [isValid] becomes false and
/// the [VerticalCaretMovementRun] must not be used. The [isValid] property must
/// be checked before calling [movePrevious] and [moveNext], or accessing
/// [current].
class VerticalCaretMovementRun extends BidirectionalIterator<TextPosition> {
VerticalCaretMovementRun._(
this._editable,
this._lineMetrics,
this._currentTextPosition,
this._currentLine,
this._currentOffset,
);
Offset _currentOffset;
int _currentLine;
TextPosition _currentTextPosition;
final List<ui.LineMetrics> _lineMetrics;
final RenderEditable _editable;
bool _isValid = true;
/// Whether this [VerticalCaretMovementRun] can still continue.
///
/// A [VerticalCaretMovementRun] run is valid if the underlying text layout
/// hasn't changed.
///
/// The [current] value and the [movePrevious] and [moveNext] methods must not
/// be accessed when [isValid] is false.
bool get isValid {
if (!_isValid) {
return false;
}
final List<ui.LineMetrics> newLineMetrics = _editable._textPainter.computeLineMetrics();
// Use the implementation detail of the computeLineMetrics method to figure
// out if the current text layout has been invalidated.
if (!identical(newLineMetrics, _lineMetrics)) {
_isValid = false;
}
return _isValid;
}
// Computes the vertical distance from the `from` line's bottom to the `to`
// lines top.
double _lineDistance(int from, int to) {
double lineHeight = 0;
for (int index = from + 1; index < to; index += 1) {
lineHeight += _lineMetrics[index].height;
}
return lineHeight;
}
final Map<int, MapEntry<Offset, TextPosition>> _positionCache = <int, MapEntry<Offset, TextPosition>>{};
MapEntry<Offset, TextPosition> _getTextPositionForLine(int lineNumber) {
assert(isValid);
assert(lineNumber >= 0);
final MapEntry<Offset, TextPosition>? cachedPosition = _positionCache[lineNumber];
if (cachedPosition != null) {
return cachedPosition;
}
assert(lineNumber != _currentLine);
final double distanceY = lineNumber > _currentLine
? _lineMetrics[_currentLine].descent + _lineMetrics[lineNumber].ascent + _lineDistance(_currentLine, lineNumber)
: - _lineMetrics[_currentLine].ascent - _lineMetrics[lineNumber].descent - _lineDistance(lineNumber, _currentLine);
final Offset newOffset = _currentOffset.translate(0, distanceY);
final TextPosition closestPosition = _editable._textPainter.getPositionForOffset(newOffset);
final MapEntry<Offset, TextPosition> position = MapEntry<Offset, TextPosition>(newOffset, closestPosition);
_positionCache[lineNumber] = position;
return position;
}
@override
TextPosition get current {
assert(isValid);
return _currentTextPosition;
}
@override
bool moveNext() {
assert(isValid);
if (_currentLine + 1 >= _lineMetrics.length) {
return false;
}
final MapEntry<Offset, TextPosition> position = _getTextPositionForLine(_currentLine + 1);
_currentLine += 1;
_currentOffset = position.key;
_currentTextPosition = position.value;
return true;
}
@override
bool movePrevious() {
assert(isValid);
if (_currentLine <= 0) {
return false;
}
final MapEntry<Offset, TextPosition> position = _getTextPositionForLine(_currentLine - 1);
_currentLine -= 1;
_currentOffset = position.key;
_currentTextPosition = position.value;
return true;
}
}
/// Displays some text in a scrollable container with a potentially blinking
/// cursor and with gesture recognizers.
///
@ -2266,6 +2414,49 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
_caretPainter.showRegularCaret = _resetFloatingCursorAnimationValue == null;
}
MapEntry<int, Offset> _lineNumberFor(TextPosition startPosition, List<ui.LineMetrics> metrics) {
// TODO(LongCatIsLooong): include line boundaries information in
// ui.LineMetrics, then we can get rid of this.
final Offset offset = _textPainter.getOffsetForCaret(startPosition, Rect.zero);
int line = 0;
double accumulatedHeight = 0;
for (final ui.LineMetrics lineMetrics in metrics) {
if (accumulatedHeight + lineMetrics.height > offset.dy) {
return MapEntry<int, Offset>(line, Offset(offset.dx, lineMetrics.baseline));
}
line += 1;
accumulatedHeight += lineMetrics.height;
}
assert(false, 'unable to find the line for $startPosition');
return MapEntry<int, Offset>(math.max(0, metrics.length - 1), Offset(offset.dx, accumulatedHeight));
}
/// Starts a [VerticalCaretMovementRun] at the given location in the text, for
/// handling consecutive vertical caret movements.
///
/// This can be used to handle consecutive upward/downward arrow key movements
/// in an input field.
///
/// {@macro flutter.rendering.RenderEditable.verticalArrowKeyMovement}
///
/// The [VerticalCaretMovementRun.isValid] property indicates whether the text
/// layout has changed and the vertical caret run is invalidated.
///
/// The caller should typically discard a [VerticalCaretMovementRun] when
/// its [VerticalCaretMovementRun.isValid] becomes false, or on other
/// occasions where the vertical caret run should be interrupted.
VerticalCaretMovementRun startVerticalCaretMovement(TextPosition startPosition) {
final List<ui.LineMetrics> metrics = _textPainter.computeLineMetrics();
final MapEntry<int, Offset> currentLine = _lineNumberFor(startPosition, metrics);
return VerticalCaretMovementRun._(
this,
metrics,
startPosition,
currentLine.key,
currentLine.value,
);
}
void _paintContents(PaintingContext context, Offset offset) {
debugAssertLayoutUpToDate();
final Offset effectiveOffset = offset + _paintOffset;

View File

@ -80,8 +80,31 @@ class TextSelection extends TextRange {
/// The position at which the selection originates.
///
/// {@template flutter.services.TextSelection.TextAffinity}
/// The [TextAffinity] of the resulting [TextPosition] is based on the
/// relative logical position in the text to the other selection endpoint:
/// * if [baseOffset] < [extentOffset], [base] will have
/// [TextAffinity.downstream] and [extent] will have
/// [TextAffinity.upstream].
/// * if [baseOffset] > [extentOffset], [base] will have
/// [TextAffinity.upstream] and [extent] will have
/// [TextAffinity.downstream].
/// * if [baseOffset] == [extentOffset], [base] and [extent] will both have
/// the collapsed selection's [affinity].
/// {@endtemplate}
///
/// Might be larger than, smaller than, or equal to extent.
TextPosition get base => TextPosition(offset: baseOffset, affinity: affinity);
TextPosition get base {
final TextAffinity affinity;
if (!isValid || baseOffset == extentOffset) {
affinity = this.affinity;
} else if (baseOffset < extentOffset) {
affinity = TextAffinity.downstream;
} else {
affinity = TextAffinity.upstream;
}
return TextPosition(offset: baseOffset, affinity: affinity);
}
/// The position at which the selection terminates.
///
@ -89,8 +112,20 @@ class TextSelection extends TextRange {
/// value that changes. Similarly, if the current theme paints a caret on one
/// side of the selection, this is the location at which to paint the caret.
///
/// {@macro flutter.services.TextSelection.TextAffinity}
///
/// Might be larger than, smaller than, or equal to base.
TextPosition get extent => TextPosition(offset: extentOffset, affinity: affinity);
TextPosition get extent {
final TextAffinity affinity;
if (!isValid || baseOffset == extentOffset) {
affinity = this.affinity;
} else if (baseOffset < extentOffset) {
affinity = TextAffinity.upstream;
} else {
affinity = TextAffinity.downstream;
}
return TextPosition(offset: extentOffset, affinity: affinity);
}
@override
String toString() {

View File

@ -834,6 +834,55 @@ class TextEditingValue {
/// programming error.
bool get isComposingRangeValid => composing.isValid && composing.isNormalized && composing.end <= text.length;
/// Returns a new [TextEditingValue], which is this [TextEditingValue] with
/// its [text] partially replaced by the `replacementString`.
///
/// The `replacementRange` parameter specifies the range of the
/// [TextEditingValue.text] that needs to be replaced.
///
/// The `replacementString` parameter specifies the string to replace the
/// given range of text with.
///
/// This method also adjusts the selection range and the composing range of the
/// resulting [TextEditingValue], such that they point to the same substrings
/// as the correspoinding ranges in the original [TextEditingValue]. For
/// example, if the original [TextEditingValue] is "Hello world" with the word
/// "world" selected, replacing "Hello" with a different string using this
/// method will not change the selected word.
///
/// This method does nothing if the given `replacementRange` is not
/// [TextRange.isValid].
TextEditingValue replaced(TextRange replacementRange, String replacementString) {
if (!replacementRange.isValid) {
return this;
}
final String newText = text.replaceRange(replacementRange.start, replacementRange.end, replacementString);
if (replacementRange.end - replacementRange.start == replacementString.length) {
return copyWith(text: newText);
}
int adjustIndex(int originalIndex) {
// The length added by adding the replacementString.
final int replacedLength = originalIndex <= replacementRange.start && originalIndex < replacementRange.end ? 0 : replacementString.length;
// The length removed by removing the replacementRange.
final int removedLength = originalIndex.clamp(replacementRange.start, replacementRange.end) - replacementRange.start;
return originalIndex + replacedLength - removedLength;
}
return TextEditingValue(
text: newText,
selection: TextSelection(
baseOffset: adjustIndex(selection.baseOffset),
extentOffset: adjustIndex(selection.extentOffset),
),
composing: TextRange(
start: adjustIndex(composing.start),
end: adjustIndex(composing.end),
),
);
}
/// Returns a representation of this object as a JSON object.
Map<String, dynamic> toJSON() {
return <String, dynamic>{

View File

@ -142,8 +142,8 @@ abstract class Action<T extends Intent> with Diagnosticable {
///
/// {@tool dartpad}
/// This sample implements a custom text input field that handles the
/// [DeleteTextIntent] intent, as well as a US telephone number input widget
/// that consists of multiple text fields for area code, prefix and line
/// [DeleteCharacterIntent] intent, as well as a US telephone number input
/// widget that consists of multiple text fields for area code, prefix and line
/// number. When the backspace key is pressed, the phone number input widget
/// sends the focus to the preceding text field when the currently focused
/// field becomes empty.

View File

@ -12,7 +12,6 @@ import 'actions.dart';
import 'banner.dart';
import 'basic.dart';
import 'binding.dart';
import 'default_text_editing_actions.dart';
import 'default_text_editing_shortcuts.dart';
import 'focus_traversal.dart';
import 'framework.dart';
@ -1053,9 +1052,6 @@ class WidgetsApp extends StatefulWidget {
/// the [actions] for this app. You may also add to the bindings, or override
/// specific bindings for a widget subtree, by adding your own [Actions]
/// widget.
///
/// Passing this will not replace [DefaultTextEditingActions]. These can be
/// overridden by placing an [Actions] widget lower in the widget tree.
/// {@endtemplate}
///
/// {@tool snippet}
@ -1676,11 +1672,9 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
child: DefaultTextEditingShortcuts(
child: Actions(
actions: widget.actions ?? WidgetsApp.defaultActions,
child: DefaultTextEditingActions(
child: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(),
child: child,
),
child: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(),
child: child,
),
),
),

View File

@ -512,16 +512,12 @@ class _AutocompleteCallbackAction<T extends Intent> extends CallbackAction<T> {
}
/// An [Intent] to highlight the previous option in the autocomplete list.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class AutocompletePreviousOptionIntent extends Intent {
/// Creates an instance of AutocompletePreviousOptionIntent.
const AutocompletePreviousOptionIntent();
}
/// An [Intent] to highlight the next option in the autocomplete list.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class AutocompleteNextOptionIntent extends Intent {
/// Creates an instance of AutocompleteNextOptionIntent.
const AutocompleteNextOptionIntent();

View File

@ -1,326 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'actions.dart';
import 'editable_text.dart';
import 'framework.dart';
import 'text_editing_action.dart';
import 'text_editing_intents.dart';
/// An [Actions] widget that handles the default text editing behavior for
/// Flutter on the current platform.
///
/// This default behavior can be overridden by placing an [Actions] widget lower
/// in the widget tree than this. See [DefaultTextEditingShortcuts] for an example of
/// remapping keyboard keys to an existing text editing [Intent].
///
/// See also:
///
/// * [DefaultTextEditingShortcuts], which maps keyboard keys to many of the
/// [Intent]s that are handled here.
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
class DefaultTextEditingActions extends Actions {
/// Creates an instance of DefaultTextEditingActions.
DefaultTextEditingActions({
Key? key,
required Widget child,
}) : super(
key: key,
actions: _shortcutsActions,
child: child,
);
// These Intents are triggered by DefaultTextEditingShortcuts. They are included
// regardless of the platform; it's up to DefaultTextEditingShortcuts to decide which
// are called on which platform.
static final Map<Type, Action<Intent>> _shortcutsActions = <Type, Action<Intent>>{
DoNothingAndStopPropagationTextIntent: _DoNothingAndStopPropagationTextAction(),
DeleteTextIntent: _DeleteTextAction(),
DeleteByWordTextIntent: _DeleteByWordTextAction(),
DeleteByLineTextIntent: _DeleteByLineTextAction(),
DeleteForwardTextIntent: _DeleteForwardTextAction(),
DeleteForwardByWordTextIntent: _DeleteForwardByWordTextAction(),
DeleteForwardByLineTextIntent: _DeleteForwardByLineTextAction(),
ExtendSelectionDownTextIntent: _ExtendSelectionDownTextAction(),
ExtendSelectionLeftByLineTextIntent: _ExtendSelectionLeftByLineTextAction(),
ExtendSelectionLeftByWordTextIntent: _ExtendSelectionLeftByWordTextAction(),
ExtendSelectionLeftByWordAndStopAtReversalTextIntent: _ExtendSelectionLeftByWordAndStopAtReversalTextAction(),
ExtendSelectionLeftTextIntent: _ExtendSelectionLeftTextAction(),
ExtendSelectionRightByWordAndStopAtReversalTextIntent: _ExtendSelectionRightByWordAndStopAtReversalTextAction(),
ExtendSelectionRightByWordTextIntent: _ExtendSelectionRightByWordTextAction(),
ExtendSelectionRightByLineTextIntent: _ExtendSelectionRightByLineTextAction(),
ExtendSelectionRightTextIntent: _ExtendSelectionRightTextAction(),
ExtendSelectionUpTextIntent: _ExtendSelectionUpTextAction(),
ExpandSelectionLeftByLineTextIntent: _ExpandSelectionLeftByLineTextAction(),
ExpandSelectionRightByLineTextIntent: _ExpandSelectionRightByLineTextAction(),
ExpandSelectionToEndTextIntent: _ExpandSelectionToEndTextAction(),
ExpandSelectionToStartTextIntent: _ExpandSelectionToStartTextAction(),
MoveSelectionDownTextIntent: _MoveSelectionDownTextAction(),
MoveSelectionLeftByLineTextIntent: _MoveSelectionLeftByLineTextAction(),
MoveSelectionLeftByWordTextIntent: _MoveSelectionLeftByWordTextAction(),
MoveSelectionLeftTextIntent: _MoveSelectionLeftTextAction(),
MoveSelectionRightByLineTextIntent: _MoveSelectionRightByLineTextAction(),
MoveSelectionRightByWordTextIntent: _MoveSelectionRightByWordTextAction(),
MoveSelectionRightTextIntent: _MoveSelectionRightTextAction(),
MoveSelectionToEndTextIntent: _MoveSelectionToEndTextAction(),
MoveSelectionToStartTextIntent: _MoveSelectionToStartTextAction(),
MoveSelectionUpTextIntent: _MoveSelectionUpTextAction(),
SelectAllTextIntent: _SelectAllTextAction(),
CopySelectionTextIntent: _CopySelectionTextAction(),
CutSelectionTextIntent: _CutSelectionTextAction(),
PasteTextIntent: _PasteTextAction(),
};
}
// This allows the web engine to handle text editing events natively while using
// the same TextEditingAction logic to only handle events from a
// TextEditingTarget.
class _DoNothingAndStopPropagationTextAction extends TextEditingAction<DoNothingAndStopPropagationTextIntent> {
_DoNothingAndStopPropagationTextAction();
@override
bool consumesKey(Intent intent) => false;
@override
void invoke(DoNothingAndStopPropagationTextIntent intent, [BuildContext? context]) {}
}
class _DeleteTextAction extends TextEditingAction<DeleteTextIntent> {
@override
Object? invoke(DeleteTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.delete(SelectionChangedCause.keyboard);
}
}
class _DeleteByWordTextAction extends TextEditingAction<DeleteByWordTextIntent> {
@override
Object? invoke(DeleteByWordTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.deleteByWord(SelectionChangedCause.keyboard, false);
}
}
class _DeleteByLineTextAction extends TextEditingAction<DeleteByLineTextIntent> {
@override
Object? invoke(DeleteByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.deleteByLine(SelectionChangedCause.keyboard);
}
}
class _DeleteForwardTextAction extends TextEditingAction<DeleteForwardTextIntent> {
@override
Object? invoke(DeleteForwardTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.deleteForward(SelectionChangedCause.keyboard);
}
}
class _DeleteForwardByWordTextAction extends TextEditingAction<DeleteForwardByWordTextIntent> {
@override
Object? invoke(DeleteForwardByWordTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.deleteForwardByWord(SelectionChangedCause.keyboard, false);
}
}
class _DeleteForwardByLineTextAction extends TextEditingAction<DeleteForwardByLineTextIntent> {
@override
Object? invoke(DeleteForwardByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.deleteForwardByLine(SelectionChangedCause.keyboard);
}
}
class _ExpandSelectionLeftByLineTextAction extends TextEditingAction<ExpandSelectionLeftByLineTextIntent> {
@override
Object? invoke(ExpandSelectionLeftByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.expandSelectionLeftByLine(SelectionChangedCause.keyboard);
}
}
class _ExpandSelectionRightByLineTextAction extends TextEditingAction<ExpandSelectionRightByLineTextIntent> {
@override
Object? invoke(ExpandSelectionRightByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.expandSelectionRightByLine(SelectionChangedCause.keyboard);
}
}
class _ExpandSelectionToEndTextAction extends TextEditingAction<ExpandSelectionToEndTextIntent> {
@override
Object? invoke(ExpandSelectionToEndTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.expandSelectionToEnd(SelectionChangedCause.keyboard);
}
}
class _ExpandSelectionToStartTextAction extends TextEditingAction<ExpandSelectionToStartTextIntent> {
@override
Object? invoke(ExpandSelectionToStartTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.expandSelectionToStart(SelectionChangedCause.keyboard);
}
}
class _ExtendSelectionDownTextAction extends TextEditingAction<ExtendSelectionDownTextIntent> {
@override
Object? invoke(ExtendSelectionDownTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionDown(SelectionChangedCause.keyboard);
}
}
class _ExtendSelectionLeftByLineTextAction extends TextEditingAction<ExtendSelectionLeftByLineTextIntent> {
@override
Object? invoke(ExtendSelectionLeftByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionLeftByLine(SelectionChangedCause.keyboard);
}
}
class _ExtendSelectionLeftByWordAndStopAtReversalTextAction extends TextEditingAction<ExtendSelectionLeftByWordAndStopAtReversalTextIntent> {
@override
Object? invoke(ExtendSelectionLeftByWordAndStopAtReversalTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionLeftByWord(SelectionChangedCause.keyboard, false, true);
}
}
class _ExtendSelectionLeftByWordTextAction extends TextEditingAction<ExtendSelectionLeftByWordTextIntent> {
@override
Object? invoke(ExtendSelectionLeftByWordTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionLeftByWord(SelectionChangedCause.keyboard, false);
}
}
class _ExtendSelectionLeftTextAction extends TextEditingAction<ExtendSelectionLeftTextIntent> {
@override
Object? invoke(ExtendSelectionLeftTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionLeft(SelectionChangedCause.keyboard);
}
}
class _ExtendSelectionRightByLineTextAction extends TextEditingAction<ExtendSelectionRightByLineTextIntent> {
@override
Object? invoke(ExtendSelectionRightByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionRightByLine(SelectionChangedCause.keyboard);
}
}
class _ExtendSelectionRightByWordAndStopAtReversalTextAction extends TextEditingAction<ExtendSelectionRightByWordAndStopAtReversalTextIntent> {
@override
Object? invoke(ExtendSelectionRightByWordAndStopAtReversalTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionRightByWord(SelectionChangedCause.keyboard, false, true);
}
}
class _ExtendSelectionRightByWordTextAction extends TextEditingAction<ExtendSelectionRightByWordTextIntent> {
@override
Object? invoke(ExtendSelectionRightByWordTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionRightByWord(SelectionChangedCause.keyboard, false);
}
}
class _ExtendSelectionRightTextAction extends TextEditingAction<ExtendSelectionRightTextIntent> {
@override
Object? invoke(ExtendSelectionRightTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionRight(SelectionChangedCause.keyboard);
}
}
class _ExtendSelectionUpTextAction extends TextEditingAction<ExtendSelectionUpTextIntent> {
@override
Object? invoke(ExtendSelectionUpTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.extendSelectionUp(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionDownTextAction extends TextEditingAction<MoveSelectionDownTextIntent> {
@override
Object? invoke(MoveSelectionDownTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionDown(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionLeftTextAction extends TextEditingAction<MoveSelectionLeftTextIntent> {
@override
Object? invoke(MoveSelectionLeftTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionLeft(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionRightTextAction extends TextEditingAction<MoveSelectionRightTextIntent> {
@override
Object? invoke(MoveSelectionRightTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionRight(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionUpTextAction extends TextEditingAction<MoveSelectionUpTextIntent> {
@override
Object? invoke(MoveSelectionUpTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionUp(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionLeftByLineTextAction extends TextEditingAction<MoveSelectionLeftByLineTextIntent> {
@override
Object? invoke(MoveSelectionLeftByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionLeftByLine(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionLeftByWordTextAction extends TextEditingAction<MoveSelectionLeftByWordTextIntent> {
@override
Object? invoke(MoveSelectionLeftByWordTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionLeftByWord(SelectionChangedCause.keyboard, false);
}
}
class _MoveSelectionRightByLineTextAction extends TextEditingAction<MoveSelectionRightByLineTextIntent> {
@override
Object? invoke(MoveSelectionRightByLineTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionRightByLine(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionRightByWordTextAction extends TextEditingAction<MoveSelectionRightByWordTextIntent> {
@override
Object? invoke(MoveSelectionRightByWordTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionRightByWord(SelectionChangedCause.keyboard, false);
}
}
class _MoveSelectionToEndTextAction extends TextEditingAction<MoveSelectionToEndTextIntent> {
@override
Object? invoke(MoveSelectionToEndTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionToEnd(SelectionChangedCause.keyboard);
}
}
class _MoveSelectionToStartTextAction extends TextEditingAction<MoveSelectionToStartTextIntent> {
@override
Object? invoke(MoveSelectionToStartTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionToStart(SelectionChangedCause.keyboard);
}
}
class _SelectAllTextAction extends TextEditingAction<SelectAllTextIntent> {
@override
Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.selectAll(SelectionChangedCause.keyboard);
}
}
class _CopySelectionTextAction extends TextEditingAction<CopySelectionTextIntent> {
@override
Object? invoke(CopySelectionTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.copySelection(SelectionChangedCause.keyboard);
}
}
class _CutSelectionTextAction extends TextEditingAction<CutSelectionTextIntent> {
@override
Object? invoke(CutSelectionTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.cutSelection(SelectionChangedCause.keyboard);
}
}
class _PasteTextAction extends TextEditingAction<PasteTextIntent> {
@override
Object? invoke(PasteTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.pasteText(SelectionChangedCause.keyboard);
}
}

View File

@ -14,8 +14,8 @@ import 'text_editing_intents.dart';
/// behavior.
///
/// This default behavior can be overridden by placing a [Shortcuts] widget
/// lower in the widget tree than this. See [DefaultTextEditingActions] for an example
/// of remapping a text editing [Intent] to a custom [Action].
/// lower in the widget tree than this. See the [Action] class for an example
/// of remapping an [Intent] to a custom [Action].
///
/// {@tool snippet}
///
@ -143,9 +143,6 @@ import 'text_editing_intents.dart';
///
/// See also:
///
/// * [DefaultTextEditingActions], which contains all of the [Action]s that
/// respond to the [Intent]s in these shortcuts with the default text editing
/// behavior.
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
class DefaultTextEditingShortcuts extends Shortcuts {
/// Creates a [Shortcuts] widget that provides the default text editing
@ -160,200 +157,103 @@ class DefaultTextEditingShortcuts extends Shortcuts {
child: child,
);
static const Map<ShortcutActivator, Intent> _androidShortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace): DeleteTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, control: true): DeleteByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.delete): DeleteForwardTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, control: true): DeleteForwardByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteForwardByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): MoveSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): MoveSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): MoveSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp): MoveSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): MoveSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): MoveSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): ExtendSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): ExtendSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this
// platform:
// * End
// * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left
// * Meta + arrow right
// * Meta + arrow up
// * Meta + shift + arrow down
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
// These are shortcuts are shared between most platforms except macOS for it
// uses different modifier keys as the line/word modifer.
static const Map<ShortcutActivator, Intent> _commonShortcuts = <ShortcutActivator, Intent>{
// Delete Shortcuts.
SingleActivator(LogicalKeyboardKey.backspace): DeleteCharacterIntent(forward: false),
SingleActivator(LogicalKeyboardKey.backspace, control: true): DeleteToNextWordBoundaryIntent(forward: false),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteToLineBreakIntent(forward: false),
SingleActivator(LogicalKeyboardKey.delete): DeleteCharacterIntent(forward: true),
SingleActivator(LogicalKeyboardKey.delete, control: true): DeleteToNextWordBoundaryIntent(forward: true),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteToLineBreakIntent(forward: true),
// Arrow: Move Selection.
SingleActivator(LogicalKeyboardKey.arrowLeft): ExtendSelectionByCharacterIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowRight): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowUp): ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowDown): ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: true),
// Shift + Arrow: Extend Selection.
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionByCharacterIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): ExtendSelectionToNextWordBoundaryIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): ExtendSelectionToNextWordBoundaryIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): ExtendSelectionToNextWordBoundaryIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): ExtendSelectionToNextWordBoundaryIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CopySelectionTextIntent.cut(SelectionChangedCause.keyboard),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent.copy,
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(SelectionChangedCause.keyboard),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(SelectionChangedCause.keyboard),
};
static const Map<ShortcutActivator, Intent> _fuchsiaShortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace): DeleteTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, control: true): DeleteByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.delete): DeleteForwardTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, control: true): DeleteForwardByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteForwardByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): MoveSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): MoveSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): MoveSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp): MoveSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): MoveSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): MoveSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): ExtendSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): ExtendSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this
// platform:
// * End
// * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left
// * Meta + arrow right
// * Meta + arrow up
// * Meta + shift + arrow down
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
};
// The following key combinations have no effect on text editing on this
// platform:
// * End
// * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left
// * Meta + arrow right
// * Meta + arrow up
// * Meta + shift + arrow down
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
static const Map<ShortcutActivator, Intent> _androidShortcuts = _commonShortcuts;
static const Map<ShortcutActivator, Intent> _iOSShortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace): DeleteTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, control: true): DeleteByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.delete): DeleteForwardTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, control: true): DeleteForwardByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteForwardByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): MoveSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): MoveSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): MoveSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp): MoveSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): MoveSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): MoveSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): ExtendSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): ExtendSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this
// platform:
// * End
// * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left
// * Meta + arrow right
// * Meta + arrow up
// * Meta + shift + arrow down
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
};
static const Map<ShortcutActivator, Intent> _fuchsiaShortcuts = _androidShortcuts;
// The following key combinations have no effect on text editing on this
// platform:
// * End
// * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left
// * Meta + arrow right
// * Meta + arrow up
// * Meta + shift + arrow down
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
static const Map<ShortcutActivator, Intent> _iOSShortcuts = _commonShortcuts;
static const Map<ShortcutActivator, Intent> _linuxShortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace): DeleteTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, control: true): DeleteByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.delete): DeleteForwardTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, control: true): DeleteForwardByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteForwardByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): MoveSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): MoveSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): MoveSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp): MoveSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): MoveSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): MoveSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): ExtendSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): ExtendSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.end): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.home): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExtendSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExtendSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
..._commonShortcuts,
SingleActivator(LogicalKeyboardKey.home): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.end): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: false),
// The following key combinations have no effect on text editing on this
// platform:
// * Meta + X
@ -372,111 +272,74 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// * Meta + backspace
};
// macOS document shortcuts: https://support.apple.com/en-us/HT201236.
// The macOS shortcuts uses different word/line modifiers than most other
// platforms.
static const Map<ShortcutActivator, Intent> _macShortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace): DeleteTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, meta: true): DeleteByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.delete): DeleteForwardTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteForwardByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, meta: true): DeleteForwardByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): MoveSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): MoveSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExtendSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExtendSelectionLeftByWordAndStopAtReversalTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExtendSelectionRightByWordAndStopAtReversalTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExtendSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): MoveSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp): MoveSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): MoveSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): MoveSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, meta: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, meta: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, meta: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, meta: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, meta: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, meta: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, meta: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, meta: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this
// platform:
// * Control + X
// * Control + C
// * Control + V
// * Control + A
// * Control + arrow left
// * Control + arrow right
// * Control + shift + arrow left
// * Control + shift + arrow right
// * End
// * Home
// * Control + delete
// * Control + backspace
SingleActivator(LogicalKeyboardKey.backspace): DeleteCharacterIntent(forward: false),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteToNextWordBoundaryIntent(forward: false),
SingleActivator(LogicalKeyboardKey.backspace, meta: true): DeleteToLineBreakIntent(forward: false),
SingleActivator(LogicalKeyboardKey.delete): DeleteCharacterIntent(forward: true),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteToNextWordBoundaryIntent(forward: true),
SingleActivator(LogicalKeyboardKey.delete, meta: true): DeleteToLineBreakIntent(forward: true),
SingleActivator(LogicalKeyboardKey.arrowLeft): ExtendSelectionByCharacterIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowRight): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowUp): ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowDown): ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: true),
// Shift + Arrow: Extend Selection.
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionByCharacterIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): ExtendSelectionToNextWordBoundaryIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): ExtendSelectionToNextWordBoundaryIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExtendSelectionToNextWordBoundaryOrCaretLocationIntent(forward: false),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExtendSelectionToNextWordBoundaryOrCaretLocationIntent(forward: true),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, meta: true): ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, meta: true): ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, meta: true): ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, meta: true): ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: false),
SingleActivator(LogicalKeyboardKey.keyX, meta: true): CopySelectionTextIntent.cut(SelectionChangedCause.keyboard),
SingleActivator(LogicalKeyboardKey.keyC, meta: true): CopySelectionTextIntent.copy,
SingleActivator(LogicalKeyboardKey.keyV, meta: true): PasteTextIntent(SelectionChangedCause.keyboard),
SingleActivator(LogicalKeyboardKey.keyA, meta: true): SelectAllTextIntent(SelectionChangedCause.keyboard),
};
static const Map<ShortcutActivator, Intent> _windowsShortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace): DeleteTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, control: true): DeleteByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.backspace, alt: true): DeleteByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.delete): DeleteForwardTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, control: true): DeleteForwardByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.delete, alt: true): DeleteForwardByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): MoveSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): MoveSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): MoveSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp): MoveSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): MoveSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): MoveSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): ExtendSelectionLeftByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): ExtendSelectionRightByWordTextIntent(),
SingleActivator(LogicalKeyboardKey.end): MoveSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.home): MoveSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): ExtendSelectionDownTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExtendSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExtendSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this
// platform:
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left
// * Meta + arrow right
// * Meta + arrow up
// * Meta + shift + arrow down
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Meta + delete
// * Meta + backspace
};
// The following key combinations have no effect on text editing on this
// platform:
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left
// * Meta + arrow right
// * Meta + arrow up
// * Meta + shift + arrow down
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Meta + delete
// * Meta + backspace
static const Map<ShortcutActivator, Intent> _windowsShortcuts = _linuxShortcuts;
// Web handles its text selection natively and doesn't use any of these
// shortcuts in Flutter.

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'actions.dart';
import 'basic.dart';
import 'editable_text.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
@ -1722,9 +1721,18 @@ class DirectionalFocusIntent extends Intent {
/// [LogicalKeyboardKey.arrowLeft], and [LogicalKeyboardKey.arrowRight] keys in
/// the [WidgetsApp], with the appropriate associated directions.
class DirectionalFocusAction extends Action<DirectionalFocusIntent> {
/// Creates a [DirectionalFocusAction].
DirectionalFocusAction() : _isForTextField = false;
/// Creates a [DirectionalFocusAction] that ignores [DirectionalFocusIntent]s
/// whose `ignoreTextFields` field is true.
DirectionalFocusAction.forTextField() : _isForTextField = true;
// Whether this action is defined in a text field.
final bool _isForTextField;
@override
void invoke(DirectionalFocusIntent intent) {
if (!intent.ignoreTextFields || primaryFocus!.context!.widget is! EditableText) {
if (!intent.ignoreTextFields || !_isForTextField) {
primaryFocus!.focusInDirection(intent.direction);
}
}

View File

@ -1,48 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'actions.dart';
import 'focus_manager.dart';
import 'framework.dart';
import 'text_editing_action_target.dart';
/// An [Action] related to editing text.
///
/// Enables itself only when a [TextEditingActionTarget], e.g. [EditableText],
/// is currently focused. The result of this is that when a
/// TextEditingActionTarget is not focused, it will fall through to any
/// non-TextEditingAction that handles the same shortcut. For example,
/// overriding the tab key in [Shortcuts] with a TextEditingAction will only
/// invoke your TextEditingAction when a TextEditingActionTarget is focused,
/// otherwise the default tab behavior will apply.
///
/// The currently focused TextEditingActionTarget is available in the [invoke]
/// method via [textEditingActionTarget].
///
/// See also:
///
/// * [CallbackAction], which is a similar Action type but unrelated to text
/// editing.
abstract class TextEditingAction<T extends Intent> extends ContextAction<T> {
/// Returns the currently focused [TextEditingAction], or null if none is
/// focused.
@protected
TextEditingActionTarget? get textEditingActionTarget {
// If a TextEditingActionTarget is not focused, then ignore this action.
if (primaryFocus?.context == null ||
primaryFocus!.context! is! StatefulElement ||
((primaryFocus!.context! as StatefulElement).state
is! TextEditingActionTarget)) {
return null;
}
return (primaryFocus!.context! as StatefulElement).state
as TextEditingActionTarget;
}
@override
bool isEnabled(T intent) {
// The Action is disabled if there is no focused TextEditingActionTarget.
return textEditingActionTarget != null;
}
}

View File

@ -2,299 +2,226 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/services.dart';
import 'actions.dart';
/// An [Intent] to delete a character in the backwards direction.
/// An [Intent] to send the event straight to the engine.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class DeleteTextIntent extends Intent {
/// Creates an instance of DeleteTextIntent.
const DeleteTextIntent();
}
/// An [Intent] to delete a word in the backwards direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class DeleteByWordTextIntent extends Intent {
/// Creates an instance of DeleteByWordTextIntent.
const DeleteByWordTextIntent();
}
/// An [Intent] to delete a line in the backwards direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class DeleteByLineTextIntent extends Intent {
/// Creates an instance of DeleteByLineTextIntent.
const DeleteByLineTextIntent();
}
/// An [Intent] to delete in the forward direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class DeleteForwardTextIntent extends Intent {
/// Creates an instance of DeleteForwardTextIntent.
const DeleteForwardTextIntent();
}
/// An [Intent] to delete a word in the forward direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class DeleteForwardByWordTextIntent extends Intent {
/// Creates an instance of DeleteByWordTextIntent.
const DeleteForwardByWordTextIntent();
}
/// An [Intent] to delete a line in the forward direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class DeleteForwardByLineTextIntent extends Intent {
/// Creates an instance of DeleteByLineTextIntent.
const DeleteForwardByLineTextIntent();
}
/// An [Intent] to send the event straight to the engine, but only if a
/// TextEditingTarget is focused.
///
/// {@template flutter.widgets.TextEditingIntents.seeAlso}
/// See also:
///
/// * [DefaultTextEditingActions], which responds to this [Intent].
/// * [DefaultTextEditingShortcuts], which triggers this [Intent].
/// {@endtemplate}
class DoNothingAndStopPropagationTextIntent extends Intent {
/// Creates an instance of DoNothingAndStopPropagationTextIntent.
/// Creates an instance of [DoNothingAndStopPropagationTextIntent].
const DoNothingAndStopPropagationTextIntent();
}
/// An [Intent] to expand the selection left to the start/end of the current
/// A text editing related [Intent] that performs an operation towards a given
/// direction of the current caret location.
abstract class DirectionalTextEditingIntent extends Intent {
/// Creates a [DirectionalTextEditingIntent].
const DirectionalTextEditingIntent(this.forward);
/// Whether the input field, if applicable, should perform the text editing
/// operation from the current caret location towards the end of the document.
///
/// Unless otherwise specified by the recipient of this intent, this parameter
/// uses the logical order of characters in the string to determind the
/// direction, and is not affected by the writing direction of the text.
final bool forward;
}
/// Deletes the character before or after the caret location, based on whether
/// `forward` is true.
///
/// {@template flutter.widgets.TextEditingIntents.logicalOrder}
/// {@endtemplate}
///
/// Typically a text field will not respond to this intent if it has no active
/// caret ([TextSelection.isValid] is false for the current selection).
class DeleteCharacterIntent extends DirectionalTextEditingIntent {
/// Creates a [DeleteCharacterIntent].
const DeleteCharacterIntent({ required bool forward }) : super(forward);
}
/// Deletes from the current caret location to the previous or next word
/// boundary, based on whether `forward` is true.
class DeleteToNextWordBoundaryIntent extends DirectionalTextEditingIntent {
/// Creates a [DeleteToNextWordBoundaryIntent].
const DeleteToNextWordBoundaryIntent({ required bool forward }) : super(forward);
}
/// Deletes from the current caret location to the previous or next soft or hard
/// line break, based on whether `forward` is true.
class DeleteToLineBreakIntent extends DirectionalTextEditingIntent {
/// Creates a [DeleteToLineBreakIntent].
const DeleteToLineBreakIntent({ required bool forward }) : super(forward);
}
/// A [DirectionalTextEditingIntent] that moves the caret or the selection to a
/// new location.
abstract class DirectionalCaretMovementIntent extends DirectionalTextEditingIntent {
/// Creates a [DirectionalCaretMovementIntent].
const DirectionalCaretMovementIntent(bool forward, this.collapseSelection)
: super(forward);
/// Whether this [Intent] should make the selection collapsed (so it becomes a
/// caret), after the movement.
///
/// When [collapseSelection] is false, the input field typically only moves
/// the current [TextSelection.extent] to the new location, while maintains
/// the current [TextSelection.base] location.
///
/// When [collapseSelection] is true, the input field typically should move
/// both the [TextSelection.base] and the [TextSelection.extent] to the new
/// location.
final bool collapseSelection;
}
/// Expands, or moves the current selection from the current
/// [TextSelection.extent] position to the previous or the next character
/// boundary.
class ExtendSelectionByCharacterIntent extends DirectionalCaretMovementIntent {
/// Creates an [ExtendSelectionByCharacterIntent].
const ExtendSelectionByCharacterIntent({
required bool forward,
required bool collapseSelection,
}) : super(forward, collapseSelection);
}
/// Expands, or moves the current selection from the current
/// [TextSelection.extent] position to the previous or the next word
/// boundary.
class ExtendSelectionToNextWordBoundaryIntent extends DirectionalCaretMovementIntent {
/// Creates an [ExtendSelectionToNextWordBoundaryIntent].
const ExtendSelectionToNextWordBoundaryIntent({
required bool forward,
required bool collapseSelection,
}) : super(forward, collapseSelection);
}
/// Expands, or moves the current selection from the current
/// [TextSelection.extent] position to the previous or the next word
/// boundary, or the [TextSelection.base] position if it's closer in the move
/// direction.
///
/// This [Intent] typically has the same effect as an
/// [ExtendSelectionToNextWordBoundaryIntent], except it collapses the selection
/// when the order of [TextSelection.base] and [TextSelection.extent] would
/// reverse.
///
/// This is typically only used on macOS.
class ExtendSelectionToNextWordBoundaryOrCaretLocationIntent extends DirectionalTextEditingIntent {
/// Creates an [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent].
const ExtendSelectionToNextWordBoundaryOrCaretLocationIntent({
required bool forward,
}) : super(forward);
}
/// Expands, or moves the current selection from the current
/// [TextSelection.extent] position to the closest line break in the direction
/// given by [forward].
class ExtendSelectionToLineBreakIntent extends DirectionalCaretMovementIntent {
/// Creates an [ExtendSelectionToLineBreakIntent].
const ExtendSelectionToLineBreakIntent({
required bool forward,
required bool collapseSelection,
}) : super(forward, collapseSelection);
}
/// Expands, or moves the current selection from the current
/// [TextSelection.extent] position to the closest position on the adjacent
/// line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExpandSelectionLeftByLineTextIntent extends Intent {
/// Creates an instance of ExpandSelectionLeftByLineTextIntent.
const ExpandSelectionLeftByLineTextIntent();
class ExtendSelectionVerticallyToAdjacentLineIntent extends DirectionalCaretMovementIntent {
/// Creates an [ExtendSelectionVerticallyToAdjacentLineIntent].
const ExtendSelectionVerticallyToAdjacentLineIntent({
required bool forward,
required bool collapseSelection,
}) : super(forward, collapseSelection);
}
/// An [Intent] to expand the selection right to the start/end of the current
/// field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExpandSelectionRightByLineTextIntent extends Intent {
/// Creates an instance of ExpandSelectionRightByLineTextIntent.
const ExpandSelectionRightByLineTextIntent();
}
/// An [Intent] to expand the selection to the end of the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExpandSelectionToEndTextIntent extends Intent {
/// Creates an instance of ExpandSelectionToEndTextIntent.
const ExpandSelectionToEndTextIntent();
}
/// An [Intent] to expand the selection to the start of the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExpandSelectionToStartTextIntent extends Intent {
/// Creates an instance of ExpandSelectionToStartTextIntent.
const ExpandSelectionToStartTextIntent();
}
/// An [Intent] to extend the selection down by one line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionDownTextIntent extends Intent {
/// Creates an instance of ExtendSelectionDownTextIntent.
const ExtendSelectionDownTextIntent();
}
/// An [Intent] to extend the selection left to the start/end of the current
/// line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionLeftByLineTextIntent extends Intent {
/// Creates an instance of ExtendSelectionLeftByLineTextIntent.
const ExtendSelectionLeftByLineTextIntent();
}
/// An [Intent] to extend the selection left past the nearest word, collapsing
/// the selection if the order of [TextSelection.extentOffset] and
/// [TextSelection.baseOffset] would reverse.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionLeftByWordAndStopAtReversalTextIntent extends Intent {
/// Creates an instance of ExtendSelectionLeftByWordAndStopAtReversalTextIntent.
const ExtendSelectionLeftByWordAndStopAtReversalTextIntent();
}
/// An [Intent] to extend the selection left past the nearest word.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionLeftByWordTextIntent extends Intent {
/// Creates an instance of ExtendSelectionLeftByWordTextIntent.
const ExtendSelectionLeftByWordTextIntent();
}
/// An [Intent] to extend the selection left by one character.
/// platform for the shift + arrow-left key event.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionLeftTextIntent extends Intent {
/// Creates an instance of ExtendSelectionLeftTextIntent.
const ExtendSelectionLeftTextIntent();
}
/// An [Intent] to extend the selection right to the start/end of the current
/// line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionRightByLineTextIntent extends Intent {
/// Creates an instance of ExtendSelectionRightByLineTextIntent.
const ExtendSelectionRightByLineTextIntent();
}
/// An [Intent] to extend the selection right past the nearest word, collapsing
/// the selection if the order of [TextSelection.extentOffset] and
/// [TextSelection.baseOffset] would reverse.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionRightByWordAndStopAtReversalTextIntent extends Intent {
/// Creates an instance of ExtendSelectionRightByWordAndStopAtReversalTextIntent.
const ExtendSelectionRightByWordAndStopAtReversalTextIntent();
}
/// An [Intent] to extend the selection right past the nearest word.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionRightByWordTextIntent extends Intent {
/// Creates an instance of ExtendSelectionRightByWordTextIntent.
const ExtendSelectionRightByWordTextIntent();
}
/// An [Intent] to extend the selection right by one character.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionRightTextIntent extends Intent {
/// Creates an instance of ExtendSelectionRightTextIntent.
const ExtendSelectionRightTextIntent();
}
/// An [Intent] to extend the selection up by one line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class ExtendSelectionUpTextIntent extends Intent {
/// Creates an instance of ExtendSelectionUpTextIntent.
const ExtendSelectionUpTextIntent();
}
/// An [Intent] to move the selection down by one line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionDownTextIntent extends Intent {
/// Creates an instance of MoveSelectionDownTextIntent.
const MoveSelectionDownTextIntent();
}
/// An [Intent] to move the selection left by one line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionLeftByLineTextIntent extends Intent {
/// Creates an instance of MoveSelectionLeftByLineTextIntent.
const MoveSelectionLeftByLineTextIntent();
}
/// An [Intent] to move the selection left past the nearest word.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionLeftByWordTextIntent extends Intent {
/// Creates an instance of MoveSelectionLeftByWordTextIntent.
const MoveSelectionLeftByWordTextIntent();
}
/// An [Intent] to move the selection left by one character.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionLeftTextIntent extends Intent {
/// Creates an instance of MoveSelectionLeftTextIntent.
const MoveSelectionLeftTextIntent();
}
/// An [Intent] to move the selection to the start of the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionToStartTextIntent extends Intent {
/// Creates an instance of MoveSelectionToStartTextIntent.
const MoveSelectionToStartTextIntent();
}
/// An [Intent] to move the selection right by one line.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionRightByLineTextIntent extends Intent {
/// Creates an instance of MoveSelectionRightByLineTextIntent.
const MoveSelectionRightByLineTextIntent();
}
/// An [Intent] to move the selection right past the nearest word.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionRightByWordTextIntent extends Intent {
/// Creates an instance of MoveSelectionRightByWordTextIntent.
const MoveSelectionRightByWordTextIntent();
}
/// An [Intent] to move the selection right by one character.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionRightTextIntent extends Intent {
/// Creates an instance of MoveSelectionRightTextIntent.
const MoveSelectionRightTextIntent();
}
/// An [Intent] to move the selection to the end of the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionToEndTextIntent extends Intent {
/// Creates an instance of MoveSelectionToEndTextIntent.
const MoveSelectionToEndTextIntent();
}
/// An [Intent] to move the selection up by one character.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class MoveSelectionUpTextIntent extends Intent {
/// Creates an instance of MoveSelectionUpTextIntent.
const MoveSelectionUpTextIntent();
/// Expands, or moves the current selection from the current
/// [TextSelection.extent] position to the start or the end of the document.
class ExtendSelectionToDocumentBoundaryIntent extends DirectionalCaretMovementIntent {
/// Creates an [ExtendSelectionToDocumentBoundaryIntent].
const ExtendSelectionToDocumentBoundaryIntent({
required bool forward,
required bool collapseSelection,
}) : super(forward, collapseSelection);
}
/// An [Intent] to select everything in the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class SelectAllTextIntent extends Intent {
/// Creates an instance of SelectAllTextIntent.
const SelectAllTextIntent();
/// Creates an instance of [SelectAllTextIntent].
const SelectAllTextIntent(this.cause);
/// {@template flutter.widgets.TextEditingIntents.cause}
/// The [SelectionChangedCause] that triggered the intent.
/// {@endtemplate}
final SelectionChangedCause cause;
}
/// An [Intent] to copy selection in the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
/// An [Intent] that represents a user interaction that attempts to copy or cut
/// the current selection in the field.
class CopySelectionTextIntent extends Intent {
/// Creates an instance of CopyTextIntent.
const CopySelectionTextIntent();
}
const CopySelectionTextIntent._(this.cause, this.collapseSelection);
/// An [Intent] to cut selection in the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class CutSelectionTextIntent extends Intent {
/// Creates an instance of CutTextIntent.
const CutSelectionTextIntent();
/// Creates an [Intent] that represents a user interaction that attempts to
/// cut the current selection in the field.
const CopySelectionTextIntent.cut(SelectionChangedCause cause) : this._(cause, true);
/// An [Intent] that represents a user interaction that attempts to copy the
/// current selection in the field.
static const CopySelectionTextIntent copy = CopySelectionTextIntent._(SelectionChangedCause.keyboard, false);
/// {@macro flutter.widgets.TextEditingIntents.cause}
final SelectionChangedCause cause;
/// Whether the original text needs to be removed from the input field if the
/// copy action was successful.
final bool collapseSelection;
}
/// An [Intent] to paste text from [Clipboard] to the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class PasteTextIntent extends Intent {
/// Creates an instance of PasteTextIntent.
const PasteTextIntent();
/// Creates an instance of [PasteTextIntent].
const PasteTextIntent(this.cause);
/// {@macro flutter.widgets.TextEditingIntents.cause}
final SelectionChangedCause cause;
}
/// An [Intent] that represents a user interaction that attempts to modify the
/// current [TextEditingValue] in an input field.
class ReplaceTextIntent extends Intent {
/// Creates a [ReplaceTextIntent].
const ReplaceTextIntent(this.currentTextEditingValue, this.replacementText, this.replacementRange, this.cause);
/// The [TextEditingValue] that this [Intent]'s action should perform on.
final TextEditingValue currentTextEditingValue;
/// The text to replace the original text within the [replacementRange] with.
final String replacementText;
/// The range of text in [currentTextEditingValue] that needs to be replaced.
final TextRange replacementRange;
/// {@macro flutter.widgets.TextEditingIntents.cause}
final SelectionChangedCause cause;
}
/// An [Intent] that represents a user interaction that attempts to change the
/// selection in an input field.
class UpdateSelectionIntent extends Intent {
/// Creates a [UpdateSelectionIntent].
const UpdateSelectionIntent(this.currentTextEditingValue, this.newSelection, this.cause);
/// The [TextEditingValue] that this [Intent]'s action should perform on.
final TextEditingValue currentTextEditingValue;
/// The new [TextSelection] the input field should adopt.
final TextSelection newSelection;
/// {@macro flutter.widgets.TextEditingIntents.cause}
final SelectionChangedCause cause;
}

View File

@ -33,7 +33,6 @@ export 'src/widgets/bottom_navigation_bar_item.dart';
export 'src/widgets/color_filter.dart';
export 'src/widgets/container.dart';
export 'src/widgets/debug.dart';
export 'src/widgets/default_text_editing_actions.dart';
export 'src/widgets/default_text_editing_shortcuts.dart';
export 'src/widgets/desktop_text_selection_toolbar_layout_delegate.dart';
export 'src/widgets/dismissible.dart';
@ -120,8 +119,6 @@ export 'src/widgets/spacer.dart';
export 'src/widgets/status_transitions.dart';
export 'src/widgets/table.dart';
export 'src/widgets/text.dart';
export 'src/widgets/text_editing_action.dart';
export 'src/widgets/text_editing_action_target.dart';
export 'src/widgets/text_editing_intents.dart';
export 'src/widgets/text_selection.dart';
export 'src/widgets/text_selection_toolbar_layout_delegate.dart';

View File

@ -263,7 +263,7 @@ void main() {
await tester.tap(find.text('Paste'));
await tester.pumpAndSettle();
expect(controller.text, 'blah1 blah2blah1');
expect(controller.selection, const TextSelection(baseOffset: 16, extentOffset: 16, affinity: TextAffinity.upstream));
expect(controller.selection, const TextSelection.collapsed(offset: 16));
// Cut the first word.
await gesture.down(midBlah1);

View File

@ -189,8 +189,6 @@ void main() {
' _FocusTraversalGroupMarker\n'
' FocusTraversalGroup\n'
' _ActionsMarker\n'
' DefaultTextEditingActions\n'
' _ActionsMarker\n'
' Actions\n'
' _ShortcutsMarker\n'
' Semantics\n'

View File

@ -79,16 +79,14 @@ Widget overlayWithEntry(OverlayEntry entry) {
MaterialLocalizationsDelegate(),
],
child: DefaultTextEditingShortcuts(
child: DefaultTextEditingActions(
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Overlay(
initialEntries: <OverlayEntry>[
entry,
],
),
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Overlay(
initialEntries: <OverlayEntry>[
entry,
],
),
),
),
@ -260,14 +258,14 @@ void main() {
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(controller.selection, const TextSelection(baseOffset: 11, extentOffset: 11, affinity: TextAffinity.upstream));
expect(controller.selection, const TextSelection.collapsed(offset: 11, affinity: TextAffinity.upstream));
expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsOneWidget);
await tester.tap(find.text('Paste'));
await tester.pumpAndSettle();
expect(controller.text, 'blah1 blah2blah1');
expect(controller.selection, const TextSelection(baseOffset: 16, extentOffset: 16, affinity: TextAffinity.upstream));
expect(controller.selection, const TextSelection.collapsed(offset: 16));
// Cut the first word.
await gesture.down(midBlah1);

View File

@ -74,14 +74,14 @@ void main() {
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(controller.selection, const TextSelection(baseOffset: 11, extentOffset: 11, affinity: TextAffinity.upstream));
expect(controller.selection, const TextSelection.collapsed(offset: 11, affinity: TextAffinity.upstream));
expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsOneWidget);
await tester.tap(find.text('Paste'));
await tester.pumpAndSettle();
expect(controller.text, 'blah1 blah2blah1');
expect(controller.selection, const TextSelection(baseOffset: 16, extentOffset: 16, affinity: TextAffinity.upstream));
expect(controller.selection, const TextSelection.collapsed(offset: 16));
// Cut the first word.
await gesture.down(midBlah1);

View File

@ -43,6 +43,110 @@ void main() {
});
});
group('TextEditingValue', () {
group('replaced', () {
const String testText = 'From a false proposition, anything follows.';
test('selection deletion', () {
const TextSelection selection = TextSelection(baseOffset: 5, extentOffset: 13);
expect(
const TextEditingValue(text: testText, selection: selection).replaced(selection, ''),
const TextEditingValue(text: 'From proposition, anything follows.', selection: TextSelection.collapsed(offset: 5)),
);
});
test('reversed selection deletion', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
const TextEditingValue(text: testText, selection: selection).replaced(selection, ''),
const TextEditingValue(text: 'From proposition, anything follows.', selection: TextSelection.collapsed(offset: 5)),
);
});
test('insert', () {
const TextSelection selection = TextSelection.collapsed(offset: 5);
expect(
const TextEditingValue(text: testText, selection: selection).replaced(selection, 'AA'),
const TextEditingValue(
text: 'From AAa false proposition, anything follows.',
// The caret moves to the end of the text inserted.
selection: TextSelection.collapsed(offset: 7),
),
);
});
test('replace before selection', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
// From |a false |proposition, anything follows.
// Replace the first whitespace with "AA".
const TextEditingValue(text: testText, selection: selection).replaced(const TextRange(start: 4, end: 5), 'AA'),
const TextEditingValue(text: 'FromAAa false proposition, anything follows.', selection: TextSelection(baseOffset: 14, extentOffset: 6)),
);
});
test('replace after selection', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
// From |a false |proposition, anything follows.
// replace the first "p" with "AA".
const TextEditingValue(text: testText, selection: selection).replaced(const TextRange(start: 13, end: 14), 'AA'),
const TextEditingValue(text: 'From a false AAroposition, anything follows.', selection: selection),
);
});
test('replace inside selection - start boundary', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
// From |a false |proposition, anything follows.
// replace the first "a" with "AA".
const TextEditingValue(text: testText, selection: selection).replaced(const TextRange(start: 5, end: 6), 'AA'),
const TextEditingValue(text: 'From AA false proposition, anything follows.', selection: TextSelection(baseOffset: 14, extentOffset: 5)),
);
});
test('replace inside selection - end boundary', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
// From |a false |proposition, anything follows.
// replace the second whitespace with "AA".
const TextEditingValue(text: testText, selection: selection).replaced(const TextRange(start: 12, end: 13), 'AA'),
const TextEditingValue(text: 'From a falseAAproposition, anything follows.', selection: TextSelection(baseOffset: 14, extentOffset: 5)),
);
});
test('delete after selection', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
// From |a false |proposition, anything follows.
// Delete the first "p".
const TextEditingValue(text: testText, selection: selection).replaced(const TextRange(start: 13, end: 14), ''),
const TextEditingValue(text: 'From a false roposition, anything follows.', selection: selection),
);
});
test('delete inside selection - start boundary', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
// From |a false |proposition, anything follows.
// Delete the first "a".
const TextEditingValue(text: testText, selection: selection).replaced(const TextRange(start: 5, end: 6), ''),
const TextEditingValue(text: 'From false proposition, anything follows.', selection: TextSelection(baseOffset: 12, extentOffset: 5)),
);
});
test('delete inside selection - end boundary', () {
const TextSelection selection = TextSelection(baseOffset: 13, extentOffset: 5);
expect(
// From |a false |proposition, anything follows.
// Delete the second whitespace.
const TextEditingValue(text: testText, selection: selection).replaced(const TextRange(start: 12, end: 13), ''),
const TextEditingValue(text: 'From a falseproposition, anything follows.', selection: TextSelection(baseOffset: 12, extentOffset: 5)),
);
});
});
});
group('TextInput message channels', () {
late FakeTextChannel fakeTextChannel;

File diff suppressed because it is too large Load Diff

View File

@ -4312,10 +4312,10 @@ void main() {
// toolbar. Until we change that, this test should remain skipped.
}, skip: kIsWeb); // [intended]
const String testText = 'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country.';
const String testText = 'Now is the time for\n' // 20
'all good people\n' // 20 + 16 => 36
'to come to the aid\n' // 36 + 19 => 55
'of their country.'; // 55 + 17 => 72
Future<void> sendKeys(
WidgetTester tester,
@ -4461,7 +4461,6 @@ void main() {
const TextSelection(
baseOffset: 0,
extentOffset: 0,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -4483,7 +4482,6 @@ void main() {
const TextSelection(
baseOffset: 0,
extentOffset: 0,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -4637,7 +4635,7 @@ void main() {
equals(
const TextSelection(
baseOffset: 20,
extentOffset: 57,
extentOffset: 39,
),
),
reason: 'on $platform',
@ -4659,14 +4657,13 @@ void main() {
equals(
const TextSelection(
baseOffset: 20,
extentOffset: 72,
extentOffset: 54,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
);
// Can't move left by line because we're already at the beginning of a line.
await sendKeys(
tester,
<LogicalKeyboardKey>[
@ -4682,7 +4679,7 @@ void main() {
equals(
const TextSelection(
baseOffset: 20,
extentOffset: 72,
extentOffset: 36,
affinity: TextAffinity.upstream,
),
),
@ -4746,6 +4743,7 @@ void main() {
equals(
const TextSelection.collapsed(
offset: testText.length,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -4767,7 +4765,6 @@ void main() {
equals(
const TextSelection.collapsed(
offset: 0,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -4832,7 +4829,7 @@ void main() {
selection,
equals(
const TextSelection(
baseOffset: testText.length,
baseOffset: 3,
extentOffset: 0,
affinity: TextAffinity.upstream,
),
@ -4854,7 +4851,6 @@ void main() {
equals(
const TextSelection.collapsed(
offset: 0,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -4878,7 +4874,6 @@ void main() {
const TextSelection(
baseOffset: 10,
extentOffset: 10,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -4948,7 +4943,6 @@ void main() {
const TextSelection(
baseOffset: 4,
extentOffset: 4,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -4985,7 +4979,6 @@ void main() {
const TextSelection(
baseOffset: 10,
extentOffset: 10,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -5031,7 +5024,6 @@ void main() {
const TextSelection(
baseOffset: 0,
extentOffset: 0,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
@ -5308,9 +5300,9 @@ void main() {
targetPlatform: defaultTargetPlatform,
);
late final int afterHomeOffset;
late final int afterEndOffset;
late final TextAffinity afterEndAffinity;
final int afterHomeOffset;
final int afterEndOffset;
final TextAffinity afterEndAffinity;
switch (defaultTargetPlatform) {
// These platforms don't handle home/end at all.
case TargetPlatform.android:
@ -7869,11 +7861,11 @@ void main() {
testWidgets('can change behavior by overriding text editing shortcuts', (WidgetTester tester) async {
const Map<SingleActivator, Intent> testShortcuts = <SingleActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.keyX, control: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.keyC, control: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.keyV, control: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
SingleActivator(LogicalKeyboardKey.keyA, control: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
};
final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection(
@ -8071,7 +8063,7 @@ void main() {
}, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended]
testWidgets('navigating multiline text', (WidgetTester tester) async {
const String multilineText = 'word word word\nword word\nword';
const String multilineText = 'word word word\nword word\nword'; // 15 + 10 + 4;
final TextEditingController controller = TextEditingController(text: multilineText);
// wo|rd wo|rd
controller.selection = const TextSelection(
@ -8134,8 +8126,8 @@ void main() {
targetPlatform: defaultTargetPlatform,
);
expect(controller.selection.isCollapsed, false);
expect(controller.selection.baseOffset, 15);
expect(controller.selection.extentOffset, 24);
expect(controller.selection.baseOffset, 17);
expect(controller.selection.extentOffset, 15);
// Set the caret to the end of a line.
controller.selection = const TextSelection(
@ -8173,8 +8165,8 @@ void main() {
targetPlatform: defaultTargetPlatform,
);
expect(controller.selection.isCollapsed, false);
expect(controller.selection.baseOffset, 15);
expect(controller.selection.extentOffset, 24);
expect(controller.selection.baseOffset, 24);
expect(controller.selection.extentOffset, 15);
// Set the caret to the start of a line.
controller.selection = const TextSelection(
@ -8267,7 +8259,7 @@ void main() {
controller.selection,
equals(
const TextSelection(
baseOffset: 9,
baseOffset: 7,
extentOffset: 0,
affinity: TextAffinity.upstream,
),
@ -8290,7 +8282,7 @@ void main() {
controller.selection,
equals(
const TextSelection(
baseOffset: 9,
baseOffset: 7,
extentOffset: 0,
affinity: TextAffinity.upstream,
),
@ -8304,7 +8296,7 @@ void main() {
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS })
);
testWidgets('can change behavior by overriding text editing actions', (WidgetTester tester) async {
testWidgets('can change text editing behavior by overriding actions', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection(
baseOffset: 0,
@ -8312,34 +8304,16 @@ void main() {
affinity: TextAffinity.upstream,
);
bool myIntentWasCalled = false;
final _MyMoveSelectionRightTextAction myMoveSelectionRightTextAction =
_MyMoveSelectionRightTextAction(
onInvoke: () {
myIntentWasCalled = true;
},
final CallbackAction<ExtendSelectionByCharacterIntent> overrideAction = CallbackAction<ExtendSelectionByCharacterIntent>(
onInvoke: (ExtendSelectionByCharacterIntent intent) { myIntentWasCalled = true; },
);
const Iterable<SingleActivator> testSingleActivators = <SingleActivator>{
SingleActivator(LogicalKeyboardKey.arrowLeft),
SingleActivator(LogicalKeyboardKey.keyX, control: true),
SingleActivator(LogicalKeyboardKey.keyC, control: true),
SingleActivator(LogicalKeyboardKey.keyV, control: true),
SingleActivator(LogicalKeyboardKey.keyA, control: true),
};
final Map<Type, Action<Intent>> testActions = <Type, Action<Intent>>{
MoveSelectionLeftTextIntent: myMoveSelectionRightTextAction,
CutSelectionTextIntent: myMoveSelectionRightTextAction,
CopySelectionTextIntent: myMoveSelectionRightTextAction,
PasteTextIntent: myMoveSelectionRightTextAction,
SelectAllTextIntent: myMoveSelectionRightTextAction,
};
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 400,
child: Actions(
actions: testActions,
actions: <Type, Action<Intent>>{ ExtendSelectionByCharacterIntent: overrideAction, },
child: EditableText(
maxLines: 10,
controller: controller,
@ -8357,32 +8331,13 @@ void main() {
),
),
));
await tester.pump(); // Wait for autofocus to take effect.
// The right arrow key moves to the right as usual.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 1);
expect(myIntentWasCalled, isFalse);
// And the testSingleActivators also moves to the right due to the Shortcuts override.
for (final SingleActivator singleActivator in testSingleActivators) {
myIntentWasCalled = false;
controller.selection = const TextSelection.collapsed(offset: 0);
await tester.pump();
await sendKeys(
tester,
<LogicalKeyboardKey>[singleActivator.trigger],
shortcutModifier: singleActivator.control,
targetPlatform: defaultTargetPlatform,
);
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 1);
expect(myIntentWasCalled, isTrue);
}
expect(controller.selection.baseOffset, 0);
expect(myIntentWasCalled, isTrue);
// On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb); // [intended]
@ -8404,10 +8359,8 @@ void main() {
width: 400,
child: Actions(
actions: <Type, Action<Intent>>{
MoveSelectionRightTextIntent: _MyMoveSelectionRightTextAction(
onInvoke: () {
myIntentWasCalled = true;
},
ExtendSelectionByCharacterIntent: CallbackAction<ExtendSelectionByCharacterIntent>(
onInvoke: (ExtendSelectionByCharacterIntent intent) { myIntentWasCalled = true; },
),
},
child: EditableText(
@ -8441,7 +8394,7 @@ void main() {
await tester.pump();
expect(myIntentWasCalled, isTrue);
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 1);
expect(controller.selection.baseOffset, 0);
}
}, variant: KeySimulatorTransitModeVariant.all());
@ -8860,20 +8813,6 @@ class _AccentColorTextEditingController extends TextEditingController {
}
}
class _MyMoveSelectionRightTextAction extends TextEditingAction<Intent> {
_MyMoveSelectionRightTextAction({
required this.onInvoke,
}) : super();
final VoidCallback onInvoke;
@override
Object? invoke(Intent intent, [BuildContext? context]) {
textEditingActionTarget!.moveSelectionRight(SelectionChangedCause.keyboard);
onInvoke();
}
}
class _TestScrollController extends ScrollController {
bool get attached => hasListeners;
}

View File

@ -184,80 +184,6 @@ void main() {
);
}
testWidgets('can use the desktop cut/copy/paste buttons on desktop', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'blah1 blah2',
);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: TextFormField(
controller: controller,
),
),
),
),
);
// Initially, the menu is not shown and there is no selection.
expect(find.byType(CupertinoButton), findsNothing);
expect(controller.selection, const TextSelection(baseOffset: -1, extentOffset: -1));
final Offset midBlah1 = textOffsetToPosition(tester, 2);
// Right clicking shows the menu.
final TestGesture gesture = await tester.startGesture(
midBlah1,
kind: PointerDeviceKind.mouse,
buttons: kSecondaryMouseButton,
);
addTearDown(gesture.removePointer);
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 5));
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
// Copy the first word.
await tester.tap(find.text('Copy'));
await tester.pumpAndSettle();
expect(controller.text, 'blah1 blah2');
expect(controller.selection, const TextSelection(baseOffset: 5, extentOffset: 5));
expect(find.byType(CupertinoButton), findsNothing);
// Paste it at the end.
await gesture.down(textOffsetToPosition(tester, controller.text.length));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(controller.selection, const TextSelection(baseOffset: 11, extentOffset: 11, affinity: TextAffinity.upstream));
expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsOneWidget);
await tester.tap(find.text('Paste'));
await tester.pumpAndSettle();
expect(controller.text, 'blah1 blah2blah1');
expect(controller.selection, const TextSelection(baseOffset: 16, extentOffset: 16, affinity: TextAffinity.upstream));
// Cut the first word.
await gesture.down(midBlah1);
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 5));
await tester.tap(find.text('Cut'));
await tester.pumpAndSettle();
expect(controller.text, ' blah2blah1');
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 0));
expect(find.byType(CupertinoButton), findsNothing);
}, variant: TargetPlatformVariant.desktop(), skip: kIsWeb); // [intended] toolbar is handled by the browser.
testWidgets('has expected defaults', (WidgetTester tester) async {
await tester.pumpWidget(
boilerplate(