Loading java/src/com/android/inputmethod/latin/LatinIME.java +58 −2 Original line number Diff line number Diff line Loading @@ -950,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; Loading Loading @@ -1468,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(); Loading Loading @@ -2120,6 +2125,53 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar return TextUtils.equals(text, beforeText); } // "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) { Loading @@ -2146,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(); Loading java/src/com/android/inputmethod/latin/TextEntryState.java +15 −0 Original line number Diff line number Diff line Loading @@ -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"); Loading java/src/com/android/inputmethod/latin/WordComposer.java +46 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -140,6 +142,50 @@ public class WordComposer { } } /** * 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.getKeyIndexAndNearbyCodes(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); } /** * Swaps the first and second values in the codes array if the primary code is not the first * value in the array but the second. This happens when the preferred key is not the key that Loading tests/src/com/android/inputmethod/latin/SuggestHelper.java +1 −18 Original line number Diff line number Diff line Loading @@ -65,26 +65,9 @@ public class SuggestHelper { return mSuggest.hasMainDictionary(); } private void addKeyInfo(WordComposer word, char c) { for (final Key key : mKeyboard.mKeys) { if (key.mCode == c) { final int x = key.mX + key.mWidth / 2; final int y = key.mY + key.mHeight / 2; final int[] codes = mKeyDetector.newCodeArray(); mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); word.add(c, codes, x, y); return; } } word.add(c, new int[] { c }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); } protected WordComposer createWordComposer(CharSequence s) { WordComposer word = new WordComposer(); for (int i = 0; i < s.length(); i++) { final char c = s.charAt(i); addKeyInfo(word, c); } word.setComposingWord(s, mKeyboard, mKeyDetector); return word; } Loading Loading
java/src/com/android/inputmethod/latin/LatinIME.java +58 −2 Original line number Diff line number Diff line Loading @@ -950,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; Loading Loading @@ -1468,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(); Loading Loading @@ -2120,6 +2125,53 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar return TextUtils.equals(text, beforeText); } // "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) { Loading @@ -2146,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(); Loading
java/src/com/android/inputmethod/latin/TextEntryState.java +15 −0 Original line number Diff line number Diff line Loading @@ -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"); Loading
java/src/com/android/inputmethod/latin/WordComposer.java +46 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -140,6 +142,50 @@ public class WordComposer { } } /** * 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.getKeyIndexAndNearbyCodes(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); } /** * Swaps the first and second values in the codes array if the primary code is not the first * value in the array but the second. This happens when the preferred key is not the key that Loading
tests/src/com/android/inputmethod/latin/SuggestHelper.java +1 −18 Original line number Diff line number Diff line Loading @@ -65,26 +65,9 @@ public class SuggestHelper { return mSuggest.hasMainDictionary(); } private void addKeyInfo(WordComposer word, char c) { for (final Key key : mKeyboard.mKeys) { if (key.mCode == c) { final int x = key.mX + key.mWidth / 2; final int y = key.mY + key.mHeight / 2; final int[] codes = mKeyDetector.newCodeArray(); mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); word.add(c, codes, x, y); return; } } word.add(c, new int[] { c }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); } protected WordComposer createWordComposer(CharSequence s) { WordComposer word = new WordComposer(); for (int i = 0; i < s.length(); i++) { final char c = s.charAt(i); addKeyInfo(word, c); } word.setComposingWord(s, mKeyboard, mKeyDetector); return word; } Loading