Unverified Commit a7aa6616 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Re-implement hardware keyboard text selection. (#42879)

This re-implements keyboard text selection so that it will work on platforms other than Android (e.g. macOS, Linux, etc.).

Also, fixed a number of bugs in editing selection via a hardware keyboard (unable to select backwards, incorrect conversion to ASCII when cutting to clipboard, lack of support for CTRL-SHIFT-ARROW word selection, etc.).

Did not address the keyboard locale issues that remain, or add platform specific switches for the bindings. All that will need some more design work before implementing them.

Related Issues
Fixes #31951
parent f7ce5ae3
......@@ -238,6 +238,21 @@ class LogicalKeyboardKey extends KeyboardKey {
return result == null ? <LogicalKeyboardKey>{} : <LogicalKeyboardKey>{result};
}
/// Takes a set of keys, and returns the same set, but with any keys that have
/// synonyms replaced.
///
/// It is used, for example, to make sets of keys with members like
/// [controlRight] and [controlLeft] and convert that set to contain just
/// [control], so that the question "is any control key down?" can be asked.
static Set<LogicalKeyboardKey> collapseSynonyms(Set<LogicalKeyboardKey> input) {
final Set<LogicalKeyboardKey> result = <LogicalKeyboardKey>{};
for (LogicalKeyboardKey key in input) {
final LogicalKeyboardKey synonym = _synonyms[key];
result.add(synonym ?? key);
}
return result;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......
......@@ -3655,8 +3655,9 @@ void main() {
await tester.pumpAndSettle();
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft);
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
});
testWidgets('Shift test 2', (WidgetTester tester) async {
......@@ -3709,7 +3710,7 @@ void main() {
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(controller.selection.extentOffset - controller.selection.baseOffset, 11);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -11);
await tester.sendKeyUpEvent(LogicalKeyboardKey.arrowUp);
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
......@@ -3774,7 +3775,7 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
await tester.pumpAndSettle();
expect(controller.selection.extentOffset - controller.selection.baseOffset, 5);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
});
testWidgets('Read only keyboard selection test', (WidgetTester tester) async {
......@@ -3794,7 +3795,7 @@ void main() {
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft);
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
});
});
......@@ -3867,8 +3868,8 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
await tester.pumpAndSettle();
const String expected = 'a biga big house\njumped over a mouse';
expect(find.text(expected), findsOneWidget);
const String expected = 'a big a bighouse\njumped over a mouse';
expect(find.text(expected), findsOneWidget, reason: 'Because text contains ${controller.text}');
});
testWidgets('Cut test', (WidgetTester tester) async {
......@@ -4100,7 +4101,7 @@ void main() {
}
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
expect(c1.selection.extentOffset - c1.selection.baseOffset, 5);
expect(c1.selection.extentOffset - c1.selection.baseOffset, -5);
await tester.pumpWidget(
MaterialApp(
......@@ -4136,7 +4137,7 @@ void main() {
}
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
expect(c1.selection.extentOffset - c1.selection.baseOffset, 10);
expect(c1.selection.extentOffset - c1.selection.baseOffset, -10);
});
......@@ -4193,7 +4194,7 @@ void main() {
}
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
expect(c1.selection.extentOffset - c1.selection.baseOffset, 5);
expect(c1.selection.extentOffset - c1.selection.baseOffset, -5);
expect(c2.selection.extentOffset - c2.selection.baseOffset, 0);
await tester.enterText(find.byType(TextField).last, testValue);
......@@ -4212,7 +4213,7 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
expect(c1.selection.extentOffset - c1.selection.baseOffset, 0);
expect(c2.selection.extentOffset - c2.selection.baseOffset, 5);
expect(c2.selection.extentOffset - c2.selection.baseOffset, -5);
});
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
......
......@@ -12,7 +12,7 @@ import '../rendering/mock_canvas.dart';
import '../rendering/recording_canvas.dart';
import 'rendering_tester.dart';
class FakeEditableTextState extends TextSelectionDelegate {
class FakeEditableTextState with TextSelectionDelegate {
@override
TextEditingValue get textEditingValue { return const TextEditingValue(); }
......
......@@ -60,5 +60,51 @@ void main() {
expect(LogicalKeyboardKey.altRight.synonyms.first, equals(LogicalKeyboardKey.alt));
expect(LogicalKeyboardKey.metaRight.synonyms.first, equals(LogicalKeyboardKey.meta));
});
test('Synonyms get collapsed properly.', () async {
expect(LogicalKeyboardKey.collapseSynonyms(<LogicalKeyboardKey>{}), isEmpty);
expect(
LogicalKeyboardKey.collapseSynonyms(<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.metaLeft,
}),
equals(<LogicalKeyboardKey>{
LogicalKeyboardKey.shift,
LogicalKeyboardKey.control,
LogicalKeyboardKey.alt,
LogicalKeyboardKey.meta,
}));
expect(
LogicalKeyboardKey.collapseSynonyms(<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.metaRight,
}),
equals(<LogicalKeyboardKey>{
LogicalKeyboardKey.shift,
LogicalKeyboardKey.control,
LogicalKeyboardKey.alt,
LogicalKeyboardKey.meta,
}));
expect(
LogicalKeyboardKey.collapseSynonyms(<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.metaRight,
}),
equals(<LogicalKeyboardKey>{
LogicalKeyboardKey.shift,
LogicalKeyboardKey.control,
LogicalKeyboardKey.alt,
LogicalKeyboardKey.meta,
}));
});
});
}
......@@ -1278,7 +1278,7 @@ void main() {
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft);
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
});
testWidgets('Shift test 2', (WidgetTester tester) async {
......@@ -1302,7 +1302,7 @@ void main() {
await tester.pumpAndSettle();
expect(controller.selection.extentOffset - controller.selection.baseOffset, 5);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
});
testWidgets('Down and up test', (WidgetTester tester) async {
......@@ -1312,7 +1312,7 @@ void main() {
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(controller.selection.extentOffset - controller.selection.baseOffset, 11);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -11);
await tester.sendKeyUpEvent(LogicalKeyboardKey.arrowUp);
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
......@@ -1371,7 +1371,7 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
await tester.pumpAndSettle();
expect(controller.selection.extentOffset - controller.selection.baseOffset, 5);
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
});
});
......@@ -1516,7 +1516,7 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
await tester.pumpAndSettle();
expect(c1.selection.extentOffset - c1.selection.baseOffset, 5);
expect(c1.selection.extentOffset - c1.selection.baseOffset, -5);
await tester.pumpWidget(
MaterialApp(
......@@ -1555,7 +1555,7 @@ void main() {
editableTextWidget = tester.widget(find.byType(EditableText).last);
c1 = editableTextWidget.controller;
expect(c1.selection.extentOffset - c1.selection.baseOffset, 10);
expect(c1.selection.extentOffset - c1.selection.baseOffset, -6);
});
......@@ -1610,7 +1610,7 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
await tester.pumpAndSettle();
expect(c1.selection.extentOffset - c1.selection.baseOffset, 5);
expect(c1.selection.extentOffset - c1.selection.baseOffset, -5);
expect(c2.selection.extentOffset - c2.selection.baseOffset, 0);
await tester.tap(find.byType(SelectableText).last);
......@@ -1625,7 +1625,7 @@ void main() {
await tester.pumpAndSettle();
expect(c1.selection.extentOffset - c1.selection.baseOffset, 0);
expect(c2.selection.extentOffset - c2.selection.baseOffset, 5);
expect(c2.selection.extentOffset - c2.selection.baseOffset, -5);
});
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
......
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