Special case quotes at start and end of words

Single quote at start of word is not considered a part of a
word any more.
Single quote at the end of a word now behave like capitalization:
lookup in the dictionary is done *disregarding* a final quote,
and it is forcefully added back into the suggestions afterwards.

Bug: 5566368
Change-Id: I14dd3815f4b743edba56d64a3abdf4b73d863a6a
main
Jean Chalard 2011-11-18 20:03:38 +09:00
parent 7800a313f1
commit c83359f974
3 changed files with 61 additions and 17 deletions

View File

@ -1503,6 +1503,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mComposingStateManager.onFinishComposingText(); mComposingStateManager.onFinishComposingText();
} }
} }
if (code == Keyboard.CODE_SINGLE_QUOTE && !isCursorTouchingWord()) {
mHasUncommittedTypedChars = false;
}
final KeyboardSwitcher switcher = mKeyboardSwitcher; final KeyboardSwitcher switcher = mKeyboardSwitcher;
if (switcher.isShiftedOrShiftLocked()) { if (switcher.isShiftedOrShiftLocked()) {
if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
@ -1775,7 +1778,15 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// a boolean flag. Right now this is handled with a slight hack in // a boolean flag. Right now this is handled with a slight hack in
// WhitelistDictionary#shouldForciblyAutoCorrectFrom. // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected( final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization()); mSuggest.getUnigramDictionaries(),
// If the typed string ends with a single quote, for dictionary lookup purposes
// we behave as if the single quote was not here. Here, we are looking up the
// typed string in the dictionary (to avoid autocorrecting from an existing
// word, so for consistency this lookup should be made WITHOUT the trailing
// single quote.
wordComposer.isLastCharASingleQuote()
? typedWord.subSequence(0, typedWord.length() - 1) : typedWord,
preferCapitalization());
if (mCorrectionMode == Suggest.CORRECTION_FULL if (mCorrectionMode == Suggest.CORRECTION_FULL
|| mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
autoCorrectionAvailable |= (!allowsToBeAutoCorrected); autoCorrectionAvailable |= (!allowsToBeAutoCorrected);

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.keyboard.ProximityInfo;
import java.io.File; import java.io.File;
@ -81,6 +82,8 @@ public class Suggest implements Dictionary.WordCallback {
public static final String DICT_KEY_USER_BIGRAM = "user_bigram"; public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
public static final String DICT_KEY_WHITELIST ="whitelist"; public static final String DICT_KEY_WHITELIST ="whitelist";
private static String SINGLE_QUOTE_AS_STRING = String.valueOf((char)Keyboard.CODE_SINGLE_QUOTE);
private static final boolean DBG = LatinImeLogger.sDBG; private static final boolean DBG = LatinImeLogger.sDBG;
private AutoCorrection mAutoCorrection; private AutoCorrection mAutoCorrection;
@ -101,11 +104,12 @@ public class Suggest implements Dictionary.WordCallback {
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>(); ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
private CharSequence mTypedWord; private CharSequence mConsideredWord;
// TODO: Remove these member variables by passing more context to addWord() callback method // TODO: Remove these member variables by passing more context to addWord() callback method
private boolean mIsFirstCharCapitalized; private boolean mIsFirstCharCapitalized;
private boolean mIsAllUpperCase; private boolean mIsAllUpperCase;
private boolean mIsLastCharASingleQuote;
private int mCorrectionMode = CORRECTION_BASIC; private int mCorrectionMode = CORRECTION_BASIC;
@ -295,17 +299,19 @@ public class Suggest implements Dictionary.WordCallback {
mAutoCorrection.init(); mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase(); mIsAllUpperCase = wordComposer.isAllUpperCase();
mIsLastCharASingleQuote = wordComposer.isLastCharASingleQuote();
collectGarbage(mSuggestions, mPrefMaxSuggestions); collectGarbage(mSuggestions, mPrefMaxSuggestions);
Arrays.fill(mScores, 0); Arrays.fill(mScores, 0);
// Save a lowercase version of the original word final String typedWord = wordComposer.getTypedWord();
String typedWord = wordComposer.getTypedWord(); final String consideredWord = mIsLastCharASingleQuote
? typedWord.substring(0, typedWord.length() - 1) : typedWord;
if (typedWord != null) { if (typedWord != null) {
// Treating USER_TYPED as UNIGRAM suggestion for logging now. // Treating USER_TYPED as UNIGRAM suggestion for logging now.
LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
Dictionary.DataType.UNIGRAM); Dictionary.DataType.UNIGRAM);
} }
mTypedWord = typedWord; mConsideredWord = consideredWord;
if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|| mCorrectionMode == CORRECTION_BASIC)) { || mCorrectionMode == CORRECTION_BASIC)) {
@ -321,7 +327,7 @@ public class Suggest implements Dictionary.WordCallback {
for (final Dictionary dictionary : mBigramDictionaries.values()) { for (final Dictionary dictionary : mBigramDictionaries.values()) {
dictionary.getBigrams(wordComposer, prevWordForBigram, this); dictionary.getBigrams(wordComposer, prevWordForBigram, this);
} }
if (TextUtils.isEmpty(typedWord)) { if (TextUtils.isEmpty(consideredWord)) {
// Nothing entered: return all bigrams for the previous word // Nothing entered: return all bigrams for the previous word
int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions); int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
for (int i = 0; i < insertCount; ++i) { for (int i = 0; i < insertCount; ++i) {
@ -330,7 +336,7 @@ public class Suggest implements Dictionary.WordCallback {
} else { } else {
// Word entered: return only bigrams that match the first char of the typed word // Word entered: return only bigrams that match the first char of the typed word
@SuppressWarnings("null") @SuppressWarnings("null")
final char currentChar = typedWord.charAt(0); final char currentChar = consideredWord.charAt(0);
// TODO: Must pay attention to locale when changing case. // TODO: Must pay attention to locale when changing case.
final char currentCharUpper = Character.toUpperCase(currentChar); final char currentCharUpper = Character.toUpperCase(currentChar);
int count = 0; int count = 0;
@ -354,24 +360,32 @@ public class Suggest implements Dictionary.WordCallback {
if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST)) if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
continue; continue;
final Dictionary dictionary = mUnigramDictionaries.get(key); final Dictionary dictionary = mUnigramDictionaries.get(key);
dictionary.getWords(wordComposer, this, proximityInfo); if (mIsLastCharASingleQuote) {
final WordComposer tmpWordComposer = new WordComposer(wordComposer);
tmpWordComposer.deleteLast();
dictionary.getWords(tmpWordComposer, this, proximityInfo);
} else {
dictionary.getWords(wordComposer, this, proximityInfo);
}
} }
} }
final String typedWordString = typedWord == null ? null : typedWord.toString(); final String consideredWordString =
consideredWord == null ? null : consideredWord.toString();
CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized, CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
mWhiteListDictionary.getWhitelistedWord(typedWordString)); mWhiteListDictionary.getWhitelistedWord(consideredWordString));
mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer, mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode, mSuggestions, mScores, consideredWord, mAutoCorrectionThreshold, mCorrectionMode,
whitelistedWord); whitelistedWord);
if (whitelistedWord != null) { if (whitelistedWord != null) {
mSuggestions.add(0, whitelistedWord); mSuggestions.add(0, mIsLastCharASingleQuote
? whitelistedWord + SINGLE_QUOTE_AS_STRING : whitelistedWord);
} }
if (typedWord != null) { if (typedWord != null) {
mSuggestions.add(0, typedWordString); mSuggestions.add(0, typedWord.toString());
} }
Utils.removeDupes(mSuggestions); Utils.removeDupes(mSuggestions);
@ -424,7 +438,7 @@ public class Suggest implements Dictionary.WordCallback {
int pos = 0; int pos = 0;
// Check if it's the same word, only caps are different // Check if it's the same word, only caps are different
if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) { if (Utils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
// TODO: remove this surrounding if clause and move this logic to // TODO: remove this surrounding if clause and move this logic to
// getSuggestedWordBuilder. // getSuggestedWordBuilder.
if (suggestions.size() > 0) { if (suggestions.size() > 0) {
@ -486,6 +500,9 @@ public class Suggest implements Dictionary.WordCallback {
} else { } else {
sb.append(word, offset, length); sb.append(word, offset, length);
} }
if (mIsLastCharASingleQuote) {
sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
}
suggestions.add(pos, sb); suggestions.add(pos, sb);
if (suggestions.size() > prefMaxSuggestions) { if (suggestions.size() > prefMaxSuggestions) {
final CharSequence garbage = suggestions.remove(prefMaxSuggestions); final CharSequence garbage = suggestions.remove(prefMaxSuggestions);

View File

@ -16,9 +16,11 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.KeyDetector;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
/** /**
* A place to store the currently composing word with information such as adjacent key codes as well * A place to store the currently composing word with information such as adjacent key codes as well
@ -41,6 +43,8 @@ public class WordComposer {
private int mCapsCount; private int mCapsCount;
private boolean mAutoCapitalized; private boolean mAutoCapitalized;
// Cache this value for performance
private boolean mIsLastCharASingleQuote;
/** /**
* Whether the user chose to capitalize the first char of the word. * Whether the user chose to capitalize the first char of the word.
@ -53,6 +57,7 @@ public class WordComposer {
mTypedWord = new StringBuilder(N); mTypedWord = new StringBuilder(N);
mXCoordinates = new int[N]; mXCoordinates = new int[N];
mYCoordinates = new int[N]; mYCoordinates = new int[N];
mIsLastCharASingleQuote = false;
} }
public WordComposer(WordComposer source) { public WordComposer(WordComposer source) {
@ -62,11 +67,12 @@ public class WordComposer {
public void init(WordComposer source) { public void init(WordComposer source) {
mCodes = new ArrayList<int[]>(source.mCodes); mCodes = new ArrayList<int[]>(source.mCodes);
mTypedWord = new StringBuilder(source.mTypedWord); mTypedWord = new StringBuilder(source.mTypedWord);
mXCoordinates = source.mXCoordinates; mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
mYCoordinates = source.mYCoordinates; mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
mCapsCount = source.mCapsCount; mCapsCount = source.mCapsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized; mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
mAutoCapitalized = source.mAutoCapitalized; mAutoCapitalized = source.mAutoCapitalized;
mIsLastCharASingleQuote = source.mIsLastCharASingleQuote;
} }
/** /**
@ -77,6 +83,7 @@ public class WordComposer {
mTypedWord.setLength(0); mTypedWord.setLength(0);
mCapsCount = 0; mCapsCount = 0;
mIsFirstCharCapitalized = false; mIsFirstCharCapitalized = false;
mIsLastCharASingleQuote = false;
} }
/** /**
@ -126,6 +133,7 @@ public class WordComposer {
mIsFirstCharCapitalized = isFirstCharCapitalized( mIsFirstCharCapitalized = isFirstCharCapitalized(
newIndex, primaryCode, mIsFirstCharCapitalized); newIndex, primaryCode, mIsFirstCharCapitalized);
if (Character.isUpperCase(primaryCode)) mCapsCount++; if (Character.isUpperCase(primaryCode)) mCapsCount++;
mIsLastCharASingleQuote = Keyboard.CODE_SINGLE_QUOTE == primaryCode;
} }
/** /**
@ -157,6 +165,10 @@ public class WordComposer {
} }
if (size() == 0) { if (size() == 0) {
mIsFirstCharCapitalized = false; mIsFirstCharCapitalized = false;
mIsLastCharASingleQuote = false;
} else {
mIsLastCharASingleQuote =
Keyboard.CODE_SINGLE_QUOTE == mTypedWord.codePointAt(mTypedWord.length() - 1);
} }
} }
@ -179,6 +191,10 @@ public class WordComposer {
return mIsFirstCharCapitalized; return mIsFirstCharCapitalized;
} }
public boolean isLastCharASingleQuote() {
return mIsLastCharASingleQuote;
}
/** /**
* Whether or not all of the user typed chars are upper case * Whether or not all of the user typed chars are upper case
* @return true if all user typed chars are upper case, false otherwise * @return true if all user typed chars are upper case, false otherwise