From 3b8517e21794073b8a983f2b78dc76ad841b0651 Mon Sep 17 00:00:00 2001 From: 4di-hello <4di-hello@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:58:01 +0200 Subject: [PATCH 1/2] Add option to delete whole words when holding the delete key After holding delete for a moment, whole words are deleted instead of single characters, with adjustable speed and acceleration. One haptic/audio feedback per deleted word. Not used for languages without spaces. Off by default. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VuiaMhLXwhErf9shhrr1uD --- .../java/helium314/keyboard/latin/LatinIME.kt | 11 ++ .../keyboard/latin/inputlogic/InputLogic.kt | 105 ++++++++++++++++++ .../keyboard/latin/settings/Defaults.kt | 3 + .../keyboard/latin/settings/Settings.kt | 3 + .../keyboard/latin/settings/SettingsValues.kt | 6 + .../settings/screens/TextCorrectionScreen.kt | 44 ++++++++ app/src/main/res/values-de/strings.xml | 14 +++ app/src/main/res/values/strings.xml | 16 +++ .../keyboard/latin/InputLogicTest.kt | 75 +++++++++++++ 9 files changed, 277 insertions(+) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.kt b/app/src/main/java/helium314/keyboard/latin/LatinIME.kt index ee35b1f67..b095635ec 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.kt +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.kt @@ -1689,10 +1689,21 @@ class LatinIME : InputMethodService(), } } + /** Haptic and audio feedback when holding delete removed a word */ + fun feedbackForDeletedWord() { + try { + hapticAndAudioFeedback(KeyCode.DELETE, 0, HapticEvent.KEY_PRESS) + } catch (e: Exception) { + Log.w(TAG, "no feedback for deleted word", e) + } + } + fun hapticAndAudioFeedback(code: Int, repeatCount: Int, hapticEvent: HapticEvent) { val keyboardView = keyboardSwitcher.mainKeyboardView if (keyboardView != null && keyboardView.isInDraggingFinger()) return + // holding delete removes whole words: feedback comes per word, see feedbackForDeletedWord + if (repeatCount > 0 && code == KeyCode.DELETE && inputLogic.isBackspaceWordModeActive(mSettings.current)) return if (repeatCount > 0) { when (code) { KeyCode.DELETE, KeyCode.ARROW_LEFT, KeyCode.ARROW_UP, KeyCode.WORD_LEFT, KeyCode.PAGE_UP -> if (!inputLogic.connection.canDeleteCharacters()) return diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt index 2c3efc08c..c1702e26a 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt @@ -89,6 +89,13 @@ class InputLogic( private var mTypedWordOnShiftPress: String? = null private var mDeleteCount = 0 + // for "backspace repeat deletes words": repeat ticks in the current hold, and words deleted so far + private var mBackspaceRepeatTicks = 0 + private var mBackspaceWordsDeleted = 0 + /** time (ms after the first key repeat) when the next word is deleted */ + private var mBackspaceNextWordTime = 0f + /** word deletion not possible in the current hold (no text available), normal deletion is used */ + private var mBackspaceWordModeFailed = false private var mLastKeyTime = 0L private var mEnteredText: String? = null private var mIsAutoCorrectionIndicatorOn = false @@ -1350,6 +1357,67 @@ class InputLogic( } } + /** + * Whether holding delete currently removes whole words. Not for languages without spaces, where + * a "word" may be a whole sentence. Also used to give feedback per word instead of per key repeat. + */ + fun isBackspaceWordModeActive(settingsValues: SettingsValues): Boolean = + settingsValues.mBackspaceRepeatDeletesWords + && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces + && !mBackspaceWordModeFailed + + /** + * When the option is enabled and the delete key is held down, switch from deleting characters + * to deleting whole words after a short while. Words are deleted with increasing speed. + * Returns true if the event was consumed (a word was deleted, or this repeat tick is skipped). + */ + private fun handleBackspaceRepeatWordDeletion(event: Event, settingsValues: SettingsValues): Boolean { + if (!event.isKeyRepeat || mDeleteCount <= 1) { + // new press of the delete key + mBackspaceRepeatTicks = 0 + mBackspaceWordsDeleted = 0 + mBackspaceNextWordTime = 0f + mBackspaceWordModeFailed = false + } + if (!event.isKeyRepeat) return false + mBackspaceRepeatTicks++ + if (!isBackspaceWordModeActive(settingsValues)) return false + // key repeats come in fixed intervals, so we can count time in repeats + val time = (mBackspaceRepeatTicks - 1) * KEY_REPEAT_INTERVAL_MS + if (time < mBackspaceNextWordTime) return true // wait between words + + if (mWordComposer.isComposingWord()) { + mConnection.finishComposingText() + resetComposingState(true) + } + // with very short intervals, more than one word may be due per key repeat + var deletedWords = 0 + while (time >= mBackspaceNextWordTime && deletedWords < 2) { + val textBefore = mConnection.getTextBeforeCursor(BACKSPACE_WORD_LOOKBACK, 0)?.toString() + if (textBefore == null) { // fall back to normal deletion + mBackspaceWordModeFailed = true + return deletedWords > 0 + } + if (textBefore.isEmpty()) return true + val length = textBefore.length - getStartOfPreviousWord(textBefore) + if (InputTypeUtils.isWebEditor(getCurrentInputEditorInfo())) { + repeat(length) { sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL) } + } else { + mConnection.deleteTextBeforeCursor(length) + } + StatsUtils.onBackspaceWordDelete(length) + mBackspaceNextWordTime += getBackspaceWordInterval( + mBackspaceWordsDeleted, settingsValues.mBackspaceWordSpeed, settingsValues.mBackspaceWordAcceleration + ) + mBackspaceWordsDeleted++ + deletedWords++ + } + mSuggestionStripViewAccessor.setNeutralSuggestionStrip() + // one feedback per deleted word, key repeat feedback is suppressed in word mode + mLatinIME.feedbackForDeletedWord() + return true + } + private fun handleBackspaceEvent(event: Event, inputTransaction: InputTransaction) { val currentKeyboardScript = inputTransaction.settingsValues.mCurrentKeyboardScript mSpaceState = SpaceState.NONE @@ -1410,6 +1478,8 @@ class InputLogic( } inputTransaction.requireShiftUpdate(shiftUpdateKind) + if (handleBackspaceRepeatWordDeletion(event, inputTransaction.settingsValues)) return + if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { unlearnWord(mWordComposer.getTypedWord(), inputTransaction.settingsValues, Constants.EVENT_BACKSPACE) resetEntireInputState(mConnection.expectedSelectionStart, mConnection.expectedSelectionEnd, true) @@ -2947,10 +3017,45 @@ class InputLogic( companion object { private const val TAG = "InputLogic" private const val INLINE_EMOJI_SEARCH_MARKER = ':' + private const val KEY_REPEAT_INTERVAL_MS = 50 + /** interval between the first words for speed 1..5 */ + private val BACKSPACE_WORD_START_INTERVALS = floatArrayOf(600f, 450f, 350f, 250f, 175f) + /** factor for the interval after each word, for acceleration 1 (off) .. 5 */ + private val BACKSPACE_WORD_ACCELERATION = floatArrayOf(1f, 0.9f, 0.8f, 0.7f, 0.6f) + private const val BACKSPACE_WORD_MIN_INTERVAL = 80f + + /** Time until the next word is deleted, after [wordsDeleted] words were deleted already */ + fun getBackspaceWordInterval(wordsDeleted: Int, speed: Int, acceleration: Int): Float { + val start = BACKSPACE_WORD_START_INTERVALS[(speed - 1).coerceIn(0, 4)] + val factor = BACKSPACE_WORD_ACCELERATION[(acceleration - 1).coerceIn(0, 4)] + var interval = start + repeat(wordsDeleted.coerceAtMost(30)) { interval *= factor } + return interval.coerceAtLeast(minOf(start, BACKSPACE_WORD_MIN_INTERVAL)) + } + private const val BACKSPACE_WORD_LOOKBACK = 200 private val THAI_LOCALE = Locale.forLanguageTag("th") private val THAI_WORD_BREAK_ITERATOR = ThreadLocal.withInitial { BreakIterator.getWordInstance(THAI_LOCALE) } private val CURSOR_PATTERN = java.util.regex.Pattern.compile("%cursor(\\d+)%") + /** + * Index where the word before the end of [text] starts, including whitespace and punctuation + * after it, but keeping the whitespace before it (so "one two " -> "one "). + */ + internal fun getStartOfPreviousWord(text: String): Int { + var i = text.length + fun skipWhile(predicate: (Int) -> Boolean) { + while (i > 0) { + val cp = Character.codePointBefore(text, i) + if (!predicate(cp)) break + i -= Character.charCount(cp) + } + } + skipWhile { Character.isWhitespace(it) } + skipWhile { !Character.isLetterOrDigit(it) && !Character.isWhitespace(it) } + skipWhile { Character.isLetterOrDigit(it) } + return i + } + fun isSpaceStrippingPunctuation(codePoint: Int): Boolean { return codePoint == '.'.code || codePoint == ','.code diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt index e21cd68ac..bb8404efe 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -94,6 +94,9 @@ object Defaults { const val PREF_AUTO_CORRECT_THRESHOLD = 0.185f const val PREF_AUTOCORRECT_SHORTCUTS = true const val PREF_BACKSPACE_REVERTS_AUTOCORRECT = true + const val PREF_BACKSPACE_REPEAT_DELETES_WORDS = false + const val PREF_BACKSPACE_WORD_SPEED = 3 + const val PREF_BACKSPACE_WORD_ACCELERATION = 3 const val PREF_CENTER_SUGGESTION_TEXT_TO_ENTER = false const val PREF_CENTER_TYPED_WORD = false const val PREF_SHOW_SUGGESTIONS = true diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt b/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt index b28526dcb..718705e47 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt @@ -343,6 +343,9 @@ class Settings private constructor() : SharedPreferences.OnSharedPreferenceChang const val PREF_AUTO_CORRECT_THRESHOLD = "auto_correct_threshold" const val PREF_AUTOCORRECT_SHORTCUTS = "autocorrect_shortcuts" const val PREF_BACKSPACE_REVERTS_AUTOCORRECT = "backspace_reverts_autocorrect" + const val PREF_BACKSPACE_REPEAT_DELETES_WORDS = "backspace_repeat_deletes_words" + const val PREF_BACKSPACE_WORD_SPEED = "backspace_word_speed" + const val PREF_BACKSPACE_WORD_ACCELERATION = "backspace_word_acceleration" const val PREF_CENTER_SUGGESTION_TEXT_TO_ENTER = "center_suggestion_text_to_enter" const val PREF_CENTER_TYPED_WORD = "center_typed_word" const val PREF_SHOW_SUGGESTIONS = "show_suggestions" diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt index decdbbdb0..5613f8ac9 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt @@ -219,6 +219,9 @@ open class SettingsValues( val mRememberFloatingKeyboard: Boolean val mPersistTextEditMode: Boolean val mBackspaceRevertsAutocorrect: Boolean + val mBackspaceRepeatDeletesWords: Boolean + val mBackspaceWordSpeed: Int + val mBackspaceWordAcceleration: Int val mDisableMultiWordSuggestions: Boolean val mPrioritizePersonalSuggestions: Boolean val mSuggestionBalance: Int @@ -360,6 +363,9 @@ open class SettingsValues( mRememberFloatingKeyboard = prefs.getBoolean(Settings.PREF_REMEMBER_FLOATING_KEYBOARD, Defaults.PREF_REMEMBER_FLOATING_KEYBOARD) mPersistTextEditMode = prefs.getBoolean(Settings.PREF_PERSIST_TEXT_EDIT_MODE, Defaults.PREF_PERSIST_TEXT_EDIT_MODE) mBackspaceRevertsAutocorrect = prefs.getBoolean(Settings.PREF_BACKSPACE_REVERTS_AUTOCORRECT, Defaults.PREF_BACKSPACE_REVERTS_AUTOCORRECT) + mBackspaceRepeatDeletesWords = prefs.getBoolean(Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, Defaults.PREF_BACKSPACE_REPEAT_DELETES_WORDS) + mBackspaceWordSpeed = prefs.getInt(Settings.PREF_BACKSPACE_WORD_SPEED, Defaults.PREF_BACKSPACE_WORD_SPEED) + mBackspaceWordAcceleration = prefs.getInt(Settings.PREF_BACKSPACE_WORD_ACCELERATION, Defaults.PREF_BACKSPACE_WORD_ACCELERATION) mDisableMultiWordSuggestions = prefs.getBoolean(Settings.PREF_DISABLE_MULTI_WORD_SUGGESTIONS, Defaults.PREF_DISABLE_MULTI_WORD_SUGGESTIONS) mBigramPredictionEnabled = prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, Defaults.PREF_BIGRAM_PREDICTIONS) mPrioritizePersonalSuggestions = prefs.getBoolean(Settings.PREF_PRIORITIZE_PERSONAL_SUGGESTIONS, Defaults.PREF_PRIORITIZE_PERSONAL_SUGGESTIONS) diff --git a/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt index 877a7a385..0f59a63ed 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt @@ -59,6 +59,9 @@ fun TextCorrectionScreen( if (autocorrectEnabled) Settings.PREF_AUTO_CORRECT_AGGRESSIVENESS else null, if (autocorrectEnabled) Settings.PREF_AUTO_CORRECT_TRIGGER else null, if (autocorrectEnabled) Settings.PREF_BACKSPACE_REVERTS_AUTOCORRECT else null, + Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, + Settings.PREF_BACKSPACE_WORD_SPEED, + Settings.PREF_BACKSPACE_WORD_ACCELERATION, if (autocorrectEnabled) Settings.PREF_EXPAND_FINE_TUNE_AUTOCORRECT else null, if (autocorrectEnabled && fineTuneAutocorrectExpanded) Settings.PREF_AUTO_CORRECT_THRESHOLD else null, if (autocorrectEnabled && fineTuneAutocorrectExpanded) Settings.PREF_MORE_AUTO_CORRECTION else null, @@ -215,6 +218,47 @@ fun createCorrectionSettings(context: Context) = listOf( Setting(context, Settings.PREF_BACKSPACE_REVERTS_AUTOCORRECT, R.string.backspace_reverts_autocorrect) { SwitchPreference(it, Defaults.PREF_BACKSPACE_REVERTS_AUTOCORRECT) }, + Setting(context, Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, + R.string.backspace_repeat_deletes_words, R.string.backspace_repeat_deletes_words_summary + ) { + SwitchPreference(it, Defaults.PREF_BACKSPACE_REPEAT_DELETES_WORDS) + }, + Setting(context, Settings.PREF_BACKSPACE_WORD_SPEED, R.string.backspace_word_speed) { setting -> + SliderPreference( + name = setting.title, + key = setting.key, + default = Defaults.PREF_BACKSPACE_WORD_SPEED, + range = 1f..5f, + stepSize = 1, + description = { value -> + when (value) { + 1 -> stringResource(R.string.backspace_word_speed_1) + 2 -> stringResource(R.string.backspace_word_speed_2) + 3 -> stringResource(R.string.backspace_word_speed_3) + 4 -> stringResource(R.string.backspace_word_speed_4) + else -> stringResource(R.string.backspace_word_speed_5) + } + } + ) + }, + Setting(context, Settings.PREF_BACKSPACE_WORD_ACCELERATION, R.string.backspace_word_acceleration) { setting -> + SliderPreference( + name = setting.title, + key = setting.key, + default = Defaults.PREF_BACKSPACE_WORD_ACCELERATION, + range = 1f..5f, + stepSize = 1, + description = { value -> + when (value) { + 1 -> stringResource(R.string.backspace_word_acceleration_1) + 2 -> stringResource(R.string.backspace_word_acceleration_2) + 3 -> stringResource(R.string.backspace_word_acceleration_3) + 4 -> stringResource(R.string.backspace_word_acceleration_4) + else -> stringResource(R.string.backspace_word_acceleration_5) + } + } + ) + }, Setting(context, Settings.PREF_AUTO_CAP, R.string.auto_cap, R.string.auto_cap_summary ) { diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index aedc47bcc..bf99c344b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -408,6 +408,8 @@ Dagbani Sesotho Rücktaste macht Autokorrektur rückgängig + Gedrückte Löschtaste löscht ganze Wörter + Nach kurzem Halten der Löschtaste wortweise löschen, zunehmend schneller Automatische Leerzeichen nach Auswählen eines Vorschlags Skaliere den Emoji-Knopf mit der Schriftgröße Backup wiederhergestellt @@ -1734,4 +1736,16 @@ Ermöglicht lokale GGUF / llama.cpp-Inferenz für Korrekturlesen und Umschreiben API-Schlüssel einschließen Holen Sie sich den API-Schlüssel Deaktiviert + Tempo beim wortweisen Löschen + Sehr langsam + Langsam + Empfohlen + Schnell + Sehr schnell + Beschleunigung beim wortweisen Löschen + Aus + Leicht + Empfohlen + Stark + Sehr stark diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7921e25d2..44c2473a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -184,6 +184,10 @@ Settings on this screen apply to large-screen layout Backspace reverts autocorrect + + Holding delete removes whole words + + After holding the delete key for a moment, delete word by word with increasing speed Off @@ -2418,4 +2422,16 @@ New dictionary: No recent items Manage Local Media Get API key + Speed of word deletion + Very slow + Slow + Recommended + Fast + Very fast + Acceleration of word deletion + Off + Light + Recommended + Strong + Very strong diff --git a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt index 23f26c2fe..c56285190 100644 --- a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt +++ b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt @@ -1576,6 +1576,81 @@ class InputLogicTest { assertEquals("", composingText) } + @Test fun `held backspace deletes whole words when enabled`() { + reset() + latinIME.prefs().edit { putBoolean(Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, true) } + setText("eins zwei drei vier fuenf sechs sieben") + functionalKeyPress(KeyCode.DELETE) // a single press deletes one character + assertEquals("eins zwei drei vier fuenf sechs siebe", text) + // the first key repeat deletes the rest of the word + functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) + assertEquals("eins zwei drei vier fuenf sechs ", text) + // some repeats pass without deleting before the next word + for (i in 1..6) { + functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) + } + assertEquals("eins zwei drei vier fuenf sechs ", text) + functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) + assertEquals("eins zwei drei vier fuenf ", text) + // keep holding: everything gets deleted word by word + for (i in 1..30) { + functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) + } + assertEquals("", text) + } + + @Test fun `held backspace word deletion accelerates`() { + reset() + latinIME.prefs().edit { putBoolean(Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, true) } + setText("a b c d e f g h") + functionalKeyPress(KeyCode.DELETE) // deletes "h" + // key repeats every 50 ms: repeat 1 at 0 ms ... repeat 22 at 1050 ms + for (i in 1..22) functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) + // recommended: words at 0, 350, 630, 854, 1033 ms + assertEquals("a b ", text) + } + + @Test fun `held backspace word deletion without acceleration has constant speed`() { + reset() + latinIME.prefs().edit { + putBoolean(Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, true) + putInt(Settings.PREF_BACKSPACE_WORD_ACCELERATION, 1) + } + setText("a b c d e f g h") + functionalKeyPress(KeyCode.DELETE) + for (i in 1..22) functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) + // words at 0, 350, 700, 1050 ms + assertEquals("a b c ", text) + } + + @Test fun `backspace word intervals`() { + assertEquals(350f, InputLogic.getBackspaceWordInterval(0, 3, 3), 0.01f) + assertEquals(280f, InputLogic.getBackspaceWordInterval(1, 3, 3), 0.01f) + assertEquals(80f, InputLogic.getBackspaceWordInterval(20, 3, 3), 0.01f) // minimum + assertEquals(600f, InputLogic.getBackspaceWordInterval(5, 1, 1), 0.01f) // no acceleration + assertEquals(175f, InputLogic.getBackspaceWordInterval(0, 5, 5), 0.01f) + } + + @Test fun `start of previous word keeps whitespace before the word`() { + fun remaining(text: String) = text.substring(0, InputLogic.getStartOfPreviousWord(text)) + assertEquals("Hallo, ", remaining("Hallo, Welt! ")) + assertEquals("", remaining("Hallo, ")) + assertEquals("", remaining("Hallo")) + assertEquals("eins ", remaining("eins zwei")) + assertEquals("", remaining(" ")) + } + + @Test fun `held backspace deletes characters only when word deletion is disabled`() { + reset() + latinIME.prefs().edit { putBoolean(Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, false) } + setText("eins zwei drei vier") + functionalKeyPress(KeyCode.DELETE) + for (i in 1..15) { + functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) + } + assertEquals("ein", text) // with word deletion, "eins" would be gone completely + } + @Test fun timestamp() { reset() chainInput("hello") From 4e2acf355cd9622b1d2f79f6b7c48f8c7a2c3231 Mon Sep 17 00:00:00 2001 From: 4di-hello <4di-hello@users.noreply.github.com> Date: Wed, 14 Oct 2026 14:59:27 +0200 Subject: [PATCH 2/2] Use fixed timing for deleting words, remove the speed and acceleration settings Only the switch is left, as suggested in the review. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VuiaMhLXwhErf9shhrr1uD --- .../keyboard/latin/inputlogic/InputLogic.kt | 21 ++++------ .../keyboard/latin/settings/Defaults.kt | 2 - .../keyboard/latin/settings/Settings.kt | 2 - .../keyboard/latin/settings/SettingsValues.kt | 4 -- .../settings/screens/TextCorrectionScreen.kt | 38 ------------------- app/src/main/res/values-de/strings.xml | 12 ------ app/src/main/res/values/strings.xml | 12 ------ .../keyboard/latin/InputLogicTest.kt | 21 ++-------- 8 files changed, 11 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt index c1702e26a..ca212935a 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt @@ -1406,9 +1406,7 @@ class InputLogic( mConnection.deleteTextBeforeCursor(length) } StatsUtils.onBackspaceWordDelete(length) - mBackspaceNextWordTime += getBackspaceWordInterval( - mBackspaceWordsDeleted, settingsValues.mBackspaceWordSpeed, settingsValues.mBackspaceWordAcceleration - ) + mBackspaceNextWordTime += getBackspaceWordInterval(mBackspaceWordsDeleted) mBackspaceWordsDeleted++ deletedWords++ } @@ -3018,19 +3016,16 @@ class InputLogic( private const val TAG = "InputLogic" private const val INLINE_EMOJI_SEARCH_MARKER = ':' private const val KEY_REPEAT_INTERVAL_MS = 50 - /** interval between the first words for speed 1..5 */ - private val BACKSPACE_WORD_START_INTERVALS = floatArrayOf(600f, 450f, 350f, 250f, 175f) - /** factor for the interval after each word, for acceleration 1 (off) .. 5 */ - private val BACKSPACE_WORD_ACCELERATION = floatArrayOf(1f, 0.9f, 0.8f, 0.7f, 0.6f) + /** interval before the second word, it gets shorter with each word until the minimum */ + private const val BACKSPACE_WORD_START_INTERVAL = 350f + private const val BACKSPACE_WORD_ACCELERATION = 0.8f private const val BACKSPACE_WORD_MIN_INTERVAL = 80f /** Time until the next word is deleted, after [wordsDeleted] words were deleted already */ - fun getBackspaceWordInterval(wordsDeleted: Int, speed: Int, acceleration: Int): Float { - val start = BACKSPACE_WORD_START_INTERVALS[(speed - 1).coerceIn(0, 4)] - val factor = BACKSPACE_WORD_ACCELERATION[(acceleration - 1).coerceIn(0, 4)] - var interval = start - repeat(wordsDeleted.coerceAtMost(30)) { interval *= factor } - return interval.coerceAtLeast(minOf(start, BACKSPACE_WORD_MIN_INTERVAL)) + fun getBackspaceWordInterval(wordsDeleted: Int): Float { + var interval = BACKSPACE_WORD_START_INTERVAL + repeat(wordsDeleted.coerceAtMost(30)) { interval *= BACKSPACE_WORD_ACCELERATION } + return interval.coerceAtLeast(BACKSPACE_WORD_MIN_INTERVAL) } private const val BACKSPACE_WORD_LOOKBACK = 200 private val THAI_LOCALE = Locale.forLanguageTag("th") diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt index bb8404efe..8462553b9 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -95,8 +95,6 @@ object Defaults { const val PREF_AUTOCORRECT_SHORTCUTS = true const val PREF_BACKSPACE_REVERTS_AUTOCORRECT = true const val PREF_BACKSPACE_REPEAT_DELETES_WORDS = false - const val PREF_BACKSPACE_WORD_SPEED = 3 - const val PREF_BACKSPACE_WORD_ACCELERATION = 3 const val PREF_CENTER_SUGGESTION_TEXT_TO_ENTER = false const val PREF_CENTER_TYPED_WORD = false const val PREF_SHOW_SUGGESTIONS = true diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt b/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt index 718705e47..00bca0c8f 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.kt @@ -344,8 +344,6 @@ class Settings private constructor() : SharedPreferences.OnSharedPreferenceChang const val PREF_AUTOCORRECT_SHORTCUTS = "autocorrect_shortcuts" const val PREF_BACKSPACE_REVERTS_AUTOCORRECT = "backspace_reverts_autocorrect" const val PREF_BACKSPACE_REPEAT_DELETES_WORDS = "backspace_repeat_deletes_words" - const val PREF_BACKSPACE_WORD_SPEED = "backspace_word_speed" - const val PREF_BACKSPACE_WORD_ACCELERATION = "backspace_word_acceleration" const val PREF_CENTER_SUGGESTION_TEXT_TO_ENTER = "center_suggestion_text_to_enter" const val PREF_CENTER_TYPED_WORD = "center_typed_word" const val PREF_SHOW_SUGGESTIONS = "show_suggestions" diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt index 5613f8ac9..494bd26c7 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.kt @@ -220,8 +220,6 @@ open class SettingsValues( val mPersistTextEditMode: Boolean val mBackspaceRevertsAutocorrect: Boolean val mBackspaceRepeatDeletesWords: Boolean - val mBackspaceWordSpeed: Int - val mBackspaceWordAcceleration: Int val mDisableMultiWordSuggestions: Boolean val mPrioritizePersonalSuggestions: Boolean val mSuggestionBalance: Int @@ -364,8 +362,6 @@ open class SettingsValues( mPersistTextEditMode = prefs.getBoolean(Settings.PREF_PERSIST_TEXT_EDIT_MODE, Defaults.PREF_PERSIST_TEXT_EDIT_MODE) mBackspaceRevertsAutocorrect = prefs.getBoolean(Settings.PREF_BACKSPACE_REVERTS_AUTOCORRECT, Defaults.PREF_BACKSPACE_REVERTS_AUTOCORRECT) mBackspaceRepeatDeletesWords = prefs.getBoolean(Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, Defaults.PREF_BACKSPACE_REPEAT_DELETES_WORDS) - mBackspaceWordSpeed = prefs.getInt(Settings.PREF_BACKSPACE_WORD_SPEED, Defaults.PREF_BACKSPACE_WORD_SPEED) - mBackspaceWordAcceleration = prefs.getInt(Settings.PREF_BACKSPACE_WORD_ACCELERATION, Defaults.PREF_BACKSPACE_WORD_ACCELERATION) mDisableMultiWordSuggestions = prefs.getBoolean(Settings.PREF_DISABLE_MULTI_WORD_SUGGESTIONS, Defaults.PREF_DISABLE_MULTI_WORD_SUGGESTIONS) mBigramPredictionEnabled = prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, Defaults.PREF_BIGRAM_PREDICTIONS) mPrioritizePersonalSuggestions = prefs.getBoolean(Settings.PREF_PRIORITIZE_PERSONAL_SUGGESTIONS, Defaults.PREF_PRIORITIZE_PERSONAL_SUGGESTIONS) diff --git a/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt index 0f59a63ed..f804aed89 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/TextCorrectionScreen.kt @@ -60,8 +60,6 @@ fun TextCorrectionScreen( if (autocorrectEnabled) Settings.PREF_AUTO_CORRECT_TRIGGER else null, if (autocorrectEnabled) Settings.PREF_BACKSPACE_REVERTS_AUTOCORRECT else null, Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, - Settings.PREF_BACKSPACE_WORD_SPEED, - Settings.PREF_BACKSPACE_WORD_ACCELERATION, if (autocorrectEnabled) Settings.PREF_EXPAND_FINE_TUNE_AUTOCORRECT else null, if (autocorrectEnabled && fineTuneAutocorrectExpanded) Settings.PREF_AUTO_CORRECT_THRESHOLD else null, if (autocorrectEnabled && fineTuneAutocorrectExpanded) Settings.PREF_MORE_AUTO_CORRECTION else null, @@ -223,42 +221,6 @@ fun createCorrectionSettings(context: Context) = listOf( ) { SwitchPreference(it, Defaults.PREF_BACKSPACE_REPEAT_DELETES_WORDS) }, - Setting(context, Settings.PREF_BACKSPACE_WORD_SPEED, R.string.backspace_word_speed) { setting -> - SliderPreference( - name = setting.title, - key = setting.key, - default = Defaults.PREF_BACKSPACE_WORD_SPEED, - range = 1f..5f, - stepSize = 1, - description = { value -> - when (value) { - 1 -> stringResource(R.string.backspace_word_speed_1) - 2 -> stringResource(R.string.backspace_word_speed_2) - 3 -> stringResource(R.string.backspace_word_speed_3) - 4 -> stringResource(R.string.backspace_word_speed_4) - else -> stringResource(R.string.backspace_word_speed_5) - } - } - ) - }, - Setting(context, Settings.PREF_BACKSPACE_WORD_ACCELERATION, R.string.backspace_word_acceleration) { setting -> - SliderPreference( - name = setting.title, - key = setting.key, - default = Defaults.PREF_BACKSPACE_WORD_ACCELERATION, - range = 1f..5f, - stepSize = 1, - description = { value -> - when (value) { - 1 -> stringResource(R.string.backspace_word_acceleration_1) - 2 -> stringResource(R.string.backspace_word_acceleration_2) - 3 -> stringResource(R.string.backspace_word_acceleration_3) - 4 -> stringResource(R.string.backspace_word_acceleration_4) - else -> stringResource(R.string.backspace_word_acceleration_5) - } - } - ) - }, Setting(context, Settings.PREF_AUTO_CAP, R.string.auto_cap, R.string.auto_cap_summary ) { diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index bf99c344b..05a5b6123 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1736,16 +1736,4 @@ Ermöglicht lokale GGUF / llama.cpp-Inferenz für Korrekturlesen und Umschreiben API-Schlüssel einschließen Holen Sie sich den API-Schlüssel Deaktiviert - Tempo beim wortweisen Löschen - Sehr langsam - Langsam - Empfohlen - Schnell - Sehr schnell - Beschleunigung beim wortweisen Löschen - Aus - Leicht - Empfohlen - Stark - Sehr stark diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44c2473a8..329aecd03 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2422,16 +2422,4 @@ New dictionary: No recent items Manage Local Media Get API key - Speed of word deletion - Very slow - Slow - Recommended - Fast - Very fast - Acceleration of word deletion - Off - Light - Recommended - Strong - Very strong diff --git a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt index c56285190..7998c1527 100644 --- a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt +++ b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt @@ -1610,25 +1610,10 @@ class InputLogicTest { assertEquals("a b ", text) } - @Test fun `held backspace word deletion without acceleration has constant speed`() { - reset() - latinIME.prefs().edit { - putBoolean(Settings.PREF_BACKSPACE_REPEAT_DELETES_WORDS, true) - putInt(Settings.PREF_BACKSPACE_WORD_ACCELERATION, 1) - } - setText("a b c d e f g h") - functionalKeyPress(KeyCode.DELETE) - for (i in 1..22) functionalKeyPress(KeyCode.DELETE, isKeyRepeat = true) - // words at 0, 350, 700, 1050 ms - assertEquals("a b c ", text) - } - @Test fun `backspace word intervals`() { - assertEquals(350f, InputLogic.getBackspaceWordInterval(0, 3, 3), 0.01f) - assertEquals(280f, InputLogic.getBackspaceWordInterval(1, 3, 3), 0.01f) - assertEquals(80f, InputLogic.getBackspaceWordInterval(20, 3, 3), 0.01f) // minimum - assertEquals(600f, InputLogic.getBackspaceWordInterval(5, 1, 1), 0.01f) // no acceleration - assertEquals(175f, InputLogic.getBackspaceWordInterval(0, 5, 5), 0.01f) + assertEquals(350f, InputLogic.getBackspaceWordInterval(0), 0.01f) + assertEquals(280f, InputLogic.getBackspaceWordInterval(1), 0.01f) + assertEquals(80f, InputLogic.getBackspaceWordInterval(20), 0.01f) // minimum } @Test fun `start of previous word keeps whitespace before the word`() {