diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart index d612f839a3..2d1953c68c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart +++ b/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(); + 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, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart index ac2f14cc1a..49f5da8798 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart +++ b/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( diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart index 021093de5d..bb2dfe128b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart @@ -52,13 +52,24 @@ List 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', diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/arrow_keys_handler_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/arrow_keys_handler_test.dart index fea6209713..25e633e490 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/arrow_keys_handler_test.dart +++ b/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 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().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().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().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().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().first; + newText = textNode.toPlainText(); + + const expectedText = 'Welcome to flowy 😁'; + expect(newText, expectedText); + }); } Future _testPressArrowKeyInNotCollapsedSelection(