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
Original file line number Diff line number Diff line change
Expand Up @@ -2269,7 +2269,8 @@ class InputLogic(
val stringToCommit = autoCorrectionOrNull?.mWord ?: typedWord
if (stringToCommit != null) {
val isBatchMode = mWordComposer.isBatchMode()
commitChosenWord(settingsValues, stringToCommit, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator)
val expanded = commitChosenWord(settingsValues, stringToCommit, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator)
if (expanded) return
if (typedWord != stringToCommit) {
mConnection.commitCorrection(
CorrectionInfo(
Expand All @@ -2289,23 +2290,28 @@ class InputLogic(
}
}

/** Returns whether a shortcut expansion replaced the chosen word. */
private fun commitChosenWord(
settingsValues: SettingsValues,
chosenWord: String,
commitType: Int,
separatorString: String
) {
): Boolean {
var startTimeMillis = 0L
if (DebugFlags.DEBUG_ENABLED) {
startTimeMillis = System.currentTimeMillis()
Log.d(TAG, "commitChosenWord() : [$chosenWord]")
}
val isEnabled = TextExpanderUtils.isEnabled(mLatinIME)
if (isEnabled) {
val isDifferentManualPick = commitType == LastComposedWord.COMMIT_TYPE_MANUAL_PICK
&& !chosenWord.equals(mWordComposer.getTypedWord(), ignoreCase = true)
if (isEnabled && !isDifferentManualPick) {
val textBefore = mConnection.getTextBeforeCursor(50, 0)
if (textBefore != null) {
val textStr = textBefore.toString()
val result = TextExpanderUtils.getExpandedWordForTyped(chosenWord, textStr, mLatinIME)
val expansionWord = if (commitType == LastComposedWord.COMMIT_TYPE_DECIDED_WORD)
mWordComposer.getTypedWord() else chosenWord
val result = TextExpanderUtils.getExpandedWordForTyped(expansionWord, textStr, mLatinIME)
if (result != null) {
if (mJustRevertedExpandedShortcut != null
&& result.matchedString.equals(mJustRevertedExpandedShortcut, ignoreCase = true)
Expand All @@ -2316,7 +2322,7 @@ class InputLogic(
mConnection.deleteTextBeforeCursor(result.prefixLength + chosenWord.length)
commitExpandedText(result.matchedString, result.expandedText)
resetComposingState(true)
return
return true
}
}
}
Expand Down Expand Up @@ -2355,6 +2361,7 @@ class InputLogic(
val runTimeMillis = System.currentTimeMillis() - startTimeMillis
Log.d(TAG, "commitChosenWord() : $runTimeMillis ms to run WordComposer.commitWord()")
}
return false
}

private fun wrapSelection(start: Int, end: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,11 +387,8 @@ object TextExpanderUtils {
if (textBeforeCursor == null || !isEnabled(context)) return null
getShortcuts(context)
val compiledList = cachedCompiledList ?: return null
val fullText = if (word != null && !textBeforeCursor.endsWith(word, ignoreCase = true)) {
textBeforeCursor + word
} else {
textBeforeCursor
}
// A pending correction is not text the user typed and must not trigger a macro.
val fullText = textBeforeCursor

for (item in compiledList) {
val entry = item.entry
Expand Down
188 changes: 186 additions & 2 deletions app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import helium314.keyboard.latin.inputlogic.SpaceState
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.ScriptUtils
import helium314.keyboard.latin.utils.SubtypeSettings
import helium314.keyboard.latin.utils.TextExpanderUtils
import helium314.keyboard.latin.utils.getTimestampFormatter
import helium314.keyboard.latin.utils.prefs
import org.junit.runner.RunWith
Expand All @@ -46,6 +47,7 @@ import kotlin.streams.asSequence
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

@RunWith(RobolectricTestRunner::class)
@Config(shadows = [
Expand Down Expand Up @@ -426,6 +428,184 @@ class InputLogicTest {
assertEquals("user_mention", text)
}

@Test fun correctionToLiteralShortcutDoesNotExpand() {
assertCorrectionDoesNotTriggerShortcut("cpp")
}

@Test fun correctionToRegexShortcutDoesNotExpand() {
assertCorrectionDoesNotTriggerShortcut("${TextExpanderUtils.REGEX_PREFIX}cpp")
}

private fun assertCorrectionDoesNotTriggerShortcut(shortcut: String) {
for (immediate in listOf(false, true)) {
configureExpansion(shortcut, immediate)
setInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_AUTO_CORRECT)
typeNoAssert("cps")
val correction = SuggestedWordInfo("cpp", "", 0, 0, Mockito.mock(Dictionary::class.java), 0, 0)
composer.setAutoCorrection(correction)
assertEquals(true, settingsValues.mAutoCorrectEnabled)
typeNoAssert(" ")
assertEquals("cpp ", text, "immediate=$immediate")
checkConnectionConsistency()
}
}

@Test fun literalShortcutStillExpandsOnlyFromActualTypedTrigger() {
assertTypedShortcutExpands("cpp")
}

@Test fun regexShortcutStillExpandsOnlyFromActualTypedTrigger() {
assertTypedShortcutExpands("${TextExpanderUtils.REGEX_PREFIX}cpp")
}

@Test fun prefixedRegexExpansionUsesTypedLengthNotCorrectionLength() {
for (correction in listOf("python", "p")) {
configureExpansion("${TextExpanderUtils.REGEX_PREFIX}\\.py", false)
setInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_AUTO_CORRECT)
setText("keep .")
typeNoAssert("py")
assertEquals("py", composer.getTypedWord())
composer.setAutoCorrection(
SuggestedWordInfo(correction, "", 0, 0, Mockito.mock(Dictionary::class.java), 0, 0)
)
typeNoAssert(" ")
assertEquals("keep EXPANDED ", text, "correction=$correction")
assertEquals(text.length, cursor)
assertEquals(emptyList(), committedCorrections)
checkConnectionConsistency()
}
}

@Test fun unexpandedAutoCorrectionStillNotifiesEditor() {
for (expansionEnabled in listOf(false, true)) {
configureExpansion("${TextExpanderUtils.REGEX_PREFIX}\\.zz", false)
latinIME.prefs().edit {
putBoolean(TextExpanderUtils.PREF_ENABLED, expansionEnabled)
}
setInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_AUTO_CORRECT)
setText("keep .")
typeNoAssert("py")
composer.setAutoCorrection(
SuggestedWordInfo("python", "", 0, 0, Mockito.mock(Dictionary::class.java), 0, 0)
)
typeNoAssert(" ")
assertEquals("keep .python ", text)
assertEquals(text.length, cursor)
val correction = assertNotNull(committedCorrections.single())
assertEquals(6, correction.offset)
assertEquals("py", correction.oldText.toString())
assertEquals("python", correction.newText.toString())
checkConnectionConsistency()
}
}

@Test fun manuallyPickedSuggestionDoesNotSynthesizeShortcutText() {
for (shortcut in listOf("cpp", "${TextExpanderUtils.REGEX_PREFIX}cpp")) {
configureExpansion(shortcut, false)
typeNoAssert("cps")
pickSuggestion("cpp")
assertEquals("cpp", text)
checkConnectionConsistency()
}
configureExpansion("cpp", false)
typeNoAssert("cpp")
pickSuggestion("chosen")
assertEquals("chosen", text)
checkConnectionConsistency()
}

@Test fun manuallyPickedLongerSuggestionOverridesMatchingRegexShortcut() {
assertManualSuggestionOverridesMatchingRegexShortcut("python")
}

@Test fun manuallyPickedShorterSuggestionOverridesMatchingRegexShortcut() {
assertManualSuggestionOverridesMatchingRegexShortcut("p")
}

@Test fun manuallyPickedSuffixSuggestionOverridesMatchingRegexShortcut() {
assertManualSuggestionOverridesMatchingRegexShortcut("y")
}

@Test fun manuallyPickedSameLengthSuggestionOverridesMatchingRegexShortcut() {
assertManualSuggestionOverridesMatchingRegexShortcut("by")
}

private fun assertManualSuggestionOverridesMatchingRegexShortcut(suggestion: String) {
configureExpansion("${TextExpanderUtils.REGEX_PREFIX}\\.py", false)
setText("keep .")
typeNoAssert("py")
assertEquals("py", composer.getTypedWord())
pickSuggestion(suggestion)
assertEquals("keep .$suggestion", text)
assertEquals(text.length, cursor)
checkConnectionConsistency()
}

@Test fun manuallyPickedDifferentSuggestionOverridesWholeRegexMatch() {
configureExpansion("${TextExpanderUtils.REGEX_PREFIX}cpp", false)
typeNoAssert("cpp")
pickSuggestion("chosen")
assertEquals("chosen", text)
assertEquals(text.length, cursor)
checkConnectionConsistency()
}

@Test fun manuallyPickedMatchingShortcutStillExpands() {
for (shortcut in listOf("cpp", "${TextExpanderUtils.REGEX_PREFIX}cpp")) {
for (suggestion in listOf("cpp", "CPP")) {
configureExpansion(shortcut, false)
typeNoAssert("cpp")
pickSuggestion(suggestion)
assertEquals("EXPANDED", text)
assertEquals(text.length, cursor)
functionalKeyPress(KeyCode.DELETE)
assertEquals("cpp", text)
checkConnectionConsistency()
}
}
}

@Test fun manuallyPickedMatchingPrefixedRegexShortcutStillExpands() {
for (suggestion in listOf("py", "PY")) {
configureExpansion("${TextExpanderUtils.REGEX_PREFIX}\\.py", false)
setText("keep .")
typeNoAssert("py")
pickSuggestion(suggestion)
assertEquals("keep EXPANDED", text)
assertEquals(text.length, cursor)
functionalKeyPress(KeyCode.DELETE)
assertEquals("keep .py", text)
checkConnectionConsistency()
}
}

private fun assertTypedShortcutExpands(shortcut: String) {
for (immediate in listOf(false, true)) {
configureExpansion(shortcut, immediate)
typeNoAssert("cps ")
assertEquals("cps ", text)
typeNoAssert("cpp")
if (!immediate) typeNoAssert(" ")
assertEquals("cps EXPANDED" + if (immediate) "" else " ", text)
if (!immediate) functionalKeyPress(KeyCode.DELETE)
functionalKeyPress(KeyCode.DELETE)
assertEquals("cps cpp", text)
checkConnectionConsistency()
}
}

private fun configureExpansion(shortcut: String, immediate: Boolean) {
reset()
latinIME.prefs().edit {
putBoolean(TextExpanderUtils.PREF_ENABLED, true)
putBoolean(TextExpanderUtils.PREF_IMMEDIATE, immediate)
putBoolean(TextExpanderUtils.PREF_BACKSPACE_REVERTS, true)
}
TextExpanderUtils.saveShortcuts(
latinIME, mapOf(shortcut to TextExpanderUtils.ShortcutEntry("EXPANDED"))
)
}

@Test fun numberShortcutWithAtPrefixExpandsOnSpace() {
reset()
latinIME.prefs().edit().apply {
Expand Down Expand Up @@ -1269,6 +1449,7 @@ class InputLogicTest {
lastNgramContext = ""
addedWords.clear()
ngramContexts.clear()
committedCorrections.clear()
messages.clear()
delayedMessages.clear()

Expand Down Expand Up @@ -1516,6 +1697,7 @@ private var selectionStart = 0
private var selectionEnd = 0
private var composingStart = -1
private var composingEnd = -1
private val committedCorrections = mutableListOf<CorrectionInfo?>()
// convenience for access
private val textBeforeCursor get() = text.substring(0, selectionStart)
private val textAfterCursor get() = text.substring(selectionEnd)
Expand Down Expand Up @@ -1672,8 +1854,10 @@ private val ic = object : InputConnection {
it.selectionEnd = selectionEnd
}
}
// only effect is flashing, so whatever...
override fun commitCorrection(p0: CorrectionInfo?): Boolean = true
override fun commitCorrection(p0: CorrectionInfo?): Boolean {
committedCorrections.add(p0)
return true
}
// implement only when necessary
override fun getCursorCapsMode(p0: Int): Int = TODO("Not yet implemented")
override fun deleteSurroundingTextInCodePoints(p0: Int, p1: Int): Boolean = TODO("Not yet implemented")
Expand Down
Loading