@openeditor/react-native-prose-editor 0.0.8 → 0.0.10
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/README.md +5 -9
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +23 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +2 -2
- package/android/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +381 -49
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +110 -169
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +1 -1
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +124 -337
- package/dist/EditorTheme.d.ts +8 -0
- package/ios/NativeBlockEditorSurface.swift +323 -61
- package/ios/NativeEditorExpoView.swift +114 -117
- package/ios/NativeEditorModule.swift +1 -1
- package/ios/NativeInputSupport.swift +0 -302
- package/ios/PositionBridge.swift +22 -548
- package/ios/RichTextEditorView.swift +229 -4664
- package/package.json +1 -1
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +0 -50
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +0 -4068
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +0 -1009
- package/android/src/main/java/com/apollohg/editor/InputSnapshotSupport.kt +0 -6
|
@@ -1,1009 +0,0 @@
|
|
|
1
|
-
package com.openeditor.editor
|
|
2
|
-
|
|
3
|
-
import android.os.Handler
|
|
4
|
-
import android.os.Looper
|
|
5
|
-
import android.os.SystemClock
|
|
6
|
-
import android.text.Selection
|
|
7
|
-
import android.view.KeyEvent
|
|
8
|
-
import android.view.inputmethod.BaseInputConnection
|
|
9
|
-
import android.view.inputmethod.CompletionInfo
|
|
10
|
-
import android.view.inputmethod.CorrectionInfo
|
|
11
|
-
import android.view.inputmethod.InputConnection
|
|
12
|
-
import android.view.inputmethod.InputConnectionWrapper
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Custom [InputConnectionWrapper] that intercepts all text input from the soft keyboard
|
|
16
|
-
* and routes it through the Rust editor-core engine via the hosting [EditorEditText].
|
|
17
|
-
*
|
|
18
|
-
* Instead of letting Android's EditText text storage handle insertions and deletions
|
|
19
|
-
* directly, this class captures the user's intent (typing, deleting, IME composition)
|
|
20
|
-
* and delegates to the Rust editor. The Rust editor returns the canonical
|
|
21
|
-
* plain-text input snapshot applied to the hidden EditText adapter; the
|
|
22
|
-
* recursive block tree is rendered by [NativeBlockEditorSurface].
|
|
23
|
-
*
|
|
24
|
-
* ## Composition (IME) Handling
|
|
25
|
-
*
|
|
26
|
-
* For CJK input methods, swipe keyboards, and some autocorrect flows, [setComposingText],
|
|
27
|
-
* [commitText], and [finishComposingText] are used together. During composition, we let
|
|
28
|
-
* the base [InputConnection] render transient composing text, but keep the original
|
|
29
|
-
* Rust-authorized replacement range so the final committed text lands at the correct
|
|
30
|
-
* document position.
|
|
31
|
-
*
|
|
32
|
-
* ## Key Events
|
|
33
|
-
*
|
|
34
|
-
* Hardware keyboard events (backspace, enter) arrive via [sendKeyEvent]. We intercept
|
|
35
|
-
* DEL and ENTER to route through the Rust editor.
|
|
36
|
-
*/
|
|
37
|
-
class EditorInputConnection(
|
|
38
|
-
private val editorView: EditorEditText,
|
|
39
|
-
baseConnection: InputConnection,
|
|
40
|
-
private val boundEditorId: Long,
|
|
41
|
-
private val boundGeneration: Long
|
|
42
|
-
) : InputConnectionWrapper(baseConnection, true) {
|
|
43
|
-
private data class SurroundingDeleteRange(
|
|
44
|
-
val scalarStart: Int,
|
|
45
|
-
val scalarEnd: Int
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
65
|
-
internal fun codePointsToUtf16Length(
|
|
66
|
-
text: String,
|
|
67
|
-
fromUtf16Offset: Int,
|
|
68
|
-
codePointCount: Int,
|
|
69
|
-
forward: Boolean
|
|
70
|
-
): Int {
|
|
71
|
-
if (codePointCount <= 0 || text.isEmpty()) return 0
|
|
72
|
-
|
|
73
|
-
var remaining = codePointCount
|
|
74
|
-
var utf16Length = 0
|
|
75
|
-
|
|
76
|
-
if (forward) {
|
|
77
|
-
var index = fromUtf16Offset.coerceIn(0, text.length)
|
|
78
|
-
while (index < text.length && remaining > 0) {
|
|
79
|
-
val codePoint = Character.codePointAt(text, index)
|
|
80
|
-
val charCount = Character.charCount(codePoint)
|
|
81
|
-
utf16Length += charCount
|
|
82
|
-
index += charCount
|
|
83
|
-
remaining--
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
var index = fromUtf16Offset.coerceIn(0, text.length)
|
|
87
|
-
while (index > 0 && remaining > 0) {
|
|
88
|
-
val codePoint = Character.codePointBefore(text, index)
|
|
89
|
-
val charCount = Character.charCount(codePoint)
|
|
90
|
-
utf16Length += charCount
|
|
91
|
-
index -= charCount
|
|
92
|
-
remaining--
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return utf16Length
|
|
97
|
-
}
|
|
98
|
-
}
|
|
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
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Called when the IME commits finalized text (single character, word,
|
|
117
|
-
* autocomplete selection, etc.).
|
|
118
|
-
*
|
|
119
|
-
* Routes the text through Rust instead of directly inserting into the EditText.
|
|
120
|
-
*/
|
|
121
|
-
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
122
|
-
if (!isCurrentInputSessionFor("commitText")) return true
|
|
123
|
-
if (!editorView.isEditable) return true
|
|
124
|
-
if (editorView.isApplyingRustState) {
|
|
125
|
-
editorView.recordImeTraceForTesting(
|
|
126
|
-
"commitTextPassthrough",
|
|
127
|
-
"reason=applyingRust ${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
128
|
-
)
|
|
129
|
-
return super.commitText(text, newCursorPosition)
|
|
130
|
-
}
|
|
131
|
-
if (editorView.editorId == 0L) {
|
|
132
|
-
editorView.recordImeTraceForTesting(
|
|
133
|
-
"commitTextPassthrough",
|
|
134
|
-
"reason=noEditor ${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
135
|
-
)
|
|
136
|
-
return super.commitText(text, newCursorPosition)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
editorView.recordImeTraceForTesting(
|
|
140
|
-
"commitText",
|
|
141
|
-
"${textTraceSummary(text)} cursor=$newCursorPosition"
|
|
142
|
-
)
|
|
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)
|
|
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
|
-
)
|
|
330
|
-
clearCompositionTracking()
|
|
331
|
-
editorView.runWithTransientInputMutationGuard {
|
|
332
|
-
super.finishComposingText()
|
|
333
|
-
}
|
|
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
|
-
}
|
|
369
|
-
} else {
|
|
370
|
-
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
371
|
-
editorView.recordImeTraceForTesting(
|
|
372
|
-
"commitTextRoute",
|
|
373
|
-
"route=restoreInvalidComposition textLength=${committedText?.length ?: 0}"
|
|
374
|
-
)
|
|
375
|
-
return
|
|
376
|
-
}
|
|
377
|
-
clearCompositionTracking()
|
|
378
|
-
editorView.recordImeTraceForTesting(
|
|
379
|
-
"commitTextRoute",
|
|
380
|
-
"route=plain textLength=${committedText?.length ?: 0}"
|
|
381
|
-
)
|
|
382
|
-
committedText?.let { editorView.handleTextCommit(it, newCursorPosition) }
|
|
383
|
-
}
|
|
384
|
-
editorView.recordImeTraceForTesting(
|
|
385
|
-
"commitTextRouteDone",
|
|
386
|
-
"textLength=${committedText?.length ?: 0} totalUs=${nanosToMicros(System.nanoTime() - startedAt)}"
|
|
387
|
-
)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Called when the IME requests deletion of text surrounding the cursor.
|
|
392
|
-
*
|
|
393
|
-
* Routes the deletion through Rust instead of directly modifying the EditText.
|
|
394
|
-
*
|
|
395
|
-
* @param beforeLength Number of UTF-16 code units to delete before the cursor.
|
|
396
|
-
* @param afterLength Number of UTF-16 code units to delete after the cursor.
|
|
397
|
-
*/
|
|
398
|
-
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
|
|
399
|
-
if (!isCurrentInputSessionFor("deleteSurroundingText")) return true
|
|
400
|
-
if (!editorView.isEditable) return true
|
|
401
|
-
if (editorView.isApplyingRustState) {
|
|
402
|
-
return super.deleteSurroundingText(beforeLength, afterLength)
|
|
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
|
-
}
|
|
417
|
-
if (trackedCompositionReplacementRange() != null) {
|
|
418
|
-
val beforeText = editorView.text?.toString()
|
|
419
|
-
var didFallbackDelete = false
|
|
420
|
-
val result = editorView.runWithTransientInputMutationGuard {
|
|
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
|
|
430
|
-
}
|
|
431
|
-
refreshComposingTextFromEditable()
|
|
432
|
-
return result || didFallbackDelete
|
|
433
|
-
}
|
|
434
|
-
if (shouldDeferPlainSurroundingDelete(beforeLength, afterLength)) {
|
|
435
|
-
return performDeferredPlainSurroundingDelete(
|
|
436
|
-
beforeLength = beforeLength,
|
|
437
|
-
afterLength = afterLength,
|
|
438
|
-
deleteInCodePoints = false
|
|
439
|
-
)
|
|
440
|
-
}
|
|
441
|
-
editorView.handleDelete(beforeLength, afterLength)
|
|
442
|
-
return true
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
|
|
446
|
-
if (!isCurrentInputSessionFor("deleteSurroundingTextInCodePoints")) return true
|
|
447
|
-
if (!editorView.isEditable) return true
|
|
448
|
-
if (editorView.isApplyingRustState) {
|
|
449
|
-
return super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
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
|
-
}
|
|
464
|
-
if (trackedCompositionReplacementRange() != null) {
|
|
465
|
-
val beforeText = editorView.text?.toString()
|
|
466
|
-
var didFallbackDelete = false
|
|
467
|
-
val result = editorView.runWithTransientInputMutationGuard {
|
|
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
|
|
480
|
-
}
|
|
481
|
-
refreshComposingTextFromEditable()
|
|
482
|
-
return result || didFallbackDelete
|
|
483
|
-
}
|
|
484
|
-
if (shouldDeferPlainSurroundingDelete(beforeLength, afterLength)) {
|
|
485
|
-
return performDeferredPlainSurroundingDelete(
|
|
486
|
-
beforeLength = beforeLength,
|
|
487
|
-
afterLength = afterLength,
|
|
488
|
-
deleteInCodePoints = true
|
|
489
|
-
)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
val currentText = editorView.text?.toString().orEmpty()
|
|
493
|
-
val cursor = editorView.selectionStart.coerceAtLeast(0)
|
|
494
|
-
val beforeUtf16Length = codePointsToUtf16Length(
|
|
495
|
-
text = currentText,
|
|
496
|
-
fromUtf16Offset = cursor,
|
|
497
|
-
codePointCount = beforeLength,
|
|
498
|
-
forward = false
|
|
499
|
-
)
|
|
500
|
-
val afterUtf16Length = codePointsToUtf16Length(
|
|
501
|
-
text = currentText,
|
|
502
|
-
fromUtf16Offset = editorView.selectionEnd.coerceAtLeast(cursor),
|
|
503
|
-
codePointCount = afterLength,
|
|
504
|
-
forward = true
|
|
505
|
-
)
|
|
506
|
-
editorView.handleDelete(beforeUtf16Length, afterUtf16Length)
|
|
507
|
-
return true
|
|
508
|
-
}
|
|
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
|
-
|
|
618
|
-
/**
|
|
619
|
-
* Called when the IME sets composing (in-progress) text for CJK/swipe input.
|
|
620
|
-
*
|
|
621
|
-
* We let the base InputConnection handle this normally so the user sees
|
|
622
|
-
* the composing text with its underline decoration. The text is NOT sent
|
|
623
|
-
* to Rust during composition — only when the IME commits or finishes it.
|
|
624
|
-
*/
|
|
625
|
-
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
626
|
-
if (!isCurrentInputSessionFor("setComposingText")) return true
|
|
627
|
-
if (!editorView.isEditable) return true
|
|
628
|
-
if (editorView.editorId == 0L) return super.setComposingText(text, newCursorPosition)
|
|
629
|
-
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
630
|
-
return finishStaleComposingUpdateAfterInvalidation()
|
|
631
|
-
}
|
|
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
|
-
}
|
|
641
|
-
editorView.recordImeTraceForTesting(
|
|
642
|
-
"setComposingText",
|
|
643
|
-
"${textTraceSummary(text)} cursor=$newCursorPosition adjusted=${adjustedComposingText != composingText}"
|
|
644
|
-
)
|
|
645
|
-
editorView.setComposingTextForEditor(adjustedComposingText)
|
|
646
|
-
return editorView.runWithTransientInputMutationGuard {
|
|
647
|
-
editorView.selectSyntheticPlaceholderForTransientComposition()
|
|
648
|
-
val result = super.setComposingText(textForBaseConnection, newCursorPosition)
|
|
649
|
-
if (result) {
|
|
650
|
-
// BaseInputConnection implementations (notably some OEM IMEs,
|
|
651
|
-
// and Robolectric's faithful approximation of them) may leave
|
|
652
|
-
// the composing range selected. Android's cursor contract says
|
|
653
|
-
// a positive newCursorPosition places the caret after the new
|
|
654
|
-
// composing text. Normalize that state here so the next
|
|
655
|
-
// hardware character extends, rather than replaces, it.
|
|
656
|
-
if (newCursorPosition > 0) {
|
|
657
|
-
editorView.collapseSelectionToComposingEndForEditor()
|
|
658
|
-
}
|
|
659
|
-
editorView.applyTransientComposingTextStyleForEditor()
|
|
660
|
-
}
|
|
661
|
-
result
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
override fun setComposingRegion(start: Int, end: Int): Boolean {
|
|
666
|
-
if (!isCurrentInputSessionFor("setComposingRegion")) return true
|
|
667
|
-
if (!editorView.isEditable) return true
|
|
668
|
-
if (editorView.editorId == 0L) return super.setComposingRegion(start, end)
|
|
669
|
-
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
670
|
-
return finishStaleComposingUpdateAfterInvalidation()
|
|
671
|
-
}
|
|
672
|
-
if (editorView.isCurrentTextAuthorizedForEditor()) {
|
|
673
|
-
editorView.setCompositionReplacementRange(start, end)
|
|
674
|
-
}
|
|
675
|
-
editorView.recordImeTraceForTesting(
|
|
676
|
-
"setComposingRegion",
|
|
677
|
-
"range=$start..$end"
|
|
678
|
-
)
|
|
679
|
-
return editorView.runWithTransientInputMutationGuard {
|
|
680
|
-
val result = super.setComposingRegion(start, end)
|
|
681
|
-
if (result) {
|
|
682
|
-
editorView.applyTransientComposingTextStyleForEditor()
|
|
683
|
-
}
|
|
684
|
-
result
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
override fun setSelection(start: Int, end: Int): Boolean {
|
|
689
|
-
if (!isCurrentInputSessionFor("setSelection")) return true
|
|
690
|
-
if (!editorView.isEditable) {
|
|
691
|
-
consumeInvalidatedCompositionReplacementRangeAndRestore()
|
|
692
|
-
return true
|
|
693
|
-
}
|
|
694
|
-
if (editorView.isApplyingRustState) {
|
|
695
|
-
return super.setSelection(start, end)
|
|
696
|
-
}
|
|
697
|
-
if (editorView.editorId == 0L) {
|
|
698
|
-
return super.setSelection(start, end)
|
|
699
|
-
}
|
|
700
|
-
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
701
|
-
return finishStaleComposingUpdateAfterInvalidation()
|
|
702
|
-
}
|
|
703
|
-
return super.setSelection(start, end)
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
/**
|
|
707
|
-
* Called when IME composition is finalized (user selects a candidate or
|
|
708
|
-
* presses space/enter to commit the composing text).
|
|
709
|
-
*
|
|
710
|
-
* At this point, the composed text is final. We notify the [EditorEditText]
|
|
711
|
-
* so it can capture the result and send it to Rust.
|
|
712
|
-
*/
|
|
713
|
-
override fun finishComposingText(): Boolean {
|
|
714
|
-
if (applyPendingCompositionCorrectionCommitIfNeeded("finishComposingText")) return true
|
|
715
|
-
return finishComposingTextInternal(blockWhenCompositionWasCancelled = false)
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
internal fun flushPendingCompositionForExternalMutation(): Boolean {
|
|
719
|
-
if (!isCurrentInputSessionFor("flushPendingComposition")) return true
|
|
720
|
-
if (!hasPendingComposition()) return true
|
|
721
|
-
return finishComposingTextInternal(blockWhenCompositionWasCancelled = true)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
internal fun hasPendingComposition(): Boolean {
|
|
725
|
-
if (!isCurrentInputSessionFor("hasPendingComposition")) return false
|
|
726
|
-
if (trackedCompositionReplacementRange() != null) return true
|
|
727
|
-
val editable = editorView.text ?: return false
|
|
728
|
-
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
729
|
-
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
730
|
-
return start >= 0 && end >= 0 && start != end
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
internal fun refreshComposingTextFromEditableForEditor() {
|
|
734
|
-
if (!isCurrentInputSessionFor("refreshComposingText")) return
|
|
735
|
-
refreshComposingTextFromEditable()
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
internal fun clearCompositionTrackingForEditor() {
|
|
739
|
-
if (!isCurrentInputSessionFor("clearCompositionTracking")) return
|
|
740
|
-
clearCompositionTracking()
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
internal fun deleteTransientTextForHardwareKeyEvent(event: KeyEvent): Boolean =
|
|
744
|
-
if (!isCurrentInputSession()) {
|
|
745
|
-
false
|
|
746
|
-
} else {
|
|
747
|
-
when (event.keyCode) {
|
|
748
|
-
KeyEvent.KEYCODE_DEL -> deleteTransientTextAroundSelectionInCodePoints(1, 0)
|
|
749
|
-
KeyEvent.KEYCODE_FORWARD_DEL -> deleteTransientTextAroundSelectionInCodePoints(0, 1)
|
|
750
|
-
else -> false
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
private fun finishComposingTextInternal(blockWhenCompositionWasCancelled: Boolean): Boolean {
|
|
755
|
-
if (!isCurrentInputSessionFor("finishComposingText")) return true
|
|
756
|
-
if (!editorView.isEditable) {
|
|
757
|
-
clearCompositionTracking()
|
|
758
|
-
editorView.restoreAuthorizedTextIfNeeded()
|
|
759
|
-
return true
|
|
760
|
-
}
|
|
761
|
-
if (editorView.editorId == 0L) return super.finishComposingText()
|
|
762
|
-
refreshComposingTextFromEditable()
|
|
763
|
-
val composed = editorView.composingTextForEditor() ?: currentComposingSpanText()
|
|
764
|
-
val trackedReplacementRange = trackedCompositionReplacementRange()
|
|
765
|
-
val didInvalidateReplacementRange = consumeInvalidatedCompositionReplacementRange()
|
|
766
|
-
val replacementRange = if (didInvalidateReplacementRange) {
|
|
767
|
-
null
|
|
768
|
-
} else {
|
|
769
|
-
trackedReplacementRange ?: currentComposingSpanRange()
|
|
770
|
-
}
|
|
771
|
-
editorView.recordImeTraceForTesting(
|
|
772
|
-
"finishComposingText",
|
|
773
|
-
"replacement=${replacementRange?.first}..${replacementRange?.second} composedLength=${composed?.length ?: 0} invalidated=$didInvalidateReplacementRange"
|
|
774
|
-
)
|
|
775
|
-
clearCompositionTracking()
|
|
776
|
-
|
|
777
|
-
// Prevent selection sync while the base connection commits the composed
|
|
778
|
-
// text, since the Rust document doesn't have it yet.
|
|
779
|
-
val result = editorView.runWithTransientInputMutationGuard {
|
|
780
|
-
super.finishComposingText()
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// Now route the composed text through Rust.
|
|
784
|
-
if (
|
|
785
|
-
replacementRange != null &&
|
|
786
|
-
(!composed.isNullOrEmpty() || replacementRange.first != replacementRange.second)
|
|
787
|
-
) {
|
|
788
|
-
editorView.runWithDeferredRustUpdateApplication {
|
|
789
|
-
editorView.handleCompositionCommit(
|
|
790
|
-
composed.orEmpty(),
|
|
791
|
-
replacementRange.first,
|
|
792
|
-
replacementRange.second
|
|
793
|
-
)
|
|
794
|
-
}
|
|
795
|
-
return true
|
|
796
|
-
} else if (replacementRange != null) {
|
|
797
|
-
editorView.restoreAuthorizedTextIfNeeded()
|
|
798
|
-
return !blockWhenCompositionWasCancelled
|
|
799
|
-
} else if (didInvalidateReplacementRange) {
|
|
800
|
-
editorView.restoreAuthorizedTextIfNeeded()
|
|
801
|
-
return !blockWhenCompositionWasCancelled
|
|
802
|
-
}
|
|
803
|
-
return result
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
private fun captureCompositionReplacementRangeIfNeeded() {
|
|
807
|
-
editorView.captureCompositionReplacementRangeIfNeeded()
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
private fun trackedCompositionReplacementRange(): Pair<Int, Int>? {
|
|
811
|
-
return editorView.compositionReplacementRange()
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
private fun clearCompositionTracking() {
|
|
815
|
-
editorView.clearCompositionTrackingForEditor()
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
private fun consumeInvalidatedCompositionReplacementRange(): Boolean =
|
|
819
|
-
editorView.consumeInvalidatedCompositionReplacementRangeForEditor()
|
|
820
|
-
|
|
821
|
-
private fun consumeInvalidatedCompositionReplacementRangeAndRestore(): Boolean {
|
|
822
|
-
if (!consumeInvalidatedCompositionReplacementRange()) return false
|
|
823
|
-
clearCompositionTracking()
|
|
824
|
-
editorView.runWithTransientInputMutationGuard {
|
|
825
|
-
super.finishComposingText()
|
|
826
|
-
}
|
|
827
|
-
editorView.restoreAuthorizedTextIfNeeded()
|
|
828
|
-
return true
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
private fun finishStaleComposingUpdateAfterInvalidation(): Boolean {
|
|
832
|
-
clearCompositionTracking()
|
|
833
|
-
val result = editorView.runWithTransientInputMutationGuard {
|
|
834
|
-
super.finishComposingText()
|
|
835
|
-
}
|
|
836
|
-
editorView.restoreAuthorizedTextIfNeeded()
|
|
837
|
-
return result
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
private fun isCurrentInputSession(): Boolean =
|
|
841
|
-
editorView.isInputConnectionCurrentForEditor(boundEditorId, boundGeneration)
|
|
842
|
-
|
|
843
|
-
private fun nanosToMicros(nanos: Long): Long = nanos / 1_000L
|
|
844
|
-
|
|
845
|
-
private fun isCurrentInputSessionFor(event: String): Boolean {
|
|
846
|
-
val isCurrent = isCurrentInputSession()
|
|
847
|
-
if (!isCurrent) {
|
|
848
|
-
editorView.recordImeTraceForTesting(
|
|
849
|
-
"${event}Ignored",
|
|
850
|
-
"reason=stale boundEditor=$boundEditorId boundGen=$boundGeneration"
|
|
851
|
-
)
|
|
852
|
-
}
|
|
853
|
-
return isCurrent
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
private fun refreshComposingTextFromEditable() {
|
|
857
|
-
val editable = editorView.text ?: return
|
|
858
|
-
val visibleReplacementText = editorView.composingTextFromVisibleReplacementForEditor()
|
|
859
|
-
if (visibleReplacementText != null) {
|
|
860
|
-
editorView.setComposingTextForEditor(visibleReplacementText)
|
|
861
|
-
return
|
|
862
|
-
}
|
|
863
|
-
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
864
|
-
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
865
|
-
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
866
|
-
editorView.setComposingTextForEditor(null)
|
|
867
|
-
return
|
|
868
|
-
}
|
|
869
|
-
editorView.setComposingTextForEditor(editable.subSequence(start, end).toString())
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
private fun deleteTransientTextAroundSelection(beforeLength: Int, afterLength: Int): Boolean {
|
|
873
|
-
val editable = editorView.text ?: return false
|
|
874
|
-
val rawStart = editorView.selectionStart
|
|
875
|
-
val rawEnd = editorView.selectionEnd
|
|
876
|
-
if (rawStart < 0 || rawEnd < 0) return false
|
|
877
|
-
val selectionStart = rawStart.coerceIn(0, editable.length)
|
|
878
|
-
val selectionEnd = rawEnd.coerceIn(0, editable.length)
|
|
879
|
-
val normalizedStart = minOf(selectionStart, selectionEnd)
|
|
880
|
-
val normalizedEnd = maxOf(selectionStart, selectionEnd)
|
|
881
|
-
val deleteStart: Int
|
|
882
|
-
val deleteEnd: Int
|
|
883
|
-
if (normalizedStart != normalizedEnd) {
|
|
884
|
-
deleteStart = normalizedStart
|
|
885
|
-
deleteEnd = normalizedEnd
|
|
886
|
-
} else {
|
|
887
|
-
deleteStart = maxOf(0, normalizedStart - beforeLength.coerceAtLeast(0))
|
|
888
|
-
deleteEnd = minOf(editable.length, normalizedEnd + afterLength.coerceAtLeast(0))
|
|
889
|
-
}
|
|
890
|
-
if (deleteStart >= deleteEnd) return false
|
|
891
|
-
val (snappedStart, snappedEnd) = PositionBridge.snapRangeToScalarBoundaries(
|
|
892
|
-
deleteStart,
|
|
893
|
-
deleteEnd,
|
|
894
|
-
editable.toString()
|
|
895
|
-
)
|
|
896
|
-
if (snappedStart >= snappedEnd) return false
|
|
897
|
-
editable.delete(snappedStart, snappedEnd)
|
|
898
|
-
Selection.setSelection(editable, snappedStart.coerceIn(0, editable.length))
|
|
899
|
-
return true
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
private fun deleteTransientTextAroundSelectionInCodePoints(
|
|
903
|
-
beforeLength: Int,
|
|
904
|
-
afterLength: Int
|
|
905
|
-
): Boolean {
|
|
906
|
-
val currentText = editorView.text?.toString() ?: return false
|
|
907
|
-
val rawStart = editorView.selectionStart
|
|
908
|
-
val rawEnd = editorView.selectionEnd
|
|
909
|
-
if (rawStart < 0 || rawEnd < 0) return false
|
|
910
|
-
val selectionStart = rawStart.coerceIn(0, currentText.length)
|
|
911
|
-
val selectionEnd = rawEnd.coerceIn(0, currentText.length)
|
|
912
|
-
val normalizedStart = minOf(selectionStart, selectionEnd)
|
|
913
|
-
val normalizedEnd = maxOf(selectionStart, selectionEnd)
|
|
914
|
-
if (normalizedStart != normalizedEnd) {
|
|
915
|
-
return deleteTransientTextAroundSelection(0, 0)
|
|
916
|
-
}
|
|
917
|
-
val beforeUtf16Length = codePointsToUtf16Length(
|
|
918
|
-
text = currentText,
|
|
919
|
-
fromUtf16Offset = normalizedStart,
|
|
920
|
-
codePointCount = beforeLength,
|
|
921
|
-
forward = false
|
|
922
|
-
)
|
|
923
|
-
val afterUtf16Length = codePointsToUtf16Length(
|
|
924
|
-
text = currentText,
|
|
925
|
-
fromUtf16Offset = normalizedEnd,
|
|
926
|
-
codePointCount = afterLength,
|
|
927
|
-
forward = true
|
|
928
|
-
)
|
|
929
|
-
return deleteTransientTextAroundSelection(beforeUtf16Length, afterUtf16Length)
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
private fun currentComposingSpanText(): String? {
|
|
933
|
-
val editable = editorView.text ?: return null
|
|
934
|
-
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
935
|
-
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
936
|
-
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
937
|
-
return null
|
|
938
|
-
}
|
|
939
|
-
return editable.subSequence(start, end).toString()
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
private fun currentComposingSpanRange(): Pair<Int, Int>? {
|
|
943
|
-
if (!editorView.isCurrentTextAuthorizedForEditor()) return null
|
|
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 editorView.authorizedUtf16Range(start, end)
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
private fun currentComposingSpanRawRange(): Pair<Int, Int>? {
|
|
954
|
-
val editable = editorView.text ?: return null
|
|
955
|
-
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
956
|
-
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
957
|
-
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
958
|
-
return null
|
|
959
|
-
}
|
|
960
|
-
return start to end
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
/**
|
|
964
|
-
* Called for hardware keyboard key events.
|
|
965
|
-
*
|
|
966
|
-
* Intercepts DEL (backspace) and ENTER to route through Rust. Other key
|
|
967
|
-
* events are passed through to the base connection.
|
|
968
|
-
*/
|
|
969
|
-
override fun sendKeyEvent(event: KeyEvent?): Boolean {
|
|
970
|
-
if (!isCurrentInputSession()) return true
|
|
971
|
-
if (
|
|
972
|
-
event?.action == KeyEvent.ACTION_UP &&
|
|
973
|
-
editorView.hasInvalidatedCompositionReplacementRangeForEditor()
|
|
974
|
-
) {
|
|
975
|
-
return finishStaleComposingUpdateAfterInvalidation()
|
|
976
|
-
}
|
|
977
|
-
if (
|
|
978
|
-
shouldConsumeInvalidatedCompositionForKeyEvent(event) &&
|
|
979
|
-
consumeInvalidatedCompositionReplacementRangeAndRestore()
|
|
980
|
-
) {
|
|
981
|
-
return true
|
|
982
|
-
}
|
|
983
|
-
if (!editorView.isEditable && event?.let { editorView.isReadOnlyTextMutationKeyEvent(it) } == true) {
|
|
984
|
-
return true
|
|
985
|
-
}
|
|
986
|
-
if (event != null && editorView.handleCompositionKeyEvent(event) {
|
|
987
|
-
super.sendKeyEvent(event)
|
|
988
|
-
}) {
|
|
989
|
-
return true
|
|
990
|
-
}
|
|
991
|
-
if (event != null && editorView.handleHardwareKeyEvent(event)) {
|
|
992
|
-
return true
|
|
993
|
-
}
|
|
994
|
-
if (event != null && editorView.handlePrintableHardwareKeyEvent(event) {
|
|
995
|
-
super.sendKeyEvent(event)
|
|
996
|
-
}) {
|
|
997
|
-
return true
|
|
998
|
-
}
|
|
999
|
-
return super.sendKeyEvent(event)
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
private fun shouldConsumeInvalidatedCompositionForKeyEvent(event: KeyEvent?): Boolean {
|
|
1003
|
-
if (event == null || event.action == KeyEvent.ACTION_UP) return false
|
|
1004
|
-
return editorView.isReadOnlyTextMutationKeyEvent(event)
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
private fun isNoOpSurroundingDelete(beforeLength: Int, afterLength: Int): Boolean =
|
|
1008
|
-
beforeLength <= 0 && afterLength <= 0
|
|
1009
|
-
}
|