feat: refactor arrow key handler

This commit is contained in:
Lucas.Xu 2022-09-06 10:21:21 +08:00
parent 43a0a02328
commit 804b2b27c1
3 changed files with 385 additions and 201 deletions

View file

@ -1,40 +1,140 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/extensions/node_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
ShortcutEventHandler arrowKeysHandler = (editorState, event) {
if (!_arrowKeys.contains(event.logicalKey)) {
ShortcutEventHandler cursorLeftSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
if (event.isMetaPressed && event.isShiftPressed) {
return _arrowKeysWithMetaAndShift(editorState, event);
} else if (event.isMetaPressed) {
return _arrowKeysWithMeta(editorState, event);
} else if (event.isShiftPressed) {
return _arrowKeysWithShift(editorState, event);
} else {
return _arrowKeysOnly(editorState, event);
final end = selection.end.goLeft(editorState);
if (end == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
selection.copyWith(end: end),
);
return KeyEventResult.handled;
};
final _arrowKeys = [
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.arrowUp,
LogicalKeyboardKey.arrowDown
];
KeyEventResult _arrowKeysWithMetaAndShift(
EditorState editorState, RawKeyEvent event) {
if (!event.isMetaPressed ||
!event.isShiftPressed ||
!_arrowKeys.contains(event.logicalKey)) {
assert(false);
ShortcutEventHandler cursorRightSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
final end = selection.end.goRight(editorState);
if (end == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
selection.copyWith(end: end),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorUpSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
final end = _goUp(editorState);
if (end == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
selection.copyWith(end: end),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorDownSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
final end = _goDown(editorState);
if (end == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
selection.copyWith(end: end),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorTop = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
if (nodes.isEmpty) {
return KeyEventResult.ignored;
}
final position = editorState.document.root.children
.whereType<TextNode>()
.first
.selectable
?.start();
if (position == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
Selection.collapsed(position),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorBottom = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
if (nodes.isEmpty) {
return KeyEventResult.ignored;
}
final position = editorState.document.root.children
.whereType<TextNode>()
.last
.selectable
?.end();
if (position == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
Selection.collapsed(position),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorBegin = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
if (nodes.isEmpty) {
return KeyEventResult.ignored;
}
final position = nodes.first.selectable?.start();
if (position == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
Selection.collapsed(position),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorEnd = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
if (nodes.isEmpty) {
return KeyEventResult.ignored;
}
final position = nodes.first.selectable?.end();
if (position == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
Selection.collapsed(position),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorTopSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
@ -43,168 +143,150 @@ KeyEventResult _arrowKeysWithMetaAndShift(
var start = selection.start;
var end = selection.end;
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
final position = nodes.first.selectable?.start();
if (position != null) {
end = position;
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
final position = nodes.first.selectable?.end();
if (position != null) {
end = position;
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
final position = editorState.document.root.children
.whereType<TextNode>()
.first
.selectable
?.start();
if (position != null) {
end = position;
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
final position = editorState.document.root.children
.whereType<TextNode>()
.last
.selectable
?.end();
if (position != null) {
end = position;
}
final position = editorState.document.root.children
.whereType<TextNode>()
.first
.selectable
?.start();
if (position != null) {
end = position;
}
editorState.service.selectionService.updateSelection(
selection.copyWith(start: start, end: end),
);
return KeyEventResult.handled;
}
// Move the cursor to top, bottom, left and right of the document.
KeyEventResult _arrowKeysWithMeta(EditorState editorState, RawKeyEvent event) {
if (!event.isMetaPressed ||
event.isShiftPressed ||
!_arrowKeys.contains(event.logicalKey)) {
assert(false);
return KeyEventResult.ignored;
}
final nodes = editorState.service.selectionService.currentSelectedNodes;
if (nodes.isEmpty) {
return KeyEventResult.ignored;
}
Position? position;
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
position = nodes.first.selectable?.start();
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
position = nodes.last.selectable?.end();
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
position = editorState.document.root.children
.whereType<TextNode>()
.first
.selectable
?.start();
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
position = editorState.document.root.children
.whereType<TextNode>()
.last
.selectable
?.end();
}
if (position == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService.updateSelection(
Selection.collapsed(position),
);
return KeyEventResult.handled;
}
KeyEventResult _arrowKeysWithShift(EditorState editorState, RawKeyEvent event) {
if (event.isMetaPressed ||
!event.isShiftPressed ||
!_arrowKeys.contains(event.logicalKey)) {
assert(false);
return KeyEventResult.ignored;
}
};
ShortcutEventHandler cursorBottomSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
Position? end;
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
end = selection.end.goLeft(editorState);
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
end = selection.end.goRight(editorState);
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
end = _goUp(editorState);
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
end = _goDown(editorState);
var start = selection.start;
var end = selection.end;
final position = editorState.document.root.children
.whereType<TextNode>()
.last
.selectable
?.end();
if (position != null) {
end = position;
}
if (end == null) {
return KeyEventResult.ignored;
}
editorState.service.selectionService
.updateSelection(selection.copyWith(end: end));
editorState.service.selectionService.updateSelection(
selection.copyWith(start: start, end: end),
);
return KeyEventResult.handled;
}
};
KeyEventResult _arrowKeysOnly(EditorState editorState, RawKeyEvent event) {
if (event.isMetaPressed ||
event.isShiftPressed ||
!_arrowKeys.contains(event.logicalKey)) {
assert(false);
ShortcutEventHandler cursorBeginSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
var start = selection.start;
var end = selection.end;
final position = nodes.last.selectable?.start();
if (position != null) {
end = position;
}
editorState.service.selectionService.updateSelection(
selection.copyWith(start: start, end: end),
);
return KeyEventResult.handled;
};
ShortcutEventHandler cursorEndSelect = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
var start = selection.start;
var end = selection.end;
final position = nodes.last.selectable?.end();
if (position != null) {
end = position;
}
editorState.service.selectionService.updateSelection(
selection.copyWith(start: start, end: end),
);
return KeyEventResult.handled;
};
KeyEventResult cursorUp(EditorState editorState, RawKeyEvent event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection =
editorState.service.selectionService.currentSelection.value?.normalize;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
if (selection.isCollapsed) {
final leftPosition = selection.start.goLeft(editorState);
if (leftPosition != null) {
editorState.service.selectionService.updateSelection(
Selection.collapsed(leftPosition),
);
}
} else {
editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.start),
);
}
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
if (selection.isCollapsed) {
final rightPosition = selection.start.goRight(editorState);
if (rightPosition != null) {
editorState.service.selectionService.updateSelection(
Selection.collapsed(rightPosition),
);
}
} else {
editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.end),
);
}
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
final upPosition = _goUp(editorState);
editorState.updateCursorSelection(
upPosition == null ? null : Selection.collapsed(upPosition),
);
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
final downPosition = _goDown(editorState);
editorState.updateCursorSelection(
downPosition == null ? null : Selection.collapsed(downPosition),
);
return KeyEventResult.handled;
final upPosition = _goUp(editorState);
editorState.updateCursorSelection(
upPosition == null ? null : Selection.collapsed(upPosition),
);
return KeyEventResult.handled;
}
KeyEventResult cursorDown(EditorState editorState, RawKeyEvent event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection =
editorState.service.selectionService.currentSelection.value?.normalize;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
return KeyEventResult.ignored;
final downPosition = _goDown(editorState);
editorState.updateCursorSelection(
downPosition == null ? null : Selection.collapsed(downPosition),
);
return KeyEventResult.handled;
}
KeyEventResult cursorLeft(EditorState editorState, RawKeyEvent event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection =
editorState.service.selectionService.currentSelection.value?.normalize;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
if (selection.isCollapsed) {
final leftPosition = selection.start.goLeft(editorState);
if (leftPosition != null) {
editorState.service.selectionService.updateSelection(
Selection.collapsed(leftPosition),
);
}
} else {
editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.start),
);
}
return KeyEventResult.handled;
}
KeyEventResult cursorRight(EditorState editorState, RawKeyEvent event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection =
editorState.service.selectionService.currentSelection.value?.normalize;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
if (selection.isCollapsed) {
final rightPosition = selection.start.goRight(editorState);
if (rightPosition != null) {
editorState.service.selectionService.updateSelection(
Selection.collapsed(rightPosition),
);
}
} else {
editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.end),
);
}
return KeyEventResult.handled;
}
extension on Position {

View file

@ -17,58 +17,92 @@ List<ShortcutEvent> builtInShortcutEvents = [
ShortcutEvent(
key: 'Move cursor up',
command: 'arrow up',
handler: arrowKeysHandler,
handler: cursorUp,
),
ShortcutEvent(
key: 'Move cursor down',
command: 'arrow down',
handler: arrowKeysHandler,
handler: cursorDown,
),
ShortcutEvent(
key: 'Move cursor left',
command: 'arrow left',
handler: arrowKeysHandler,
handler: cursorLeft,
),
ShortcutEvent(
key: 'Move cursor right',
command: 'arrow right',
handler: arrowKeysHandler,
handler: cursorRight,
),
ShortcutEvent(
key: 'Cursor up select',
command: 'shift+arrow up',
handler: cursorUpSelect,
),
ShortcutEvent(
key: 'Cursor down select',
command: 'shift+arrow down',
handler: cursorDownSelect,
),
ShortcutEvent(
key: 'Cursor left select',
command: 'shift+arrow left',
handler: cursorLeftSelect,
),
ShortcutEvent(
key: 'Cursor right select',
command: 'shift+arrow right',
handler: cursorRightSelect,
),
ShortcutEvent(
key: 'Move cursor top',
command: 'meta+arrow up',
windowsCommand: 'ctrl+arrow up',
handler: cursorBegin,
),
ShortcutEvent(
key: 'Move cursor bottom',
command: 'meta+arrow down',
windowsCommand: 'ctrl+arrow down',
handler: cursorBottom,
),
ShortcutEvent(
key: 'Move cursor begin',
command: 'meta+arrow left',
windowsCommand: 'ctrl+arrow left',
handler: cursorBegin,
),
ShortcutEvent(
key: 'Move cursor end',
command: 'meta+arrow right',
windowsCommand: 'ctrl+arrow right',
handler: cursorEnd,
),
ShortcutEvent(
key: 'Cursor top select',
command: 'meta+shift+arrow up',
windowsCommand: 'ctrl+shift+arrow up',
handler: cursorTopSelect,
),
ShortcutEvent(
key: 'Cursor bottom select',
command: 'meta+shift+arrow down',
windowsCommand: 'ctrl+shift+arrow down',
handler: cursorBottomSelect,
),
ShortcutEvent(
key: 'Cursor begin select',
command: 'meta+shift+arrow left',
windowsCommand: 'ctrl+shift+arrow left',
handler: cursorBeginSelect,
),
ShortcutEvent(
key: 'Cursor end select',
command: 'meta+shift+arrow right',
windowsCommand: 'ctrl+shift+arrow right',
handler: cursorEndSelect,
),
// TODO: split the keys.
ShortcutEvent(
key: 'Shift + Arrow Keys',
command:
'shift+arrow up,shift+arrow down,shift+arrow left,shift+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Control + Arrow Keys',
command: 'meta+arrow up,meta+arrow down,meta+arrow left,meta+arrow right',
windowsCommand:
'ctrl+arrow up,ctrl+arrow down,ctrl+arrow left,ctrl+arrow right',
macOSCommand: 'cmd+arrow up,cmd+arrow down,cmd+arrow left,cmd+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Meta + Shift + Arrow Keys',
command:
'meta+shift+arrow up,meta+shift+arrow down,meta+shift+arrow left,meta+shift+arrow right',
windowsCommand:
'ctrl+shift+arrow up,ctrl+shift+arrow down,ctrl+shift+arrow left,ctrl+shift+arrow right',
macOSCommand:
'cmd+shift+arrow up,cmd+shift+arrow down,cmd+shift+arrow left,cmd+shift+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Meta + Shift + Arrow Keys',
command:
'meta+shift+arrow up,meta+shift+arrow down,meta+shift+arrow left,meta+shift+arrow right',
windowsCommand:
'ctrl+shift+arrow up,ctrl+shift+arrow down,ctrl+shift+arrow left,ctrl+shift+arrow right',
macOSCommand:
'cmd+shift+arrow up,cmd+shift+arrow down,cmd+shift+arrow left,cmd+shift+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Delete Text',
command: 'delete,backspace',

View file

@ -273,6 +273,74 @@ void main() async {
),
);
});
testWidgets('Presses shift + arrow down and meta/ctrl + shift + right',
(tester) async {
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor
..insertTextNode(text)
..insertTextNode(text);
await editor.startTesting();
final selection = Selection.single(path: [0], startOffset: 8);
await editor.updateSelection(selection);
await editor.pressLogicKey(
LogicalKeyboardKey.arrowDown,
isShiftPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowRight,
isShiftPressed: true,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowRight,
isShiftPressed: true,
isMetaPressed: true,
);
}
expect(
editor.documentSelection,
selection.copyWith(
end: Position(path: [1], offset: text.length),
),
);
});
testWidgets('Presses shift + arrow up and meta/ctrl + shift + left',
(tester) async {
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor
..insertTextNode(text)
..insertTextNode(text);
await editor.startTesting();
final selection = Selection.single(path: [1], startOffset: 8);
await editor.updateSelection(selection);
await editor.pressLogicKey(
LogicalKeyboardKey.arrowUp,
isShiftPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowLeft,
isShiftPressed: true,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowLeft,
isShiftPressed: true,
isMetaPressed: true,
);
}
expect(
editor.documentSelection,
selection.copyWith(
end: Position(path: [0], offset: 0),
),
);
});
}
Future<void> _testPressArrowKeyInNotCollapsedSelection(