Skip to content
Open
Show file tree
Hide file tree
Changes from all 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,15 @@ internal abstract class NativeInputEventsProcessor(
collectedEvents.clear()
}

private fun InputEventExt.createDeleteWordCommand(): EditCommand? {
val keydown = lastProcessedKeydown ?: return null
if (!keydown.isBackspace() || !keydown.repeat) return null
val layoutResult = composeSender.currentTextLayoutResult() ?: return null

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

private fun InputEventExt.process(currentTextFieldValue: TextFieldValue) {
val editCommands = when (inputType) {
"deleteContentBackward" -> buildList {
Expand All @@ -168,7 +178,7 @@ 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.
} else {
// 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.
Expand All @@ -184,25 +194,16 @@ internal abstract class NativeInputEventsProcessor(
// Otherwise, under specific circumstance previous symbol can be deleted while inputting the new one
// see https://youtrack.jetbrains.com/issue/CMP-8773
add(BackspaceCommand())
} else {
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import kotlin.test.Test

class DeleteWordBackwardTests : TextFieldTestSpec, BasicTextFieldWithValue {

fun sendPhysicalDeleteWordBackward() {
private fun sendPhysicalDeleteWordBackward() {
sendToHtmlInput(
keyEvent(
key = "Backspace",
Expand All @@ -39,7 +39,7 @@ class DeleteWordBackwardTests : TextFieldTestSpec, BasicTextFieldWithValue {
)
}

fun sendVirtualDeleteWordBackward() {
private fun sendVirtualDeleteWordBackward() {
sendToHtmlInput(
keyEvent(
key = "Backspace",
Expand All @@ -51,6 +51,18 @@ class DeleteWordBackwardTests : TextFieldTestSpec, BasicTextFieldWithValue {
)
}

private fun sendVirtualFastDeleteAsContentBackward() {
sendToHtmlInput(
keyEvent(
key = "Backspace",
code = "Backspace",
type = "keydown",
repeat = true,
),
beforeInput("deleteContentBackward", null)
)
}


@Test
fun deletePrevWordVirtualMiddle() = runApplicationTest {
Expand Down Expand Up @@ -191,4 +203,19 @@ class DeleteWordBackwardTests : TextFieldTestSpec, BasicTextFieldWithValue {
textFieldValue.awaitAndAssertTextEquals("천천히 ")
}

@Test
fun deletePrevWordVirtualMiddle_viaDeleteContentBackward_CMP_10086() = runApplicationTest {
val textFieldValue = createApplicationWithHolder(
"here we go again!!!",
initialSelection = TextRange(14, 14)
)
awaitAnimationFrame()

sendVirtualFastDeleteAsContentBackward()
textFieldValue.awaitAndAssertTextEquals(
"here go again!!!",
"deleteContentBackward with repeating Backspace should behave as deleteWordBackward (CMP-10086)"
)
}

}
Loading