Skip to content

Commit c11904c

Browse files
committed
feat: optimize text insertion
1 parent 19b1c84 commit c11904c

File tree

3 files changed

+81
-24
lines changed

3 files changed

+81
-24
lines changed

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/text_robot.dart

+76-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import 'dart:async';
2+
13
import 'package:appflowy_editor/appflowy_editor.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:synchronized/synchronized.dart';
26

37
enum TextRobotInputType {
48
character,
@@ -7,28 +11,49 @@ enum TextRobotInputType {
711
}
812

913
class TextRobot {
10-
const TextRobot({
14+
TextRobot({
1115
required this.editorState,
1216
});
1317

1418
final EditorState editorState;
19+
final Lock lock = Lock();
1520

21+
/// This function is used to insert text in a synchronized way
22+
///
23+
/// It is suitable for inserting text in a loop.
24+
Future<void> autoInsertTextSync(
25+
String text, {
26+
TextRobotInputType inputType = TextRobotInputType.word,
27+
Duration delay = const Duration(milliseconds: 10),
28+
String separator = '\n',
29+
}) async {
30+
await lock.synchronized(() async {
31+
await autoInsertText(
32+
text,
33+
inputType: inputType,
34+
delay: delay,
35+
separator: separator,
36+
);
37+
});
38+
}
39+
40+
/// This function is used to insert text in an asynchronous way
41+
///
42+
/// It is suitable for inserting a long paragraph or a long sentence.
1643
Future<void> autoInsertText(
1744
String text, {
1845
TextRobotInputType inputType = TextRobotInputType.word,
1946
Duration delay = const Duration(milliseconds: 10),
2047
String separator = '\n',
2148
}) async {
2249
if (text == separator) {
23-
await editorState.insertNewLine();
24-
await Future.delayed(delay);
50+
await insertNewParagraph(delay);
2551
return;
2652
}
2753
final lines = _splitText(text, separator);
2854
for (final line in lines) {
2955
if (line.isEmpty) {
30-
await editorState.insertNewLine();
31-
await Future.delayed(delay);
56+
await insertNewParagraph(delay);
3257
continue;
3358
}
3459
switch (inputType) {
@@ -48,33 +73,68 @@ class TextRobot {
4873
Future<void> insertCharacter(String line, Duration delay) async {
4974
final iterator = line.runes.iterator;
5075
while (iterator.moveNext()) {
51-
await editorState.insertTextAtCurrentSelection(
52-
iterator.currentAsString,
53-
);
54-
await Future.delayed(delay);
76+
await insertText(iterator.currentAsString, delay);
5577
}
5678
}
5779

5880
Future<void> insertWord(String line, Duration delay) async {
5981
final words = line.split(' ');
6082
if (words.length == 1 ||
6183
(words.length == 2 && (words.first.isEmpty || words.last.isEmpty))) {
62-
await editorState.insertTextAtCurrentSelection(
63-
line,
64-
);
84+
await insertText(line, delay);
6585
} else {
6686
for (final word in words.map((e) => '$e ')) {
67-
await editorState.insertTextAtCurrentSelection(
68-
word,
69-
);
87+
await insertText(word, delay);
7088
}
7189
}
7290
await Future.delayed(delay);
7391
}
7492

7593
Future<void> insertSentence(String line, Duration delay) async {
76-
await editorState.insertTextAtCurrentSelection(line);
94+
await insertText(line, delay);
95+
}
96+
97+
Future<void> insertNewParagraph(Duration delay) async {
98+
final selection = editorState.selection;
99+
if (selection == null || !selection.isCollapsed) {
100+
return;
101+
}
102+
final next = selection.end.path.next;
103+
final transaction = editorState.transaction;
104+
transaction.insertNode(
105+
next,
106+
paragraphNode(),
107+
);
108+
transaction.afterSelection = Selection.collapsed(
109+
Position(path: next),
110+
);
111+
await editorState.apply(transaction);
112+
debugPrint(
113+
'AI insertNewParagraph: path: ${editorState.selection!.end.path}, index: ${editorState.selection!.endIndex}',
114+
);
115+
await Future.delayed(const Duration(milliseconds: 10));
116+
}
117+
118+
Future<void> insertText(String text, Duration delay) async {
119+
final selection = editorState.selection;
120+
debugPrint(
121+
'AI insertText: get selection, path: ${selection!.end.path}, index: ${selection.endIndex}',
122+
);
123+
if (!selection.isCollapsed) {
124+
return;
125+
}
126+
final node = editorState.getNodeAtPath(selection.end.path);
127+
if (node == null) {
128+
return;
129+
}
130+
final transaction = editorState.transaction;
131+
transaction.insertText(node, selection.endIndex, text);
132+
await editorState.apply(transaction);
77133
await Future.delayed(delay);
134+
135+
debugPrint(
136+
'AI insertText: path: ${selection.end.path}, index: ${selection.endIndex}, text: "$text"',
137+
);
78138
}
79139
}
80140

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/auto_completion_node_widget.dart

+3-6
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ class _AutoCompletionBlockComponentState
229229
}
230230
},
231231
onProcess: (text) async {
232-
await textRobot.autoInsertText(
232+
await textRobot.autoInsertTextSync(
233233
text,
234234
separator: r'\n\n',
235235
inputType: TextRobotInputType.sentence,
@@ -269,10 +269,7 @@ class _AutoCompletionBlockComponentState
269269
start,
270270
end.last - start.last + 1,
271271
);
272-
await editorState.apply(
273-
transaction,
274-
options: const ApplyOptions(inMemoryUpdate: true),
275-
);
272+
await editorState.apply(transaction);
276273
await _makeSurePreviousNodeIsEmptyParagraphNode();
277274
}
278275
}
@@ -321,7 +318,7 @@ class _AutoCompletionBlockComponentState
321318
await _makeSurePreviousNodeIsEmptyParagraphNode();
322319
},
323320
onProcess: (text) async {
324-
await textRobot.autoInsertText(
321+
await textRobot.autoInsertTextSync(
325322
text,
326323
inputType: TextRobotInputType.sentence,
327324
separator: r'\n\n',

frontend/appflowy_flutter/test/unit_test/document/text_robot/text_robot_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ void main() {
2020
editorState: editorState,
2121
);
2222
for (final text in _sample1) {
23-
await textRobot.autoInsertText(
23+
await textRobot.autoInsertTextSync(
2424
text,
2525
separator: r'\n\n',
2626
inputType: TextRobotInputType.sentence,
@@ -58,7 +58,7 @@ void main() {
5858
if (text.contains('\n\n')) {
5959
breakCount++;
6060
}
61-
await textRobot.autoInsertText(
61+
await textRobot.autoInsertTextSync(
6262
text,
6363
separator: r'\n\n',
6464
inputType: TextRobotInputType.sentence,

0 commit comments

Comments
 (0)