From cb70ee2656bead63ee86e093fbe48aa2850ebe2c Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 30 Nov 2023 14:57:38 +0100 Subject: [PATCH] tune glide typing suggestions prefer multi-letter over single letter suggestions prefer suggestions that also occur in next word suggestions for this ngram context (and do some tiny tuning) --- .../keyboard_parser/KeyboardParser.kt | 5 +- .../openboard/inputmethod/latin/Suggest.java | 65 +++++++++++++++++-- .../suggestions/SuggestionStripView.java | 2 +- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt index ea38991bc..8d50e4e09 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/KeyboardParser.kt @@ -477,9 +477,10 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co if (params.mIconsSet.getIconDrawable(KeyboardIconsSet.getIconId(this)) != null) return this val id = context.resources.getIdentifier("label_$this", "string", context.packageName) if (id == 0) { - Log.w(this::class.simpleName, "no resource for label $this") + val message = "no resource for label $this in ${params.mId}" + Log.w(this::class.simpleName, message) if (DebugFlags.DEBUG_ENABLED) - Toast.makeText(context, "no resource for label $this", Toast.LENGTH_LONG).show() + Toast.makeText(context, message, Toast.LENGTH_LONG).show() return this } val ril = object : RunInLocale() { // todo (later): simpler way of doing this in a single line? diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/Suggest.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/Suggest.java index c13c6678b..9cdae0e48 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/Suggest.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/Suggest.java @@ -7,6 +7,7 @@ package org.dslul.openboard.inputmethod.latin; import android.text.TextUtils; +import android.util.Log; import org.dslul.openboard.inputmethod.annotations.UsedForTesting; import org.dslul.openboard.inputmethod.keyboard.Keyboard; @@ -16,6 +17,7 @@ import org.dslul.openboard.inputmethod.latin.common.ComposedData; import org.dslul.openboard.inputmethod.latin.common.Constants; import org.dslul.openboard.inputmethod.latin.common.InputPointers; import org.dslul.openboard.inputmethod.latin.common.StringUtils; +import org.dslul.openboard.inputmethod.latin.define.DebugFlags; import org.dslul.openboard.inputmethod.latin.settings.Settings; import org.dslul.openboard.inputmethod.latin.settings.SettingsValuesForSuggestion; import org.dslul.openboard.inputmethod.latin.suggestions.SuggestionStripView; @@ -25,6 +27,7 @@ import org.dslul.openboard.inputmethod.latin.utils.SuggestionResults; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -32,6 +35,7 @@ import static org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConsta import static org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import kotlin.collections.CollectionsKt; @@ -424,11 +428,11 @@ public final class Suggest { final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( wordComposer.getComposedDataSnapshot(), ngramContext, keyboard, settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle); + replaceSingleLetterFirstSuggestion(suggestionResults); // For transforming words that don't come from a dictionary, because it's our best bet final Locale locale = mDictionaryFacilitator.getLocale(); - final ArrayList suggestionsContainer = - new ArrayList<>(suggestionResults); + final ArrayList suggestionsContainer = new ArrayList<>(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); @@ -443,12 +447,15 @@ public final class Suggest { } } + final SuggestedWordInfo rejected; if (SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION && suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord, wordComposer.getRejectedBatchModeSuggestion())) { - final SuggestedWordInfo rejected = suggestionsContainer.remove(0); + rejected = suggestionsContainer.remove(0); suggestionsContainer.add(1, rejected); + } else { + rejected = null; } SuggestedWordInfo.removeDupsAndTypedWord(null /* typedWord */, suggestionsContainer); @@ -464,8 +471,8 @@ public final class Suggest { // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false). // Note that because this method is never used to get predictions, there is no need to // modify inputType such in getSuggestedWordsForNonBatchInput. - final SuggestedWordInfo pseudoTypedWordInfo = suggestionsContainer.isEmpty() ? null - : suggestionsContainer.get(0); + final SuggestedWordInfo pseudoTypedWordInfo = preferNextWordSuggestion(suggestionsContainer.isEmpty() ? null : suggestionsContainer.get(0), + suggestionsContainer, getNextWordSuggestions(ngramContext, keyboard, inputStyle, settingsValuesForSuggestion), rejected); final ArrayList suggestionsList; if (SuggestionStripView.DEBUG_SUGGESTIONS && !suggestionsContainer.isEmpty()) { @@ -483,6 +490,54 @@ public final class Suggest { inputStyle, sequenceNumber)); } + /** reduces score of the first suggestion if next one is close and has more than a single letter */ + private void replaceSingleLetterFirstSuggestion(final SuggestionResults suggestionResults) { + if (suggestionResults.size() < 2 || suggestionResults.first().mWord.length() != 1) return; + // suppress single letter suggestions if next suggestion is close and has more than one letter + final Iterator iterator = suggestionResults.iterator(); + final SuggestedWordInfo first = iterator.next(); + final SuggestedWordInfo second = iterator.next(); + if (second.mWord.length() > 1 && second.mScore > 0.94 * first.mScore) { + suggestionResults.remove(first); // remove and re-add with lower score + suggestionResults.add(new SuggestedWordInfo(first.mWord, first.mPrevWordsContext, (int) (first.mScore * 0.93), + first.mKindAndFlags, first.mSourceDict, first.mIndexOfTouchPointOfSecondWord, first.mAutoCommitFirstWordConfidence)); + if (DebugFlags.DEBUG_ENABLED) + Log.d(TAG, "reduced score of "+first.mWord+" from "+first.mScore +", new first: "+suggestionResults.first().mWord+" ("+suggestionResults.first().mScore+")"); + } + } + + // returns new pseudoTypedWordInfo, puts it in suggestionsContainer, modifies nextWordSuggestions + @Nullable + private SuggestedWordInfo preferNextWordSuggestion(@Nullable final SuggestedWordInfo pseudoTypedWordInfo, + @NonNull final ArrayList suggestionsContainer, + @NonNull final SuggestionResults nextWordSuggestions, @Nullable final SuggestedWordInfo rejected) { + if (pseudoTypedWordInfo == null + || !Settings.getInstance().getCurrent().mUsePersonalizedDicts + || !pseudoTypedWordInfo.mSourceDict.mDictType.equals(Dictionary.TYPE_MAIN) + || suggestionsContainer.size() < 2 + ) + return pseudoTypedWordInfo; + CollectionsKt.removeAll(nextWordSuggestions, (info) -> info.mScore < 170); // we only want reasonably often typed words, value may require tuning + if (nextWordSuggestions.isEmpty()) + return pseudoTypedWordInfo; + // for each suggestion, check whether the word was already typed in this ngram context (i.e. is nextWordSuggestion) + for (final SuggestedWordInfo suggestion : suggestionsContainer) { + if (suggestion.mScore < pseudoTypedWordInfo.mScore * 0.93) break; // we only want reasonably good suggestions, value may require tuning + if (suggestion == rejected) continue; // ignore rejected suggestions + for (final SuggestedWordInfo nextWordSuggestion : nextWordSuggestions) { + if (!nextWordSuggestion.mWord.equals(suggestion.mWord)) + continue; + // if we have a high scoring suggestion in next word suggestions, take it (because it's expected that user might want to type it again) + suggestionsContainer.remove(suggestion); + suggestionsContainer.add(0, suggestion); + if (DebugFlags.DEBUG_ENABLED) + Log.d(TAG, "replaced batch word "+pseudoTypedWordInfo+" with "+suggestion); + return suggestion; + } + } + return pseudoTypedWordInfo; + } + private static ArrayList getSuggestionsInfoListWithDebugInfo( final String typedWord, final ArrayList suggestions) { final int suggestionsSize = suggestions.size(); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java index deca7b4c2..c66a1d8ad 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java @@ -73,7 +73,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } public static boolean DEBUG_SUGGESTIONS; - private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.0f; + private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.5f; private static final String VOICE_KEY_TAG = "voice_key"; private static final String CLIPBOARD_KEY_TAG = "clipboard_key"; private static final String SETTINGS_KEY_TAG = "settings_key";