Просмотр исходного кода

Improve Editing and Navigating shortcuts with Ctrl/Meta (#1845)

* feat: handler for deleting a word

* chore: typo

* test: ctrl and backspace to delete word

* feat: add ctrl alt arrows to select words

* fix: remove print statement

* fix: remove additional shortcut

* fix: handle nodes empty case

* test: edge cases with delete word

* fix: press meta on macos
Mayur Mahajan 2 лет назад
Родитель
Сommit
ea9d8d03ad

+ 27 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart

@@ -322,6 +322,33 @@ ShortcutEventHandler cursorRightWordSelect = (editorState, event) {
   return KeyEventResult.handled;
 };
 
+ShortcutEventHandler cursorLeftWordDelete = (editorState, event) {
+  final textNodes = editorState.service.selectionService.currentSelectedNodes
+      .whereType<TextNode>();
+  final selection = editorState.service.selectionService.currentSelection.value;
+
+  if (textNodes.isEmpty || selection == null) {
+    return KeyEventResult.ignored;
+  }
+
+  final textNode = textNodes.first;
+
+  final startOfWord =
+      selection.end.goLeft(editorState, selectionRange: _SelectionRange.word);
+
+  if (startOfWord == null) {
+    return KeyEventResult.ignored;
+  }
+
+  final transaction = editorState.transaction;
+  transaction.deleteText(
+      textNode, startOfWord.offset, selection.end.offset - startOfWord.offset);
+
+  editorState.apply(transaction);
+
+  return KeyEventResult.handled;
+};
+
 enum _SelectionRange {
   character,
   word,

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart

@@ -253,8 +253,8 @@ void _deleteTextNodes(
   final last = textNodes.last;
   var content = textNodes.last.toPlainText();
   content = content.substring(selection.end.offset, content.length);
-  // Merge the fist and the last text node content,
-  //  and delete the all nodes expect for the first.
+  // Merge the first and the last text node content,
+  //  and delete all the nodes except for the first.
   transaction
     ..deleteNodes(textNodes.sublist(1))
     ..mergeText(

+ 11 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart

@@ -52,13 +52,24 @@ List<ShortcutEvent> builtInShortcutEvents = [
   ShortcutEvent(
     key: 'Cursor down select',
     command: 'shift+alt+arrow left',
+    windowsCommand: 'shift+alt+arrow left',
+    linuxCommand: 'shift+alt+arrow left',
     handler: cursorLeftWordSelect,
   ),
   ShortcutEvent(
     key: 'Cursor down select',
     command: 'shift+alt+arrow right',
+    windowsCommand: 'shift+alt+arrow right',
+    linuxCommand: 'shift+alt+arrow right',
     handler: cursorRightWordSelect,
   ),
+  ShortcutEvent(
+    key: 'Cursor word delete',
+    command: 'meta+backspace',
+    windowsCommand: 'ctrl+backspace',
+    linuxCommand: 'ctrl+backspace',
+    handler: cursorLeftWordDelete,
+  ),
   ShortcutEvent(
     key: 'Cursor left select',
     command: 'shift+arrow left',

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

@@ -466,6 +466,133 @@ void main() async {
       ),
     );
   });
+
+  testWidgets('Presses ctrl + backspace to delete a word', (tester) async {
+    List<String> words = ["Welcome", " ", "to", " ", "Appflowy", " ", "😁"];
+    final text = words.join();
+    final editor = tester.editor..insertTextNode(text);
+
+    await editor.startTesting();
+    var selection = Selection.single(path: [0], startOffset: text.length);
+    await editor.updateSelection(selection);
+
+    if (Platform.isWindows || Platform.isLinux) {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isControlPressed: true,
+      );
+    } else {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isMetaPressed: true,
+      );
+    }
+
+    //fetching all the text that is still on the editor.
+    var nodes =
+        editor.editorState.service.selectionService.currentSelectedNodes;
+    var textNode = nodes.whereType<TextNode>().first;
+    var newText = textNode.toPlainText();
+
+    words.removeLast();
+    expect(newText, words.join());
+
+    if (Platform.isWindows || Platform.isLinux) {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isControlPressed: true,
+      );
+    } else {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isMetaPressed: true,
+      );
+    }
+
+    //fetching all the text that is still on the editor.
+    nodes = editor.editorState.service.selectionService.currentSelectedNodes;
+    textNode = nodes.whereType<TextNode>().first;
+
+    newText = textNode.toPlainText();
+
+    words.removeLast();
+    expect(newText, words.join());
+
+    for (var i = 0; i < words.length; i++) {
+      if (Platform.isWindows || Platform.isLinux) {
+        await editor.pressLogicKey(
+          LogicalKeyboardKey.backspace,
+          isControlPressed: true,
+        );
+      } else {
+        await editor.pressLogicKey(
+          LogicalKeyboardKey.backspace,
+          isMetaPressed: true,
+        );
+      }
+    }
+
+    nodes = editor.editorState.service.selectionService.currentSelectedNodes;
+    textNode = nodes.whereType<TextNode>().toList(growable: false).first;
+
+    newText = textNode.toPlainText();
+
+    expect(newText, '');
+  });
+
+  testWidgets('Testing ctrl + backspace edge cases', (tester) async {
+    const text = 'Welcome to Appflowy 😁';
+    final editor = tester.editor..insertTextNode(text);
+
+    await editor.startTesting();
+    var selection = Selection.single(path: [0], startOffset: 0);
+    await editor.updateSelection(selection);
+
+    if (Platform.isWindows || Platform.isLinux) {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isControlPressed: true,
+      );
+    } else {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isMetaPressed: true,
+      );
+    }
+
+    //fetching all the text that is still on the editor.
+    var nodes =
+        editor.editorState.service.selectionService.currentSelectedNodes;
+    var textNode = nodes.whereType<TextNode>().first;
+    var newText = textNode.toPlainText();
+
+    //nothing happens
+    expect(newText, text);
+
+    selection = Selection.single(path: [0], startOffset: 14);
+    await editor.updateSelection(selection);
+    //Welcome to App|flowy 😁
+
+    if (Platform.isWindows || Platform.isLinux) {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isControlPressed: true,
+      );
+    } else {
+      await editor.pressLogicKey(
+        LogicalKeyboardKey.backspace,
+        isMetaPressed: true,
+      );
+    }
+
+    //fetching all the text that is still on the editor.
+    nodes = editor.editorState.service.selectionService.currentSelectedNodes;
+    textNode = nodes.whereType<TextNode>().first;
+    newText = textNode.toPlainText();
+
+    const expectedText = 'Welcome to flowy 😁';
+    expect(newText, expectedText);
+  });
 }
 
 Future<void> _testPressArrowKeyInNotCollapsedSelection(