From 2006d35a50e0908c52a2edd1cb0165fc9dd4a5d5 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 4 Jan 2023 15:10:11 +0800 Subject: [PATCH 1/4] fix: the settings view of path configuration is not displayed completely --- .../settings/settings_location_cubit.dart | 18 +++++++++++++++--- .../settings_file_customize_location_view.dart | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart b/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart index f46850cd01..08a21f22e0 100644 --- a/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart +++ b/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart @@ -12,10 +12,22 @@ const String kSettingsLocationDefaultLocation = class SettingsLocation { SettingsLocation({ - this.path, - }); + String? path, + }) : _path = path; - String? path; + String? _path; + + set path(String? path) { + _path = path; + } + + String? get path { + if (Platform.isMacOS) { + // remove the prefix `/Volumes/Macintosh HD/Users/` + return _path?.replaceFirst('/Volumes/Macintosh HD/Users', ''); + } + return _path; + } SettingsLocation copyWith({String? path}) { return SettingsLocation( diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart index 99323f0f43..5df13b9ac5 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart @@ -40,6 +40,7 @@ class SettingsFileLocationCustomzierState title: FlowyText.regular( LocaleKeys.settings_files_defaultLocation.tr(), fontSize: 15.0, + overflow: TextOverflow.ellipsis, ), subtitle: Tooltip( message: LocaleKeys.settings_files_doubleTapToCopy.tr(), @@ -52,6 +53,7 @@ class SettingsFileLocationCustomzierState child: FlowyText.regular( state.path ?? '', fontSize: 10.0, + overflow: TextOverflow.ellipsis, ), ), ), From 35a72f701b9f3b342ed7a8df41ee4de3ae484901 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 4 Jan 2023 16:44:51 +0800 Subject: [PATCH 2/4] feat: #1624 add shortcut for Shift + Option + Left/Right Arrow --- .../settings/settings_location_cubit.dart | 4 +- .../render/rich_text/default_selectable.dart | 7 +- .../src/render/rich_text/flowy_rich_text.dart | 11 +- .../lib/src/render/selection/selectable.dart | 10 +- .../arrow_keys_handler.dart | 105 +++++++++++++++-- .../lib/src/service/selection_service.dart | 2 +- .../built_in_shortcut_events.dart | 10 ++ .../arrow_keys_handler_test.dart | 108 ++++++++++++++++++ 8 files changed, 238 insertions(+), 19 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart b/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart index 08a21f22e0..6ac179184d 100644 --- a/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart +++ b/frontend/app_flowy/lib/workspace/application/settings/settings_location_cubit.dart @@ -23,8 +23,8 @@ class SettingsLocation { String? get path { if (Platform.isMacOS) { - // remove the prefix `/Volumes/Macintosh HD/Users/` - return _path?.replaceFirst('/Volumes/Macintosh HD/Users', ''); + // remove the prefix `/Volumes/*` + return _path?.replaceFirst(RegExp(r'^/Volumes/[^/]+'), ''); } return _path; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart index 724a45b469..9a1ea224ef 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart @@ -35,8 +35,11 @@ mixin DefaultSelectable { Offset localToGlobal(Offset offset) => forward.localToGlobal(offset) - baseOffset; - Selection? getWorldBoundaryInOffset(Offset offset) => - forward.getWorldBoundaryInOffset(offset); + Selection? getWordBoundaryInOffset(Offset offset) => + forward.getWordBoundaryInOffset(offset); + + Selection? getWordBoundaryInPosition(Position position) => + forward.getWordBoundaryInPosition(position); Position start() => forward.start(); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart index 528f61e5e9..0ac4092eb4 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart @@ -112,7 +112,7 @@ class _FlowyRichTextState extends State with SelectableMixin { } @override - Selection? getWorldBoundaryInOffset(Offset offset) { + Selection? getWordBoundaryInOffset(Offset offset) { final localOffset = _renderParagraph.globalToLocal(offset); final textPosition = _renderParagraph.getPositionForOffset(localOffset); final textRange = _renderParagraph.getWordBoundary(textPosition); @@ -121,6 +121,15 @@ class _FlowyRichTextState extends State with SelectableMixin { return Selection(start: start, end: end); } + @override + Selection? getWordBoundaryInPosition(Position position) { + final textPosition = TextPosition(offset: position.offset); + final textRange = _renderParagraph.getWordBoundary(textPosition); + final start = Position(path: widget.textNode.path, offset: textRange.start); + final end = Position(path: widget.textNode.path, offset: textRange.end); + return Selection(start: start, end: end); + } + @override List getRectsInSelection(Selection selection) { assert(selection.isSingle && diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart index 523f60de47..c05d52940d 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart @@ -55,9 +55,13 @@ mixin SelectableMixin on State { /// /// Only the widget rendered by [TextNode] need to implement the detail, /// and the rest can return null. - Selection? getWorldBoundaryInOffset(Offset start) { - return null; - } + Selection? getWordBoundaryInOffset(Offset start) => null; + + /// For [TextNode] only. + /// + /// Only the widget rendered by [TextNode] need to implement the detail, + /// and the rest can return null. + Selection? getWordBoundaryInPosition(Position position) => null; bool get shouldCursorBlink => true; 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 94c857547b..417c4ff99c 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 @@ -289,8 +289,50 @@ ShortcutEventHandler cursorRight = (editorState, event) { return KeyEventResult.handled; }; +ShortcutEventHandler cursorLeftWordSelect = (editorState, event) { + final nodes = editorState.service.selectionService.currentSelectedNodes; + final selection = editorState.service.selectionService.currentSelection.value; + 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; +}; + +ShortcutEventHandler cursorRightWordSelect = (editorState, event) { + final nodes = editorState.service.selectionService.currentSelectedNodes; + final selection = editorState.service.selectionService.currentSelection.value; + 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; +}; + +enum _SelectionRange { + character, + word, +} + extension on Position { - Position? goLeft(EditorState editorState) { + Position? goLeft( + EditorState editorState, { + _SelectionRange selectionRange = _SelectionRange.character, + }) { final node = editorState.document.nodeAtPath(path); if (node == null) { return null; @@ -302,14 +344,38 @@ extension on Position { } return null; } - if (node is TextNode) { - return Position(path: path, offset: node.delta.prevRunePosition(offset)); - } else { - return Position(path: path, offset: offset); + switch (selectionRange) { + case _SelectionRange.character: + if (node is TextNode) { + return Position( + path: path, + offset: node.delta.prevRunePosition(offset), + ); + } else { + return Position(path: path, offset: offset); + } + case _SelectionRange.word: + if (node is TextNode) { + final result = node.selectable?.getWordBoundaryInPosition( + Position( + path: path, + offset: node.delta.prevRunePosition(offset), + ), + ); + if (result != null) { + return result.start; + } + } else { + return Position(path: path, offset: offset); + } } + return null; } - Position? goRight(EditorState editorState) { + Position? goRight( + EditorState editorState, { + _SelectionRange selectionRange = _SelectionRange.character, + }) { final node = editorState.document.nodeAtPath(path); if (node == null) { return null; @@ -322,11 +388,30 @@ extension on Position { } return null; } - if (node is TextNode) { - return Position(path: path, offset: node.delta.nextRunePosition(offset)); - } else { - return Position(path: path, offset: offset); + 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); + } + case _SelectionRange.word: + if (node is TextNode) { + final result = node.selectable?.getWordBoundaryInPosition( + Position( + path: path, + offset: node.delta.nextRunePosition(offset), + ), + ); + if (result != null) { + return result.end; + } + } else { + return Position(path: path, offset: offset); + } } + return null; } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart index 22de73c429..6e63de8747 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart @@ -298,7 +298,7 @@ class _AppFlowySelectionState extends State void _onDoubleTapDown(TapDownDetails details) { final offset = details.globalPosition; final node = getNodeInOffset(offset); - final selection = node?.selectable?.getWorldBoundaryInOffset(offset); + final selection = node?.selectable?.getWordBoundaryInOffset(offset); if (selection == null) { clearSelection(); return; 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 88cbec35e2..c960558b24 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 @@ -48,6 +48,16 @@ List builtInShortcutEvents = [ command: 'shift+arrow down', handler: cursorDownSelect, ), + ShortcutEvent( + key: 'Cursor down select', + command: 'shift+alt+arrow left', + handler: cursorLeftWordSelect, + ), + ShortcutEvent( + key: 'Cursor down select', + command: 'shift+alt+arrow right', + handler: cursorRightWordSelect, + ), 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 b9b163f489..5772f69e7e 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 @@ -341,6 +341,114 @@ void main() async { ), ); }); + + testWidgets('Presses shift + alt + arrow left to select a word', + (tester) async { + const text = 'Welcome to Appflowy 😁'; + final editor = tester.editor + ..insertTextNode(text) + ..insertTextNode(text); + await editor.startTesting(); + final selection = Selection.single(path: [1], startOffset: 10); + await editor.updateSelection(selection); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowLeft, + isShiftPressed: true, + isAltPressed: true, + ); + // + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [1], offset: 8), + ), + ); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowLeft, + isShiftPressed: true, + isAltPressed: true, + ); + // < to> + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [1], offset: 7), + ), + ); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowLeft, + isShiftPressed: true, + isAltPressed: true, + ); + // + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [1], offset: 0), + ), + ); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowLeft, + isShiftPressed: true, + isAltPressed: true, + ); + // <😁> + // + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [0], offset: 22), + ), + ); + }); + + testWidgets('Presses shift + alt + arrow left to select a word', + (tester) async { + const text = 'Welcome to Appflowy 😁'; + final editor = tester.editor + ..insertTextNode(text) + ..insertTextNode(text); + await editor.startTesting(); + final selection = Selection.single(path: [0], startOffset: 10); + await editor.updateSelection(selection); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowRight, + isShiftPressed: true, + isAltPressed: true, + ); + // < Appflowy> + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [0], offset: 19), + ), + ); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowRight, + isShiftPressed: true, + isAltPressed: true, + ); + // < Appflowy 😁> + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [0], offset: 22), + ), + ); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowRight, + isShiftPressed: true, + isAltPressed: true, + ); + // < Appflowy 😁> + // <> + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [1], offset: 0), + ), + ); + }); } Future _testPressArrowKeyInNotCollapsedSelection( From d7410cd6e89c0b748a7b98b569b2f119b45acc31 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 4 Jan 2023 20:10:41 +0800 Subject: [PATCH 3/4] feat: #1624 add shortcut for Shift + Option + Left/Right Arrow --- .../arrow_keys_handler.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 417c4ff99c..c296c773bb 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 @@ -392,18 +392,15 @@ extension on Position { case _SelectionRange.character: if (node is TextNode) { return Position( - path: path, offset: node.delta.nextRunePosition(offset)); + path: path, + offset: node.delta.nextRunePosition(offset), + ); } else { return Position(path: path, offset: offset); } case _SelectionRange.word: if (node is TextNode) { - final result = node.selectable?.getWordBoundaryInPosition( - Position( - path: path, - offset: node.delta.nextRunePosition(offset), - ), - ); + final result = node.selectable?.getWordBoundaryInPosition(this); if (result != null) { return result.end; } From e08ab1fc10b634606d5ca52be6c751223666f46d Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 5 Jan 2023 09:15:18 +0800 Subject: [PATCH 4/4] fix: Presses shift + alt + arrow right to select a word --- .../arrow_keys_handler_test.dart | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 5772f69e7e..fea6209713 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 @@ -402,7 +402,7 @@ void main() async { ); }); - testWidgets('Presses shift + alt + arrow left to select a word', + testWidgets('Presses shift + alt + arrow right to select a word', (tester) async { const text = 'Welcome to Appflowy 😁'; final editor = tester.editor @@ -416,6 +416,18 @@ void main() async { isShiftPressed: true, isAltPressed: true, ); + // < > + expect( + editor.documentSelection, + selection.copyWith( + end: Position(path: [0], offset: 11), + ), + ); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowRight, + isShiftPressed: true, + isAltPressed: true, + ); // < Appflowy> expect( editor.documentSelection, @@ -428,6 +440,11 @@ void main() async { isShiftPressed: true, isAltPressed: true, ); + await editor.pressLogicKey( + LogicalKeyboardKey.arrowRight, + isShiftPressed: true, + isAltPressed: true, + ); // < Appflowy 😁> expect( editor.documentSelection,