Skip to content

Commit

Permalink
[SuperEditor][iOS] Fix exception when moving floating cursor between …
Browse files Browse the repository at this point in the history
…paragraphs (Resolves #1449) (#1471)
  • Loading branch information
angelosilvestre authored and matthew-carroll committed Sep 29, 2023
1 parent 6e40da6 commit 3f813c8
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class DocumentImeInputClient extends TextInputConnectionDecorator with TextInput
/// For the list of selectors, see [MacOsSelectors].
final void Function(String selectorName) onPerformSelector;

/// Whether the floating cursor is being displayed.
///
/// This value is updated on [updateFloatingCursor].
bool _isFloatingCursorVisible = false;

void _onContentChange() {
if (!attached) {
return;
Expand Down Expand Up @@ -187,6 +192,18 @@ class DocumentImeInputClient extends TextInputConnectionDecorator with TextInput
return;
}

if (_isFloatingCursorVisible && textEditingDeltas.every((e) => e is TextEditingDeltaNonTextUpdate)) {
// On iOS, dragging the floating cursor generates non-text deltas to update the selection.
//
// When dragging the floating cursor between paragraphs, we receive a non-text delta for the previously
// selected paragraph when our selection already changed to another paragraph. If the previously selected
// paragraph is bigger than the newly selected paragraph, a mapping error occurs, because we try
// to select an offset bigger than the paragraph's length.
//
// As we already change the selection when the floating cursor moves, we ignore these deltas.
return;
}

editorImeLog.fine("Received edit deltas from platform: ${textEditingDeltas.length} deltas");
for (final delta in textEditingDeltas) {
editorImeLog.fine("$delta");
Expand Down Expand Up @@ -277,10 +294,14 @@ class DocumentImeInputClient extends TextInputConnectionDecorator with TextInput
void updateFloatingCursor(RawFloatingCursorPoint point) {
switch (point.state) {
case FloatingCursorDragState.Start:
_isFloatingCursorVisible = true;
_floatingCursorController?.offset = point.offset;
break;
case FloatingCursorDragState.Update:
_floatingCursorController?.offset = point.offset;
break;
case FloatingCursorDragState.End:
_isFloatingCursorVisible = false;
_floatingCursorController?.offset = null;
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_robots/flutter_test_robots.dart';
import 'package:flutter_test_runners/flutter_test_runners.dart';
import 'package:super_editor/src/core/document.dart';
import 'package:super_editor/src/core/document_selection.dart';
import 'package:super_editor/src/default_editor/text.dart';
import 'package:super_editor/src/infrastructure/blinking_caret.dart';
import 'package:super_editor/src/test/ime.dart';
import 'package:super_editor/src/test/super_editor_test/supereditor_inspector.dart';
import 'package:super_editor/src/test/super_editor_test/supereditor_robot.dart';

Expand Down Expand Up @@ -175,6 +178,50 @@ void main() {
),
);
});

testWidgetsOnIos('moves selection between paragraphs', (tester) async {
final testContext = await tester //
.createDocument()
.fromMarkdown('''
This is the first paragraph
Second paragraph''') //
.pump();

// Place the caret at the end of the first paragraph.
await tester.placeCaretInParagraph(testContext.document.nodes.first.id, 27);

// Show the floating cursor.
await tester.startFloatingCursorGesture();
await tester.pump();

// Move the floating cursor down to the next paragraph.
await tester.updateFloatingCursorGesture(const Offset(0, 30));
await tester.pump();

// Simulate iOS IME generating deltas as a result of moving the floating cursor.
// At this point, the selection already changed to the second paragraph, which is
// smaller than the selection offset reported in the delta.
await tester.ime.sendDeltas([
const TextEditingDeltaNonTextUpdate(
oldText: 'This is the first paragraph',
selection: TextSelection.collapsed(offset: 27),
composing: TextRange.empty,
)
], getter: imeClientGetter);
await tester.pump();

// Ensure the selection changed to the end of the second paragraph.
expect(
SuperEditorInspector.findDocumentSelection(),
DocumentSelection.collapsed(
position: DocumentPosition(
nodeId: testContext.document.nodes.last.id,
nodePosition: const TextNodePosition(offset: 16, affinity: TextAffinity.upstream),
),
),
);
});
});
});
}
Expand Down

0 comments on commit 3f813c8

Please sign in to comment.