aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java69
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java30
-rw-r--r--java/src/com/android/inputmethod/latin/TextEntryState.java15
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java74
4 files changed, 162 insertions, 26 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f7a77cae7..60b436f69 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -403,7 +403,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
resetPendingImsCallback();
mIsOrientationChanging = true;
final LatinIME latinIme = getOuterInstance();
- latinIme.mKeyboardSwitcher.saveKeyboardState();
+ if (latinIme.isInputViewShown()) {
+ latinIme.mKeyboardSwitcher.saveKeyboardState();
+ }
}
private void resetPendingImsCallback() {
@@ -948,6 +950,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
mExpectingUpdateSelection = false;
mHandler.postUpdateShiftKeyState();
+ // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
+ // here. It would probably be too expensive to call directly here but we may want to post a
+ // message to delay it. The point would be to unify behavior between backspace to the
+ // end of a word and manually put the pointer at the end of the word.
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
@@ -1466,10 +1472,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// inconsistent with backspacing after selecting other suggestions.
revertLastWord(ic);
} else {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ ic.deleteSurroundingText(1, 0);
if (mDeleteCount > DELETE_ACCELERATE_AT) {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ ic.deleteSurroundingText(1, 0);
}
+ restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
}
}
ic.endBatchEdit();
@@ -1793,6 +1800,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// The whitelist should be case-insensitive, so it's not possible to be consistent with
// a boolean flag. Right now this is handled with a slight hack in
// WhitelistDictionary#shouldForciblyAutoCorrectFrom.
+ final int quotesCount = wordComposer.trailingSingleQuotesCount();
final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
mSuggest.getUnigramDictionaries(),
// If the typed string ends with a single quote, for dictionary lookup purposes
@@ -1800,8 +1808,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// 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,
+ quotesCount > 0
+ ? typedWord.subSequence(0, typedWord.length() - quotesCount) : typedWord,
preferCapitalization());
if (mCorrectionMode == Suggest.CORRECTION_FULL
|| mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
@@ -2118,6 +2126,53 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
// "ic" must not be null
+ /**
+ * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
+ * word, else do nothing.
+ */
+ private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(
+ final InputConnection ic) {
+ // Bail out if the cursor is not at the end of a word (cursor must be preceded by
+ // non-whitespace, non-separator, non-start-of-text)
+ // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
+ final CharSequence textBeforeCursor = ic.getTextBeforeCursor(1, 0);
+ if (TextUtils.isEmpty(textBeforeCursor)
+ || mSettingsValues.isWordSeparator(textBeforeCursor.charAt(0))) return;
+
+ // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
+ // separator or end of line/text)
+ // Example: "test|"<EOL> "te|st" get rejected here
+ final CharSequence textAfterCursor = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(textAfterCursor)
+ && !mSettingsValues.isWordSeparator(textAfterCursor.charAt(0))) return;
+
+ // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
+ // Example: " '|" gets rejected here but "I'|" and "I|" are okay
+ final CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
+ if (TextUtils.isEmpty(word)) return;
+ if (word.length() == 1 && !Character.isLetter(word.charAt(0))) return;
+
+ // Okay, we are at the end of a word. Restart suggestions.
+ restartSuggestionsOnWordBeforeCursor(ic, word);
+ }
+
+ // "ic" must not be null
+ private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
+ final CharSequence word) {
+ mWordComposer.setComposingWord(word, mKeyboardSwitcher.getLatinKeyboard());
+ mComposingStringBuilder.setLength(0);
+ mComposingStringBuilder.append(word);
+ // mBestWord will be set appropriately by updateSuggestions() called by the handler
+ mBestWord = null;
+ mHasUncommittedTypedChars = true;
+ mComposingStateManager.onStartComposingText();
+ TextEntryState.restartSuggestionsOnWordBeforeCursor();
+ ic.deleteSurroundingText(word.length(), 0);
+ ic.setComposingText(word, 1);
+ mHandler.postUpdateSuggestions();
+ }
+
+ // "ic" must not be null
private void revertLastWord(final InputConnection ic) {
if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
@@ -2143,6 +2198,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Clear composing text
mComposingStringBuilder.setLength(0);
} else {
+ // Note: this relies on the last word still being held in the WordComposer
+ // Note: in the interest of code simplicity, we may want to just call
+ // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving
+ // the old WordComposer allows to reuse the actual typed coordinates.
mHasUncommittedTypedChars = true;
ic.setComposingText(mComposingStringBuilder, 1);
TextEntryState.backspace();
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 5a3c348a9..2a36f8266 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -82,8 +82,6 @@ public class Suggest implements Dictionary.WordCallback {
public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
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 AutoCorrection mAutoCorrection;
@@ -109,7 +107,7 @@ public class Suggest implements Dictionary.WordCallback {
// TODO: Remove these member variables by passing more context to addWord() callback method
private boolean mIsFirstCharCapitalized;
private boolean mIsAllUpperCase;
- private boolean mIsLastCharASingleQuote;
+ private int mTrailingSingleQuotesCount;
private int mCorrectionMode = CORRECTION_BASIC;
@@ -299,13 +297,14 @@ public class Suggest implements Dictionary.WordCallback {
mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase();
- mIsLastCharASingleQuote = wordComposer.isLastCharASingleQuote();
+ mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
collectGarbage(mSuggestions, mPrefMaxSuggestions);
Arrays.fill(mScores, 0);
final String typedWord = wordComposer.getTypedWord();
- final String consideredWord = mIsLastCharASingleQuote
- ? typedWord.substring(0, typedWord.length() - 1) : typedWord;
+ final String consideredWord = mTrailingSingleQuotesCount > 0
+ ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
+ : typedWord;
if (typedWord != null) {
// Treating USER_TYPED as UNIGRAM suggestion for logging now.
LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
@@ -360,9 +359,11 @@ public class Suggest implements Dictionary.WordCallback {
if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
continue;
final Dictionary dictionary = mUnigramDictionaries.get(key);
- if (mIsLastCharASingleQuote) {
+ if (mTrailingSingleQuotesCount > 0) {
final WordComposer tmpWordComposer = new WordComposer(wordComposer);
- tmpWordComposer.deleteLast();
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ tmpWordComposer.deleteLast();
+ }
dictionary.getWords(tmpWordComposer, this, proximityInfo);
} else {
dictionary.getWords(wordComposer, this, proximityInfo);
@@ -380,8 +381,15 @@ public class Suggest implements Dictionary.WordCallback {
whitelistedWord);
if (whitelistedWord != null) {
- mSuggestions.add(0, mIsLastCharASingleQuote
- ? whitelistedWord + SINGLE_QUOTE_AS_STRING : whitelistedWord);
+ if (mTrailingSingleQuotesCount > 0) {
+ final StringBuilder sb = new StringBuilder(whitelistedWord);
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+ }
+ mSuggestions.add(0, sb.toString());
+ } else {
+ mSuggestions.add(0, whitelistedWord);
+ }
}
if (typedWord != null) {
@@ -500,7 +508,7 @@ public class Suggest implements Dictionary.WordCallback {
} else {
sb.append(word, offset, length);
}
- if (mIsLastCharASingleQuote) {
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
}
suggestions.add(pos, sb);
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 82242f87e..a6041b310 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -146,9 +146,24 @@ public class TextEntryState {
} else if (sState == UNDO_COMMIT) {
setState(IN_WORD);
}
+ // TODO: tidy up this logic. At the moment, for example, writing a word goes to
+ // ACCEPTED_DEFAULT, backspace will go to UNDO_COMMIT, another backspace will go to IN_WORD,
+ // and subsequent backspaces will leave the status at IN_WORD, even if the user backspaces
+ // past the end of the word. We are not in a word any more but the state is still IN_WORD.
if (DEBUG) displayState("backspace");
}
+ public static void restartSuggestionsOnWordBeforeCursor() {
+ if (UNKNOWN == sState || ACCEPTED_DEFAULT == sState) {
+ // Here we can come from pretty much any state, except the ones that we can't
+ // come from after backspace, so supposedly anything except UNKNOWN and
+ // ACCEPTED_DEFAULT. Note : we could be in UNDO_COMMIT if
+ // LatinIME#revertLastWord() was calling LatinIME#restartSuggestions...()
+ Log.e(TAG, "Strange state change : coming from state " + sState);
+ }
+ setState(IN_WORD);
+ }
+
public static void reset() {
setState(START);
if (DEBUG) displayState("reset");
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 612b16071..44c89f73c 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -17,7 +17,9 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.LatinKeyboard;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,7 +46,7 @@ public class WordComposer {
private boolean mAutoCapitalized;
// Cache this value for performance
- private boolean mIsLastCharASingleQuote;
+ private int mTrailingSingleQuotesCount;
/**
* Whether the user chose to capitalize the first char of the word.
@@ -57,7 +59,7 @@ public class WordComposer {
mTypedWord = new StringBuilder(N);
mXCoordinates = new int[N];
mYCoordinates = new int[N];
- mIsLastCharASingleQuote = false;
+ mTrailingSingleQuotesCount = 0;
}
public WordComposer(WordComposer source) {
@@ -72,7 +74,7 @@ public class WordComposer {
mCapsCount = source.mCapsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
mAutoCapitalized = source.mAutoCapitalized;
- mIsLastCharASingleQuote = source.mIsLastCharASingleQuote;
+ mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
}
/**
@@ -83,7 +85,7 @@ public class WordComposer {
mTypedWord.setLength(0);
mCapsCount = 0;
mIsFirstCharCapitalized = false;
- mIsLastCharASingleQuote = false;
+ mTrailingSingleQuotesCount = 0;
}
/**
@@ -133,7 +135,55 @@ public class WordComposer {
mIsFirstCharCapitalized = isFirstCharCapitalized(
newIndex, primaryCode, mIsFirstCharCapitalized);
if (Character.isUpperCase(primaryCode)) mCapsCount++;
- mIsLastCharASingleQuote = Keyboard.CODE_SINGLE_QUOTE == primaryCode;
+ if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+ ++mTrailingSingleQuotesCount;
+ } else {
+ mTrailingSingleQuotesCount = 0;
+ }
+ }
+
+ /**
+ * Internal method to retrieve reasonable proximity info for a character.
+ */
+ private void addKeyInfo(final int codePoint, final LatinKeyboard keyboard,
+ final KeyDetector keyDetector) {
+ for (final Key key : keyboard.mKeys) {
+ if (key.mCode == codePoint) {
+ final int x = key.mX + key.mWidth / 2;
+ final int y = key.mY + key.mHeight / 2;
+ final int[] codes = keyDetector.newCodeArray();
+ keyDetector.getKeyAndNearbyCodes(x, y, codes);
+ add(codePoint, codes, x, y);
+ return;
+ }
+ }
+ add(codePoint, new int[] { codePoint },
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ }
+
+ /**
+ * Set the currently composing word to the one passed as an argument.
+ * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+ */
+ public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard,
+ final KeyDetector keyDetector) {
+ reset();
+ final int length = word.length();
+ for (int i = 0; i < length; ++i) {
+ int codePoint = word.charAt(i);
+ addKeyInfo(codePoint, keyboard, keyDetector);
+ }
+ }
+
+ /**
+ * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
+ */
+ public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard) {
+ final KeyDetector keyDetector = new KeyDetector(0);
+ keyDetector.setKeyboard(keyboard, 0, 0);
+ keyDetector.setProximityCorrectionEnabled(true);
+ keyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
+ setComposingWord(word, keyboard, keyDetector);
}
/**
@@ -165,10 +215,14 @@ public class WordComposer {
}
if (size() == 0) {
mIsFirstCharCapitalized = false;
- mIsLastCharASingleQuote = false;
+ }
+ if (mTrailingSingleQuotesCount > 0) {
+ --mTrailingSingleQuotesCount;
} else {
- mIsLastCharASingleQuote =
- Keyboard.CODE_SINGLE_QUOTE == mTypedWord.codePointAt(mTypedWord.length() - 1);
+ for (int i = mTypedWord.length() - 1; i >= 0; --i) {
+ if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+ ++mTrailingSingleQuotesCount;
+ }
}
}
@@ -191,8 +245,8 @@ public class WordComposer {
return mIsFirstCharCapitalized;
}
- public boolean isLastCharASingleQuote() {
- return mIsLastCharASingleQuote;
+ public int trailingSingleQuotesCount() {
+ return mTrailingSingleQuotesCount;
}
/**