Merge pull request #788 from LucasXu0/fix/ime_error

fix: could not delete character when using IME
This commit is contained in:
Nathan.fooo 2022-08-08 20:08:12 +08:00 committed by GitHub
commit ef8d154736
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 41 deletions

View File

@ -43,7 +43,7 @@ class NodeIterator implements Iterator<Node> {
if (nextOfParent == null) {
_currentNode = null;
} else {
_currentNode = _findLeadingChild(node);
_currentNode = _findLeadingChild(nextOfParent);
}
}

View File

@ -110,11 +110,17 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
.map(
(child) => widget.editorState.service.renderPluginService
.buildPluginWidget(
NodeWidgetContext(
context: context,
node: child,
editorState: widget.editorState,
),
child is TextNode
? NodeWidgetContext<TextNode>(
context: context,
node: child,
editorState: widget.editorState,
)
: NodeWidgetContext<Node>(
context: context,
node: child,
editorState: widget.editorState,
),
),
)
.toList(),

View File

@ -35,7 +35,7 @@ class FlowyRichText extends StatefulWidget {
this.cursorHeight,
this.cursorWidth = 2.0,
this.textSpanDecorator,
this.placeholderText = ' ',
this.placeholderText = 'Type \'/\' for commands',
this.placeholderTextSpanDecorator,
required this.textNode,
required this.editorState,

View File

@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/document/path.dart';
import 'package:flowy_editor/document/position.dart';
import 'package:flowy_editor/document/selection.dart';
import 'package:flowy_editor/editor_state.dart';
import 'package:flowy_editor/extensions/node_extensions.dart';
@ -83,22 +81,29 @@ class _FlowyInputState extends State<FlowyInput>
void apply(List<TextEditingDelta> deltas) {
// TODO: implement the detail
for (final delta in deltas) {
if (delta is TextEditingDeltaInsertion) {
if (_composingTextRange != null) {
_composingTextRange = TextRange(
start: _composingTextRange!.start,
end: delta.composing.end,
);
} else {
_composingTextRange = delta.composing;
}
_updateComposing(delta);
if (delta is TextEditingDeltaInsertion) {
_applyInsert(delta);
} else if (delta is TextEditingDeltaDeletion) {
_applyDelete(delta);
} else if (delta is TextEditingDeltaReplacement) {
_applyReplacement(delta);
} else if (delta is TextEditingDeltaNonTextUpdate) {
_composingTextRange = null;
} else if (delta is TextEditingDeltaNonTextUpdate) {}
}
}
void _updateComposing(TextEditingDelta delta) {
if (delta is! TextEditingDeltaNonTextUpdate) {
if (_composingTextRange != null &&
delta.composing.end != -1 &&
_composingTextRange!.start != -1) {
_composingTextRange = TextRange(
start: _composingTextRange!.start,
end: delta.composing.end,
);
} else {
_composingTextRange = delta.composing;
}
}
}
@ -123,6 +128,23 @@ class _FlowyInputState extends State<FlowyInput>
}
}
void _applyDelete(TextEditingDeltaDeletion delta) {
final selectionService = _editorState.service.selectionService;
final currentSelection = selectionService.currentSelection.value;
if (currentSelection == null) {
return;
}
if (currentSelection.isSingle) {
final textNode = selectionService.currentSelectedNodes.first as TextNode;
final length = delta.deletedRange.end - delta.deletedRange.start;
TransactionBuilder(_editorState)
..deleteText(textNode, delta.deletedRange.start, length)
..commit();
} else {
// TODO: implement
}
}
void _applyReplacement(TextEditingDeltaReplacement delta) {
final selectionService = _editorState.service.selectionService;
final currentSelection = selectionService.currentSelection.value;

View File

@ -109,13 +109,19 @@ void showPopupList(
.removeListener(clearPopupList);
editorState.service.selectionService.currentSelection
.addListener(clearPopupList);
editorState.service.scrollService?.disable();
}
void clearPopupList() {
if (_popupListOverlay == null || _editorState == null) {
return;
}
_popupListOverlay?.remove();
_popupListOverlay = null;
_editorState?.service.keyboardService?.enable();
_editorState?.service.scrollService?.enable();
_editorState = null;
}
@ -214,14 +220,17 @@ class _PopupListWidgetState extends State<PopupListWidget> {
if (event.logicalKey == LogicalKeyboardKey.enter) {
if (0 <= selectedIndex && selectedIndex < widget.items.length) {
_deleteSlash();
widget.items[selectedIndex].handler(widget.editorState);
return KeyEventResult.handled;
}
}
if (event.logicalKey == LogicalKeyboardKey.escape) {
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
clearPopupList();
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
clearPopupList();
_deleteSlash();
return KeyEventResult.handled;
}
var newSelectedIndex = selectedIndex;
@ -242,6 +251,22 @@ class _PopupListWidgetState extends State<PopupListWidget> {
}
return KeyEventResult.ignored;
}
void _deleteSlash() {
final selection =
widget.editorState.service.selectionService.currentSelection.value;
final nodes =
widget.editorState.service.selectionService.currentSelectedNodes;
if (selection != null && nodes.length == 1) {
TransactionBuilder(widget.editorState)
..deleteText(
nodes.first as TextNode,
selection.start.offset - 1,
1,
)
..commit();
}
}
}
class _PopupListItemWidget extends StatelessWidget {

View File

@ -6,7 +6,8 @@ mixin FlowyScrollService<T extends StatefulWidget> on State<T> {
void scrollTo(double dy);
RenderObject? scrollRenderObject();
void enable();
void disable();
}
class FlowyScroll extends StatefulWidget {
@ -25,6 +26,8 @@ class _FlowyScrollState extends State<FlowyScroll> with FlowyScrollService {
final _scrollController = ScrollController();
final _scrollViewKey = GlobalKey();
bool _scrollEnabled = true;
@override
double get dy => _scrollController.position.pixels;
@ -51,15 +54,22 @@ class _FlowyScrollState extends State<FlowyScroll> with FlowyScrollService {
);
}
@override
void disable() {
_scrollEnabled = false;
debugPrint('[scroll] $_scrollEnabled');
}
@override
void enable() {
_scrollEnabled = true;
debugPrint('[scroll] $_scrollEnabled');
}
void _onPointerSignal(PointerSignalEvent event) {
if (event is PointerScrollEvent) {
if (event is PointerScrollEvent && _scrollEnabled) {
final dy = (_scrollController.position.pixels + event.scrollDelta.dy);
scrollTo(dy);
}
}
@override
RenderObject? scrollRenderObject() {
return _scrollViewKey.currentContext?.findRenderObject();
}
}

View File

@ -287,6 +287,7 @@ class _FlowySelectionState extends State<FlowySelection>
editorState.updateCursorSelection(selection);
editorState.service.keyboardService?.enable();
editorState.service.scrollService?.enable();
}
@override
@ -333,13 +334,9 @@ class _FlowySelectionState extends State<FlowySelection>
panStartOffsetWithScrollDyGap.translate(0, panStartScrollDy! - dy);
}
final sortedNodes =
editorState.document.root.children.toList(growable: false);
final first = _lowerBound(
sortedNodes, panStartOffsetWithScrollDyGap, 0, sortedNodes.length)
.selectable;
final last = _upperBound(sortedNodes, panEndOffset!, 0, sortedNodes.length)
.selectable;
final first =
_lowerBoundInDocument(panStartOffsetWithScrollDyGap).selectable;
final last = _upperBoundInDocument(panEndOffset!).selectable;
// compute the selection in range.
if (first != null && last != null) {
@ -538,19 +535,20 @@ class _FlowySelectionState extends State<FlowySelection>
Node _lowerBoundInDocument(Offset offset) {
final sortedNodes =
editorState.document.root.children.toList(growable: false);
return _lowerBound(sortedNodes, offset, 0, sortedNodes.length);
return _lowerBound(sortedNodes, offset, 0, sortedNodes.length - 1);
}
Node _upperBoundInDocument(Offset offset) {
final sortedNodes =
editorState.document.root.children.toList(growable: false);
return _upperBound(sortedNodes, offset, 0, sortedNodes.length);
return _upperBound(sortedNodes, offset, 0, sortedNodes.length - 1);
}
/// TODO: Supports multi-level nesting,
/// currently only single-level nesting is supported
// find the first node's rect.bottom <= offset.dy
Node _lowerBound(List<Node> sortedNodes, Offset offset, int start, int end) {
assert(start >= 0 && end < sortedNodes.length);
var min = start;
var max = end;
while (min <= max) {
@ -561,7 +559,12 @@ class _FlowySelectionState extends State<FlowySelection>
max = mid - 1;
}
}
return sortedNodes[min];
final node = sortedNodes[min];
if (node.children.isNotEmpty && node.children.first.rect.top <= offset.dy) {
final children = node.children.toList(growable: false);
return _lowerBound(children, offset, 0, children.length - 1);
}
return node;
}
/// TODO: Supports multi-level nesting,
@ -573,6 +576,7 @@ class _FlowySelectionState extends State<FlowySelection>
int start,
int end,
) {
assert(start >= 0 && end < sortedNodes.length);
var min = start;
var max = end;
while (min <= max) {
@ -583,7 +587,12 @@ class _FlowySelectionState extends State<FlowySelection>
max = mid - 1;
}
}
return sortedNodes[max];
final node = sortedNodes[max];
if (node.children.isNotEmpty && node.children.first.rect.top <= offset.dy) {
final children = node.children.toList(growable: false);
return _lowerBound(children, offset, 0, children.length - 1);
}
return node;
}
}