Skip to content

Commit

Permalink
feat: delete "sentence" shortcut (AppFlowy-IO#32)
Browse files Browse the repository at this point in the history
* feat: delete sentence shortcut

Closes: AppFlowy-IO#4

* fix: error in shortcut_event_test.dart

* fix: prettify and add missing test

* test: improve coverage for arrow_keys_handler.dart

* chore: Apply suggestions from code review

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
  • Loading branch information
Xazin and LucasXu0 committed Apr 13, 2023
1 parent 6485578 commit a3c34bc
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 106 deletions.
2 changes: 1 addition & 1 deletion lib/src/core/document/text_delta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ class Delta extends Iterable<TextOperation> {
/// Delete operations have a Number `delete` key defined representing the number of characters to delete.
void delete(int length) => add(TextDelete(length: length));

/// The length of the string fo the [Delta].
/// The length of the string of the [Delta].
@override
int get length {
return _operations.fold(
Expand Down
185 changes: 91 additions & 94 deletions lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ ShortcutEventHandler cursorUpSelect = (editorState, event) {
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
final end = _goUp(editorState);
final end = _moveVertical(editorState);
if (end == null) {
return KeyEventResult.ignored;
}
Expand All @@ -55,7 +55,7 @@ ShortcutEventHandler cursorDownSelect = (editorState, event) {
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
final end = _goDown(editorState);
final end = _moveVertical(editorState, upwards: false);
if (end == null) {
return KeyEventResult.ignored;
}
Expand Down Expand Up @@ -191,6 +191,7 @@ ShortcutEventHandler cursorBeginSelect = (editorState, event) {
if (position != null) {
end = position;
}

editorState.service.selectionService.updateSelection(
selection.copyWith(start: start, end: end),
);
Expand Down Expand Up @@ -223,10 +224,12 @@ ShortcutEventHandler cursorUp = (editorState, event) {
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
final upPosition = _goUp(editorState);

final upPosition = _moveVertical(editorState);
editorState.updateCursorSelection(
upPosition == null ? null : Selection.collapsed(upPosition),
);

return KeyEventResult.handled;
};

Expand All @@ -237,10 +240,12 @@ ShortcutEventHandler cursorDown = (editorState, event) {
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
final downPosition = _goDown(editorState);

final downPosition = _moveVertical(editorState, upwards: false);
editorState.updateCursorSelection(
downPosition == null ? null : Selection.collapsed(downPosition),
);

return KeyEventResult.handled;
};

Expand All @@ -251,18 +256,15 @@ ShortcutEventHandler cursorLeft = (editorState, event) {
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}
if (selection.isCollapsed) {
final leftPosition = selection.start.goLeft(editorState);
if (leftPosition != null) {
editorState.service.selectionService.updateSelection(
Selection.collapsed(leftPosition),
);
}
} else {
editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.start),
);
}

Position newPosition = selection.isCollapsed
? selection.start.goLeft(editorState) ?? selection.start
: selection.start;

editorState.service.selectionService.updateSelection(
Selection.collapsed(newPosition),
);

return KeyEventResult.handled;
};

Expand All @@ -273,18 +275,15 @@ ShortcutEventHandler cursorRight = (editorState, event) {
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(rightPosition),
);
}
} else {
editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.end),
);
}

final newPosition = selection.isCollapsed
? selection.start.goRight(editorState) ?? selection.end
: selection.end;

editorState.service.selectionService.updateSelection(
Selection.collapsed(newPosition),
);

return KeyEventResult.handled;
};

Expand All @@ -294,14 +293,17 @@ ShortcutEventHandler cursorLeftWordSelect = (editorState, event) {
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}

final end =
selection.end.goLeft(editorState, selectionRange: _SelectionRange.word);
if (end == null) {
return KeyEventResult.ignored;
}

editorState.service.selectionService.updateSelection(
selection.copyWith(end: end),
);

return KeyEventResult.handled;
};

Expand All @@ -314,21 +316,13 @@ ShortcutEventHandler cursorLeftWordMove = (editorState, event) {
return KeyEventResult.ignored;
}

if (selection.isCollapsed) {
final leftPosition = selection.start
.goLeft(editorState, selectionRange: _SelectionRange.word);
if (leftPosition != null) {
editorState.service.selectionService.updateSelection(
Selection.collapsed(leftPosition),
);

return KeyEventResult.handled;
}
final newPosition = selection.start
.goLeft(editorState, selectionRange: _SelectionRange.word) ??
selection.start;

editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.start),
);
}
editorState.service.selectionService.updateSelection(
Selection.collapsed(newPosition),
);

return KeyEventResult.handled;
};
Expand All @@ -342,21 +336,13 @@ ShortcutEventHandler cursorRightWordMove = (editorState, event) {
return KeyEventResult.ignored;
}

if (selection.isCollapsed) {
final rightPosition = selection.start
.goRight(editorState, selectionRange: _SelectionRange.word);
if (rightPosition != null) {
editorState.service.selectionService.updateSelection(
Selection.collapsed(rightPosition),
);

return KeyEventResult.handled;
}
final newPosition = selection.start
.goRight(editorState, selectionRange: _SelectionRange.word) ??
selection.end;

editorState.service.selectionService.updateSelection(
Selection.collapsed(selection.end),
);
}
editorState.service.selectionService.updateSelection(
Selection.collapsed(newPosition),
);

return KeyEventResult.handled;
};
Expand All @@ -367,14 +353,17 @@ ShortcutEventHandler cursorRightWordSelect = (editorState, event) {
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}

final end =
selection.end.goRight(editorState, selectionRange: _SelectionRange.word);
if (end == null) {
return KeyEventResult.ignored;
}

editorState.service.selectionService.updateSelection(
selection.copyWith(end: end),
);

return KeyEventResult.handled;
};

Expand Down Expand Up @@ -405,6 +394,30 @@ ShortcutEventHandler cursorLeftWordDelete = (editorState, event) {
return KeyEventResult.handled;
};

ShortcutEventHandler cursorLeftSentenceDelete = (editorState, event) {
final nodes = editorState.service.selectionService.currentSelectedNodes;
final selection = editorState.service.selectionService.currentSelection.value;
if (nodes.isEmpty || selection == null) {
return KeyEventResult.ignored;
}

if (nodes.length == 1 && nodes.first is TextNode) {
final textNode = nodes.first as TextNode;
if (textNode.toPlainText().isEmpty) {
return KeyEventResult.ignored;
}
}

if (selection.isCollapsed) {
final deleteTransaction = editorState.transaction;
deleteTransaction.deleteText(
nodes.first as TextNode, 0, selection.end.offset);
editorState.apply(deleteTransaction, withUpdateCursor: true);
}

return KeyEventResult.handled;
};

enum _SelectionRange {
character,
word,
Expand Down Expand Up @@ -435,9 +448,9 @@ extension on Position {
path: path,
offset: node.delta.prevRunePosition(offset),
);
} else {
return Position(path: path, offset: offset);
}

return Position(path: path, offset: offset);
case _SelectionRange.word:
if (node is TextNode) {
final result = node.selectable?.getWordBoundaryInPosition(
Expand All @@ -449,11 +462,10 @@ extension on Position {
if (result != null) {
return result.start;
}
} else {
return Position(path: path, offset: offset);
}

return Position(path: path, offset: offset);
}
return null;
}

Position? goRight(
Expand All @@ -464,76 +476,61 @@ extension on Position {
if (node == null) {
return null;
}

final end = node.selectable?.end();
if (end != null && offset >= end.offset) {
final nextStart = node.next?.selectable?.start();
if (nextStart != null) {
return nextStart;
}
return null;
return node.next?.selectable?.start();
}

switch (selectionRange) {
case _SelectionRange.character:
if (node is TextNode) {
return Position(
path: path,
offset: node.delta.nextRunePosition(offset),
);
} else {
return Position(path: path, offset: offset);
}

return Position(path: path, offset: offset);
case _SelectionRange.word:
if (node is TextNode) {
final result = node.selectable?.getWordBoundaryInPosition(this);
if (result != null) {
return result.end;
}
} else {
return Position(path: path, offset: offset);
}

return Position(path: path, offset: offset);
}
return null;
}
}

Position? _goUp(EditorState editorState) {
Position? _moveVertical(
EditorState editorState, {
bool upwards = true,
}) {
final selection = editorState.service.selectionService.currentSelection.value;
final rects = editorState.service.selectionService.selectionRects;
if (rects.isEmpty || selection == null) {
return null;
}
Offset offset;
if (selection.isBackward) {
final rect = rects.reduce(
(current, next) => current.bottom >= next.bottom ? current : next,
);
offset = rect.topRight.translate(0, -rect.height);
} else {
final rect = rects.reduce(
(current, next) => current.top <= next.top ? current : next,
);
offset = rect.topLeft.translate(0, -rect.height);
}
return editorState.service.selectionService.getPositionInOffset(offset);
}

Position? _goDown(EditorState editorState) {
final selection = editorState.service.selectionService.currentSelection.value;
final rects = editorState.service.selectionService.selectionRects;
if (rects.isEmpty || selection == null) {
return null;
}
Offset offset;
if (selection.isBackward) {
final rect = rects.reduce(
(current, next) => current.bottom >= next.bottom ? current : next,
);
offset = rect.bottomRight.translate(0, rect.height);
offset = upwards
? rect.topRight.translate(0, -rect.height)
: rect.bottomRight.translate(0, rect.height);
} else {
final rect = rects.reduce(
(current, next) => current.top <= next.top ? current : next,
);
offset = rect.bottomLeft.translate(0, rect.height);
offset = upwards
? rect.topLeft.translate(0, -rect.height)
: rect.bottomLeft.translate(0, rect.height);
}

return editorState.service.selectionService.getPositionInOffset(offset);
}
9 changes: 8 additions & 1 deletion lib/src/service/shortcut_event/built_in_shortcut_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,18 @@ List<ShortcutEvent> builtInShortcutEvents = [
),
ShortcutEvent(
key: 'Cursor word delete',
command: 'meta+backspace',
command: 'alt+backspace',
windowsCommand: 'ctrl+backspace',
linuxCommand: 'ctrl+backspace',
handler: cursorLeftWordDelete,
),
ShortcutEvent(
key: 'Cursor sentence delete',
command: 'meta+backspace',
windowsCommand: 'ctrl+alt+backspace',
linuxCommand: 'ctrl+alt+backspace',
handler: cursorLeftSentenceDelete,
),
ShortcutEvent(
key: 'Cursor left select',
command: 'shift+arrow left',
Expand Down
Loading

0 comments on commit a3c34bc

Please sign in to comment.