From cc063e3bc71a3df0946653f0d0dc29305ca91bd1 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Fri, 7 Jul 2023 19:19:52 +0800 Subject: [PATCH] feat: implement delete command --- .../delete_command.dart | 81 +++++++------------ .../select_all_command.dart | 2 +- lib/src/extensions/node_extensions.dart | 30 ++++++- .../delete_command_test.dart | 20 +++-- 4 files changed, 68 insertions(+), 65 deletions(-) diff --git a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart index e71fc6adf..69ca64460 100644 --- a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart +++ b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart @@ -34,69 +34,44 @@ CommandShortcutEventHandler _deleteInCollapsedSelection = (editorState) { final position = selection.start; final node = editorState.getNodeAtPath(position.path); - if (node == null || node.delta == null) { + final delta = node?.delta; + if (node == null || delta == null) { return KeyEventResult.ignored; } - // Why do we use nextRunePosition instead of the position end offset? - // Because some character's length > 1, for example, emoji. - final index = node.delta!.nextRunePosition(position.offset); final transaction = editorState.transaction; - if (index == node.delta!.length) { - // merge the next node in the current node. - final nextNode = node.next; - if (nextNode == null) { - return KeyEventResult.ignored; - } - //TODO(Lucas): add logic for merging a bulletList or numberedList item. - if (nextNode.next == null && nextNode.children.isEmpty) { - final path = node.path; + + // merge the next node with delta + if (position.offset == delta.length) { + final next = node.findDownward((element) => element.delta != null); + if (next != null) { + if (next.children.isNotEmpty) { + final path = node.path + [node.children.length]; + transaction.insertNodes(path, next.children); + } transaction - ..mergeText(node, nextNode) - ..deleteNode(nextNode) - ..afterSelection = Selection.collapsed( - Position( - path: path, - offset: index, - ), + ..deleteNode(next) + ..mergeText( + node, + next, ); - } else { - // merge with the previous node contains delta. - final nextNodeWithDelta = - node.lastNodeWhere((element) => element.delta != null); - if (nextNodeWithDelta != null) { - assert(nextNodeWithDelta.delta != null); - transaction - ..mergeText(nextNodeWithDelta, node) - ..insertNodes( - // insert children to previous node - nextNodeWithDelta.path.next, - node.children.toList(), - ) - ..deleteNode(node) - ..afterSelection = Selection.collapsed( - Position( - path: nextNodeWithDelta.path, - offset: nextNodeWithDelta.delta!.length, - ), - ); - } else { - // do nothing if there is no previous node contains delta. - return KeyEventResult.ignored; - } + editorState.apply(transaction); + return KeyEventResult.handled; } } else { - // Although the selection may be collapsed, - // its length may not always be equal to 1 because some characters have a length greater than 1. - transaction.deleteText( - node, - position.offset, - index - position.offset, - ); + final nextIndex = delta.nextRunePosition(position.offset); + if (nextIndex <= delta.length) { + transaction.deleteText( + node, + position.offset, + nextIndex - position.offset, + ); + editorState.apply(transaction); + return KeyEventResult.handled; + } } - editorState.apply(transaction); - return KeyEventResult.handled; + return KeyEventResult.ignored; }; /// Handle delete key event when selection is not collapsed. diff --git a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/select_all_command.dart b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/select_all_command.dart index 3601b0cfa..63cc1aab9 100644 --- a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/select_all_command.dart +++ b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/select_all_command.dart @@ -29,7 +29,7 @@ CommandShortcutEventHandler _selectAllCommandHandler = (editorState) { ) ?.selectable; final lastSelectable = editorState.document.root - .lastNodeWhere( + .lastChildWhere( (element) => element.selectable != null, ) ?.selectable; diff --git a/lib/src/extensions/node_extensions.dart b/lib/src/extensions/node_extensions.dart index f2437d910..ae128e5ae 100644 --- a/lib/src/extensions/node_extensions.dart +++ b/lib/src/extensions/node_extensions.dart @@ -29,7 +29,7 @@ extension NodeExtensions on Node { Node? previousNodeWhere(bool Function(Node element) test) { var previous = this.previous; while (previous != null) { - final last = previous.lastNodeWhere(test); + final last = previous.lastChildWhere(test); if (last != null) { return last; } @@ -49,11 +49,11 @@ extension NodeExtensions on Node { } /// Returns the last node in the subtree that satisfies the given predicate - Node? lastNodeWhere(bool Function(Node element) test) { + Node? lastChildWhere(bool Function(Node element) test) { final children = this.children.toList().reversed; for (final child in children) { if (child.children.isNotEmpty) { - final last = child.lastNodeWhere(test); + final last = child.lastChildWhere(test); if (last != null) { return last; } @@ -65,6 +65,30 @@ extension NodeExtensions on Node { return null; } + // find the node from it's children or it's next sibling to find the node that matches the given predicate + Node? findDownward(bool Function(Node element) test) { + final children = this.children.toList(); + for (final child in children) { + if (test(child)) { + return child; + } + if (child.children.isNotEmpty) { + final node = child.findDownward(test); + if (node != null) { + return node; + } + } + } + final next = this.next; + if (next != null) { + if (test(next)) { + return next; + } + return next.findDownward(test); + } + return null; + } + bool allSatisfyInSelection( Selection selection, bool Function(Delta delta) test, diff --git a/test/new/service/shortcuts/command_shortcut_events/delete_command_test.dart b/test/new/service/shortcuts/command_shortcut_events/delete_command_test.dart index 192cb5a56..72e250736 100644 --- a/test/new/service/shortcuts/command_shortcut_events/delete_command_test.dart +++ b/test/new/service/shortcuts/command_shortcut_events/delete_command_test.dart @@ -326,10 +326,12 @@ void main() async { final delta = Delta()..insert(text); final editor = tester.editor ..addNode(headingNode(level: 1, delta: delta)) - ..addNode(bulletedListNode( - delta: delta, - children: [bulletedListNode(delta: delta)], - )); + ..addNode( + bulletedListNode( + delta: delta, + children: [bulletedListNode(delta: delta)], + ), + ); await editor.startTesting(); @@ -374,10 +376,12 @@ void main() async { final delta = Delta()..insert(text); final editor = tester.editor ..addNode(bulletedListNode(delta: delta)) - ..addNode(bulletedListNode( - delta: delta, - children: [bulletedListNode(delta: delta)], - )); + ..addNode( + bulletedListNode( + delta: delta, + children: [bulletedListNode(delta: delta)], + ), + ); await editor.startTesting();