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