Browse Source

feat: refactor arrow key handler

Lucas.Xu 2 years ago
parent
commit
804b2b27c1

+ 218 - 136
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart

@@ -1,114 +1,130 @@
 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
-];
+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;
+};
 
-KeyEventResult _arrowKeysWithMetaAndShift(
-    EditorState editorState, RawKeyEvent event) {
-  if (!event.isMetaPressed ||
-      !event.isShiftPressed ||
-      !_arrowKeys.contains(event.logicalKey)) {
-    assert(false);
+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;
+};
 
-  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;
-    }
+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.copyWith(start: start, end: end),
+    Selection.collapsed(position),
   );
   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);
+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;
   }
-  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();
+  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;
   }
@@ -116,95 +132,161 @@ KeyEventResult _arrowKeysWithMeta(EditorState editorState, RawKeyEvent event) {
     Selection.collapsed(position),
   );
   return KeyEventResult.handled;
-}
+};
 
-KeyEventResult _arrowKeysWithShift(EditorState editorState, RawKeyEvent event) {
-  if (event.isMetaPressed ||
-      !event.isShiftPressed ||
-      !_arrowKeys.contains(event.logicalKey)) {
-    assert(false);
+ShortcutEventHandler cursorTopSelect = (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 = 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;
+};
+
+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) {
+  editorState.service.selectionService.updateSelection(
+    selection.copyWith(start: start, end: end),
+  );
+  return KeyEventResult.handled;
+};
+
+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;
   }
-  editorState.service.selectionService
-      .updateSelection(selection.copyWith(end: end));
+  final upPosition = _goUp(editorState);
+  editorState.updateCursorSelection(
+    upPosition == null ? null : Selection.collapsed(upPosition),
+  );
   return KeyEventResult.handled;
 }
 
-KeyEventResult _arrowKeysOnly(EditorState editorState, RawKeyEvent event) {
-  if (event.isMetaPressed ||
-      event.isShiftPressed ||
-      !_arrowKeys.contains(event.logicalKey)) {
-    assert(false);
+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;
   }
+  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 (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
-    if (selection.isCollapsed) {
-      final leftPosition = selection.start.goLeft(editorState);
-      if (leftPosition != null) {
-        editorState.service.selectionService.updateSelection(
-          Selection.collapsed(leftPosition),
-        );
-      }
-    } else {
+  if (selection.isCollapsed) {
+    final leftPosition = selection.start.goLeft(editorState);
+    if (leftPosition != null) {
       editorState.service.selectionService.updateSelection(
-        Selection.collapsed(selection.start),
+        Selection.collapsed(leftPosition),
       );
     }
-    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 {
+  } 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(selection.end),
+        Selection.collapsed(rightPosition),
       );
     }
-    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),
+  } else {
+    editorState.service.selectionService.updateSelection(
+      Selection.collapsed(selection.end),
     );
-    return KeyEventResult.handled;
   }
-  return KeyEventResult.ignored;
+  return KeyEventResult.handled;
 }
 
 extension on Position {

+ 65 - 31
frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart

@@ -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,
   ),
-  // TODO: split the keys.
   ShortcutEvent(
-    key: 'Shift + Arrow Keys',
-    command:
-        'shift+arrow up,shift+arrow down,shift+arrow left,shift+arrow right',
-    handler: arrowKeysHandler,
+    key: 'Move cursor top',
+    command: 'meta+arrow up',
+    windowsCommand: 'ctrl+arrow up',
+    handler: cursorBegin,
   ),
   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,
+    key: 'Move cursor bottom',
+    command: 'meta+arrow down',
+    windowsCommand: 'ctrl+arrow down',
+    handler: cursorBottom,
   ),
   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,
+    key: 'Move cursor begin',
+    command: 'meta+arrow left',
+    windowsCommand: 'ctrl+arrow left',
+    handler: cursorBegin,
   ),
   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,
+    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: 'Delete Text',
     command: 'delete,backspace',

+ 68 - 0
frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/arrow_keys_handler_test.dart

@@ -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(