@apollohg/react-native-prose-editor 0.5.15 → 0.5.17
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 +1454 -127
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +403 -59
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1666 -79
- 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 +787 -51
- 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,10 @@
|
|
|
1
1
|
package com.apollohg.editor
|
|
2
2
|
|
|
3
|
+
import android.text.Selection
|
|
3
4
|
import android.view.KeyEvent
|
|
4
5
|
import android.view.inputmethod.BaseInputConnection
|
|
6
|
+
import android.view.inputmethod.CompletionInfo
|
|
7
|
+
import android.view.inputmethod.CorrectionInfo
|
|
5
8
|
import android.view.inputmethod.InputConnection
|
|
6
9
|
import android.view.inputmethod.InputConnectionWrapper
|
|
7
10
|
|
|
@@ -30,7 +33,9 @@ import android.view.inputmethod.InputConnectionWrapper
|
|
|
30
33
|
*/
|
|
31
34
|
class EditorInputConnection(
|
|
32
35
|
private val editorView: EditorEditText,
|
|
33
|
-
baseConnection: InputConnection
|
|
36
|
+
baseConnection: InputConnection,
|
|
37
|
+
private val boundEditorId: Long,
|
|
38
|
+
private val boundGeneration: Long
|
|
34
39
|
) : InputConnectionWrapper(baseConnection, true) {
|
|
35
40
|
|
|
36
41
|
companion object {
|
|
@@ -69,11 +74,6 @@ class EditorInputConnection(
|
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
/** Tracks the current composing text for CJK/swipe input. */
|
|
73
|
-
private var composingText: String? = null
|
|
74
|
-
private var composingReplacementStartUtf16: Int? = null
|
|
75
|
-
private var composingReplacementEndUtf16: Int? = null
|
|
76
|
-
|
|
77
77
|
/**
|
|
78
78
|
* Called when the IME commits finalized text (single character, word,
|
|
79
79
|
* autocomplete selection, etc.).
|
|
@@ -81,7 +81,8 @@ class EditorInputConnection(
|
|
|
81
81
|
* Routes the text through Rust instead of directly inserting into the EditText.
|
|
82
82
|
*/
|
|
83
83
|
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
84
|
-
if (!
|
|
84
|
+
if (!isCurrentInputSession()) return true
|
|
85
|
+
if (!editorView.isEditable) return true
|
|
85
86
|
if (editorView.isApplyingRustState) {
|
|
86
87
|
return super.commitText(text, newCursorPosition)
|
|
87
88
|
}
|
|
@@ -89,23 +90,98 @@ class EditorInputConnection(
|
|
|
89
90
|
return super.commitText(text, newCursorPosition)
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
editorView.recordImeTraceForTesting(
|
|
94
|
+
"commitText",
|
|
95
|
+
"textLength=${text?.length ?: 0} cursor=$newCursorPosition"
|
|
96
|
+
)
|
|
97
|
+
commitTextToEditor(text?.toString(), newCursorPosition)
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun commitCompletion(text: CompletionInfo?): Boolean {
|
|
102
|
+
if (!isCurrentInputSession()) return true
|
|
103
|
+
if (!editorView.isEditable) return true
|
|
104
|
+
if (editorView.isApplyingRustState) {
|
|
105
|
+
return super.commitCompletion(text)
|
|
106
|
+
}
|
|
107
|
+
if (editorView.editorId == 0L) {
|
|
108
|
+
return super.commitCompletion(text)
|
|
109
|
+
}
|
|
110
|
+
editorView.recordImeTraceForTesting(
|
|
111
|
+
"commitCompletion",
|
|
112
|
+
"textLength=${text?.text?.length ?: 0}"
|
|
113
|
+
)
|
|
114
|
+
commitTextToEditor(text?.text?.toString(), 1)
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean {
|
|
119
|
+
if (!isCurrentInputSession()) return true
|
|
120
|
+
if (!editorView.isEditable) return true
|
|
121
|
+
if (editorView.isApplyingRustState) {
|
|
122
|
+
return super.commitCorrection(correctionInfo)
|
|
123
|
+
}
|
|
124
|
+
if (editorView.editorId == 0L) {
|
|
125
|
+
return super.commitCorrection(correctionInfo)
|
|
126
|
+
}
|
|
127
|
+
val newText = correctionInfo?.newText?.toString()
|
|
128
|
+
if (newText == null) return true
|
|
129
|
+
editorView.recordImeTraceForTesting(
|
|
130
|
+
"commitCorrection",
|
|
131
|
+
"offset=${correctionInfo.offset} oldMissing=${correctionInfo.oldText == null} newLength=${newText.length}"
|
|
132
|
+
)
|
|
133
|
+
if (trackedCompositionReplacementRange() != null) {
|
|
134
|
+
editorView.recordImeTraceForTesting(
|
|
135
|
+
"commitCorrectionComposition",
|
|
136
|
+
"newLength=${newText.length}"
|
|
137
|
+
)
|
|
138
|
+
commitTextToEditor(newText, 1)
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
142
|
+
editorView.recordImeTraceForTesting("commitCorrectionRestoredInvalidComposition")
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
val oldText = correctionInfo.oldText?.toString()
|
|
146
|
+
val offset = correctionInfo.offset
|
|
147
|
+
val applied = if (oldText != null && offset >= 0) {
|
|
148
|
+
editorView.handleCorrectionCommit(offset, oldText, newText)
|
|
149
|
+
} else if (oldText == null) {
|
|
150
|
+
editorView.handleMissingOldTextCorrectionCommit(offset, newText)
|
|
151
|
+
} else {
|
|
152
|
+
false
|
|
153
|
+
}
|
|
154
|
+
editorView.recordImeTraceForTesting(
|
|
155
|
+
"commitCorrectionResult",
|
|
156
|
+
"applied=$applied"
|
|
157
|
+
)
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun commitTextToEditor(committedText: String?, newCursorPosition: Int) {
|
|
93
162
|
val replacementRange = trackedCompositionReplacementRange()
|
|
94
|
-
if (replacementRange != null
|
|
163
|
+
if (replacementRange != null) {
|
|
95
164
|
clearCompositionTracking()
|
|
96
165
|
editorView.runWithTransientInputMutationGuard {
|
|
97
166
|
super.finishComposingText()
|
|
98
167
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
168
|
+
if (committedText != null) {
|
|
169
|
+
editorView.handleCompositionCommit(
|
|
170
|
+
committedText,
|
|
171
|
+
replacementRange.first,
|
|
172
|
+
replacementRange.second,
|
|
173
|
+
newCursorPosition
|
|
174
|
+
)
|
|
175
|
+
} else {
|
|
176
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
177
|
+
}
|
|
104
178
|
} else {
|
|
179
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
105
182
|
clearCompositionTracking()
|
|
106
|
-
committedText?.let { editorView.handleTextCommit(it) }
|
|
183
|
+
committedText?.let { editorView.handleTextCommit(it, newCursorPosition) }
|
|
107
184
|
}
|
|
108
|
-
return true
|
|
109
185
|
}
|
|
110
186
|
|
|
111
187
|
/**
|
|
@@ -117,32 +193,75 @@ class EditorInputConnection(
|
|
|
117
193
|
* @param afterLength Number of UTF-16 code units to delete after the cursor.
|
|
118
194
|
*/
|
|
119
195
|
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
|
|
120
|
-
if (!
|
|
196
|
+
if (!isCurrentInputSession()) return true
|
|
197
|
+
if (!editorView.isEditable) return true
|
|
121
198
|
if (editorView.isApplyingRustState) {
|
|
122
199
|
return super.deleteSurroundingText(beforeLength, afterLength)
|
|
123
200
|
}
|
|
201
|
+
if (
|
|
202
|
+
editorView.hasInvalidatedCompositionReplacementRangeForEditor() &&
|
|
203
|
+
isNoOpSurroundingDelete(beforeLength, afterLength)
|
|
204
|
+
) {
|
|
205
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
206
|
+
}
|
|
207
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
124
210
|
if (trackedCompositionReplacementRange() != null) {
|
|
211
|
+
val beforeText = editorView.text?.toString()
|
|
212
|
+
var didFallbackDelete = false
|
|
125
213
|
val result = editorView.runWithTransientInputMutationGuard {
|
|
126
|
-
super.deleteSurroundingText(beforeLength, afterLength)
|
|
214
|
+
val baseResult = super.deleteSurroundingText(beforeLength, afterLength)
|
|
215
|
+
if (
|
|
216
|
+
beforeText != null &&
|
|
217
|
+
beforeText == editorView.text?.toString() &&
|
|
218
|
+
(beforeLength > 0 || afterLength > 0)
|
|
219
|
+
) {
|
|
220
|
+
didFallbackDelete = deleteTransientTextAroundSelection(beforeLength, afterLength)
|
|
221
|
+
}
|
|
222
|
+
baseResult
|
|
127
223
|
}
|
|
128
224
|
refreshComposingTextFromEditable()
|
|
129
|
-
return result
|
|
225
|
+
return result || didFallbackDelete
|
|
130
226
|
}
|
|
131
227
|
editorView.handleDelete(beforeLength, afterLength)
|
|
132
228
|
return true
|
|
133
229
|
}
|
|
134
230
|
|
|
135
231
|
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
|
|
136
|
-
if (!
|
|
232
|
+
if (!isCurrentInputSession()) return true
|
|
233
|
+
if (!editorView.isEditable) return true
|
|
137
234
|
if (editorView.isApplyingRustState) {
|
|
138
235
|
return super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
139
236
|
}
|
|
237
|
+
if (
|
|
238
|
+
editorView.hasInvalidatedCompositionReplacementRangeForEditor() &&
|
|
239
|
+
isNoOpSurroundingDelete(beforeLength, afterLength)
|
|
240
|
+
) {
|
|
241
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
242
|
+
}
|
|
243
|
+
if (consumeInvalidatedCompositionReplacementRangeAndRestore()) {
|
|
244
|
+
return true
|
|
245
|
+
}
|
|
140
246
|
if (trackedCompositionReplacementRange() != null) {
|
|
247
|
+
val beforeText = editorView.text?.toString()
|
|
248
|
+
var didFallbackDelete = false
|
|
141
249
|
val result = editorView.runWithTransientInputMutationGuard {
|
|
142
|
-
super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
250
|
+
val baseResult = super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
251
|
+
if (
|
|
252
|
+
beforeText != null &&
|
|
253
|
+
beforeText == editorView.text?.toString() &&
|
|
254
|
+
(beforeLength > 0 || afterLength > 0)
|
|
255
|
+
) {
|
|
256
|
+
didFallbackDelete = deleteTransientTextAroundSelectionInCodePoints(
|
|
257
|
+
beforeLength,
|
|
258
|
+
afterLength
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
baseResult
|
|
143
262
|
}
|
|
144
263
|
refreshComposingTextFromEditable()
|
|
145
|
-
return result
|
|
264
|
+
return result || didFallbackDelete
|
|
146
265
|
}
|
|
147
266
|
|
|
148
267
|
val currentText = editorView.text?.toString().orEmpty()
|
|
@@ -171,26 +290,60 @@ class EditorInputConnection(
|
|
|
171
290
|
* to Rust during composition — only when the IME commits or finishes it.
|
|
172
291
|
*/
|
|
173
292
|
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
174
|
-
if (!
|
|
293
|
+
if (!isCurrentInputSession()) return true
|
|
294
|
+
if (!editorView.isEditable) return true
|
|
175
295
|
if (editorView.editorId == 0L) return super.setComposingText(text, newCursorPosition)
|
|
296
|
+
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
297
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
298
|
+
}
|
|
176
299
|
captureCompositionReplacementRangeIfNeeded()
|
|
177
|
-
|
|
300
|
+
editorView.recordImeTraceForTesting(
|
|
301
|
+
"setComposingText",
|
|
302
|
+
"textLength=${text?.length ?: 0} cursor=$newCursorPosition"
|
|
303
|
+
)
|
|
304
|
+
editorView.setComposingTextForEditor(text?.toString())
|
|
178
305
|
return editorView.runWithTransientInputMutationGuard {
|
|
179
306
|
super.setComposingText(text, newCursorPosition)
|
|
180
307
|
}
|
|
181
308
|
}
|
|
182
309
|
|
|
183
310
|
override fun setComposingRegion(start: Int, end: Int): Boolean {
|
|
184
|
-
if (!
|
|
311
|
+
if (!isCurrentInputSession()) return true
|
|
312
|
+
if (!editorView.isEditable) return true
|
|
185
313
|
if (editorView.editorId == 0L) return super.setComposingRegion(start, end)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
314
|
+
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
315
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
316
|
+
}
|
|
317
|
+
if (editorView.isCurrentTextAuthorizedForEditor()) {
|
|
318
|
+
editorView.setCompositionReplacementRange(start, end)
|
|
319
|
+
}
|
|
320
|
+
editorView.recordImeTraceForTesting(
|
|
321
|
+
"setComposingRegion",
|
|
322
|
+
"range=$start..$end"
|
|
323
|
+
)
|
|
189
324
|
return editorView.runWithTransientInputMutationGuard {
|
|
190
325
|
super.setComposingRegion(start, end)
|
|
191
326
|
}
|
|
192
327
|
}
|
|
193
328
|
|
|
329
|
+
override fun setSelection(start: Int, end: Int): Boolean {
|
|
330
|
+
if (!isCurrentInputSession()) return true
|
|
331
|
+
if (!editorView.isEditable) {
|
|
332
|
+
consumeInvalidatedCompositionReplacementRangeAndRestore()
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
335
|
+
if (editorView.isApplyingRustState) {
|
|
336
|
+
return super.setSelection(start, end)
|
|
337
|
+
}
|
|
338
|
+
if (editorView.editorId == 0L) {
|
|
339
|
+
return super.setSelection(start, end)
|
|
340
|
+
}
|
|
341
|
+
if (editorView.hasInvalidatedCompositionReplacementRangeForEditor()) {
|
|
342
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
343
|
+
}
|
|
344
|
+
return super.setSelection(start, end)
|
|
345
|
+
}
|
|
346
|
+
|
|
194
347
|
/**
|
|
195
348
|
* Called when IME composition is finalized (user selects a candidate or
|
|
196
349
|
* presses space/enter to commit the composing text).
|
|
@@ -199,10 +352,66 @@ class EditorInputConnection(
|
|
|
199
352
|
* so it can capture the result and send it to Rust.
|
|
200
353
|
*/
|
|
201
354
|
override fun finishComposingText(): Boolean {
|
|
202
|
-
|
|
355
|
+
return finishComposingTextInternal(blockWhenCompositionWasCancelled = false)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
internal fun flushPendingCompositionForExternalMutation(): Boolean {
|
|
359
|
+
if (!isCurrentInputSession()) return true
|
|
360
|
+
if (!hasPendingComposition()) return true
|
|
361
|
+
return finishComposingTextInternal(blockWhenCompositionWasCancelled = true)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
internal fun hasPendingComposition(): Boolean {
|
|
365
|
+
if (!isCurrentInputSession()) return false
|
|
366
|
+
if (trackedCompositionReplacementRange() != null) return true
|
|
367
|
+
val editable = editorView.text ?: return false
|
|
368
|
+
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
369
|
+
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
370
|
+
return start >= 0 && end >= 0 && start != end
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
internal fun refreshComposingTextFromEditableForEditor() {
|
|
374
|
+
if (!isCurrentInputSession()) return
|
|
375
|
+
refreshComposingTextFromEditable()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
internal fun clearCompositionTrackingForEditor() {
|
|
379
|
+
if (!isCurrentInputSession()) return
|
|
380
|
+
clearCompositionTracking()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
internal fun deleteTransientTextForHardwareKeyEvent(event: KeyEvent): Boolean =
|
|
384
|
+
if (!isCurrentInputSession()) {
|
|
385
|
+
false
|
|
386
|
+
} else {
|
|
387
|
+
when (event.keyCode) {
|
|
388
|
+
KeyEvent.KEYCODE_DEL -> deleteTransientTextAroundSelectionInCodePoints(1, 0)
|
|
389
|
+
KeyEvent.KEYCODE_FORWARD_DEL -> deleteTransientTextAroundSelectionInCodePoints(0, 1)
|
|
390
|
+
else -> false
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private fun finishComposingTextInternal(blockWhenCompositionWasCancelled: Boolean): Boolean {
|
|
395
|
+
if (!isCurrentInputSession()) return true
|
|
396
|
+
if (!editorView.isEditable) {
|
|
397
|
+
clearCompositionTracking()
|
|
398
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
399
|
+
return true
|
|
400
|
+
}
|
|
203
401
|
if (editorView.editorId == 0L) return super.finishComposingText()
|
|
204
|
-
|
|
205
|
-
val
|
|
402
|
+
refreshComposingTextFromEditable()
|
|
403
|
+
val composed = editorView.composingTextForEditor() ?: currentComposingSpanText()
|
|
404
|
+
val trackedReplacementRange = trackedCompositionReplacementRange()
|
|
405
|
+
val didInvalidateReplacementRange = consumeInvalidatedCompositionReplacementRange()
|
|
406
|
+
val replacementRange = if (didInvalidateReplacementRange) {
|
|
407
|
+
null
|
|
408
|
+
} else {
|
|
409
|
+
trackedReplacementRange ?: currentComposingSpanRange()
|
|
410
|
+
}
|
|
411
|
+
editorView.recordImeTraceForTesting(
|
|
412
|
+
"finishComposingText",
|
|
413
|
+
"replacement=${replacementRange?.first}..${replacementRange?.second} composedLength=${composed?.length ?: 0} invalidated=$didInvalidateReplacementRange"
|
|
414
|
+
)
|
|
206
415
|
clearCompositionTracking()
|
|
207
416
|
|
|
208
417
|
// Prevent selection sync while the base connection commits the composed
|
|
@@ -212,46 +421,158 @@ class EditorInputConnection(
|
|
|
212
421
|
}
|
|
213
422
|
|
|
214
423
|
// Now route the composed text through Rust.
|
|
215
|
-
if (
|
|
424
|
+
if (
|
|
425
|
+
replacementRange != null &&
|
|
426
|
+
(!composed.isNullOrEmpty() || replacementRange.first != replacementRange.second)
|
|
427
|
+
) {
|
|
216
428
|
editorView.handleCompositionCommit(
|
|
217
|
-
composed,
|
|
429
|
+
composed.orEmpty(),
|
|
218
430
|
replacementRange.first,
|
|
219
431
|
replacementRange.second
|
|
220
432
|
)
|
|
433
|
+
return true
|
|
434
|
+
} else if (replacementRange != null) {
|
|
435
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
436
|
+
return !blockWhenCompositionWasCancelled
|
|
437
|
+
} else if (didInvalidateReplacementRange) {
|
|
438
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
439
|
+
return !blockWhenCompositionWasCancelled
|
|
221
440
|
}
|
|
222
441
|
return result
|
|
223
442
|
}
|
|
224
443
|
|
|
225
444
|
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)
|
|
445
|
+
editorView.captureCompositionReplacementRangeIfNeeded()
|
|
232
446
|
}
|
|
233
447
|
|
|
234
448
|
private fun trackedCompositionReplacementRange(): Pair<Int, Int>? {
|
|
235
|
-
|
|
236
|
-
val end = composingReplacementEndUtf16 ?: return null
|
|
237
|
-
return start to end
|
|
449
|
+
return editorView.compositionReplacementRange()
|
|
238
450
|
}
|
|
239
451
|
|
|
240
452
|
private fun clearCompositionTracking() {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
453
|
+
editorView.clearCompositionTrackingForEditor()
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private fun consumeInvalidatedCompositionReplacementRange(): Boolean =
|
|
457
|
+
editorView.consumeInvalidatedCompositionReplacementRangeForEditor()
|
|
458
|
+
|
|
459
|
+
private fun consumeInvalidatedCompositionReplacementRangeAndRestore(): Boolean {
|
|
460
|
+
if (!consumeInvalidatedCompositionReplacementRange()) return false
|
|
461
|
+
clearCompositionTracking()
|
|
462
|
+
editorView.runWithTransientInputMutationGuard {
|
|
463
|
+
super.finishComposingText()
|
|
464
|
+
}
|
|
465
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
466
|
+
return true
|
|
244
467
|
}
|
|
245
468
|
|
|
469
|
+
private fun finishStaleComposingUpdateAfterInvalidation(): Boolean {
|
|
470
|
+
clearCompositionTracking()
|
|
471
|
+
val result = editorView.runWithTransientInputMutationGuard {
|
|
472
|
+
super.finishComposingText()
|
|
473
|
+
}
|
|
474
|
+
editorView.restoreAuthorizedTextIfNeeded()
|
|
475
|
+
return result
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private fun isCurrentInputSession(): Boolean =
|
|
479
|
+
editorView.isInputConnectionCurrentForEditor(boundEditorId, boundGeneration)
|
|
480
|
+
|
|
246
481
|
private fun refreshComposingTextFromEditable() {
|
|
247
482
|
val editable = editorView.text ?: return
|
|
483
|
+
val visibleReplacementText = editorView.composingTextFromVisibleReplacementForEditor()
|
|
484
|
+
if (visibleReplacementText != null) {
|
|
485
|
+
editorView.setComposingTextForEditor(visibleReplacementText)
|
|
486
|
+
return
|
|
487
|
+
}
|
|
248
488
|
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
249
489
|
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
250
490
|
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
251
|
-
|
|
491
|
+
editorView.setComposingTextForEditor(null)
|
|
252
492
|
return
|
|
253
493
|
}
|
|
254
|
-
|
|
494
|
+
editorView.setComposingTextForEditor(editable.subSequence(start, end).toString())
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private fun deleteTransientTextAroundSelection(beforeLength: Int, afterLength: Int): Boolean {
|
|
498
|
+
val editable = editorView.text ?: return false
|
|
499
|
+
val rawStart = editorView.selectionStart
|
|
500
|
+
val rawEnd = editorView.selectionEnd
|
|
501
|
+
if (rawStart < 0 || rawEnd < 0) return false
|
|
502
|
+
val selectionStart = rawStart.coerceIn(0, editable.length)
|
|
503
|
+
val selectionEnd = rawEnd.coerceIn(0, editable.length)
|
|
504
|
+
val normalizedStart = minOf(selectionStart, selectionEnd)
|
|
505
|
+
val normalizedEnd = maxOf(selectionStart, selectionEnd)
|
|
506
|
+
val deleteStart: Int
|
|
507
|
+
val deleteEnd: Int
|
|
508
|
+
if (normalizedStart != normalizedEnd) {
|
|
509
|
+
deleteStart = normalizedStart
|
|
510
|
+
deleteEnd = normalizedEnd
|
|
511
|
+
} else {
|
|
512
|
+
deleteStart = maxOf(0, normalizedStart - beforeLength.coerceAtLeast(0))
|
|
513
|
+
deleteEnd = minOf(editable.length, normalizedEnd + afterLength.coerceAtLeast(0))
|
|
514
|
+
}
|
|
515
|
+
if (deleteStart >= deleteEnd) return false
|
|
516
|
+
val (snappedStart, snappedEnd) = PositionBridge.snapRangeToScalarBoundaries(
|
|
517
|
+
deleteStart,
|
|
518
|
+
deleteEnd,
|
|
519
|
+
editable.toString()
|
|
520
|
+
)
|
|
521
|
+
if (snappedStart >= snappedEnd) return false
|
|
522
|
+
editable.delete(snappedStart, snappedEnd)
|
|
523
|
+
Selection.setSelection(editable, snappedStart.coerceIn(0, editable.length))
|
|
524
|
+
return true
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private fun deleteTransientTextAroundSelectionInCodePoints(
|
|
528
|
+
beforeLength: Int,
|
|
529
|
+
afterLength: Int
|
|
530
|
+
): Boolean {
|
|
531
|
+
val currentText = editorView.text?.toString() ?: return false
|
|
532
|
+
val rawStart = editorView.selectionStart
|
|
533
|
+
val rawEnd = editorView.selectionEnd
|
|
534
|
+
if (rawStart < 0 || rawEnd < 0) return false
|
|
535
|
+
val selectionStart = rawStart.coerceIn(0, currentText.length)
|
|
536
|
+
val selectionEnd = rawEnd.coerceIn(0, currentText.length)
|
|
537
|
+
val normalizedStart = minOf(selectionStart, selectionEnd)
|
|
538
|
+
val normalizedEnd = maxOf(selectionStart, selectionEnd)
|
|
539
|
+
if (normalizedStart != normalizedEnd) {
|
|
540
|
+
return deleteTransientTextAroundSelection(0, 0)
|
|
541
|
+
}
|
|
542
|
+
val beforeUtf16Length = codePointsToUtf16Length(
|
|
543
|
+
text = currentText,
|
|
544
|
+
fromUtf16Offset = normalizedStart,
|
|
545
|
+
codePointCount = beforeLength,
|
|
546
|
+
forward = false
|
|
547
|
+
)
|
|
548
|
+
val afterUtf16Length = codePointsToUtf16Length(
|
|
549
|
+
text = currentText,
|
|
550
|
+
fromUtf16Offset = normalizedEnd,
|
|
551
|
+
codePointCount = afterLength,
|
|
552
|
+
forward = true
|
|
553
|
+
)
|
|
554
|
+
return deleteTransientTextAroundSelection(beforeUtf16Length, afterUtf16Length)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private fun currentComposingSpanText(): String? {
|
|
558
|
+
val editable = editorView.text ?: return null
|
|
559
|
+
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
560
|
+
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
561
|
+
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
562
|
+
return null
|
|
563
|
+
}
|
|
564
|
+
return editable.subSequence(start, end).toString()
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private fun currentComposingSpanRange(): Pair<Int, Int>? {
|
|
568
|
+
if (!editorView.isCurrentTextAuthorizedForEditor()) return null
|
|
569
|
+
val editable = editorView.text ?: return null
|
|
570
|
+
val start = BaseInputConnection.getComposingSpanStart(editable)
|
|
571
|
+
val end = BaseInputConnection.getComposingSpanEnd(editable)
|
|
572
|
+
if (start < 0 || end < 0 || start > end || end > editable.length) {
|
|
573
|
+
return null
|
|
574
|
+
}
|
|
575
|
+
return editorView.authorizedUtf16Range(start, end)
|
|
255
576
|
}
|
|
256
577
|
|
|
257
578
|
/**
|
|
@@ -261,20 +582,43 @@ class EditorInputConnection(
|
|
|
261
582
|
* events are passed through to the base connection.
|
|
262
583
|
*/
|
|
263
584
|
override fun sendKeyEvent(event: KeyEvent?): Boolean {
|
|
264
|
-
if (!
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
585
|
+
if (!isCurrentInputSession()) return true
|
|
586
|
+
if (
|
|
587
|
+
event?.action == KeyEvent.ACTION_UP &&
|
|
588
|
+
editorView.hasInvalidatedCompositionReplacementRangeForEditor()
|
|
589
|
+
) {
|
|
590
|
+
return finishStaleComposingUpdateAfterInvalidation()
|
|
269
591
|
}
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
592
|
+
if (
|
|
593
|
+
shouldConsumeInvalidatedCompositionForKeyEvent(event) &&
|
|
594
|
+
consumeInvalidatedCompositionReplacementRangeAndRestore()
|
|
595
|
+
) {
|
|
596
|
+
return true
|
|
597
|
+
}
|
|
598
|
+
if (!editorView.isEditable && event?.let { editorView.isReadOnlyTextMutationKeyEvent(it) } == true) {
|
|
599
|
+
return true
|
|
600
|
+
}
|
|
601
|
+
if (event != null && editorView.handleCompositionKeyEvent(event) {
|
|
602
|
+
super.sendKeyEvent(event)
|
|
603
|
+
}) {
|
|
604
|
+
return true
|
|
605
|
+
}
|
|
606
|
+
if (event != null && editorView.handleHardwareKeyEvent(event)) {
|
|
607
|
+
return true
|
|
608
|
+
}
|
|
609
|
+
if (event != null && editorView.handlePrintableHardwareKeyEvent(event) {
|
|
610
|
+
super.sendKeyEvent(event)
|
|
611
|
+
}) {
|
|
612
|
+
return true
|
|
277
613
|
}
|
|
278
614
|
return super.sendKeyEvent(event)
|
|
279
615
|
}
|
|
616
|
+
|
|
617
|
+
private fun shouldConsumeInvalidatedCompositionForKeyEvent(event: KeyEvent?): Boolean {
|
|
618
|
+
if (event == null || event.action == KeyEvent.ACTION_UP) return false
|
|
619
|
+
return editorView.isReadOnlyTextMutationKeyEvent(event)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private fun isNoOpSurroundingDelete(beforeLength: Int, afterLength: Int): Boolean =
|
|
623
|
+
beforeLength <= 0 && afterLength <= 0
|
|
280
624
|
}
|