Skip to content

Commit

Permalink
fix(macos): Implement actions for ExpandSelectionToDocumentBoundaryIn…
Browse files Browse the repository at this point in the history
…tent and ExpandSelectionToLineBreakIntent to use keyboard shortcuts, unrelated cleanup to the bug fix. (#2279)
  • Loading branch information
EchoEllet authored Oct 23, 2024
1 parent 0ac3188 commit e7d3391
Show file tree
Hide file tree
Showing 6 changed files with 537 additions and 422 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';

import '../../../common/utils/platform.dart';
import '../../../document/attribute.dart';
import 'editor_keyboard_shortcut_actions.dart';

final _isDesktopMacOS = isMacOS;

@internal
Map<SingleActivator, Intent> defaultSinlgeActivatorIntents() {
return {
const SingleActivator(
LogicalKeyboardKey.escape,
): const HideSelectionToolbarIntent(),
SingleActivator(
LogicalKeyboardKey.keyZ,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const UndoTextIntent(SelectionChangedCause.keyboard),
SingleActivator(
LogicalKeyboardKey.keyY,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const RedoTextIntent(SelectionChangedCause.keyboard),

// Selection formatting.
SingleActivator(
LogicalKeyboardKey.keyB,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ToggleTextStyleIntent(Attribute.bold),
SingleActivator(
LogicalKeyboardKey.keyU,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ToggleTextStyleIntent(Attribute.underline),
SingleActivator(
LogicalKeyboardKey.keyI,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ToggleTextStyleIntent(Attribute.italic),
SingleActivator(
LogicalKeyboardKey.keyS,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
shift: true,
): const ToggleTextStyleIntent(Attribute.strikeThrough),
SingleActivator(
LogicalKeyboardKey.backquote,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ToggleTextStyleIntent(Attribute.inlineCode),
SingleActivator(
LogicalKeyboardKey.tilde,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
shift: true,
): const ToggleTextStyleIntent(Attribute.codeBlock),
SingleActivator(
LogicalKeyboardKey.keyB,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
shift: true,
): const ToggleTextStyleIntent(Attribute.blockQuote),
SingleActivator(
LogicalKeyboardKey.keyK,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyLinkIntent(),

// Lists
SingleActivator(
LogicalKeyboardKey.keyL,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
shift: true,
): const ToggleTextStyleIntent(Attribute.ul),
SingleActivator(
LogicalKeyboardKey.keyO,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
shift: true,
): const ToggleTextStyleIntent(Attribute.ol),
SingleActivator(
LogicalKeyboardKey.keyC,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
shift: true,
): const QuillEditorApplyCheckListIntent(),

// Indents
SingleActivator(
LogicalKeyboardKey.keyM,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const IndentSelectionIntent(true),
SingleActivator(
LogicalKeyboardKey.keyM,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
shift: true,
): const IndentSelectionIntent(false),

// Headers
SingleActivator(
LogicalKeyboardKey.digit1,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyHeaderIntent(Attribute.h1),
SingleActivator(
LogicalKeyboardKey.digit2,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyHeaderIntent(Attribute.h2),
SingleActivator(
LogicalKeyboardKey.digit3,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyHeaderIntent(Attribute.h3),
SingleActivator(
LogicalKeyboardKey.digit4,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyHeaderIntent(Attribute.h4),
SingleActivator(
LogicalKeyboardKey.digit5,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyHeaderIntent(Attribute.h5),
SingleActivator(
LogicalKeyboardKey.digit6,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyHeaderIntent(Attribute.h6),
SingleActivator(
LogicalKeyboardKey.digit0,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorApplyHeaderIntent(Attribute.header),

SingleActivator(
LogicalKeyboardKey.keyG,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const QuillEditorInsertEmbedIntent(Attribute.image),

SingleActivator(
LogicalKeyboardKey.keyF,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const OpenSearchIntent(),

// Arrow key scrolling
SingleActivator(
LogicalKeyboardKey.arrowUp,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ScrollIntent(direction: AxisDirection.up),
SingleActivator(
LogicalKeyboardKey.arrowDown,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ScrollIntent(direction: AxisDirection.down),
SingleActivator(
LogicalKeyboardKey.pageUp,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ScrollIntent(
direction: AxisDirection.up, type: ScrollIncrementType.page),
SingleActivator(
LogicalKeyboardKey.pageDown,
control: !_isDesktopMacOS,
meta: _isDesktopMacOS,
): const ScrollIntent(
direction: AxisDirection.down, type: ScrollIncrementType.page),
};
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import 'package:flutter/material.dart';

import '../../../translations.dart';
import '../../document/attribute.dart';
import '../../document/style.dart';
import '../../toolbar/buttons/link_style2_button.dart';
import '../../toolbar/buttons/search/search_dialog.dart';
import '../editor.dart';
import '../widgets/link.dart';
import 'raw_editor_state.dart';
import 'raw_editor_text_boundaries.dart';
import '../../../../translations.dart';
import '../../../document/attribute.dart';
import '../../../document/style.dart';
import '../../../toolbar/buttons/link_style2_button.dart';
import '../../../toolbar/buttons/search/search_dialog.dart';
import '../../editor.dart';
import '../../widgets/link.dart';
import '../raw_editor_state.dart';
import '../raw_editor_text_boundaries.dart';

// ------------------------------- Text Actions -------------------------------
class QuillEditorDeleteTextAction<T extends DirectionalTextEditingIntent>
Expand Down Expand Up @@ -268,6 +268,98 @@ class QuillEditorExtendSelectionOrCaretPositionAction extends ContextAction<
state.textEditingValue.selection.isValid;
}

/// Expands the selection to the start/end of the document.
///
/// This matches macOS behavior and differs from [ExpandSelectionToLineBreakIntent].
///
/// See: [ExpandSelectionToDocumentBoundaryIntent].
class ExpandSelectionToDocumentBoundaryAction
extends ContextAction<ExpandSelectionToDocumentBoundaryIntent> {
ExpandSelectionToDocumentBoundaryAction(this.state);

final QuillRawEditorState state;

@override
Object? invoke(ExpandSelectionToDocumentBoundaryIntent intent,
[BuildContext? context]) {
final currentSelection = state.controller.selection;
final documentLength = state.controller.document.length;

final newSelection = intent.forward
? currentSelection.copyWith(
extentOffset: documentLength,
)
: currentSelection.copyWith(
extentOffset: 0,
);
return Actions.invoke(
context ?? (throw StateError('BuildContext should not be null.')),
UpdateSelectionIntent(
state.textEditingValue,
newSelection,
SelectionChangedCause.keyboard,
),
);
}
}

/// Extends the selection to the next/previous line break (`\n`).
///
/// This behavior is standard on macOS.
///
/// See: [ExpandSelectionToLineBreakIntent]
class ExpandSelectionToLineBreakAction
extends ContextAction<ExpandSelectionToLineBreakIntent> {
ExpandSelectionToLineBreakAction(this.state);

final QuillRawEditorState state;
@override
Object? invoke(ExpandSelectionToLineBreakIntent intent,
[BuildContext? context]) {
// Plain text of the document (needed to find line breaks)
final text = state.controller.plainTextEditingValue.text;

final currentSelection = state.controller.selection;

// Calculate the next or previous line break based on direction
final searchStartOffset = currentSelection.extentOffset;

final targetLineBreak = () {
if (intent.forward) {
final nextLineBreak = text.indexOf('\n', searchStartOffset);
final noNextLineBreak = nextLineBreak == -1;
return noNextLineBreak ? text.length : nextLineBreak + 1;
}

// Backward

// Ensure (searchStartOffset - 1) is not negative to avoid [RangeError]
final safePreviousSearchOffset =
(searchStartOffset > 0) ? (searchStartOffset - 1) : 0;

final previousLineBreak =
text.lastIndexOf('\n', safePreviousSearchOffset);

final noPreviousLineBreak = previousLineBreak == -1;
return noPreviousLineBreak ? 0 : previousLineBreak;
}();

// Create a new selection, extending it to the line break was found
final newSelection = currentSelection.copyWith(
extentOffset: targetLineBreak,
);

return Actions.invoke(
context ?? (throw StateError('BuildContext should not be null.')),
UpdateSelectionIntent(
state.textEditingValue,
newSelection,
SelectionChangedCause.keyboard,
),
);
}
}

class QuillEditorUpdateTextSelectionToAdjacentLineAction<
T extends DirectionalCaretMovementIntent> extends ContextAction<T> {
QuillEditorUpdateTextSelectionToAdjacentLineAction(this.state);
Expand Down Expand Up @@ -627,6 +719,7 @@ class QuillEditorInsertEmbedIntent extends Intent {
final Attribute type;
}

/// Navigate to the start or end of the document
class NavigateToDocumentBoundaryAction
extends ContextAction<ScrollToDocumentBoundaryIntent> {
NavigateToDocumentBoundaryAction(this.state);
Expand Down
Loading

0 comments on commit e7d3391

Please sign in to comment.