Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.input.BackspaceCommand
import androidx.compose.ui.text.input.CommitTextCommand
import androidx.compose.ui.text.input.DeleteSurroundingTextCommand
import androidx.compose.ui.text.input.EditCommand
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.SetComposingTextCommand
import androidx.compose.ui.text.input.SetSelectionCommand
Expand Down Expand Up @@ -156,6 +157,21 @@ internal abstract class NativeInputEventsProcessor(
collectedEvents.clear()
}

private fun InputEventExt.createDeleteWordCommand(): EditCommand? {
val shouldTriggerDelete = when {
lastProcessedKeydown?.isBackspace() != true -> false
lastProcessedKeydown?.repeat == true -> true
else -> false

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot check latest revisions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest revision addresses this thread: commit d9634e95 adds deletePrevWordVirtualMiddle_viaDeleteContentBackward_CMP_10086 in DeleteWordBackwardTests, covering the repeated-Backspace + deleteContentBackward path. I didn’t make further code changes here.

}

return if (shouldTriggerDelete) {
val layoutResult = composeSender.currentTextLayoutResult() ?: return null

val offset = layoutResult.getPrevWordOffset(textRangeEnd)
DeleteSurroundingTextCommand((textRangeEnd - offset).coerceAtLeast(0), 0)
} else null
}

private fun InputEventExt.process(currentTextFieldValue: TextFieldValue) {
val editCommands = when (inputType) {
"deleteContentBackward" -> buildList {
Expand All @@ -168,41 +184,31 @@ internal abstract class NativeInputEventsProcessor(
// When Compose TextField has text selection, a good UX for deleteContentBackward would be to emulate Backspace.
add(BackspaceCommand())
}
} else { // Empty selection case.
// This happens when an autocorrection is applied on mobile:
// The system first tells us to delete the old text,
// and then it would send the "insertText" event.
if (textRangeSize > 0) {
// deleteContentBackward can happen under very non-trivial circumstances:
// - for instance, when an input suggestion on Android Chrome is accepted,
// the browser then deletes space after the word just to add space again;
// - or when a browser performs Fast Delete;
add(SetSelectionCommand(textRangeStart, textRangeEnd))
add(BackspaceCommand())
} else if (textRangeSize == 0 && lastProcessedKeydown?.isBackspace() != true) {
// We skip this branch if the lastProcessedKeydown is Backspace, because Compose must have already processed this.
// Otherwise, under specific circumstance previous symbol can be deleted while inputting the new one
// see https://youtrack.jetbrains.com/issue/CMP-8773
} else {
// We skip this branch if the lastProcessedKeydown is Backspace, because Compose must have already processed this.
// Otherwise, under specific circumstance previous symbol can be deleted while inputting the new one
// see https://youtrack.jetbrains.com/issue/CMP-8773
if (lastProcessedKeydown?.isBackspace() != true) {
// This happens when an autocorrection is applied on mobile:
// The system first tells us to delete the old text,
// and then it would send the "insertText" event.
if (textRangeSize > 0) {
add(SetSelectionCommand(textRangeStart, textRangeEnd))
}

add(BackspaceCommand())
} else {
// certain keyboard layout trigger deleteContentBackward on fast delete
// https://youtrack.jetbrains.com/issue/CMP-10086
createDeleteWordCommand()?.let { add(it) }
}
}
}

"deleteWordBackward" -> buildList {
if (lastProcessedKeydown?.isBackspace() != true) return@buildList

// This would mean event was triggered by long press on mobile device (iOS)
if (lastProcessedKeydown?.repeat == true) {
val layoutResult = composeSender.currentTextLayoutResult() ?: return@buildList


val offset = layoutResult.getPrevWordOffset(textRangeEnd)
val deleteCommand = DeleteSurroundingTextCommand((textRangeEnd - offset).coerceAtLeast(0), 0)
add(deleteCommand)
}
createDeleteWordCommand()?.let { add(it) }
}


"insertReplacementText" -> buildList {
if (data == null) return@buildList
if (textRangeSize > 0) {
Expand Down
Loading