diff --git a/app/src/main/java/helium314/keyboard/latin/ShortWordSwap.kt b/app/src/main/java/helium314/keyboard/latin/ShortWordSwap.kt new file mode 100644 index 000000000..0d4cb045e --- /dev/null +++ b/app/src/main/java/helium314/keyboard/latin/ShortWordSwap.kt @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-only +package helium314.keyboard.latin + +import java.util.Locale + +/** + * Exception to LeanType's rule that unknown words of up to 3 letters are not auto-corrected (see [Suggest]): + * two neighboring letters typed in the wrong order, like "dsa" for "das", "ihc" for "ich" or "teh" for "the", + * are corrected. Abbreviations like "db" or "yt" stay, their swapped form is no word. + */ +object ShortWordSwap { + const val LENGTH = 3 + + /** Whether [word] is [typed] with two neighboring (different) letters swapped, case is ignored */ + fun isSwapOfNeighbors(typed: String, word: String): Boolean { + if (typed.length != LENGTH || word.length != LENGTH) return false + val a = typed.lowercase(Locale.ROOT) + val b = word.lowercase(Locale.ROOT) + val i = a.indices.firstOrNull { a[it] != b[it] } ?: return false + if (i + 1 >= a.length) return false + return a[i] == b[i + 1] && a[i + 1] == b[i] && a.substring(i + 2) == b.substring(i + 2) + && a.all { it.isLetter() } + } +} diff --git a/app/src/main/java/helium314/keyboard/latin/Suggest.kt b/app/src/main/java/helium314/keyboard/latin/Suggest.kt index d44801164..70ad9e4be 100644 --- a/app/src/main/java/helium314/keyboard/latin/Suggest.kt +++ b/app/src/main/java/helium314/keyboard/latin/Suggest.kt @@ -333,7 +333,9 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) { val isUserHistory = firstSuggestion.mSourceDict?.mDictType == Dictionary.TYPE_USER_HISTORY val isWhitelist = firstSuggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST) val isShortcut = firstSuggestion.isKindOf(SuggestedWordInfo.KIND_SHORTCUT) && Settings.getValues().mAutoCorrectShortcuts - if (!isExactOrCaseMatch && !isUserHistory && !isWhitelist && !isShortcut && expectedContraction == null) { + // two neighboring letters typed in the wrong order, like "teh" for "the", are a typo, not an abbreviation + val isSwap = ShortWordSwap.isSwapOfNeighbors(consideredWord, firstSuggestion.mWord) + if (!isExactOrCaseMatch && !isUserHistory && !isWhitelist && !isShortcut && !isSwap && expectedContraction == null) { return true to false } } diff --git a/app/src/test/java/helium314/keyboard/latin/ShortWordSwapTest.kt b/app/src/test/java/helium314/keyboard/latin/ShortWordSwapTest.kt new file mode 100644 index 000000000..5c4734dc7 --- /dev/null +++ b/app/src/test/java/helium314/keyboard/latin/ShortWordSwapTest.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-only +package helium314.keyboard.latin + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class ShortWordSwapTest { + @Test + fun swappedNeighborsAreFound() { + for ((typed, word) in listOf("dsa" to "das", "ihc" to "ich", "dei" to "die", "nud" to "und", "teh" to "the", + "adn" to "and", "Dsa" to "Das", "IHC" to "ich", "eni" to "ein")) { + assertTrue("$typed -> $word", ShortWordSwap.isSwapOfNeighbors(typed, word)) + } + } + + @Test + fun otherDifferencesAreNoSwap() { + assertFalse(ShortWordSwap.isSwapOfNeighbors("das", "das")) // same word + assertFalse(ShortWordSwap.isSwapOfNeighbors("dsa", "sad")) // not neighbors + assertFalse(ShortWordSwap.isSwapOfNeighbors("das", "dar")) // a different letter + assertFalse(ShortWordSwap.isSwapOfNeighbors("db", "bd")) // only 3 letters + assertFalse(ShortWordSwap.isSwapOfNeighbors("ders", "dres")) // only 3 letters + assertFalse(ShortWordSwap.isSwapOfNeighbors("a1b", "1ab")) // only letters + assertFalse(ShortWordSwap.isSwapOfNeighbors("abc", "bca")) + } +}