Skip to content

Commit 6c9698e

Browse files
authored
Add typing shortcuts (singerdmx#994)
Start lists with "1." or "-". Indent / Dedent lists with <Tab> and <Shift+Tab>.
1 parent d15174e commit 6c9698e

File tree

4 files changed

+145
-41
lines changed

4 files changed

+145
-41
lines changed

lib/src/utils/cast.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
T? castOrNull<T>(dynamic x) => x is T ? x : null;

lib/src/widgets/controller.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,26 @@ class QuillController extends ChangeNotifier {
102102
.mergeAll(toggledStyle);
103103
}
104104

105+
// Increases or decreases the indent of the current selection by 1.
106+
void indentSelection(bool isIncrease) {
107+
final indent = getSelectionStyle().attributes[Attribute.indent.key];
108+
if (indent == null) {
109+
if (isIncrease) {
110+
formatSelection(Attribute.indentL1);
111+
}
112+
return;
113+
}
114+
if (indent.value == 1 && !isIncrease) {
115+
formatSelection(Attribute.clone(Attribute.indentL1, null));
116+
return;
117+
}
118+
if (isIncrease) {
119+
formatSelection(Attribute.getIndentLevel(indent.value + 1));
120+
return;
121+
}
122+
formatSelection(Attribute.getIndentLevel(indent.value - 1));
123+
}
124+
105125
/// Returns all styles for each node within selection
106126
List<Tuple2<int, Style>> getAllIndividualSelectionStyles() {
107127
final styles = document.collectAllIndividualStyles(

lib/src/widgets/raw_editor.dart

Lines changed: 123 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import '../models/documents/nodes/block.dart';
1919
import '../models/documents/nodes/embeddable.dart';
2020
import '../models/documents/nodes/line.dart';
2121
import '../models/documents/nodes/node.dart';
22+
import '../models/documents/nodes/leaf.dart' as leaf;
2223
import '../models/documents/style.dart';
24+
import '../utils/cast.dart';
2325
import '../utils/delta.dart';
2426
import '../utils/embeds.dart';
2527
import '../utils/platform.dart';
@@ -428,6 +430,7 @@ class RawEditorState extends EditorState
428430
actions: _actions,
429431
child: Focus(
430432
focusNode: widget.focusNode,
433+
onKey: _onKey,
431434
child: QuillKeyboardListener(
432435
child: Container(
433436
constraints: constraints,
@@ -440,6 +443,125 @@ class RawEditorState extends EditorState
440443
);
441444
}
442445

446+
KeyEventResult _onKey(node, RawKeyEvent event) {
447+
// Don't handle key if there is a meta key pressed.
448+
if (event.isAltPressed || event.isControlPressed || event.isMetaPressed) {
449+
return KeyEventResult.ignored;
450+
}
451+
452+
if (event is! RawKeyDownEvent) {
453+
return KeyEventResult.ignored;
454+
}
455+
456+
// Don't handle key if there is an active selection.
457+
if (controller.selection.baseOffset != controller.selection.extentOffset) {
458+
return KeyEventResult.ignored;
459+
}
460+
461+
// Handle indenting blocks when pressing the tab key.
462+
if (event.logicalKey == LogicalKeyboardKey.tab) {
463+
return _handleTabKey(event);
464+
}
465+
466+
// Handle inserting lists when space is pressed following
467+
// a list initiating phrase.
468+
if (event.logicalKey == LogicalKeyboardKey.space) {
469+
return _handleSpaceKey(event);
470+
}
471+
472+
return KeyEventResult.ignored;
473+
}
474+
475+
KeyEventResult _handleSpaceKey(RawKeyEvent event) {
476+
final child =
477+
controller.document.queryChild(controller.selection.baseOffset);
478+
if (child.node == null) {
479+
return KeyEventResult.ignored;
480+
}
481+
482+
final line = child.node as Line?;
483+
if (line == null) {
484+
return KeyEventResult.ignored;
485+
}
486+
487+
final text = castOrNull<leaf.Text>(line.first);
488+
if (text == null) {
489+
return KeyEventResult.ignored;
490+
}
491+
492+
const olKeyPhrase = '1.';
493+
const ulKeyPhrase = '-';
494+
495+
if (text.value == olKeyPhrase) {
496+
_updateSelectionForKeyPhrase(olKeyPhrase, Attribute.ol);
497+
} else if (text.value == ulKeyPhrase) {
498+
_updateSelectionForKeyPhrase(ulKeyPhrase, Attribute.ul);
499+
} else {
500+
return KeyEventResult.ignored;
501+
}
502+
503+
return KeyEventResult.handled;
504+
}
505+
506+
KeyEventResult _handleTabKey(RawKeyEvent event) {
507+
final child =
508+
controller.document.queryChild(controller.selection.baseOffset);
509+
510+
KeyEventResult insertTabCharacter() {
511+
controller.replaceText(controller.selection.baseOffset, 0, '\t', null);
512+
_moveCursor(1);
513+
return KeyEventResult.handled;
514+
}
515+
516+
if (child.node == null) {
517+
return insertTabCharacter();
518+
}
519+
520+
final node = child.node!;
521+
522+
final parent = node.parent;
523+
if (parent == null || parent is! Block) {
524+
return insertTabCharacter();
525+
}
526+
527+
if (node is! Line || (node.isNotEmpty && node.first is! leaf.Text)) {
528+
return insertTabCharacter();
529+
}
530+
531+
if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty) {
532+
return insertTabCharacter();
533+
}
534+
535+
final parentBlock = parent;
536+
if (parentBlock.style.containsKey(Attribute.ol.key) ||
537+
parentBlock.style.containsKey(Attribute.ul.key) ||
538+
parentBlock.style.containsKey(Attribute.checked.key)) {
539+
controller.indentSelection(!event.isShiftPressed);
540+
return KeyEventResult.handled;
541+
}
542+
543+
return insertTabCharacter();
544+
}
545+
546+
void _moveCursor(int chars) {
547+
final selection = controller.selection;
548+
controller.updateSelection(
549+
controller.selection.copyWith(
550+
baseOffset: selection.baseOffset + chars,
551+
extentOffset: selection.baseOffset + chars),
552+
ChangeSource.LOCAL);
553+
}
554+
555+
void _updateSelectionForKeyPhrase(String phrase, Attribute attribute) {
556+
controller
557+
..formatSelection(attribute)
558+
..replaceText(controller.selection.baseOffset - phrase.length,
559+
phrase.length, '', null);
560+
561+
// It is unclear why the selection moves forward the edit distance.
562+
_moveCursor(-2);
563+
}
564+
443565
void _handleSelectionChanged(
444566
TextSelection selection, SelectionChangedCause cause) {
445567
final oldSelection = controller.selection;
@@ -2076,26 +2198,7 @@ class _IndentSelectionAction extends Action<IndentSelectionIntent> {
20762198

20772199
@override
20782200
void invoke(IndentSelectionIntent intent, [BuildContext? context]) {
2079-
final indent =
2080-
state.controller.getSelectionStyle().attributes[Attribute.indent.key];
2081-
if (indent == null) {
2082-
if (intent.isIncrease) {
2083-
state.controller.formatSelection(Attribute.indentL1);
2084-
}
2085-
return;
2086-
}
2087-
if (indent.value == 1 && !intent.isIncrease) {
2088-
state.controller
2089-
.formatSelection(Attribute.clone(Attribute.indentL1, null));
2090-
return;
2091-
}
2092-
if (intent.isIncrease) {
2093-
state.controller
2094-
.formatSelection(Attribute.getIndentLevel(indent.value + 1));
2095-
return;
2096-
}
2097-
state.controller
2098-
.formatSelection(Attribute.getIndentLevel(indent.value - 1));
2201+
state.controller.indentSelection(intent.isIncrease);
20992202
}
21002203

21012204
@override

lib/src/widgets/toolbar/indent_button.dart

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,27 +42,7 @@ class _IndentButtonState extends State<IndentButton> {
4242
fillColor: iconFillColor,
4343
borderRadius: widget.iconTheme?.borderRadius ?? 2,
4444
onPressed: () {
45-
final indent = widget.controller
46-
.getSelectionStyle()
47-
.attributes[Attribute.indent.key];
48-
if (indent == null) {
49-
if (widget.isIncrease) {
50-
widget.controller.formatSelection(Attribute.indentL1);
51-
}
52-
return;
53-
}
54-
if (indent.value == 1 && !widget.isIncrease) {
55-
widget.controller
56-
.formatSelection(Attribute.clone(Attribute.indentL1, null));
57-
return;
58-
}
59-
if (widget.isIncrease) {
60-
widget.controller
61-
.formatSelection(Attribute.getIndentLevel(indent.value + 1));
62-
return;
63-
}
64-
widget.controller
65-
.formatSelection(Attribute.getIndentLevel(indent.value - 1));
45+
widget.controller.indentSelection(widget.isIncrease);
6646
},
6747
afterPressed: widget.afterButtonPressed,
6848
);

0 commit comments

Comments
 (0)