@apollohg/react-native-prose-editor 0.5.16 → 0.5.18
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 +2440 -275
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +783 -64
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1767 -81
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +209 -87
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +27 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +58 -9
- package/dist/NativeEditorBridge.d.ts +34 -1
- package/dist/NativeEditorBridge.js +243 -83
- package/dist/NativeRichTextEditor.js +998 -137
- package/dist/addons.d.ts +7 -0
- 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 +830 -17
- package/ios/NativeEditorModule.swift +304 -108
- package/ios/PositionBridge.swift +24 -1
- package/ios/RichTextEditorView.swift +912 -89
- package/package.json +2 -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,7 +1,13 @@
|
|
|
1
1
|
package com.apollohg.editor
|
|
2
2
|
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import android.os.SystemClock
|
|
6
|
+
import android.text.Selection
|
|
3
7
|
import android.view.KeyEvent
|
|
4
8
|
import android.view.inputmethod.BaseInputConnection
|
|
9
|
+
import android.view.inputmethod.CompletionInfo
|
|
10
|
+
import android.view.inputmethod.CorrectionInfo
|
|
5
11
|
import android.view.inputmethod.InputConnection
|
|
6
12
|
import android.view.inputmethod.InputConnectionWrapper
|
|
7
13
|
|
|
@@ -30,10 +36,32 @@ import android.view.inputmethod.InputConnectionWrapper
|
|
|
30
36
|
*/
|
|
31
37
|
class EditorInputConnection(
|
|
32
38
|
private val editorView: EditorEditText,
|
|
33
|
-
baseConnection: InputConnection
|
|
39
|
+
baseConnection: InputConnection,
|
|
40
|
+
private val boundEditorId: Long,
|
|
41
|
+
private val boundGeneration: Long
|
|
34
42
|
) : InputConnectionWrapper(baseConnection, true) {
|
|
43
|
+
private data class SurroundingDeleteRange(
|
|
44
|
+
val scalarStart: Int,
|
|
45
|
+
val scalarEnd: Int
|
|
46
|
+
)
|
|
47
|
+
|
|
35
48
|
|
|
36
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
|
+
|
|
37
65
|
internal fun codePointsToUtf16Length(
|
|
38
66
|
text: String,
|
|
39
67
|
fromUtf16Offset: Int,
|
|
@@ -69,10 +97,20 @@ class EditorInputConnection(
|
|
|
69
97
|
}
|
|
70
98
|
}
|
|
71
99
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
76
114
|
|
|
77
115
|
/**
|
|
78
116
|
* Called when the IME commits finalized text (single character, word,
|
|
@@ -81,31 +119,272 @@ 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
|
|
123
|
+
if (!editorView.isEditable) return true
|
|
85
124
|
if (editorView.isApplyingRustState) {
|
|
125
|
+
editorView.recordImeTraceForTesting(
|
|
126
|
+
"commitTextPassthrough",
|
|
127
|
+
"reason=applyingRust ${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
128
|
+
)
|
|
86
129
|
return super.commitText(text, newCursorPosition)
|
|
87
130
|
}
|
|
88
131
|
if (editorView.editorId == 0L) {
|
|
132
|
+
editorView.recordImeTraceForTesting(
|
|
133
|
+
"commitTextPassthrough",
|
|
134
|
+
"reason=noEditor ${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
135
|
+
)
|
|
89
136
|
return super.commitText(text, newCursorPosition)
|
|
90
137
|
}
|
|
91
138
|
|
|
139
|
+
editorView.recordImeTraceForTesting(
|
|
140
|
+
"commitText",
|
|
141
|
+
"${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
142
|
+
)
|
|
92
143
|
val committedText = text?.toString()
|
|
93
|
-
|
|
94
|
-
|
|
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)
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
override fun commitCompletion(text: CompletionInfo?): Boolean {
|
|
160
|
+
if (!isCurrentInputSessionFor("commitCompletion")) return true
|
|
161
|
+
if (!editorView.isEditable) return true
|
|
162
|
+
if (editorView.isApplyingRustState) {
|
|
163
|
+
return super.commitCompletion(text)
|
|
164
|
+
}
|
|
165
|
+
if (editorView.editorId == 0L) {
|
|
166
|
+
return super.commitCompletion(text)
|
|
167
|
+
}
|
|
168
|
+
editorView.recordImeTraceForTesting(
|
|
169
|
+
"commitCompletion",
|
|
170
|
+
textTraceSummary(text?.text)
|
|
171
|
+
)
|
|
172
|
+
commitTextToEditor(text?.text?.toString(), 1)
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
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
|
+
|
|
203
|
+
override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean {
|
|
204
|
+
if (!isCurrentInputSessionFor("commitCorrection")) return true
|
|
205
|
+
if (!editorView.isEditable) return true
|
|
206
|
+
if (editorView.isApplyingRustState) {
|
|
207
|
+
return super.commitCorrection(correctionInfo)
|
|
208
|
+
}
|
|
209
|
+
if (editorView.editorId == 0L) {
|
|
210
|
+
return super.commitCorrection(correctionInfo)
|
|
211
|
+
}
|
|
212
|
+
val newText = correctionInfo?.newText?.toString()
|
|
213
|
+
if (newText == null) return true
|
|
214
|
+
editorView.recordImeTraceForTesting(
|
|
215
|
+
"commitCorrection",
|
|
216
|
+
"offset=${correctionInfo.offset} oldMissing=${correctionInfo.oldText == null} newLength=${newText.length}"
|
|
217
|
+
)
|
|
218
|
+
if (trackedCompositionReplacementRange() != null) {
|
|
219
|
+
editorView.recordImeTraceForTesting(
|
|
220
|
+
"commitCorrectionComposition",
|
|
221
|
+
"newLength=${newText.length}"
|
|
222
|
+
)
|
|
223
|
+
rememberPendingCompositionCorrectionCommit(newText)
|
|
224
|
+
return true
|
|
225
|
+
}
|
|
226
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
227
|
+
editorView.recordImeTraceForTesting("commitCorrectionRestoredInvalidComposition")
|
|
228
|
+
return true
|
|
229
|
+
}
|
|
230
|
+
val oldText = correctionInfo.oldText?.toString()
|
|
231
|
+
val offset = correctionInfo.offset
|
|
232
|
+
val applied = if (oldText != null && offset >= 0) {
|
|
233
|
+
editorView.handleCorrectionCommit(offset, oldText, newText)
|
|
234
|
+
} else if (oldText == null) {
|
|
235
|
+
editorView.handleMissingOldTextCorrectionCommit(offset, newText)
|
|
236
|
+
} else {
|
|
237
|
+
false
|
|
238
|
+
}
|
|
239
|
+
editorView.recordImeTraceForTesting(
|
|
240
|
+
"commitCorrectionResult",
|
|
241
|
+
"applied=$applied"
|
|
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)
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private fun commitTextToEditor(committedText: String?, newCursorPosition: Int) {
|
|
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
|
+
}
|
|
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
|
+
)
|
|
95
330
|
clearCompositionTracking()
|
|
96
331
|
editorView.runWithTransientInputMutationGuard {
|
|
97
332
|
super.finishComposingText()
|
|
98
333
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
334
|
+
if (committedText != null) {
|
|
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
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
368
|
+
}
|
|
104
369
|
} else {
|
|
370
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
371
|
+
editorView.recordImeTraceForTesting(
|
|
372
|
+
"commitTextRoute",
|
|
373
|
+
"route=restoreInvalidComposition textLength=${committedText?.length ?: 0}"
|
|
374
|
+
)
|
|
375
|
+
return
|
|
376
|
+
}
|
|
105
377
|
clearCompositionTracking()
|
|
106
|
-
|
|
378
|
+
editorView.recordImeTraceForTesting(
|
|
379
|
+
"commitTextRoute",
|
|
380
|
+
"route=plain textLength=${committedText?.length ?: 0}"
|
|
381
|
+
)
|
|
382
|
+
committedText?.let { editorView.handleTextCommit(it, newCursorPosition) }
|
|
107
383
|
}
|
|
108
|
-
|
|
384
|
+
editorView.recordImeTraceForTesting(
|
|
385
|
+
"commitTextRouteDone",
|
|
386
|
+
"textLength=${committedText?.length ?: 0} totalUs=${nanosToMicros(System.nanoTime() - startedAt)}"
|
|
387
|
+
)
|
|
109
388
|
}
|
|
110
389
|
|
|
111
390
|
/**
|
|
@@ -117,32 +396,97 @@ class EditorInputConnection(
|
|
|
117
396
|
* @param afterLength Number of UTF-16 code units to delete after the cursor.
|
|
118
397
|
*/
|
|
119
398
|
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
|
|
120
|
-
if (!
|
|
399
|
+
if (!isCurrentInputSessionFor("deleteSurroundingText")) return true
|
|
400
|
+
if (!editorView.isEditable) return true
|
|
121
401
|
if (editorView.isApplyingRustState) {
|
|
122
402
|
return super.deleteSurroundingText(beforeLength, afterLength)
|
|
123
403
|
}
|
|
404
|
+
editorView.recordImeTraceForTesting(
|
|
405
|
+
"deleteSurroundingText",
|
|
406
|
+
"before=$beforeLength after=$afterLength"
|
|
407
|
+
)
|
|
408
|
+
if (
|
|
409
|
+
editorView.hasInvalidatedCompositionReplacementRangeForEditor() &&
|
|
410
|
+
isNoOpSurroundingDelete(beforeLength, afterLength)
|
|
411
|
+
) {
|
|
412
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
413
|
+
}
|
|
414
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
124
417
|
if (trackedCompositionReplacementRange() != null) {
|
|
418
|
+
val beforeText = editorView.text?.toString()
|
|
419
|
+
var didFallbackDelete = false
|
|
125
420
|
val result = editorView.runWithTransientInputMutationGuard {
|
|
126
|
-
super.deleteSurroundingText(beforeLength, afterLength)
|
|
421
|
+
val baseResult = super.deleteSurroundingText(beforeLength, afterLength)
|
|
422
|
+
if (
|
|
423
|
+
beforeText != null &&
|
|
424
|
+
beforeText == editorView.text?.toString() &&
|
|
425
|
+
(beforeLength > 0 || afterLength > 0)
|
|
426
|
+
) {
|
|
427
|
+
didFallbackDelete = deleteTransientTextAroundSelection(beforeLength, afterLength)
|
|
428
|
+
}
|
|
429
|
+
baseResult
|
|
127
430
|
}
|
|
128
431
|
refreshComposingTextFromEditable()
|
|
129
|
-
return result
|
|
432
|
+
return result || didFallbackDelete
|
|
433
|
+
}
|
|
434
|
+
if (shouldDeferPlainSurroundingDelete(beforeLength, afterLength)) {
|
|
435
|
+
return performDeferredPlainSurroundingDelete(
|
|
436
|
+
beforeLength = beforeLength,
|
|
437
|
+
afterLength = afterLength,
|
|
438
|
+
deleteInCodePoints = false
|
|
439
|
+
)
|
|
130
440
|
}
|
|
131
441
|
editorView.handleDelete(beforeLength, afterLength)
|
|
132
442
|
return true
|
|
133
443
|
}
|
|
134
444
|
|
|
135
445
|
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
|
|
136
|
-
if (!
|
|
446
|
+
if (!isCurrentInputSessionFor("deleteSurroundingTextInCodePoints")) return true
|
|
447
|
+
if (!editorView.isEditable) return true
|
|
137
448
|
if (editorView.isApplyingRustState) {
|
|
138
449
|
return super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
139
450
|
}
|
|
451
|
+
editorView.recordImeTraceForTesting(
|
|
452
|
+
"deleteSurroundingTextInCodePoints",
|
|
453
|
+
"before=$beforeLength after=$afterLength"
|
|
454
|
+
)
|
|
455
|
+
if (
|
|
456
|
+
editorView.hasInvalidatedCompositionReplacementRangeForEditor() &&
|
|
457
|
+
isNoOpSurroundingDelete(beforeLength, afterLength)
|
|
458
|
+
) {
|
|
459
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
460
|
+
}
|
|
461
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
462
|
+
return true
|
|
463
|
+
}
|
|
140
464
|
if (trackedCompositionReplacementRange() != null) {
|
|
465
|
+
val beforeText = editorView.text?.toString()
|
|
466
|
+
var didFallbackDelete = false
|
|
141
467
|
val result = editorView.runWithTransientInputMutationGuard {
|
|
142
|
-
super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
468
|
+
val baseResult = super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
469
|
+
if (
|
|
470
|
+
beforeText != null &&
|
|
471
|
+
beforeText == editorView.text?.toString() &&
|
|
472
|
+
(beforeLength > 0 || afterLength > 0)
|
|
473
|
+
) {
|
|
474
|
+
didFallbackDelete = deleteTransientTextAroundSelectionInCodePoints(
|
|
475
|
+
beforeLength,
|
|
476
|
+
afterLength
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
baseResult
|
|
143
480
|
}
|
|
144
481
|
refreshComposingTextFromEditable()
|
|
145
|
-
return result
|
|
482
|
+
return result || didFallbackDelete
|
|
483
|
+
}
|
|
484
|
+
if (shouldDeferPlainSurroundingDelete(beforeLength, afterLength)) {
|
|
485
|
+
return performDeferredPlainSurroundingDelete(
|
|
486
|
+
beforeLength = beforeLength,
|
|
487
|
+
afterLength = afterLength,
|
|
488
|
+
deleteInCodePoints = true
|
|
489
|
+
)
|
|
146
490
|
}
|
|
147
491
|
|
|
148
492
|
val currentText = editorView.text?.toString().orEmpty()
|
|
@@ -163,6 +507,114 @@ class EditorInputConnection(
|
|
|
163
507
|
return true
|
|
164
508
|
}
|
|
165
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
|
+
|
|
166
618
|
/**
|
|
167
619
|
* Called when the IME sets composing (in-progress) text for CJK/swipe input.
|
|
168
620
|
*
|
|
@@ -171,26 +623,76 @@ class EditorInputConnection(
|
|
|
171
623
|
* to Rust during composition — only when the IME commits or finishes it.
|
|
172
624
|
*/
|
|
173
625
|
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
174
|
-
if (!
|
|
626
|
+
if (!isCurrentInputSessionFor("setComposingText")) return true
|
|
627
|
+
if (!editorView.isEditable) return true
|
|
175
628
|
if (editorView.editorId == 0L) return super.setComposingText(text, newCursorPosition)
|
|
629
|
+
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
630
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
631
|
+
}
|
|
176
632
|
captureCompositionReplacementRangeIfNeeded()
|
|
177
|
-
composingText = text?.toString()
|
|
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
|
+
}
|
|
641
|
+
editorView.recordImeTraceForTesting(
|
|
642
|
+
"setComposingText",
|
|
643
|
+
"${textTraceSummary(text)} cursor=$newCursorPosition adjusted=${adjustedComposingText != composingText}"
|
|
644
|
+
)
|
|
645
|
+
editorView.setComposingTextForEditor(adjustedComposingText)
|
|
178
646
|
return editorView.runWithTransientInputMutationGuard {
|
|
179
|
-
super.setComposingText(
|
|
647
|
+
val result = super.setComposingText(textForBaseConnection, newCursorPosition)
|
|
648
|
+
if (result) {
|
|
649
|
+
editorView.applyTransientComposingTextStyleForEditor()
|
|
650
|
+
}
|
|
651
|
+
result
|
|
180
652
|
}
|
|
181
653
|
}
|
|
182
654
|
|
|
183
655
|
override fun setComposingRegion(start: Int, end: Int): Boolean {
|
|
184
|
-
if (!
|
|
656
|
+
if (!isCurrentInputSessionFor("setComposingRegion")) return true
|
|
657
|
+
if (!editorView.isEditable) return true
|
|
185
658
|
if (editorView.editorId == 0L) return super.setComposingRegion(start, end)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
659
|
+
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
660
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
661
|
+
}
|
|
662
|
+
if (editorView.isCurrentTextAuthorizedForEditor()) {
|
|
663
|
+
editorView.setCompositionReplacementRange(start, end)
|
|
664
|
+
}
|
|
665
|
+
editorView.recordImeTraceForTesting(
|
|
666
|
+
"setComposingRegion",
|
|
667
|
+
"range=$start..$end"
|
|
668
|
+
)
|
|
189
669
|
return editorView.runWithTransientInputMutationGuard {
|
|
190
|
-
super.setComposingRegion(start, end)
|
|
670
|
+
val result = super.setComposingRegion(start, end)
|
|
671
|
+
if (result) {
|
|
672
|
+
editorView.applyTransientComposingTextStyleForEditor()
|
|
673
|
+
}
|
|
674
|
+
result
|
|
191
675
|
}
|
|
192
676
|
}
|
|
193
677
|
|
|
678
|
+
override fun setSelection(start: Int, end: Int): Boolean {
|
|
679
|
+
if (!isCurrentInputSessionFor("setSelection")) return true
|
|
680
|
+
if (!editorView.isEditable) {
|
|
681
|
+
consumeInvalidatedCompositionReplacementRangeAndRestore()
|
|
682
|
+
return true
|
|
683
|
+
}
|
|
684
|
+
if (editorView.isApplyingRustState) {
|
|
685
|
+
return super.setSelection(start, end)
|
|
686
|
+
}
|
|
687
|
+
if (editorView.editorId == 0L) {
|
|
688
|
+
return super.setSelection(start, end)
|
|
689
|
+
}
|
|
690
|
+
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
691
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
692
|
+
}
|
|
693
|
+
return super.setSelection(start, end)
|
|
694
|
+
}
|
|
695
|
+
|
|
194
696
|
/**
|
|
195
697
|
* Called when IME composition is finalized (user selects a candidate or
|
|
196
698
|
* presses space/enter to commit the composing text).
|
|
@@ -199,10 +701,67 @@ class EditorInputConnection(
|
|
|
199
701
|
* so it can capture the result and send it to Rust.
|
|
200
702
|
*/
|
|
201
703
|
override fun finishComposingText(): Boolean {
|
|
202
|
-
if (
|
|
704
|
+
if (applyPendingCompositionCorrectionCommitIfNeeded("finishComposingText")) return true
|
|
705
|
+
return finishComposingTextInternal(blockWhenCompositionWasCancelled = false)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
internal fun flushPendingCompositionForExternalMutation(): Boolean {
|
|
709
|
+
if (!isCurrentInputSessionFor("flushPendingComposition")) return true
|
|
710
|
+
if (!hasPendingComposition()) return true
|
|
711
|
+
return finishComposingTextInternal(blockWhenCompositionWasCancelled = true)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
internal fun hasPendingComposition(): Boolean {
|
|
715
|
+
if (!isCurrentInputSessionFor("hasPendingComposition")) return false
|
|
716
|
+
if (trackedCompositionReplacementRange() != null) return true
|
|
717
|
+
val editable = editorView.text ?: return false
|
|
718
|
+
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
719
|
+
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
720
|
+
return start >= 0 && end >= 0 && start != end
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
internal fun refreshComposingTextFromEditableForEditor() {
|
|
724
|
+
if (!isCurrentInputSessionFor("refreshComposingText")) return
|
|
725
|
+
refreshComposingTextFromEditable()
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
internal fun clearCompositionTrackingForEditor() {
|
|
729
|
+
if (!isCurrentInputSessionFor("clearCompositionTracking")) return
|
|
730
|
+
clearCompositionTracking()
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
internal fun deleteTransientTextForHardwareKeyEvent(event: KeyEvent): Boolean =
|
|
734
|
+
if (!isCurrentInputSession()) {
|
|
735
|
+
false
|
|
736
|
+
} else {
|
|
737
|
+
when (event.keyCode) {
|
|
738
|
+
KeyEvent.KEYCODE_DEL -> deleteTransientTextAroundSelectionInCodePoints(1, 0)
|
|
739
|
+
KeyEvent.KEYCODE_FORWARD_DEL -> deleteTransientTextAroundSelectionInCodePoints(0, 1)
|
|
740
|
+
else -> false
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private fun finishComposingTextInternal(blockWhenCompositionWasCancelled: Boolean): Boolean {
|
|
745
|
+
if (!isCurrentInputSessionFor("finishComposingText")) return true
|
|
746
|
+
if (!editorView.isEditable) {
|
|
747
|
+
clearCompositionTracking()
|
|
748
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
749
|
+
return true
|
|
750
|
+
}
|
|
203
751
|
if (editorView.editorId == 0L) return super.finishComposingText()
|
|
204
|
-
|
|
205
|
-
val
|
|
752
|
+
refreshComposingTextFromEditable()
|
|
753
|
+
val composed = editorView.composingTextForEditor() ?: currentComposingSpanText()
|
|
754
|
+
val trackedReplacementRange = trackedCompositionReplacementRange()
|
|
755
|
+
val didInvalidateReplacementRange = consumeInvalidatedCompositionReplacementRange()
|
|
756
|
+
val replacementRange = if (didInvalidateReplacementRange) {
|
|
757
|
+
null
|
|
758
|
+
} else {
|
|
759
|
+
trackedReplacementRange ?: currentComposingSpanRange()
|
|
760
|
+
}
|
|
761
|
+
editorView.recordImeTraceForTesting(
|
|
762
|
+
"finishComposingText",
|
|
763
|
+
"replacement=${replacementRange?.first}..${replacementRange?.second} composedLength=${composed?.length ?: 0} invalidated=$didInvalidateReplacementRange"
|
|
764
|
+
)
|
|
206
765
|
clearCompositionTracking()
|
|
207
766
|
|
|
208
767
|
// Prevent selection sync while the base connection commits the composed
|
|
@@ -212,46 +771,183 @@ class EditorInputConnection(
|
|
|
212
771
|
}
|
|
213
772
|
|
|
214
773
|
// Now route the composed text through Rust.
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
774
|
+
if (
|
|
775
|
+
replacementRange != null &&
|
|
776
|
+
(!composed.isNullOrEmpty() || replacementRange.first != replacementRange.second)
|
|
777
|
+
) {
|
|
778
|
+
editorView.runWithDeferredRustUpdateApplication {
|
|
779
|
+
editorView.handleCompositionCommit(
|
|
780
|
+
composed.orEmpty(),
|
|
781
|
+
replacementRange.first,
|
|
782
|
+
replacementRange.second
|
|
783
|
+
)
|
|
784
|
+
}
|
|
785
|
+
return true
|
|
786
|
+
} else if (replacementRange != null) {
|
|
787
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
788
|
+
return !blockWhenCompositionWasCancelled
|
|
789
|
+
} else if (didInvalidateReplacementRange) {
|
|
790
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
791
|
+
return !blockWhenCompositionWasCancelled
|
|
221
792
|
}
|
|
222
793
|
return result
|
|
223
794
|
}
|
|
224
795
|
|
|
225
796
|
private fun captureCompositionReplacementRangeIfNeeded() {
|
|
226
|
-
|
|
227
|
-
val start = editorView.selectionStart.coerceAtLeast(0)
|
|
228
|
-
val end = editorView.selectionEnd.coerceAtLeast(0)
|
|
229
|
-
val authorizedLength = editorView.text?.length ?: 0
|
|
230
|
-
composingReplacementStartUtf16 = minOf(start, end).coerceIn(0, authorizedLength)
|
|
231
|
-
composingReplacementEndUtf16 = maxOf(start, end).coerceIn(0, authorizedLength)
|
|
797
|
+
editorView.captureCompositionReplacementRangeIfNeeded()
|
|
232
798
|
}
|
|
233
799
|
|
|
234
800
|
private fun trackedCompositionReplacementRange(): Pair<Int, Int>? {
|
|
235
|
-
|
|
236
|
-
val end = composingReplacementEndUtf16 ?: return null
|
|
237
|
-
return start to end
|
|
801
|
+
return editorView.compositionReplacementRange()
|
|
238
802
|
}
|
|
239
803
|
|
|
240
804
|
private fun clearCompositionTracking() {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
805
|
+
editorView.clearCompositionTrackingForEditor()
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
private fun consumeInvalidatedCompositionReplacementRange(): Boolean =
|
|
809
|
+
editorView.consumeInvalidatedCompositionReplacementRangeForEditor()
|
|
810
|
+
|
|
811
|
+
private fun consumeInvalidatedCompositionReplacementRangeAndRestore(): Boolean {
|
|
812
|
+
if (!consumeInvalidatedCompositionReplacementRange()) return false
|
|
813
|
+
clearCompositionTracking()
|
|
814
|
+
editorView.runWithTransientInputMutationGuard {
|
|
815
|
+
super.finishComposingText()
|
|
816
|
+
}
|
|
817
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
818
|
+
return true
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
private fun finishStaleComposingUpdateAfterInvalidation(): Boolean {
|
|
822
|
+
clearCompositionTracking()
|
|
823
|
+
val result = editorView.runWithTransientInputMutationGuard {
|
|
824
|
+
super.finishComposingText()
|
|
825
|
+
}
|
|
826
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
827
|
+
return result
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
private fun isCurrentInputSession(): Boolean =
|
|
831
|
+
editorView.isInputConnectionCurrentForEditor(boundEditorId, boundGeneration)
|
|
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
|
|
244
844
|
}
|
|
245
845
|
|
|
246
846
|
private fun refreshComposingTextFromEditable() {
|
|
247
847
|
val editable = editorView.text ?: return
|
|
848
|
+
val visibleReplacementText = editorView.composingTextFromVisibleReplacementForEditor()
|
|
849
|
+
if (visibleReplacementText != null) {
|
|
850
|
+
editorView.setComposingTextForEditor(visibleReplacementText)
|
|
851
|
+
return
|
|
852
|
+
}
|
|
248
853
|
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
249
854
|
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
250
855
|
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
251
|
-
|
|
856
|
+
editorView.setComposingTextForEditor(null)
|
|
252
857
|
return
|
|
253
858
|
}
|
|
254
|
-
|
|
859
|
+
editorView.setComposingTextForEditor(editable.subSequence(start, end).toString())
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private fun deleteTransientTextAroundSelection(beforeLength: Int, afterLength: Int): Boolean {
|
|
863
|
+
val editable = editorView.text ?: return false
|
|
864
|
+
val rawStart = editorView.selectionStart
|
|
865
|
+
val rawEnd = editorView.selectionEnd
|
|
866
|
+
if (rawStart < 0 || rawEnd < 0) return false
|
|
867
|
+
val selectionStart = rawStart.coerceIn(0, editable.length)
|
|
868
|
+
val selectionEnd = rawEnd.coerceIn(0, editable.length)
|
|
869
|
+
val normalizedStart = minOf(selectionStart, selectionEnd)
|
|
870
|
+
val normalizedEnd = maxOf(selectionStart, selectionEnd)
|
|
871
|
+
val deleteStart: Int
|
|
872
|
+
val deleteEnd: Int
|
|
873
|
+
if (normalizedStart != normalizedEnd) {
|
|
874
|
+
deleteStart = normalizedStart
|
|
875
|
+
deleteEnd = normalizedEnd
|
|
876
|
+
} else {
|
|
877
|
+
deleteStart = maxOf(0, normalizedStart - beforeLength.coerceAtLeast(0))
|
|
878
|
+
deleteEnd = minOf(editable.length, normalizedEnd + afterLength.coerceAtLeast(0))
|
|
879
|
+
}
|
|
880
|
+
if (deleteStart >= deleteEnd) return false
|
|
881
|
+
val (snappedStart, snappedEnd) = PositionBridge.snapRangeToScalarBoundaries(
|
|
882
|
+
deleteStart,
|
|
883
|
+
deleteEnd,
|
|
884
|
+
editable.toString()
|
|
885
|
+
)
|
|
886
|
+
if (snappedStart >= snappedEnd) return false
|
|
887
|
+
editable.delete(snappedStart, snappedEnd)
|
|
888
|
+
Selection.setSelection(editable, snappedStart.coerceIn(0, editable.length))
|
|
889
|
+
return true
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private fun deleteTransientTextAroundSelectionInCodePoints(
|
|
893
|
+
beforeLength: Int,
|
|
894
|
+
afterLength: Int
|
|
895
|
+
): Boolean {
|
|
896
|
+
val currentText = editorView.text?.toString() ?: return false
|
|
897
|
+
val rawStart = editorView.selectionStart
|
|
898
|
+
val rawEnd = editorView.selectionEnd
|
|
899
|
+
if (rawStart < 0 || rawEnd < 0) return false
|
|
900
|
+
val selectionStart = rawStart.coerceIn(0, currentText.length)
|
|
901
|
+
val selectionEnd = rawEnd.coerceIn(0, currentText.length)
|
|
902
|
+
val normalizedStart = minOf(selectionStart, selectionEnd)
|
|
903
|
+
val normalizedEnd = maxOf(selectionStart, selectionEnd)
|
|
904
|
+
if (normalizedStart != normalizedEnd) {
|
|
905
|
+
return deleteTransientTextAroundSelection(0, 0)
|
|
906
|
+
}
|
|
907
|
+
val beforeUtf16Length = codePointsToUtf16Length(
|
|
908
|
+
text = currentText,
|
|
909
|
+
fromUtf16Offset = normalizedStart,
|
|
910
|
+
codePointCount = beforeLength,
|
|
911
|
+
forward = false
|
|
912
|
+
)
|
|
913
|
+
val afterUtf16Length = codePointsToUtf16Length(
|
|
914
|
+
text = currentText,
|
|
915
|
+
fromUtf16Offset = normalizedEnd,
|
|
916
|
+
codePointCount = afterLength,
|
|
917
|
+
forward = true
|
|
918
|
+
)
|
|
919
|
+
return deleteTransientTextAroundSelection(beforeUtf16Length, afterUtf16Length)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private fun currentComposingSpanText(): String? {
|
|
923
|
+
val editable = editorView.text ?: return null
|
|
924
|
+
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
925
|
+
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
926
|
+
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
927
|
+
return null
|
|
928
|
+
}
|
|
929
|
+
return editable.subSequence(start, end).toString()
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
private fun currentComposingSpanRange(): Pair<Int, Int>? {
|
|
933
|
+
if (!editorView.isCurrentTextAuthorizedForEditor()) return null
|
|
934
|
+
val editable = editorView.text ?: return null
|
|
935
|
+
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
936
|
+
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
937
|
+
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
938
|
+
return null
|
|
939
|
+
}
|
|
940
|
+
return editorView.authorizedUtf16Range(start, end)
|
|
941
|
+
}
|
|
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
|
|
255
951
|
}
|
|
256
952
|
|
|
257
953
|
/**
|
|
@@ -261,20 +957,43 @@ class EditorInputConnection(
|
|
|
261
957
|
* events are passed through to the base connection.
|
|
262
958
|
*/
|
|
263
959
|
override fun sendKeyEvent(event: KeyEvent?): Boolean {
|
|
264
|
-
if (!
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
960
|
+
if (!isCurrentInputSession()) return true
|
|
961
|
+
if (
|
|
962
|
+
event?.action == KeyEvent.ACTION_UP &&
|
|
963
|
+
editorView.hasInvalidatedCompositionReplacementRangeForEditor()
|
|
964
|
+
) {
|
|
965
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
269
966
|
}
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
967
|
+
if (
|
|
968
|
+
shouldConsumeInvalidatedCompositionForKeyEvent(event) &&
|
|
969
|
+
consumeInvalidatedCompositionReplacementRangeAndRestore()
|
|
970
|
+
) {
|
|
971
|
+
return true
|
|
972
|
+
}
|
|
973
|
+
if (!editorView.isEditable && event?.let { editorView.isReadOnlyTextMutationKeyEvent(it) } == true) {
|
|
974
|
+
return true
|
|
975
|
+
}
|
|
976
|
+
if (event != null && editorView.handleCompositionKeyEvent(event) {
|
|
977
|
+
super.sendKeyEvent(event)
|
|
978
|
+
}) {
|
|
979
|
+
return true
|
|
980
|
+
}
|
|
981
|
+
if (event != null && editorView.handleHardwareKeyEvent(event)) {
|
|
982
|
+
return true
|
|
983
|
+
}
|
|
984
|
+
if (event != null && editorView.handlePrintableHardwareKeyEvent(event) {
|
|
985
|
+
super.sendKeyEvent(event)
|
|
986
|
+
}) {
|
|
987
|
+
return true
|
|
277
988
|
}
|
|
278
989
|
return super.sendKeyEvent(event)
|
|
279
990
|
}
|
|
991
|
+
|
|
992
|
+
private fun shouldConsumeInvalidatedCompositionForKeyEvent(event: KeyEvent?): Boolean {
|
|
993
|
+
if (event == null || event.action == KeyEvent.ACTION_UP) return false
|
|
994
|
+
return editorView.isReadOnlyTextMutationKeyEvent(event)
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
private fun isNoOpSurroundingDelete(beforeLength: Int, afterLength: Int): Boolean =
|
|
998
|
+
beforeLength <= 0 && afterLength <= 0
|
|
280
999
|
}
|