From d91350524a15e71653c7d0495dd29e2c8cfe95a5 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 12 Jun 2024 22:46:42 +0200 Subject: [PATCH] move cursor in whole codepoints on space / delete swipe and when space-swiping across emoji, send fake arrow keypress instead (discouraged, but avoids e.g. splitting pirate flag emoji and thus justified) fixes #859 --- .../keyboard/KeyboardActionListenerImpl.kt | 56 +++++++++++++++---- .../keyboard/latin/common/StringUtils.java | 2 +- .../keyboard/latin/common/StringUtils.kt | 2 +- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index 36ad42ebe..1df14f525 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -6,6 +6,9 @@ import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodManager import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.InputPointers +import helium314.keyboard.latin.common.StringUtils +import helium314.keyboard.latin.common.loopOverCodePoints +import helium314.keyboard.latin.common.loopOverCodePointsBackwards import helium314.keyboard.latin.inputlogic.InputLogic import helium314.keyboard.latin.settings.Settings import kotlin.math.abs @@ -80,7 +83,23 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp override fun onMoveDeletePointer(steps: Int) { inputLogic.finishInput() val end = inputLogic.mConnection.expectedSelectionEnd - val start = inputLogic.mConnection.expectedSelectionStart + steps + var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint + if (steps > 0) { + val text = inputLogic.mConnection.getSelectedText(0) + if (text == null) actualSteps = steps + else loopOverCodePoints(text) { + actualSteps += Character.charCount(it) + actualSteps >= steps + } + } else { + val text = inputLogic.mConnection.getTextBeforeCursor(-steps * 4, 0) + if (text == null) actualSteps = steps + else loopOverCodePointsBackwards(text) { + actualSteps -= Character.charCount(it) + actualSteps <= steps + } + } + val start = inputLogic.mConnection.expectedSelectionStart + actualSteps if (start > end) return inputLogic.mConnection.setSelection(start, end) } @@ -120,29 +139,44 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp private fun onMoveCursorHorizontally(rawSteps: Int): Boolean { if (rawSteps == 0) return false - var steps = rawSteps // for RTL languages we want to invert pointer movement - if (RichInputMethodManager.getInstance().currentSubtype.isRtlSubtype) steps = -steps + val steps = if (RichInputMethodManager.getInstance().currentSubtype.isRtlSubtype) -rawSteps else rawSteps val moveSteps: Int if (steps < 0) { - val availableCharacters = inputLogic.mConnection.getTextBeforeCursor(64, 0)?.length ?: return false - moveSteps = if (availableCharacters < -steps) -availableCharacters else steps + var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint + val text = inputLogic.mConnection.getTextBeforeCursor(-steps * 4, 0) ?: return false + loopOverCodePointsBackwards(text) { + if (StringUtils.mightBeEmoji(it)) { + actualSteps = 0 + return@loopOverCodePointsBackwards true + } + actualSteps -= Character.charCount(it) + actualSteps <= steps + } + moveSteps = -text.length.coerceAtMost(abs(actualSteps)) if (moveSteps == 0) { // some apps don't return any text via input connection, and the cursor can't be moved // we fall back to virtually pressing the left/right key one or more times instead - while (steps != 0) { + repeat(-steps) { onCodeInput(KeyCode.ARROW_LEFT, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false) - ++steps } return true } } else { - val availableCharacters = inputLogic.mConnection.getTextAfterCursor(64, 0)?.length ?: return false - moveSteps = availableCharacters.coerceAtMost(steps) + var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint + val text = inputLogic.mConnection.getTextAfterCursor(steps * 4, 0) ?: return false + loopOverCodePoints(text) { + if (StringUtils.mightBeEmoji(it)) { + actualSteps = 0 + return@loopOverCodePoints true + } + actualSteps += Character.charCount(it) + actualSteps >= steps + } + moveSteps = text.length.coerceAtMost(actualSteps) if (moveSteps == 0) { - while (steps != 0) { + repeat(steps) { onCodeInput(KeyCode.ARROW_RIGHT, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false) - --steps } return true } diff --git a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java index 36da83ac2..6aa5f83bc 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java +++ b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java @@ -625,7 +625,7 @@ public final class StringUtils { return false; } - public static boolean mightBeEmoji(final String s) { + public static boolean mightBeEmoji(final CharSequence s) { int offset = 0; final int length = s.length(); while (offset < length) { diff --git a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt index a12220387..3b53668a1 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt @@ -109,7 +109,7 @@ fun String.decapitalize(locale: Locale): String { fun isEmoji(c: Int): Boolean = mightBeEmoji(c) && isEmoji(newSingleCodePointString(c)) -fun isEmoji(s: String): Boolean = mightBeEmoji(s) && s.matches(emoRegex) +fun isEmoji(s: CharSequence): Boolean = mightBeEmoji(s) && s.matches(emoRegex) fun String.splitOnWhitespace() = split(whitespaceSplitRegex)