@apollohg/react-native-prose-editor 0.5.17 → 0.5.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +950 -38
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +408 -33
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +104 -5
- package/dist/NativeRichTextEditor.js +37 -2
- package/ios/EditorAddons.swift +78 -6
- package/ios/EditorCore.xcframework/Info.plist +5 -5
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/NativeEditorExpoView.swift +127 -18
- package/ios/RenderBridge.swift +3 -1
- package/ios/RichTextEditorView.swift +211 -14
- package/package.json +1 -1
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
package com.apollohg.editor
|
|
2
2
|
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import android.os.SystemClock
|
|
3
6
|
import android.text.Selection
|
|
4
7
|
import android.view.KeyEvent
|
|
5
8
|
import android.view.inputmethod.BaseInputConnection
|
|
@@ -37,8 +40,28 @@ class EditorInputConnection(
|
|
|
37
40
|
private val boundEditorId: Long,
|
|
38
41
|
private val boundGeneration: Long
|
|
39
42
|
) : InputConnectionWrapper(baseConnection, true) {
|
|
43
|
+
private data class SurroundingDeleteRange(
|
|
44
|
+
val scalarStart: Int,
|
|
45
|
+
val scalarEnd: Int
|
|
46
|
+
)
|
|
47
|
+
|
|
40
48
|
|
|
41
49
|
companion object {
|
|
50
|
+
private fun textTraceSummary(text: CharSequence?): String {
|
|
51
|
+
if (text == null) return "text=null"
|
|
52
|
+
val value = text.toString()
|
|
53
|
+
val codePoints = mutableListOf<String>()
|
|
54
|
+
var index = 0
|
|
55
|
+
while (index < value.length && codePoints.size < 4) {
|
|
56
|
+
val codePoint = Character.codePointAt(value, index)
|
|
57
|
+
codePoints.add(codePoint.toString(16))
|
|
58
|
+
index += Character.charCount(codePoint)
|
|
59
|
+
}
|
|
60
|
+
return "textLength=${value.length} codePoints=${codePoints.joinToString(",")}"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private const val DUPLICATE_CORRECTION_COMMIT_WINDOW_MS = 1_000L
|
|
64
|
+
|
|
42
65
|
internal fun codePointsToUtf16Length(
|
|
43
66
|
text: String,
|
|
44
67
|
fromUtf16Offset: Int,
|
|
@@ -74,6 +97,21 @@ class EditorInputConnection(
|
|
|
74
97
|
}
|
|
75
98
|
}
|
|
76
99
|
|
|
100
|
+
private data class PendingDuplicateCorrectionCommit(
|
|
101
|
+
val text: String,
|
|
102
|
+
val deadlineMs: Long
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
private data class PendingCompositionCorrectionCommit(
|
|
106
|
+
val text: String,
|
|
107
|
+
val deadlineMs: Long,
|
|
108
|
+
val generation: Long
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
private var pendingDuplicateCorrectionCommit: PendingDuplicateCorrectionCommit? = null
|
|
112
|
+
private var pendingCompositionCorrectionCommit: PendingCompositionCorrectionCommit? = null
|
|
113
|
+
private var pendingCompositionCorrectionGeneration: Long = 0L
|
|
114
|
+
|
|
77
115
|
/**
|
|
78
116
|
* Called when the IME commits finalized text (single character, word,
|
|
79
117
|
* autocomplete selection, etc.).
|
|
@@ -81,25 +119,45 @@ class EditorInputConnection(
|
|
|
81
119
|
* Routes the text through Rust instead of directly inserting into the EditText.
|
|
82
120
|
*/
|
|
83
121
|
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
84
|
-
if (!
|
|
122
|
+
if (!isCurrentInputSessionFor("commitText")) return true
|
|
85
123
|
if (!editorView.isEditable) return true
|
|
86
124
|
if (editorView.isApplyingRustState) {
|
|
125
|
+
editorView.recordImeTraceForTesting(
|
|
126
|
+
"commitTextPassthrough",
|
|
127
|
+
"reason=applyingRust ${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
128
|
+
)
|
|
87
129
|
return super.commitText(text, newCursorPosition)
|
|
88
130
|
}
|
|
89
131
|
if (editorView.editorId == 0L) {
|
|
132
|
+
editorView.recordImeTraceForTesting(
|
|
133
|
+
"commitTextPassthrough",
|
|
134
|
+
"reason=noEditor ${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
135
|
+
)
|
|
90
136
|
return super.commitText(text, newCursorPosition)
|
|
91
137
|
}
|
|
92
138
|
|
|
93
139
|
editorView.recordImeTraceForTesting(
|
|
94
140
|
"commitText",
|
|
95
|
-
"
|
|
141
|
+
"${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
96
142
|
)
|
|
97
|
-
|
|
143
|
+
val committedText = text?.toString()
|
|
144
|
+
if (consumePendingCompositionCorrectionCommitIfNeeded(committedText, newCursorPosition)) {
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
applyPendingCompositionCorrectionCommitIfNeeded("commitTextBeforePlain")
|
|
148
|
+
if (consumePendingDuplicateCorrectionCommitIfNeeded(committedText)) {
|
|
149
|
+
editorView.recordImeTraceForTesting(
|
|
150
|
+
"commitTextDuplicateCorrectionIgnored",
|
|
151
|
+
"textLength=${committedText?.length ?: 0}"
|
|
152
|
+
)
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
commitTextToEditor(committedText, newCursorPosition)
|
|
98
156
|
return true
|
|
99
157
|
}
|
|
100
158
|
|
|
101
159
|
override fun commitCompletion(text: CompletionInfo?): Boolean {
|
|
102
|
-
if (!
|
|
160
|
+
if (!isCurrentInputSessionFor("commitCompletion")) return true
|
|
103
161
|
if (!editorView.isEditable) return true
|
|
104
162
|
if (editorView.isApplyingRustState) {
|
|
105
163
|
return super.commitCompletion(text)
|
|
@@ -109,14 +167,41 @@ class EditorInputConnection(
|
|
|
109
167
|
}
|
|
110
168
|
editorView.recordImeTraceForTesting(
|
|
111
169
|
"commitCompletion",
|
|
112
|
-
|
|
170
|
+
textTraceSummary(text?.text)
|
|
113
171
|
)
|
|
114
172
|
commitTextToEditor(text?.text?.toString(), 1)
|
|
115
173
|
return true
|
|
116
174
|
}
|
|
117
175
|
|
|
176
|
+
override fun getCursorCapsMode(reqModes: Int): Int {
|
|
177
|
+
val baseCapsMode = super.getCursorCapsMode(reqModes)
|
|
178
|
+
if (!isCurrentInputSession()) return baseCapsMode
|
|
179
|
+
val capsMode = editorView.cursorCapsModeForEditor(reqModes, baseCapsMode)
|
|
180
|
+
if (capsMode != baseCapsMode) {
|
|
181
|
+
editorView.recordImeTraceForTesting(
|
|
182
|
+
"getCursorCapsModeAdjusted",
|
|
183
|
+
"req=$reqModes base=$baseCapsMode caps=$capsMode"
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
return capsMode
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
override fun getTextBeforeCursor(n: Int, flags: Int): CharSequence? {
|
|
190
|
+
if (!isCurrentInputSession()) return super.getTextBeforeCursor(n, flags)
|
|
191
|
+
val textBeforeCursor = editorView.textBeforeCursorForImeContextForEditor(n, flags)
|
|
192
|
+
?: return super.getTextBeforeCursor(n, flags)
|
|
193
|
+
val raw = super.getTextBeforeCursor(n, flags)
|
|
194
|
+
if (raw?.toString() != textBeforeCursor.toString()) {
|
|
195
|
+
editorView.recordImeTraceForTesting(
|
|
196
|
+
"getTextBeforeCursorAdjusted",
|
|
197
|
+
"requested=$n rawLength=${raw?.length ?: -1} adjustedLength=${textBeforeCursor.length}"
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
return textBeforeCursor
|
|
201
|
+
}
|
|
202
|
+
|
|
118
203
|
override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean {
|
|
119
|
-
if (!
|
|
204
|
+
if (!isCurrentInputSessionFor("commitCorrection")) return true
|
|
120
205
|
if (!editorView.isEditable) return true
|
|
121
206
|
if (editorView.isApplyingRustState) {
|
|
122
207
|
return super.commitCorrection(correctionInfo)
|
|
@@ -135,7 +220,7 @@ class EditorInputConnection(
|
|
|
135
220
|
"commitCorrectionComposition",
|
|
136
221
|
"newLength=${newText.length}"
|
|
137
222
|
)
|
|
138
|
-
|
|
223
|
+
rememberPendingCompositionCorrectionCommit(newText)
|
|
139
224
|
return true
|
|
140
225
|
}
|
|
141
226
|
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
@@ -155,33 +240,151 @@ class EditorInputConnection(
|
|
|
155
240
|
"commitCorrectionResult",
|
|
156
241
|
"applied=$applied"
|
|
157
242
|
)
|
|
243
|
+
if (applied) {
|
|
244
|
+
rememberPendingDuplicateCorrectionCommit(newText)
|
|
245
|
+
}
|
|
246
|
+
return true
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private fun rememberPendingDuplicateCorrectionCommit(text: String) {
|
|
250
|
+
pendingDuplicateCorrectionCommit = PendingDuplicateCorrectionCommit(
|
|
251
|
+
text = text,
|
|
252
|
+
deadlineMs = SystemClock.uptimeMillis() + DUPLICATE_CORRECTION_COMMIT_WINDOW_MS
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private fun consumePendingDuplicateCorrectionCommitIfNeeded(text: String?): Boolean {
|
|
257
|
+
val pending = pendingDuplicateCorrectionCommit ?: return false
|
|
258
|
+
pendingDuplicateCorrectionCommit = null
|
|
259
|
+
if (text == null) return false
|
|
260
|
+
if (SystemClock.uptimeMillis() > pending.deadlineMs) return false
|
|
261
|
+
return text == pending.text
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private fun rememberPendingCompositionCorrectionCommit(text: String) {
|
|
265
|
+
val generation = ++pendingCompositionCorrectionGeneration
|
|
266
|
+
pendingCompositionCorrectionCommit = PendingCompositionCorrectionCommit(
|
|
267
|
+
text = text,
|
|
268
|
+
deadlineMs = SystemClock.uptimeMillis() + DUPLICATE_CORRECTION_COMMIT_WINDOW_MS,
|
|
269
|
+
generation = generation
|
|
270
|
+
)
|
|
271
|
+
Handler(Looper.getMainLooper()).post {
|
|
272
|
+
val pending = pendingCompositionCorrectionCommit ?: return@post
|
|
273
|
+
if (pending.generation != generation) return@post
|
|
274
|
+
applyPendingCompositionCorrectionCommitIfNeeded("commitCorrectionDeferred")
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private fun consumePendingCompositionCorrectionCommitIfNeeded(
|
|
279
|
+
text: String?,
|
|
280
|
+
newCursorPosition: Int
|
|
281
|
+
): Boolean {
|
|
282
|
+
val pending = pendingCompositionCorrectionCommit ?: return false
|
|
283
|
+
if (SystemClock.uptimeMillis() > pending.deadlineMs) {
|
|
284
|
+
pendingCompositionCorrectionCommit = null
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
if (text != pending.text) return false
|
|
288
|
+
pendingCompositionCorrectionCommit = null
|
|
289
|
+
pendingCompositionCorrectionGeneration += 1L
|
|
290
|
+
editorView.recordImeTraceForTesting(
|
|
291
|
+
"commitTextConsumesPendingCorrection",
|
|
292
|
+
"textLength=${text.length}"
|
|
293
|
+
)
|
|
294
|
+
commitTextToEditor(text, newCursorPosition)
|
|
295
|
+
return true
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private fun applyPendingCompositionCorrectionCommitIfNeeded(source: String): Boolean {
|
|
299
|
+
val pending = pendingCompositionCorrectionCommit ?: return false
|
|
300
|
+
pendingCompositionCorrectionCommit = null
|
|
301
|
+
pendingCompositionCorrectionGeneration += 1L
|
|
302
|
+
if (!isCurrentInputSessionFor("applyPendingCompositionCorrection")) return false
|
|
303
|
+
if (!editorView.isEditable || editorView.editorId == 0L) return false
|
|
304
|
+
editorView.recordImeTraceForTesting(
|
|
305
|
+
"applyPendingCompositionCorrection",
|
|
306
|
+
"source=$source textLength=${pending.text.length}"
|
|
307
|
+
)
|
|
308
|
+
commitTextToEditor(pending.text, 1)
|
|
158
309
|
return true
|
|
159
310
|
}
|
|
160
311
|
|
|
161
312
|
private fun commitTextToEditor(committedText: String?, newCursorPosition: Int) {
|
|
162
|
-
val
|
|
313
|
+
val startedAt = System.nanoTime()
|
|
314
|
+
val trackedReplacementRange = trackedCompositionReplacementRange()
|
|
315
|
+
val rawComposingSpanRange = currentComposingSpanRawRange()
|
|
316
|
+
val currentAuthorizedComposingSpanRange = currentComposingSpanRange()
|
|
317
|
+
val visibleReplacementRange = rawComposingSpanRange ?: trackedReplacementRange
|
|
318
|
+
val replacementRange = trackedReplacementRange?.let { range ->
|
|
319
|
+
if (range.first == range.second) {
|
|
320
|
+
currentAuthorizedComposingSpanRange ?: range
|
|
321
|
+
} else {
|
|
322
|
+
range
|
|
323
|
+
}
|
|
324
|
+
}
|
|
163
325
|
if (replacementRange != null) {
|
|
326
|
+
editorView.recordImeTraceForTesting(
|
|
327
|
+
"commitTextRoute",
|
|
328
|
+
"route=composition replacement=${replacementRange.first}..${replacementRange.second} visible=${visibleReplacementRange?.first}..${visibleReplacementRange?.second} textLength=${committedText?.length ?: 0}"
|
|
329
|
+
)
|
|
164
330
|
clearCompositionTracking()
|
|
165
331
|
editorView.runWithTransientInputMutationGuard {
|
|
166
332
|
super.finishComposingText()
|
|
167
333
|
}
|
|
168
334
|
if (committedText != null) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
335
|
+
var didCommitAlreadyVisibleMutation = false
|
|
336
|
+
if (
|
|
337
|
+
trackedReplacementRange?.first == trackedReplacementRange?.second &&
|
|
338
|
+
rawComposingSpanRange == null
|
|
339
|
+
) {
|
|
340
|
+
editorView.runWithDeferredRustUpdateApplication {
|
|
341
|
+
didCommitAlreadyVisibleMutation =
|
|
342
|
+
editorView.commitAlreadyVisibleCompositionMutationForPendingImeOperationForEditor(
|
|
343
|
+
committedText,
|
|
344
|
+
newCursorPosition
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!didCommitAlreadyVisibleMutation) {
|
|
349
|
+
visibleReplacementRange?.let { visibleRange ->
|
|
350
|
+
editorView.applyVisibleCompositionCommitForPendingImeOperationForEditor(
|
|
351
|
+
committedText,
|
|
352
|
+
visibleRange.first,
|
|
353
|
+
visibleRange.second,
|
|
354
|
+
newCursorPosition
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
editorView.runWithDeferredRustUpdateApplication {
|
|
358
|
+
editorView.handleCompositionCommit(
|
|
359
|
+
committedText,
|
|
360
|
+
replacementRange.first,
|
|
361
|
+
replacementRange.second,
|
|
362
|
+
newCursorPosition
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
175
366
|
} else {
|
|
176
367
|
editorView.restoreAuthorizedTextIfNeeded()
|
|
177
368
|
}
|
|
178
369
|
} else {
|
|
179
370
|
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
371
|
+
editorView.recordImeTraceForTesting(
|
|
372
|
+
"commitTextRoute",
|
|
373
|
+
"route=restoreInvalidComposition textLength=${committedText?.length ?: 0}"
|
|
374
|
+
)
|
|
180
375
|
return
|
|
181
376
|
}
|
|
182
377
|
clearCompositionTracking()
|
|
378
|
+
editorView.recordImeTraceForTesting(
|
|
379
|
+
"commitTextRoute",
|
|
380
|
+
"route=plain textLength=${committedText?.length ?: 0}"
|
|
381
|
+
)
|
|
183
382
|
committedText?.let { editorView.handleTextCommit(it, newCursorPosition) }
|
|
184
383
|
}
|
|
384
|
+
editorView.recordImeTraceForTesting(
|
|
385
|
+
"commitTextRouteDone",
|
|
386
|
+
"textLength=${committedText?.length ?: 0} totalUs=${nanosToMicros(System.nanoTime() - startedAt)}"
|
|
387
|
+
)
|
|
185
388
|
}
|
|
186
389
|
|
|
187
390
|
/**
|
|
@@ -193,11 +396,15 @@ class EditorInputConnection(
|
|
|
193
396
|
* @param afterLength Number of UTF-16 code units to delete after the cursor.
|
|
194
397
|
*/
|
|
195
398
|
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
|
|
196
|
-
if (!
|
|
399
|
+
if (!isCurrentInputSessionFor("deleteSurroundingText")) return true
|
|
197
400
|
if (!editorView.isEditable) return true
|
|
198
401
|
if (editorView.isApplyingRustState) {
|
|
199
402
|
return super.deleteSurroundingText(beforeLength, afterLength)
|
|
200
403
|
}
|
|
404
|
+
editorView.recordImeTraceForTesting(
|
|
405
|
+
"deleteSurroundingText",
|
|
406
|
+
"before=$beforeLength after=$afterLength"
|
|
407
|
+
)
|
|
201
408
|
if (
|
|
202
409
|
editorView.hasInvalidatedCompositionReplacementRangeForEditor() &&
|
|
203
410
|
isNoOpSurroundingDelete(beforeLength, afterLength)
|
|
@@ -224,16 +431,27 @@ class EditorInputConnection(
|
|
|
224
431
|
refreshComposingTextFromEditable()
|
|
225
432
|
return result || didFallbackDelete
|
|
226
433
|
}
|
|
434
|
+
if (shouldDeferPlainSurroundingDelete(beforeLength, afterLength)) {
|
|
435
|
+
return performDeferredPlainSurroundingDelete(
|
|
436
|
+
beforeLength = beforeLength,
|
|
437
|
+
afterLength = afterLength,
|
|
438
|
+
deleteInCodePoints = false
|
|
439
|
+
)
|
|
440
|
+
}
|
|
227
441
|
editorView.handleDelete(beforeLength, afterLength)
|
|
228
442
|
return true
|
|
229
443
|
}
|
|
230
444
|
|
|
231
445
|
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
|
|
232
|
-
if (!
|
|
446
|
+
if (!isCurrentInputSessionFor("deleteSurroundingTextInCodePoints")) return true
|
|
233
447
|
if (!editorView.isEditable) return true
|
|
234
448
|
if (editorView.isApplyingRustState) {
|
|
235
449
|
return super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
236
450
|
}
|
|
451
|
+
editorView.recordImeTraceForTesting(
|
|
452
|
+
"deleteSurroundingTextInCodePoints",
|
|
453
|
+
"before=$beforeLength after=$afterLength"
|
|
454
|
+
)
|
|
237
455
|
if (
|
|
238
456
|
editorView.hasInvalidatedCompositionReplacementRangeForEditor() &&
|
|
239
457
|
isNoOpSurroundingDelete(beforeLength, afterLength)
|
|
@@ -263,6 +481,13 @@ class EditorInputConnection(
|
|
|
263
481
|
refreshComposingTextFromEditable()
|
|
264
482
|
return result || didFallbackDelete
|
|
265
483
|
}
|
|
484
|
+
if (shouldDeferPlainSurroundingDelete(beforeLength, afterLength)) {
|
|
485
|
+
return performDeferredPlainSurroundingDelete(
|
|
486
|
+
beforeLength = beforeLength,
|
|
487
|
+
afterLength = afterLength,
|
|
488
|
+
deleteInCodePoints = true
|
|
489
|
+
)
|
|
490
|
+
}
|
|
266
491
|
|
|
267
492
|
val currentText = editorView.text?.toString().orEmpty()
|
|
268
493
|
val cursor = editorView.selectionStart.coerceAtLeast(0)
|
|
@@ -282,6 +507,114 @@ class EditorInputConnection(
|
|
|
282
507
|
return true
|
|
283
508
|
}
|
|
284
509
|
|
|
510
|
+
private fun shouldDeferPlainSurroundingDelete(beforeLength: Int, afterLength: Int): Boolean =
|
|
511
|
+
beforeLength.coerceAtLeast(0) + afterLength.coerceAtLeast(0) > 0
|
|
512
|
+
|
|
513
|
+
private fun performDeferredPlainSurroundingDelete(
|
|
514
|
+
beforeLength: Int,
|
|
515
|
+
afterLength: Int,
|
|
516
|
+
deleteInCodePoints: Boolean
|
|
517
|
+
): Boolean {
|
|
518
|
+
val beforeText = editorView.text?.toString() ?: return true
|
|
519
|
+
val beforeUtf16Length: Int
|
|
520
|
+
val afterUtf16Length: Int
|
|
521
|
+
if (deleteInCodePoints) {
|
|
522
|
+
val cursor = editorView.selectionStart.coerceAtLeast(0)
|
|
523
|
+
beforeUtf16Length = codePointsToUtf16Length(
|
|
524
|
+
text = beforeText,
|
|
525
|
+
fromUtf16Offset = cursor,
|
|
526
|
+
codePointCount = beforeLength,
|
|
527
|
+
forward = false
|
|
528
|
+
)
|
|
529
|
+
afterUtf16Length = codePointsToUtf16Length(
|
|
530
|
+
text = beforeText,
|
|
531
|
+
fromUtf16Offset = editorView.selectionEnd.coerceAtLeast(cursor),
|
|
532
|
+
codePointCount = afterLength,
|
|
533
|
+
forward = true
|
|
534
|
+
)
|
|
535
|
+
} else {
|
|
536
|
+
beforeUtf16Length = beforeLength
|
|
537
|
+
afterUtf16Length = afterLength
|
|
538
|
+
}
|
|
539
|
+
val deleteRange = surroundingDeleteRange(
|
|
540
|
+
text = beforeText,
|
|
541
|
+
beforeUtf16Length = beforeUtf16Length,
|
|
542
|
+
afterUtf16Length = afterUtf16Length
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
editorView.recordImeTraceForTesting(
|
|
546
|
+
"deferredSurroundingDeleteBegin",
|
|
547
|
+
"before=$beforeLength after=$afterLength codePoints=$deleteInCodePoints utf16=$beforeUtf16Length,$afterUtf16Length scalar=${deleteRange?.scalarStart}..${deleteRange?.scalarEnd}"
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
var didFallbackDelete = false
|
|
551
|
+
val result = editorView.runWithTransientInputMutationGuard {
|
|
552
|
+
val baseResult = if (deleteInCodePoints) {
|
|
553
|
+
super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
554
|
+
} else {
|
|
555
|
+
super.deleteSurroundingText(beforeLength, afterLength)
|
|
556
|
+
}
|
|
557
|
+
if (
|
|
558
|
+
beforeText == editorView.text?.toString() &&
|
|
559
|
+
(beforeLength > 0 || afterLength > 0)
|
|
560
|
+
) {
|
|
561
|
+
didFallbackDelete = if (deleteInCodePoints) {
|
|
562
|
+
deleteTransientTextAroundSelectionInCodePoints(beforeLength, afterLength)
|
|
563
|
+
} else {
|
|
564
|
+
deleteTransientTextAroundSelection(beforeLength, afterLength)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
baseResult
|
|
568
|
+
}
|
|
569
|
+
val didDeleteVisibleText = editorView.text?.toString() != beforeText
|
|
570
|
+
if (didDeleteVisibleText && deleteRange != null) {
|
|
571
|
+
editorView.authorizeCurrentVisibleTextForPendingImeOperationForEditor()
|
|
572
|
+
editorView.runWithDeferredRustUpdateApplication {
|
|
573
|
+
editorView.deleteScalarRangeForPendingImeOperationForEditor(
|
|
574
|
+
deleteRange.scalarStart,
|
|
575
|
+
deleteRange.scalarEnd
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
editorView.recordImeTraceForTesting(
|
|
580
|
+
"deferredSurroundingDeleteEnd",
|
|
581
|
+
"result=$result fallback=$didFallbackDelete visibleDeleted=$didDeleteVisibleText visibleLength=${editorView.text?.length ?: -1}"
|
|
582
|
+
)
|
|
583
|
+
return result || didFallbackDelete
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private fun surroundingDeleteRange(
|
|
587
|
+
text: String,
|
|
588
|
+
beforeUtf16Length: Int,
|
|
589
|
+
afterUtf16Length: Int
|
|
590
|
+
): SurroundingDeleteRange? {
|
|
591
|
+
val rawStart = editorView.selectionStart
|
|
592
|
+
val rawEnd = editorView.selectionEnd
|
|
593
|
+
if (rawStart < 0 || rawEnd < 0) return null
|
|
594
|
+
val selectionStart = rawStart.coerceIn(0, text.length)
|
|
595
|
+
val selectionEnd = rawEnd.coerceIn(0, text.length)
|
|
596
|
+
val normalizedStart = minOf(selectionStart, selectionEnd)
|
|
597
|
+
val normalizedEnd = maxOf(selectionStart, selectionEnd)
|
|
598
|
+
val rawDeleteStart: Int
|
|
599
|
+
val rawDeleteEnd: Int
|
|
600
|
+
if (normalizedStart != normalizedEnd) {
|
|
601
|
+
rawDeleteStart = normalizedStart
|
|
602
|
+
rawDeleteEnd = normalizedEnd
|
|
603
|
+
} else {
|
|
604
|
+
rawDeleteStart = maxOf(0, normalizedStart - beforeUtf16Length.coerceAtLeast(0))
|
|
605
|
+
rawDeleteEnd = minOf(text.length, normalizedEnd + afterUtf16Length.coerceAtLeast(0))
|
|
606
|
+
}
|
|
607
|
+
val (deleteStart, deleteEnd) = PositionBridge.snapRangeToScalarBoundaries(
|
|
608
|
+
rawDeleteStart,
|
|
609
|
+
rawDeleteEnd,
|
|
610
|
+
text
|
|
611
|
+
)
|
|
612
|
+
val scalarStart = PositionBridge.utf16ToScalar(deleteStart, text)
|
|
613
|
+
val scalarEnd = PositionBridge.utf16ToScalar(deleteEnd, text)
|
|
614
|
+
if (scalarStart >= scalarEnd) return null
|
|
615
|
+
return SurroundingDeleteRange(scalarStart, scalarEnd)
|
|
616
|
+
}
|
|
617
|
+
|
|
285
618
|
/**
|
|
286
619
|
* Called when the IME sets composing (in-progress) text for CJK/swipe input.
|
|
287
620
|
*
|
|
@@ -290,25 +623,37 @@ class EditorInputConnection(
|
|
|
290
623
|
* to Rust during composition — only when the IME commits or finishes it.
|
|
291
624
|
*/
|
|
292
625
|
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
293
|
-
if (!
|
|
626
|
+
if (!isCurrentInputSessionFor("setComposingText")) return true
|
|
294
627
|
if (!editorView.isEditable) return true
|
|
295
628
|
if (editorView.editorId == 0L) return super.setComposingText(text, newCursorPosition)
|
|
296
629
|
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
297
630
|
return finishStaleComposingUpdateAfterInvalidation()
|
|
298
631
|
}
|
|
299
632
|
captureCompositionReplacementRangeIfNeeded()
|
|
633
|
+
val composingText = text?.toString()
|
|
634
|
+
val adjustedComposingText =
|
|
635
|
+
editorView.samsungSentenceCapsComposingTextForEditor(composingText)
|
|
636
|
+
val textForBaseConnection = if (adjustedComposingText != composingText) {
|
|
637
|
+
adjustedComposingText
|
|
638
|
+
} else {
|
|
639
|
+
text
|
|
640
|
+
}
|
|
300
641
|
editorView.recordImeTraceForTesting(
|
|
301
642
|
"setComposingText",
|
|
302
|
-
"
|
|
643
|
+
"${textTraceSummary(text)} cursor=$newCursorPosition adjusted=${adjustedComposingText != composingText}"
|
|
303
644
|
)
|
|
304
|
-
editorView.setComposingTextForEditor(
|
|
645
|
+
editorView.setComposingTextForEditor(adjustedComposingText)
|
|
305
646
|
return editorView.runWithTransientInputMutationGuard {
|
|
306
|
-
super.setComposingText(
|
|
647
|
+
val result = super.setComposingText(textForBaseConnection, newCursorPosition)
|
|
648
|
+
if (result) {
|
|
649
|
+
editorView.applyTransientComposingTextStyleForEditor()
|
|
650
|
+
}
|
|
651
|
+
result
|
|
307
652
|
}
|
|
308
653
|
}
|
|
309
654
|
|
|
310
655
|
override fun setComposingRegion(start: Int, end: Int): Boolean {
|
|
311
|
-
if (!
|
|
656
|
+
if (!isCurrentInputSessionFor("setComposingRegion")) return true
|
|
312
657
|
if (!editorView.isEditable) return true
|
|
313
658
|
if (editorView.editorId == 0L) return super.setComposingRegion(start, end)
|
|
314
659
|
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
@@ -322,12 +667,16 @@ class EditorInputConnection(
|
|
|
322
667
|
"range=$start..$end"
|
|
323
668
|
)
|
|
324
669
|
return editorView.runWithTransientInputMutationGuard {
|
|
325
|
-
super.setComposingRegion(start, end)
|
|
670
|
+
val result = super.setComposingRegion(start, end)
|
|
671
|
+
if (result) {
|
|
672
|
+
editorView.applyTransientComposingTextStyleForEditor()
|
|
673
|
+
}
|
|
674
|
+
result
|
|
326
675
|
}
|
|
327
676
|
}
|
|
328
677
|
|
|
329
678
|
override fun setSelection(start: Int, end: Int): Boolean {
|
|
330
|
-
if (!
|
|
679
|
+
if (!isCurrentInputSessionFor("setSelection")) return true
|
|
331
680
|
if (!editorView.isEditable) {
|
|
332
681
|
consumeInvalidatedCompositionReplacementRangeAndRestore()
|
|
333
682
|
return true
|
|
@@ -352,17 +701,18 @@ class EditorInputConnection(
|
|
|
352
701
|
* so it can capture the result and send it to Rust.
|
|
353
702
|
*/
|
|
354
703
|
override fun finishComposingText(): Boolean {
|
|
704
|
+
if (applyPendingCompositionCorrectionCommitIfNeeded("finishComposingText")) return true
|
|
355
705
|
return finishComposingTextInternal(blockWhenCompositionWasCancelled = false)
|
|
356
706
|
}
|
|
357
707
|
|
|
358
708
|
internal fun flushPendingCompositionForExternalMutation(): Boolean {
|
|
359
|
-
if (!
|
|
709
|
+
if (!isCurrentInputSessionFor("flushPendingComposition")) return true
|
|
360
710
|
if (!hasPendingComposition()) return true
|
|
361
711
|
return finishComposingTextInternal(blockWhenCompositionWasCancelled = true)
|
|
362
712
|
}
|
|
363
713
|
|
|
364
714
|
internal fun hasPendingComposition(): Boolean {
|
|
365
|
-
if (!
|
|
715
|
+
if (!isCurrentInputSessionFor("hasPendingComposition")) return false
|
|
366
716
|
if (trackedCompositionReplacementRange() != null) return true
|
|
367
717
|
val editable = editorView.text ?: return false
|
|
368
718
|
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
@@ -371,12 +721,12 @@ class EditorInputConnection(
|
|
|
371
721
|
}
|
|
372
722
|
|
|
373
723
|
internal fun refreshComposingTextFromEditableForEditor() {
|
|
374
|
-
if (!
|
|
724
|
+
if (!isCurrentInputSessionFor("refreshComposingText")) return
|
|
375
725
|
refreshComposingTextFromEditable()
|
|
376
726
|
}
|
|
377
727
|
|
|
378
728
|
internal fun clearCompositionTrackingForEditor() {
|
|
379
|
-
if (!
|
|
729
|
+
if (!isCurrentInputSessionFor("clearCompositionTracking")) return
|
|
380
730
|
clearCompositionTracking()
|
|
381
731
|
}
|
|
382
732
|
|
|
@@ -392,7 +742,7 @@ class EditorInputConnection(
|
|
|
392
742
|
}
|
|
393
743
|
|
|
394
744
|
private fun finishComposingTextInternal(blockWhenCompositionWasCancelled: Boolean): Boolean {
|
|
395
|
-
if (!
|
|
745
|
+
if (!isCurrentInputSessionFor("finishComposingText")) return true
|
|
396
746
|
if (!editorView.isEditable) {
|
|
397
747
|
clearCompositionTracking()
|
|
398
748
|
editorView.restoreAuthorizedTextIfNeeded()
|
|
@@ -425,11 +775,13 @@ class EditorInputConnection(
|
|
|
425
775
|
replacementRange != null &&
|
|
426
776
|
(!composed.isNullOrEmpty() || replacementRange.first != replacementRange.second)
|
|
427
777
|
) {
|
|
428
|
-
editorView.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
778
|
+
editorView.runWithDeferredRustUpdateApplication {
|
|
779
|
+
editorView.handleCompositionCommit(
|
|
780
|
+
composed.orEmpty(),
|
|
781
|
+
replacementRange.first,
|
|
782
|
+
replacementRange.second
|
|
783
|
+
)
|
|
784
|
+
}
|
|
433
785
|
return true
|
|
434
786
|
} else if (replacementRange != null) {
|
|
435
787
|
editorView.restoreAuthorizedTextIfNeeded()
|
|
@@ -478,6 +830,19 @@ class EditorInputConnection(
|
|
|
478
830
|
private fun isCurrentInputSession(): Boolean =
|
|
479
831
|
editorView.isInputConnectionCurrentForEditor(boundEditorId, boundGeneration)
|
|
480
832
|
|
|
833
|
+
private fun nanosToMicros(nanos: Long): Long = nanos / 1_000L
|
|
834
|
+
|
|
835
|
+
private fun isCurrentInputSessionFor(event: String): Boolean {
|
|
836
|
+
val isCurrent = isCurrentInputSession()
|
|
837
|
+
if (!isCurrent) {
|
|
838
|
+
editorView.recordImeTraceForTesting(
|
|
839
|
+
"${event}Ignored",
|
|
840
|
+
"reason=stale boundEditor=$boundEditorId boundGen=$boundGeneration"
|
|
841
|
+
)
|
|
842
|
+
}
|
|
843
|
+
return isCurrent
|
|
844
|
+
}
|
|
845
|
+
|
|
481
846
|
private fun refreshComposingTextFromEditable() {
|
|
482
847
|
val editable = editorView.text ?: return
|
|
483
848
|
val visibleReplacementText = editorView.composingTextFromVisibleReplacementForEditor()
|
|
@@ -575,6 +940,16 @@ class EditorInputConnection(
|
|
|
575
940
|
return editorView.authorizedUtf16Range(start, end)
|
|
576
941
|
}
|
|
577
942
|
|
|
943
|
+
private fun currentComposingSpanRawRange(): Pair<Int, Int>? {
|
|
944
|
+
val editable = editorView.text ?: return null
|
|
945
|
+
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
946
|
+
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
947
|
+
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
948
|
+
return null
|
|
949
|
+
}
|
|
950
|
+
return start to end
|
|
951
|
+
}
|
|
952
|
+
|
|
578
953
|
/**
|
|
579
954
|
* Called for hardware keyboard key events.
|
|
580
955
|
*
|