Skip to content
Closed
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
42 changes: 32 additions & 10 deletions app/src/main/java/helium314/keyboard/latin/Suggest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,23 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
val firstOccurrenceOfTypedWordInSuggestions = SuggestedWordInfo.removeDupsAndTypedWord(capitalizedTypedWord, suggestionsContainer)
makeFirstTwoSuggestionsNonEmoji(suggestionsContainer)

val firstSuggestion = suggestionsContainer.firstOrNull()
val expectedContraction = getExpectedContraction(
capitalizedTypedWord.dropLast(trailingSingleQuotesCount), trailingSingleQuotesCount)
val correctionCandidate = if (typedWordFirstOccurrenceWordInfo == null && expectedContraction != null
&& firstSuggestion?.isKindOf(SuggestedWordInfo.KIND_WHITELIST) != true
&& firstSuggestion?.isKindOf(SuggestedWordInfo.KIND_SHORTCUT) != true) {
// Prefer the existing canonical spelling, then a Shift/CapsLock or dictionary variant.
suggestionsContainer.firstOrNull { it.mWord == expectedContraction }
?: suggestionsContainer.firstOrNull { it.mWord.equals(expectedContraction, ignoreCase = true) }
?: firstSuggestion
} else firstSuggestion
val (allowsToBeAutoCorrected, hasAutoCorrection) = shouldBeAutoCorrected(
trailingSingleQuotesCount,
capitalizedTypedWord,
suggestionsContainer.firstOrNull(),
correctionCandidate,
{
val first = suggestionsContainer.firstOrNull() ?: suggestionResults.first()
val first = correctionCandidate ?: suggestionResults.first()
val suggestions = getNextWordSuggestions(ngramContext, keyboard, inputStyleIfNotPrediction, settingsValuesForSuggestion)
val suggestionForFirstInContainer = suggestions.firstOrNull { it.mWord == first.word }
val suggestionForTypedWord = suggestions.firstOrNull { it.mWord == capitalizedTypedWord }
Expand All @@ -135,6 +146,10 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
firstOccurrenceOfTypedWordInSuggestions,
typedWordFirstOccurrenceWordInfo
)
if (hasAutoCorrection && correctionCandidate != null && correctionCandidate !== firstSuggestion) {
suggestionsContainer.remove(correctionCandidate)
suggestionsContainer.add(0, correctionCandidate)
}
val typedWordInfo = SuggestedWordInfo(capitalizedTypedWord, "", SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, typedWordFirstOccurrenceWordInfo?.mSourceDict ?: Dictionary.DICTIONARY_USER_TYPED,
SuggestedWordInfo.NOT_AN_INDEX , SuggestedWordInfo.NOT_A_CONFIDENCE)
Expand Down Expand Up @@ -272,19 +287,20 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
hasAutoCorrection = false
} else {
val firstSuggestion = firstSuggestionInContainer ?: suggestionResults.first()
val expectedContraction = getExpectedContraction(consideredWord, trailingSingleQuotesCount)
if (expectedContraction != null && typedWordInfo == null
&& firstSuggestion.isKindOf(SuggestedWordInfo.KIND_SHORTCUT)
&& !Settings.getValues().mAutoCorrectShortcuts) {
return true to false
}
if (suggestionResults.mFirstSuggestionExceedsConfidenceThreshold && firstOccurrenceOfTypedWordInSuggestions != 0) {
// mFirstSuggestionExceedsConfidenceThreshold is always set to false, so currently this branch is useless
return true to true
}

val lowerConsidered = consideredWord.lowercase(Locale.ROOT)
val expectedContraction = COMMON_CONTRACTIONS[lowerConsidered]
if (expectedContraction != null && typedWordInfo == null) {
// If typed word matches a missing-apostrophe contraction (e.g. dont -> don't), promote it
val contractionMatch = suggestionResults.firstOrNull { it.mWord.equals(expectedContraction, ignoreCase = true) }
if (contractionMatch != null || firstSuggestion.mWord.equals(expectedContraction, ignoreCase = true)) {
return true to true
}
if (expectedContraction != null && typedWordInfo == null
&& firstSuggestion.mWord.equals(expectedContraction, ignoreCase = true)) {
return true to true
}

// For short words (<= 3 chars) not in the dictionary (e.g. Ab, yt, tg, db),
Expand Down Expand Up @@ -585,6 +601,12 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
"lets" to "let's", "itll" to "it'll", "youll" to "you'll", "theyll" to "they'll"
)

private fun getExpectedContraction(consideredWord: String, trailingSingleQuotesCount: Int): String? {
val contraction = COMMON_CONTRACTIONS[consideredWord.lowercase(Locale.ROOT)] ?: return null
// Contractions already contain one apostrophe; match the normal trailing-quote transform.
return contraction + "'".repeat((trailingSingleQuotesCount - 1).coerceAtLeast(0))
}

private fun isDeveloperTokenOrSpecialSyntax(word: String): Boolean {
if (word.isEmpty()) return false
if (word.startsWith('#') || word.startsWith('@') || word.startsWith('/') || word.startsWith('.')) return true
Expand Down
43 changes: 43 additions & 0 deletions app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import android.view.KeyEvent
import android.view.inputmethod.*
import androidx.core.content.edit
import org.junit.Ignore
import helium314.keyboard.ShadowBinaryDictionaryUtils
import helium314.keyboard.ShadowInputMethodManager2
import helium314.keyboard.ShadowLocaleManagerCompat
import helium314.keyboard.event.Event
import helium314.keyboard.keyboard.Keyboard
import helium314.keyboard.keyboard.KeyboardId
import helium314.keyboard.keyboard.KeyboardLayoutSet
import helium314.keyboard.keyboard.KeyboardSwitcher
import helium314.keyboard.keyboard.MainKeyboardView
import helium314.keyboard.keyboard.internal.KeyboardParams
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.ShadowFacilitator2.Companion.addedWords
import helium314.keyboard.latin.ShadowFacilitator2.Companion.lastAddedWord
Expand All @@ -28,7 +33,9 @@ import helium314.keyboard.latin.dictionary.Dictionary
import helium314.keyboard.latin.inputlogic.InputLogic
import helium314.keyboard.latin.inputlogic.SpaceState
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion
import helium314.keyboard.latin.utils.ScriptUtils
import helium314.keyboard.latin.utils.SuggestionResults
import helium314.keyboard.latin.utils.SubtypeSettings
import helium314.keyboard.latin.utils.getTimestampFormatter
import helium314.keyboard.latin.utils.prefs
Expand All @@ -46,6 +53,7 @@ import kotlin.streams.asSequence
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@RunWith(RobolectricTestRunner::class)
@Config(shadows = [
Expand Down Expand Up @@ -98,6 +106,41 @@ class InputLogicTest {
assertEquals("", composingText)
}

@Config(shadows = [ShadowFacilitator::class, ShadowBinaryDictionaryUtils::class])
@Test fun `space commits selected contraction rather than higher ranked distractors`() {
for (distractor in listOf("Youre", "yours", "You're")) {
typingSuggestionResults = SuggestionResults(0, false, false)
reset()
latinIME.prefs().edit { putBoolean(Settings.PREF_MORE_AUTO_CORRECTION, true) }
chainInput("I think youre")
assertEquals("youre", composer.typedWord)
assertTrue(settingsValues.mAutoCorrectEnabled)
typingSuggestionResults = SuggestionResults(2, false, false).apply {
add(suggestion(distractor, 900000, Locale.ENGLISH))
add(suggestion("you're", 100000, Locale.ENGLISH))
}
val keyboard = Keyboard(KeyboardParams().apply {
mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
GRID_WIDTH = 1
GRID_HEIGHT = 1
})
val result = inputLogic.suggest.getSuggestedWords(
composer, NgramContext(NgramContext.WordInfo("think")), keyboard,
SettingsValuesForSuggestion(false, false, ""), true, SuggestedWords.INPUT_STYLE_TYPING, 42
)
inputLogic.setSuggestedWords(result)
assertTrue(result.mWillAutoCorrect)
assertEquals("you're", composer.getAutoCorrectionOrNull()?.mWord)
latinIME.mHandler.cancelUpdateSuggestionStrip()
latinIME.onEvent(Event.createEventForCodePointFromUnknownSource(Constants.CODE_SPACE))
handleMessages()

assertEquals("I think you're ", text)
assertEquals("", composingText)
checkConnectionConsistency()
}
}

@Test fun delete() {
reset()
setText("hello there ")
Expand Down
Loading
Loading