@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.
@@ -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 (!editorView.isEditable) return false
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
- val committedText = text?.toString()
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 && committedText != null) {
163
+ if (replacementRange != null) {
95
164
  clearCompositionTracking()
96
165
  editorView.runWithTransientInputMutationGuard {
97
166
  super.finishComposingText()
98
167
  }
99
- editorView.handleCompositionCommit(
100
- committedText,
101
- replacementRange.first,
102
- replacementRange.second
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 (!editorView.isEditable) return false
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 (!editorView.isEditable) return false
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 (!editorView.isEditable) return super.setComposingText(text, newCursorPosition)
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
- composingText = text?.toString()
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 (!editorView.isEditable) return super.setComposingRegion(start, end)
311
+ if (!isCurrentInputSession()) return true
312
+ if (!editorView.isEditable) return true
185
313
  if (editorView.editorId == 0L) return super.setComposingRegion(start, end)
186
- val authorizedLength = editorView.text?.length ?: 0
187
- composingReplacementStartUtf16 = minOf(start, end).coerceIn(0, authorizedLength)
188
- composingReplacementEndUtf16 = maxOf(start, end).coerceIn(0, authorizedLength)
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
- if (!editorView.isEditable) return super.finishComposingText()
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
- val composed = composingText
205
- val replacementRange = trackedCompositionReplacementRange()
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 (replacementRange != null && !composed.isNullOrEmpty()) {
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
- if (trackedCompositionReplacementRange() != null) return
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
- val start = composingReplacementStartUtf16 ?: return null
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
- composingText = null
242
- composingReplacementStartUtf16 = null
243
- composingReplacementEndUtf16 = null
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
- composingText = null
491
+ editorView.setComposingTextForEditor(null)
252
492
  return
253
493
  }
254
- composingText = editable.subSequence(start, end).toString()
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 (!editorView.isEditable) return false
265
- if (event != null && event.action == KeyEvent.ACTION_DOWN) {
266
- if (editorView.handleHardwareKeyDown(event.keyCode, event.isShiftPressed)) {
267
- return true
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 (event != null && event.action == KeyEvent.ACTION_UP) {
271
- when (event.keyCode) {
272
- KeyEvent.KEYCODE_DEL,
273
- KeyEvent.KEYCODE_ENTER,
274
- KeyEvent.KEYCODE_NUMPAD_ENTER,
275
- KeyEvent.KEYCODE_TAB -> return true
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
  }