mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: #953 improve arrow keys handler
This commit is contained in:
parent
b61e3f4601
commit
f098c543e6
@ -46,9 +46,9 @@ class Selection {
|
|||||||
(start.path <= end.path && !pathEquals(start.path, end.path)) ||
|
(start.path <= end.path && !pathEquals(start.path, end.path)) ||
|
||||||
(isSingle && start.offset < end.offset);
|
(isSingle && start.offset < end.offset);
|
||||||
|
|
||||||
Selection normalize() {
|
Selection get normalize {
|
||||||
if (isForward) {
|
if (isForward) {
|
||||||
return Selection(start: end, end: start);
|
return reversed;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,8 @@ mixin DefaultSelectable {
|
|||||||
Selection getSelectionInRange(Offset start, Offset end) =>
|
Selection getSelectionInRange(Offset start, Offset end) =>
|
||||||
forward.getSelectionInRange(start, end);
|
forward.getSelectionInRange(start, end);
|
||||||
|
|
||||||
Offset localToGlobal(Offset offset) => forward.localToGlobal(offset);
|
Offset localToGlobal(Offset offset) =>
|
||||||
|
forward.localToGlobal(offset) - baseOffset;
|
||||||
|
|
||||||
Selection? getWorldBoundaryInOffset(Offset offset) =>
|
Selection? getWorldBoundaryInOffset(Offset offset) =>
|
||||||
forward.getWorldBoundaryInOffset(offset);
|
forward.getWorldBoundaryInOffset(offset);
|
||||||
|
@ -132,17 +132,24 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Rect> getRectsInSelection(Selection selection) {
|
List<Rect> getRectsInSelection(Selection selection) {
|
||||||
assert(pathEquals(selection.start.path, selection.end.path) &&
|
assert(selection.isSingle &&
|
||||||
pathEquals(selection.start.path, widget.textNode.path));
|
pathEquals(selection.start.path, widget.textNode.path));
|
||||||
|
|
||||||
final textSelection = TextSelection(
|
final textSelection = TextSelection(
|
||||||
baseOffset: selection.start.offset,
|
baseOffset: selection.start.offset,
|
||||||
extentOffset: selection.end.offset,
|
extentOffset: selection.end.offset,
|
||||||
);
|
);
|
||||||
return _renderParagraph
|
final rects = _renderParagraph
|
||||||
.getBoxesForSelection(textSelection, boxHeightStyle: BoxHeightStyle.max)
|
.getBoxesForSelection(textSelection, boxHeightStyle: BoxHeightStyle.max)
|
||||||
.map((box) => box.toRect())
|
.map((box) => box.toRect())
|
||||||
.toList();
|
.toList(growable: false);
|
||||||
|
if (rects.isEmpty) {
|
||||||
|
// If the rich text widget does not contain any text,
|
||||||
|
// there will be no selection boxes,
|
||||||
|
// so we need to return to the default selection.
|
||||||
|
return [Rect.fromLTWH(0, 0, 0, _renderParagraph.size.height)];
|
||||||
|
}
|
||||||
|
return rects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1,26 +1,225 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor/src/extensions/node_extensions.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
int _endOffsetOfNode(Node node) {
|
AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
|
||||||
if (node is TextNode) {
|
if (!_arrowKeys.contains(event.logicalKey)) {
|
||||||
return node.delta.length;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
|
if (event.isMetaPressed && event.isShiftPressed) {
|
||||||
|
return _arrowKeysWithMetaAndShift(editorState, event);
|
||||||
|
} else if (event.isMetaPressed) {
|
||||||
|
return _arrowKeysWithMeta(editorState, event);
|
||||||
|
} else if (event.isShiftPressed) {
|
||||||
|
return _arrowKeysWithShift(editorState, event);
|
||||||
|
} else {
|
||||||
|
return _arrowKeysOnly(editorState, event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final _arrowKeys = [
|
||||||
|
LogicalKeyboardKey.arrowLeft,
|
||||||
|
LogicalKeyboardKey.arrowRight,
|
||||||
|
LogicalKeyboardKey.arrowUp,
|
||||||
|
LogicalKeyboardKey.arrowDown
|
||||||
|
];
|
||||||
|
|
||||||
|
KeyEventResult _arrowKeysWithMetaAndShift(
|
||||||
|
EditorState editorState, RawKeyEvent event) {
|
||||||
|
if (!event.isMetaPressed ||
|
||||||
|
!event.isShiftPressed ||
|
||||||
|
!_arrowKeys.contains(event.logicalKey)) {
|
||||||
|
assert(false);
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||||
|
final selection = editorState.service.selectionService.currentSelection.value;
|
||||||
|
if (nodes.isEmpty || selection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = selection.start;
|
||||||
|
var end = selection.end;
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
final position = nodes.first.selectable?.start();
|
||||||
|
if (position != null) {
|
||||||
|
end = position;
|
||||||
|
}
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
|
final position = nodes.first.selectable?.end();
|
||||||
|
if (position != null) {
|
||||||
|
end = position;
|
||||||
|
}
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
final position = editorState.document.root.children
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.first
|
||||||
|
.selectable
|
||||||
|
?.start();
|
||||||
|
if (position != null) {
|
||||||
|
end = position;
|
||||||
|
}
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
final position = editorState.document.root.children
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.last
|
||||||
|
.selectable
|
||||||
|
?.end();
|
||||||
|
if (position != null) {
|
||||||
|
end = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editorState.service.selectionService.updateSelection(
|
||||||
|
selection.copyWith(start: start, end: end),
|
||||||
|
);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the cursor to top, bottom, left and right of the document.
|
||||||
|
KeyEventResult _arrowKeysWithMeta(EditorState editorState, RawKeyEvent event) {
|
||||||
|
if (!event.isMetaPressed ||
|
||||||
|
event.isShiftPressed ||
|
||||||
|
!_arrowKeys.contains(event.logicalKey)) {
|
||||||
|
assert(false);
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||||
|
if (nodes.isEmpty) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
Position? position;
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
position = nodes.first.selectable?.start();
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
|
position = nodes.last.selectable?.end();
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
position = editorState.document.root.children
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.first
|
||||||
|
.selectable
|
||||||
|
?.start();
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
position = editorState.document.root.children
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.last
|
||||||
|
.selectable
|
||||||
|
?.end();
|
||||||
|
}
|
||||||
|
if (position == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
editorState.service.selectionService.updateSelection(
|
||||||
|
Selection.collapsed(position),
|
||||||
|
);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEventResult _arrowKeysWithShift(EditorState editorState, RawKeyEvent event) {
|
||||||
|
if (event.isMetaPressed ||
|
||||||
|
!event.isShiftPressed ||
|
||||||
|
!_arrowKeys.contains(event.logicalKey)) {
|
||||||
|
assert(false);
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||||
|
final selection = editorState.service.selectionService.currentSelection.value;
|
||||||
|
if (nodes.isEmpty || selection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
Position? end;
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
end = selection.end.goLeft(editorState);
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
|
end = selection.end.goRight(editorState);
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
end = _goUp(editorState);
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
end = _goDown(editorState);
|
||||||
|
}
|
||||||
|
if (end == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
editorState.service.selectionService
|
||||||
|
.updateSelection(selection.copyWith(end: end));
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEventResult _arrowKeysOnly(EditorState editorState, RawKeyEvent event) {
|
||||||
|
if (event.isMetaPressed ||
|
||||||
|
event.isShiftPressed ||
|
||||||
|
!_arrowKeys.contains(event.logicalKey)) {
|
||||||
|
assert(false);
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||||
|
final selection =
|
||||||
|
editorState.service.selectionService.currentSelection.value?.normalize;
|
||||||
|
if (nodes.isEmpty || selection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
final upPosition = _goUp(editorState);
|
||||||
|
editorState.updateCursorSelection(
|
||||||
|
upPosition == null ? null : Selection.collapsed(upPosition),
|
||||||
|
);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
final downPosition = _goDown(editorState);
|
||||||
|
editorState.updateCursorSelection(
|
||||||
|
downPosition == null ? null : Selection.collapsed(downPosition),
|
||||||
|
);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on Position {
|
extension on Position {
|
||||||
Position? goLeft(EditorState editorState) {
|
Position? goLeft(EditorState editorState) {
|
||||||
final node = editorState.document.nodeAtPath(path)!;
|
final node = editorState.document.nodeAtPath(path);
|
||||||
|
if (node == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
final prevNode = node.previous;
|
final previousEnd = node.previous?.selectable?.end();
|
||||||
if (prevNode != null) {
|
if (previousEnd != null) {
|
||||||
return Position(
|
return previousEnd;
|
||||||
path: prevNode.path, offset: _endOffsetOfNode(prevNode));
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node is TextNode) {
|
if (node is TextNode) {
|
||||||
return Position(path: path, offset: node.delta.prevRunePosition(offset));
|
return Position(path: path, offset: node.delta.prevRunePosition(offset));
|
||||||
} else {
|
} else {
|
||||||
@ -29,16 +228,18 @@ extension on Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Position? goRight(EditorState editorState) {
|
Position? goRight(EditorState editorState) {
|
||||||
final node = editorState.document.nodeAtPath(path)!;
|
final node = editorState.document.nodeAtPath(path);
|
||||||
final lengthOfNode = _endOffsetOfNode(node);
|
if (node == null) {
|
||||||
if (offset >= lengthOfNode) {
|
return null;
|
||||||
final nextNode = node.next;
|
}
|
||||||
if (nextNode != null) {
|
final end = node.selectable?.end();
|
||||||
return Position(path: nextNode.path, offset: 0);
|
if (end != null && offset >= end.offset) {
|
||||||
|
final nextStart = node.next?.selectable?.start();
|
||||||
|
if (nextStart != null) {
|
||||||
|
return nextStart;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node is TextNode) {
|
if (node is TextNode) {
|
||||||
return Position(path: path, offset: node.delta.nextRunePosition(offset));
|
return Position(path: path, offset: node.delta.nextRunePosition(offset));
|
||||||
} else {
|
} else {
|
||||||
@ -48,106 +249,43 @@ extension on Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Position? _goUp(EditorState editorState) {
|
Position? _goUp(EditorState editorState) {
|
||||||
|
final selection = editorState.service.selectionService.currentSelection.value;
|
||||||
final rects = editorState.service.selectionService.selectionRects;
|
final rects = editorState.service.selectionService.selectionRects;
|
||||||
if (rects.isEmpty) {
|
if (rects.isEmpty || selection == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final first = rects.first;
|
Offset offset;
|
||||||
final firstOffset = Offset(first.left, first.top);
|
if (selection.isBackward) {
|
||||||
final hitOffset = firstOffset - Offset(0, first.height * 0.5);
|
final rect = rects.reduce(
|
||||||
return editorState.service.selectionService.getPositionInOffset(hitOffset);
|
(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) {
|
Position? _goDown(EditorState editorState) {
|
||||||
|
final selection = editorState.service.selectionService.currentSelection.value;
|
||||||
final rects = editorState.service.selectionService.selectionRects;
|
final rects = editorState.service.selectionService.selectionRects;
|
||||||
if (rects.isEmpty) {
|
if (rects.isEmpty || selection == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final first = rects.last;
|
Offset offset;
|
||||||
final firstOffset = Offset(first.right, first.bottom);
|
if (selection.isBackward) {
|
||||||
final hitOffset = firstOffset + Offset(0, first.height * 0.5);
|
final rect = rects.reduce(
|
||||||
return editorState.service.selectionService.getPositionInOffset(hitOffset);
|
(current, next) => current.bottom >= next.bottom ? current : next,
|
||||||
|
);
|
||||||
|
offset = 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);
|
||||||
|
}
|
||||||
|
return editorState.service.selectionService.getPositionInOffset(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEventResult _handleShiftKey(EditorState editorState, RawKeyEvent event) {
|
|
||||||
final currentSelection = editorState.cursorSelection;
|
|
||||||
if (currentSelection == null) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
|
||||||
final leftPosition = currentSelection.end.goLeft(editorState);
|
|
||||||
editorState.updateCursorSelection(leftPosition == null
|
|
||||||
? null
|
|
||||||
: Selection(start: currentSelection.start, end: leftPosition));
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
|
||||||
final rightPosition = currentSelection.start.goRight(editorState);
|
|
||||||
editorState.updateCursorSelection(rightPosition == null
|
|
||||||
? null
|
|
||||||
: Selection(start: rightPosition, end: currentSelection.end));
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
|
||||||
final position = _goUp(editorState);
|
|
||||||
editorState.updateCursorSelection(position == null
|
|
||||||
? null
|
|
||||||
: Selection(start: position, end: currentSelection.end));
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
|
||||||
final position = _goDown(editorState);
|
|
||||||
editorState.updateCursorSelection(position == null
|
|
||||||
? null
|
|
||||||
: Selection(start: currentSelection.start, end: position));
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
|
|
||||||
if (event.isShiftPressed) {
|
|
||||||
return _handleShiftKey(editorState, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentSelection = editorState.cursorSelection;
|
|
||||||
if (currentSelection == null) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
|
||||||
if (currentSelection.isCollapsed) {
|
|
||||||
final leftPosition = currentSelection.start.goLeft(editorState);
|
|
||||||
if (leftPosition != null) {
|
|
||||||
editorState.updateCursorSelection(Selection.collapsed(leftPosition));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
editorState.updateCursorSelection(
|
|
||||||
currentSelection.collapse(atStart: currentSelection.isBackward),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
|
||||||
if (currentSelection.isCollapsed) {
|
|
||||||
final rightPosition = currentSelection.end.goRight(editorState);
|
|
||||||
if (rightPosition != null) {
|
|
||||||
editorState.updateCursorSelection(Selection.collapsed(rightPosition));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
editorState.updateCursorSelection(
|
|
||||||
currentSelection.collapse(atStart: !currentSelection.isBackward),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
|
||||||
final position = _goUp(editorState);
|
|
||||||
editorState.updateCursorSelection(
|
|
||||||
position == null ? null : Selection.collapsed(position));
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
|
||||||
final position = _goDown(editorState);
|
|
||||||
editorState.updateCursorSelection(
|
|
||||||
position == null ? null : Selection.collapsed(position));
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
};
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/infra/html_converter.dart';
|
import 'package:appflowy_editor/src/infra/html_converter.dart';
|
||||||
import 'package:appflowy_editor/src/document/node_iterator.dart';
|
import 'package:appflowy_editor/src/document/node_iterator.dart';
|
||||||
import 'package:appflowy_editor/src/infra/log.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:rich_clipboard/rich_clipboard.dart';
|
import 'package:rich_clipboard/rich_clipboard.dart';
|
||||||
|
|
||||||
_handleCopy(EditorState editorState) async {
|
_handleCopy(EditorState editorState) async {
|
||||||
final selection = editorState.cursorSelection?.normalize();
|
final selection = editorState.cursorSelection?.normalize;
|
||||||
if (selection == null || selection.isCollapsed) {
|
if (selection == null || selection.isCollapsed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -43,7 +42,7 @@ _handleCopy(EditorState editorState) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pasteHTML(EditorState editorState, String html) {
|
_pasteHTML(EditorState editorState, String html) {
|
||||||
final selection = editorState.cursorSelection?.normalize();
|
final selection = editorState.cursorSelection?.normalize;
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -191,7 +190,7 @@ Delta _lineContentToDelta(String lineContent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handlePastePlainText(EditorState editorState, String plainText) {
|
_handlePastePlainText(EditorState editorState, String plainText) {
|
||||||
final selection = editorState.cursorSelection?.normalize();
|
final selection = editorState.cursorSelection?.normalize;
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -256,7 +255,7 @@ _handleCut(EditorState editorState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_deleteSelectedContent(EditorState editorState) {
|
_deleteSelectedContent(EditorState editorState) {
|
||||||
final selection = editorState.cursorSelection?.normalize();
|
final selection = editorState.cursorSelection?.normalize;
|
||||||
if (selection == null || selection.isCollapsed) {
|
if (selection == null || selection.isCollapsed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
|
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
|
||||||
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart';
|
||||||
@ -13,6 +13,7 @@ import 'package:appflowy_editor/src/service/keyboard_service.dart';
|
|||||||
List<AppFlowyKeyEventHandler> defaultKeyEventHandlers = [
|
List<AppFlowyKeyEventHandler> defaultKeyEventHandlers = [
|
||||||
deleteTextHandler,
|
deleteTextHandler,
|
||||||
slashShortcutHandler,
|
slashShortcutHandler,
|
||||||
|
// arrowKeysHandler,
|
||||||
arrowKeysHandler,
|
arrowKeysHandler,
|
||||||
copyPasteKeysHandler,
|
copyPasteKeysHandler,
|
||||||
redoUndoKeysHandler,
|
redoUndoKeysHandler,
|
||||||
|
@ -0,0 +1,153 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
int _endOffsetOfNode(Node node) {
|
||||||
|
if (node is TextNode) {
|
||||||
|
return node.delta.length;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on Position {
|
||||||
|
Position? goLeft(EditorState editorState) {
|
||||||
|
final node = editorState.document.nodeAtPath(path)!;
|
||||||
|
if (offset == 0) {
|
||||||
|
final prevNode = node.previous;
|
||||||
|
if (prevNode != null) {
|
||||||
|
return Position(
|
||||||
|
path: prevNode.path, offset: _endOffsetOfNode(prevNode));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node is TextNode) {
|
||||||
|
return Position(path: path, offset: node.delta.prevRunePosition(offset));
|
||||||
|
} else {
|
||||||
|
return Position(path: path, offset: offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Position? goRight(EditorState editorState) {
|
||||||
|
final node = editorState.document.nodeAtPath(path)!;
|
||||||
|
final lengthOfNode = _endOffsetOfNode(node);
|
||||||
|
if (offset >= lengthOfNode) {
|
||||||
|
final nextNode = node.next;
|
||||||
|
if (nextNode != null) {
|
||||||
|
return Position(path: nextNode.path, offset: 0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node is TextNode) {
|
||||||
|
return Position(path: path, offset: node.delta.nextRunePosition(offset));
|
||||||
|
} else {
|
||||||
|
return Position(path: path, offset: offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Position? _goUp(EditorState editorState) {
|
||||||
|
final rects = editorState.service.selectionService.selectionRects;
|
||||||
|
if (rects.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final first = rects.first;
|
||||||
|
final firstOffset = Offset(first.left, first.top);
|
||||||
|
final hitOffset = firstOffset - Offset(0, first.height * 0.5);
|
||||||
|
return editorState.service.selectionService.getPositionInOffset(hitOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Position? _goDown(EditorState editorState) {
|
||||||
|
final rects = editorState.service.selectionService.selectionRects;
|
||||||
|
if (rects.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final first = rects.last;
|
||||||
|
final firstOffset = Offset(first.right, first.bottom);
|
||||||
|
final hitOffset = firstOffset + Offset(0, first.height * 0.5);
|
||||||
|
return editorState.service.selectionService.getPositionInOffset(hitOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEventResult _handleShiftKey(EditorState editorState, RawKeyEvent event) {
|
||||||
|
final currentSelection = editorState.cursorSelection;
|
||||||
|
if (currentSelection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
final leftPosition = currentSelection.end.goLeft(editorState);
|
||||||
|
editorState.updateCursorSelection(leftPosition == null
|
||||||
|
? null
|
||||||
|
: Selection(start: currentSelection.start, end: leftPosition));
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
|
final rightPosition = currentSelection.start.goRight(editorState);
|
||||||
|
editorState.updateCursorSelection(rightPosition == null
|
||||||
|
? null
|
||||||
|
: Selection(start: rightPosition, end: currentSelection.end));
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
final position = _goUp(editorState);
|
||||||
|
editorState.updateCursorSelection(position == null
|
||||||
|
? null
|
||||||
|
: Selection(start: position, end: currentSelection.end));
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
final position = _goDown(editorState);
|
||||||
|
editorState.updateCursorSelection(position == null
|
||||||
|
? null
|
||||||
|
: Selection(start: currentSelection.start, end: position));
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
|
||||||
|
if (event.isShiftPressed) {
|
||||||
|
return _handleShiftKey(editorState, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentSelection = editorState.cursorSelection;
|
||||||
|
if (currentSelection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
if (currentSelection.isCollapsed) {
|
||||||
|
final leftPosition = currentSelection.start.goLeft(editorState);
|
||||||
|
if (leftPosition != null) {
|
||||||
|
editorState.updateCursorSelection(Selection.collapsed(leftPosition));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editorState.updateCursorSelection(
|
||||||
|
currentSelection.collapse(atStart: currentSelection.isBackward),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
|
if (currentSelection.isCollapsed) {
|
||||||
|
final rightPosition = currentSelection.end.goRight(editorState);
|
||||||
|
if (rightPosition != null) {
|
||||||
|
editorState.updateCursorSelection(Selection.collapsed(rightPosition));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editorState.updateCursorSelection(
|
||||||
|
currentSelection.collapse(atStart: !currentSelection.isBackward),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
final position = _goUp(editorState);
|
||||||
|
editorState.updateCursorSelection(
|
||||||
|
position == null ? null : Selection.collapsed(position));
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
final position = _goDown(editorState);
|
||||||
|
editorState.updateCursorSelection(
|
||||||
|
position == null ? null : Selection.collapsed(position));
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
};
|
@ -25,10 +25,6 @@ AppFlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
|
|||||||
if (selection == null || context == null || selectable == null) {
|
if (selection == null || context == null || selectable == null) {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
final selectionRects = editorState.service.selectionService.selectionRects;
|
|
||||||
if (selectionRects.isEmpty) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
TransactionBuilder(editorState)
|
TransactionBuilder(editorState)
|
||||||
..replaceText(textNode, selection.start.offset,
|
..replaceText(textNode, selection.start.offset,
|
||||||
selection.end.offset - selection.start.offset, event.character ?? '')
|
selection.end.offset - selection.start.offset, event.character ?? '')
|
||||||
|
@ -348,10 +348,8 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||||||
|
|
||||||
final backwardNodes =
|
final backwardNodes =
|
||||||
selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
|
selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
|
||||||
final backwardSelection = selection.isBackward
|
final normalizedSelection = selection.normalize;
|
||||||
? selection
|
assert(normalizedSelection.isBackward);
|
||||||
: selection.copyWith(start: selection.end, end: selection.start);
|
|
||||||
assert(backwardSelection.isBackward);
|
|
||||||
|
|
||||||
for (var i = 0; i < backwardNodes.length; i++) {
|
for (var i = 0; i < backwardNodes.length; i++) {
|
||||||
final node = backwardNodes[i];
|
final node = backwardNodes[i];
|
||||||
@ -360,7 +358,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newSelection = backwardSelection.copy();
|
var newSelection = normalizedSelection.copy();
|
||||||
|
|
||||||
/// In the case of multiple selections,
|
/// In the case of multiple selections,
|
||||||
/// we need to return a new selection for each selected node individually.
|
/// we need to return a new selection for each selected node individually.
|
||||||
@ -370,7 +368,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||||||
/// text: ghijkl
|
/// text: ghijkl
|
||||||
/// text: mn>opqr
|
/// text: mn>opqr
|
||||||
///
|
///
|
||||||
if (!backwardSelection.isSingle) {
|
if (!normalizedSelection.isSingle) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
newSelection = newSelection.copyWith(end: selectable.end());
|
newSelection = newSelection.copyWith(end: selectable.end());
|
||||||
} else if (i == nodes.length - 1) {
|
} else if (i == nodes.length - 1) {
|
||||||
|
@ -103,6 +103,9 @@ extension on LogicalKeyboardKey {
|
|||||||
if (this == LogicalKeyboardKey.slash) {
|
if (this == LogicalKeyboardKey.slash) {
|
||||||
return PhysicalKeyboardKey.slash;
|
return PhysicalKeyboardKey.slash;
|
||||||
}
|
}
|
||||||
|
if (this == LogicalKeyboardKey.arrowUp) {
|
||||||
|
return PhysicalKeyboardKey.arrowUp;
|
||||||
|
}
|
||||||
if (this == LogicalKeyboardKey.arrowDown) {
|
if (this == LogicalKeyboardKey.arrowDown) {
|
||||||
return PhysicalKeyboardKey.arrowDown;
|
return PhysicalKeyboardKey.arrowDown;
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,219 @@ void main() async {
|
|||||||
(tester) async {
|
(tester) async {
|
||||||
await _testPressArrowKeyInNotCollapsedSelection(tester, false);
|
await _testPressArrowKeyInNotCollapsedSelection(tester, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses arrow left/right + shift in collapsed selection',
|
||||||
|
(tester) async {
|
||||||
|
const text = 'Welcome to Appflowy';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
const offset = 8;
|
||||||
|
final selection = Selection.single(path: [1], startOffset: offset);
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
for (var i = offset - 1; i >= 0; i--) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowLeft,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [1], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (var i = text.length; i >= 0; i--) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowLeft,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [0], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (var i = 1; i <= text.length; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowRight,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [0], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowRight,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [1], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Presses arrow left/right + shift in not collapsed and backward selection',
|
||||||
|
(tester) async {
|
||||||
|
const text = 'Welcome to Appflowy';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
const start = 8;
|
||||||
|
const end = 12;
|
||||||
|
final selection = Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: start,
|
||||||
|
endOffset: end,
|
||||||
|
);
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
for (var i = end + 1; i <= text.length; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowRight,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [0], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (var i = text.length - 1; i >= 0; i--) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowLeft,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [0], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Presses arrow left/right + command in not collapsed and forward selection',
|
||||||
|
(tester) async {
|
||||||
|
const text = 'Welcome to Appflowy';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
const start = 12;
|
||||||
|
const end = 8;
|
||||||
|
final selection = Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: start,
|
||||||
|
endOffset: end,
|
||||||
|
);
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
for (var i = end - 1; i >= 0; i--) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowLeft,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [0], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (var i = 1; i <= text.length; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowRight,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [0], offset: i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses arrow left/right/up/down + meta in collapsed selection',
|
||||||
|
(tester) async {
|
||||||
|
await _testPressArrowKeyWithMetaInSelection(tester, true, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Presses arrow left/right/up/down + meta in not collapsed and backward selection',
|
||||||
|
(tester) async {
|
||||||
|
await _testPressArrowKeyWithMetaInSelection(tester, false, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Presses arrow left/right/up/down + meta in not collapsed and forward selection',
|
||||||
|
(tester) async {
|
||||||
|
await _testPressArrowKeyWithMetaInSelection(tester, false, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses arrow up/down + shift in not collapsed selection',
|
||||||
|
(tester) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(null)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(null)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
final selection = Selection.single(path: [3], startOffset: 8);
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowUp,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [0], offset: 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowDown,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [6], offset: 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowUp,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
selection.copyWith(
|
||||||
|
end: Position(path: [3], offset: 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _testPressArrowKeyInNotCollapsedSelection(
|
Future<void> _testPressArrowKeyInNotCollapsedSelection(
|
||||||
@ -82,3 +295,72 @@ Future<void> _testPressArrowKeyInNotCollapsedSelection(
|
|||||||
await editor.pressLogicKey(LogicalKeyboardKey.arrowRight);
|
await editor.pressLogicKey(LogicalKeyboardKey.arrowRight);
|
||||||
expect(editor.documentSelection?.end, end);
|
expect(editor.documentSelection?.end, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _testPressArrowKeyWithMetaInSelection(
|
||||||
|
WidgetTester tester,
|
||||||
|
bool isSingle,
|
||||||
|
bool isBackward,
|
||||||
|
) async {
|
||||||
|
const text = 'Welcome to Appflowy';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
Selection selection;
|
||||||
|
if (isSingle) {
|
||||||
|
selection = Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: 8,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (isBackward) {
|
||||||
|
selection = Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: 8,
|
||||||
|
endOffset: text.length,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
selection = Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: text.length,
|
||||||
|
endOffset: 8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowLeft,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection.single(path: [0], startOffset: 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowRight,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection.single(path: [0], startOffset: text.length),
|
||||||
|
);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowUp,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection.single(path: [0], startOffset: 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.arrowDown,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection.single(path: [1], startOffset: text.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user