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
This commit is contained in:
Mayur Mahajan 2023-02-15 11:56:18 +05:30 committed by GitHub
parent 64902f763b
commit ea9d8d03ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 167 additions and 2 deletions

View File

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

View File

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

View File

@ -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',

View File

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