feat: optimize text insertion

This commit is contained in:
Lucas.Xu 2024-11-25 21:17:48 +08:00
parent 19b1c84ea3
commit c11904c2e9
3 changed files with 81 additions and 24 deletions

View file

@ -1,4 +1,8 @@
import 'dart:async';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:synchronized/synchronized.dart';
enum TextRobotInputType {
character,
@ -7,12 +11,35 @@ enum TextRobotInputType {
}
class TextRobot {
const TextRobot({
TextRobot({
required this.editorState,
});
final EditorState editorState;
final Lock lock = Lock();
/// This function is used to insert text in a synchronized way
///
/// It is suitable for inserting text in a loop.
Future<void> autoInsertTextSync(
String text, {
TextRobotInputType inputType = TextRobotInputType.word,
Duration delay = const Duration(milliseconds: 10),
String separator = '\n',
}) async {
await lock.synchronized(() async {
await autoInsertText(
text,
inputType: inputType,
delay: delay,
separator: separator,
);
});
}
/// This function is used to insert text in an asynchronous way
///
/// It is suitable for inserting a long paragraph or a long sentence.
Future<void> autoInsertText(
String text, {
TextRobotInputType inputType = TextRobotInputType.word,
@ -20,15 +47,13 @@ class TextRobot {
String separator = '\n',
}) async {
if (text == separator) {
await editorState.insertNewLine();
await Future.delayed(delay);
await insertNewParagraph(delay);
return;
}
final lines = _splitText(text, separator);
for (final line in lines) {
if (line.isEmpty) {
await editorState.insertNewLine();
await Future.delayed(delay);
await insertNewParagraph(delay);
continue;
}
switch (inputType) {
@ -48,10 +73,7 @@ class TextRobot {
Future<void> insertCharacter(String line, Duration delay) async {
final iterator = line.runes.iterator;
while (iterator.moveNext()) {
await editorState.insertTextAtCurrentSelection(
iterator.currentAsString,
);
await Future.delayed(delay);
await insertText(iterator.currentAsString, delay);
}
}
@ -59,22 +81,60 @@ class TextRobot {
final words = line.split(' ');
if (words.length == 1 ||
(words.length == 2 && (words.first.isEmpty || words.last.isEmpty))) {
await editorState.insertTextAtCurrentSelection(
line,
);
await insertText(line, delay);
} else {
for (final word in words.map((e) => '$e ')) {
await editorState.insertTextAtCurrentSelection(
word,
);
await insertText(word, delay);
}
}
await Future.delayed(delay);
}
Future<void> insertSentence(String line, Duration delay) async {
await editorState.insertTextAtCurrentSelection(line);
await insertText(line, delay);
}
Future<void> insertNewParagraph(Duration delay) async {
final selection = editorState.selection;
if (selection == null || !selection.isCollapsed) {
return;
}
final next = selection.end.path.next;
final transaction = editorState.transaction;
transaction.insertNode(
next,
paragraphNode(),
);
transaction.afterSelection = Selection.collapsed(
Position(path: next),
);
await editorState.apply(transaction);
debugPrint(
'AI insertNewParagraph: path: ${editorState.selection!.end.path}, index: ${editorState.selection!.endIndex}',
);
await Future.delayed(const Duration(milliseconds: 10));
}
Future<void> insertText(String text, Duration delay) async {
final selection = editorState.selection;
debugPrint(
'AI insertText: get selection, path: ${selection!.end.path}, index: ${selection.endIndex}',
);
if (!selection.isCollapsed) {
return;
}
final node = editorState.getNodeAtPath(selection.end.path);
if (node == null) {
return;
}
final transaction = editorState.transaction;
transaction.insertText(node, selection.endIndex, text);
await editorState.apply(transaction);
await Future.delayed(delay);
debugPrint(
'AI insertText: path: ${selection.end.path}, index: ${selection.endIndex}, text: "$text"',
);
}
}

View file

@ -229,7 +229,7 @@ class _AutoCompletionBlockComponentState
}
},
onProcess: (text) async {
await textRobot.autoInsertText(
await textRobot.autoInsertTextSync(
text,
separator: r'\n\n',
inputType: TextRobotInputType.sentence,
@ -269,10 +269,7 @@ class _AutoCompletionBlockComponentState
start,
end.last - start.last + 1,
);
await editorState.apply(
transaction,
options: const ApplyOptions(inMemoryUpdate: true),
);
await editorState.apply(transaction);
await _makeSurePreviousNodeIsEmptyParagraphNode();
}
}
@ -321,7 +318,7 @@ class _AutoCompletionBlockComponentState
await _makeSurePreviousNodeIsEmptyParagraphNode();
},
onProcess: (text) async {
await textRobot.autoInsertText(
await textRobot.autoInsertTextSync(
text,
inputType: TextRobotInputType.sentence,
separator: r'\n\n',

View file

@ -20,7 +20,7 @@ void main() {
editorState: editorState,
);
for (final text in _sample1) {
await textRobot.autoInsertText(
await textRobot.autoInsertTextSync(
text,
separator: r'\n\n',
inputType: TextRobotInputType.sentence,
@ -58,7 +58,7 @@ void main() {
if (text.contains('\n\n')) {
breakCount++;
}
await textRobot.autoInsertText(
await textRobot.autoInsertTextSync(
text,
separator: r'\n\n',
inputType: TextRobotInputType.sentence,