Skip to content

Commit

Permalink
fix: the cursor is inaccuracy when the text contains special emoji (#238
Browse files Browse the repository at this point in the history
)
  • Loading branch information
LucasXu0 authored Jun 22, 2023
1 parent 3614cc5 commit eec1d19
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 34 deletions.
41 changes: 11 additions & 30 deletions lib/src/core/document/text_delta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,11 @@ import 'dart:math';
import 'package:flutter/foundation.dart';

import 'package:appflowy_editor/src/core/document/attributes.dart';
import 'package:flutter/services.dart';

// constant number: 2^53 - 1
const int _maxInt = 9007199254740991;

List<int> stringIndexes(String text) {
final indexes = List<int>.filled(text.length, 0);
final iterator = text.runes.iterator;

while (iterator.moveNext()) {
for (var i = 0; i < iterator.currentSize; i++) {
indexes[iterator.rawIndex + i] = iterator.rawIndex;
}
}

return indexes;
}

abstract class TextOperation {
Attributes? get attributes;
int get length;
Expand Down Expand Up @@ -163,7 +151,6 @@ class Delta extends Iterable<TextOperation> {

final List<TextOperation> _operations;
String? _plainText;
List<int>? _runeIndexes;

void addAll(Iterable<TextOperation> textOperations) {
textOperations.forEach(add);
Expand Down Expand Up @@ -399,10 +386,10 @@ class Delta extends Iterable<TextOperation> {
if (pos == 0) {
return pos - 1;
}
_plainText ??=
_operations.whereType<TextInsert>().map((op) => op.text).join();
_runeIndexes ??= stringIndexes(_plainText!);
return _runeIndexes![pos - 1];
final content = toPlainText();
final boundary = CharacterBoundary(content);
final index = boundary.getLeadingTextBoundaryAt(pos - 1);
return index ?? 0;
}

/// This method will return the position of the next rune.
Expand All @@ -413,19 +400,13 @@ class Delta extends Iterable<TextOperation> {
///
/// This method can help you to compute the position of the next character.
int nextRunePosition(int pos) {
final stringContent = toPlainText();
if (pos >= stringContent.length - 1) {
return stringContent.length;
}
_runeIndexes ??= stringIndexes(_plainText!);

for (var i = pos + 1; i < _runeIndexes!.length; i++) {
if (_runeIndexes![i] != pos) {
return _runeIndexes![i];
}
final content = toPlainText();
if (pos >= content.length - 1) {
return content.length;
}

return stringContent.length;
final boundary = CharacterBoundary(content);
final index = boundary.getTrailingTextBoundaryAt(pos);
return index ?? content.length;
}

String toPlainText() {
Expand Down
49 changes: 45 additions & 4 deletions test/core/document/text_delta_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,48 @@ void main() {
});
});
group('runes', () {
test("stringIndexes", () {
final indexes = stringIndexes('😊');
expect(indexes[0], 0);
expect(indexes[1], 0);
test('emoji next rune', () {
final text = '😊👫👩‍👩‍👧‍👧👨‍👨‍👧👩‍👧🧑‍🚀'; // 6 emojis
final delta = Delta()..insert(text);
final pos = [0];
for (var i = 0; i < 6; i++) {
pos.add(delta.nextRunePosition(pos.last));
}
expect(pos, [0, 2, 4, 15, 23, 28, 33]);
});

test('text next rune', () {
final text = 'Hello你好こんにちは안녕하세요';
final delta = Delta()..insert(text);
var pos = 0;
for (var i = 0; i < text.length; i++) {
expect(pos, i);
pos = delta.nextRunePosition(pos);
}
expect(pos, text.length);
});

test('emoji previous rune', () {
final text = '😊👫👩‍👩‍👧‍👧👨‍👨‍👧👩‍👧🧑‍🚀'; // 6 emojis
final delta = Delta()..insert(text);
final pos = [text.length];
for (var i = 0; i < 6; i++) {
pos.add(delta.prevRunePosition(pos.last));
}
expect(pos.reversed, [0, 2, 4, 15, 23, 28, 33]);
});

test('text previous rune', () {
final text = 'Hello你好こんにちは안녕하세요';
final delta = Delta()..insert(text);
var pos = text.length;
for (var i = text.length; i > 0; i--) {
expect(pos, i);
pos = delta.prevRunePosition(pos);
}
expect(pos, 0);
});

test("next rune 1", () {
final delta = Delta()..insert('😊');
expect(delta.nextRunePosition(0), 2);
Expand All @@ -342,6 +379,10 @@ void main() {
final delta = Delta()..insert('😊陈');
expect(delta.nextRunePosition(2), 3);
});
test("next rune 4", () {
final delta = Delta()..insert('😊陈');
expect(delta.nextRunePosition(2), 3);
});
test("prev rune 1", () {
final delta = Delta()..insert('😊陈');
expect(delta.prevRunePosition(2), 0);
Expand Down

0 comments on commit eec1d19

Please sign in to comment.