From 3db02f2e00aa2a62950a65e0ebcd4cef3cf13556 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 22 Jun 2023 11:05:04 +0800 Subject: [PATCH] fix: the cursor is inaccuracy when the text contains special emoji --- lib/src/core/document/text_delta.dart | 41 ++++++--------------- test/core/document/text_delta_test.dart | 49 +++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/lib/src/core/document/text_delta.dart b/lib/src/core/document/text_delta.dart index 8a2241ade..3a984d541 100644 --- a/lib/src/core/document/text_delta.dart +++ b/lib/src/core/document/text_delta.dart @@ -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 stringIndexes(String text) { - final indexes = List.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; @@ -163,7 +151,6 @@ class Delta extends Iterable { final List _operations; String? _plainText; - List? _runeIndexes; void addAll(Iterable textOperations) { textOperations.forEach(add); @@ -399,10 +386,10 @@ class Delta extends Iterable { if (pos == 0) { return pos - 1; } - _plainText ??= - _operations.whereType().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. @@ -413,19 +400,13 @@ class Delta extends Iterable { /// /// 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() { diff --git a/test/core/document/text_delta_test.dart b/test/core/document/text_delta_test.dart index 8706c6b8c..f2af8c506 100644 --- a/test/core/document/text_delta_test.dart +++ b/test/core/document/text_delta_test.dart @@ -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); @@ -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);