Unverified Commit d5868732 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Select All via Shortcuts (#85081)

Control + A to select all text is now overridable.
parent b4f36ee4
...@@ -644,7 +644,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -644,7 +644,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
}; };
static final Set<LogicalKeyboardKey> _shortcutKeys = <LogicalKeyboardKey>{ static final Set<LogicalKeyboardKey> _shortcutKeys = <LogicalKeyboardKey>{
LogicalKeyboardKey.keyA,
LogicalKeyboardKey.keyC, LogicalKeyboardKey.keyC,
LogicalKeyboardKey.keyV, LogicalKeyboardKey.keyV,
LogicalKeyboardKey.keyX, LogicalKeyboardKey.keyX,
...@@ -2232,8 +2231,21 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -2232,8 +2231,21 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
_setSelection(nextSelection, cause); _setSelection(nextSelection, cause);
} }
// Handles shortcut functionality including cut, copy, paste and select all /// Set the current [selection] to contain the entire text value.
// using control/command + (X, C, V, A). ///
/// {@macro flutter.rendering.RenderEditable.cause}
void selectAll(SelectionChangedCause cause) {
_setSelection(
selection!.copyWith(
baseOffset: 0,
extentOffset: textSelectionDelegate.textEditingValue.text.length,
),
cause,
);
}
// Handles shortcut functionality including cut, copy, paste using
// using control/command + (X, C, V).
Future<void> _handleShortcuts(LogicalKeyboardKey key) async { Future<void> _handleShortcuts(LogicalKeyboardKey key) async {
final TextSelection selection = textSelectionDelegate.textEditingValue.selection; final TextSelection selection = textSelectionDelegate.textEditingValue.selection;
final String text = textSelectionDelegate.textEditingValue.text; final String text = textSelectionDelegate.textEditingValue.text;
...@@ -2266,14 +2278,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -2266,14 +2278,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
), ),
); );
} }
} else if (key == LogicalKeyboardKey.keyA) {
value = TextEditingValue(
text: text,
selection: selection.copyWith(
baseOffset: 0,
extentOffset: textSelectionDelegate.textEditingValue.text.length,
),
);
} }
if (value != null) { if (value != null) {
_setTextEditingValue( _setTextEditingValue(
......
...@@ -66,6 +66,7 @@ class DefaultTextEditingActions extends Actions{ ...@@ -66,6 +66,7 @@ class DefaultTextEditingActions extends Actions{
MoveSelectionToEndTextIntent: _MoveSelectionToEndTextAction(), MoveSelectionToEndTextIntent: _MoveSelectionToEndTextAction(),
MoveSelectionToStartTextIntent: _MoveSelectionToStartTextAction(), MoveSelectionToStartTextIntent: _MoveSelectionToStartTextAction(),
MoveSelectionUpTextIntent: _MoveSelectionUpTextAction(), MoveSelectionUpTextIntent: _MoveSelectionUpTextAction(),
SelectAllTextIntent: _SelectAllTextAction(),
}; };
} }
...@@ -291,3 +292,10 @@ class _MoveSelectionToStartTextAction extends TextEditingAction<MoveSelectionToS ...@@ -291,3 +292,10 @@ class _MoveSelectionToStartTextAction extends TextEditingAction<MoveSelectionToS
textEditingActionTarget!.renderEditable.moveSelectionToStart(SelectionChangedCause.keyboard); textEditingActionTarget!.renderEditable.moveSelectionToStart(SelectionChangedCause.keyboard);
} }
} }
class _SelectAllTextAction extends TextEditingAction<SelectAllTextIntent> {
@override
Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.renderEditable.selectAll(SelectionChangedCause.keyboard);
}
}
...@@ -187,10 +187,12 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -187,10 +187,12 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * End // * End
// * Home // * Home
// * Meta + A
// * Meta + arrow down // * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
// * Meta + arrow right // * Meta + arrow right
...@@ -232,11 +234,13 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -232,11 +234,13 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * Meta + arrow down
// * End // * End
// * Home // * Home
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
// * Meta + arrow right // * Meta + arrow right
// * Meta + arrow up // * Meta + arrow up
...@@ -277,11 +281,13 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -277,11 +281,13 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * Meta + arrow down
// * End // * End
// * Home // * Home
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
// * Meta + arrow right // * Meta + arrow right
// * Meta + arrow up // * Meta + arrow up
...@@ -322,11 +328,13 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -322,11 +328,13 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * Meta + arrow down
// * End // * End
// * Home // * Home
// * Meta + A
// * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
// * Meta + arrow right // * Meta + arrow right
// * Meta + arrow up // * Meta + arrow up
...@@ -371,8 +379,10 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -371,8 +379,10 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, meta: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * Control + A
// * Control + arrow left // * Control + arrow left
// * Control + arrow right // * Control + arrow right
// * Control + shift + arrow left // * Control + shift + arrow left
...@@ -416,8 +426,10 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -416,8 +426,10 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExpandSelectionRightByLineTextIntent(), SingleActivator(LogicalKeyboardKey.end, shift: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExpandSelectionLeftByLineTextIntent(), SingleActivator(LogicalKeyboardKey.home, shift: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * Meta + A
// * Meta + arrow down // * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
// * Meta + arrow right // * Meta + arrow right
...@@ -474,6 +486,8 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -474,6 +486,8 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.home, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.space): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.space): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, meta: true): DoNothingAndStopPropagationTextIntent(),
}; };
static Map<ShortcutActivator, Intent> get _shortcuts { static Map<ShortcutActivator, Intent> get _shortcuts {
......
...@@ -266,3 +266,11 @@ class MoveSelectionUpTextIntent extends Intent{ ...@@ -266,3 +266,11 @@ class MoveSelectionUpTextIntent extends Intent{
/// Creates an instance of MoveSelectionUpTextIntent. /// Creates an instance of MoveSelectionUpTextIntent.
const MoveSelectionUpTextIntent(); const MoveSelectionUpTextIntent();
} }
/// An [Intent] to select everything in the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class SelectAllTextIntent extends Intent{
/// Creates an instance of SelectAllTextIntent.
const SelectAllTextIntent();
}
...@@ -7263,6 +7263,69 @@ void main() { ...@@ -7263,6 +7263,69 @@ void main() {
// On web, using keyboard for selection is handled by the browser. // On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb); }, skip: kIsWeb);
testWidgets('can override select all via Shortcuts', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 0,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 400,
child: Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.keyA, control: true): MoveSelectionRightTextIntent(),
},
child: EditableText(
maxLines: 10,
controller: controller,
showSelectionHandles: true,
autofocus: true,
focusNode: focusNode,
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
textAlign: TextAlign.right,
),
),
),
),
));
await tester.pump(); // Wait for autofocus to take effect.
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 0);
// meta + A moves the cursor right instead of doing select all.
final String targetPlatform = defaultTargetPlatform.toString();
final String platform = targetPlatform.substring(targetPlatform.indexOf('.') + 1).toLowerCase();
await sendKeys(
tester,
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA],
shortcutModifier: true,
platform: platform,
);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 1);
await sendKeys(
tester,
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA],
shortcutModifier: true,
platform: platform,
);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 2);
// On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb);
testWidgets('navigating by word', (WidgetTester tester) async { testWidgets('navigating by word', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: 'word word word'); final TextEditingController controller = TextEditingController(text: 'word word word');
// word wo|rd| word // word wo|rd| word
...@@ -7461,6 +7524,75 @@ void main() { ...@@ -7461,6 +7524,75 @@ void main() {
// On web, using keyboard for selection is handled by the browser. // On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb); }, skip: kIsWeb);
testWidgets('can override select all via Actions', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 0,
affinity: TextAffinity.upstream,
);
late bool myIntentWasCalled;
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 400,
child: Actions(
actions: <Type, Action<Intent>>{
SelectAllTextIntent: _MyMoveSelectionRightTextAction(
onInvoke: () {
myIntentWasCalled = true;
},
),
},
child: EditableText(
maxLines: 10,
controller: controller,
showSelectionHandles: true,
autofocus: true,
focusNode: focusNode,
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
textAlign: TextAlign.right,
),
),
),
),
));
await tester.pump(); // Wait for autofocus to take effect.
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 0);
// meta + A moves the cursor right instead of doing select all.
final String targetPlatform = defaultTargetPlatform.toString();
final String platform = targetPlatform.substring(targetPlatform.indexOf('.') + 1).toLowerCase();
await sendKeys(
tester,
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA],
shortcutModifier: true,
platform: platform,
);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 1);
expect(myIntentWasCalled, isTrue);
await sendKeys(
tester,
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA],
shortcutModifier: true,
platform: platform,
);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 2);
// On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb);
testWidgets('ignore key event from web platform', (WidgetTester tester) async { testWidgets('ignore key event from web platform', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController( final TextEditingController controller = TextEditingController(
text: 'test\ntest', text: 'test\ntest',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment