Resume suggestion when backspacing to the end of a word
Bug: 5515381 Change-Id: I26fea896feaf2e9716c7ae3d4f2630360f23ac50main
parent
c1f7d39b4a
commit
6b1f500da4
|
@ -948,6 +948,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
||||||
}
|
}
|
||||||
mExpectingUpdateSelection = false;
|
mExpectingUpdateSelection = false;
|
||||||
mHandler.postUpdateShiftKeyState();
|
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
|
// Make a note of the cursor position
|
||||||
mLastSelectionStart = newSelStart;
|
mLastSelectionStart = newSelStart;
|
||||||
|
@ -1466,10 +1470,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
||||||
// inconsistent with backspacing after selecting other suggestions.
|
// inconsistent with backspacing after selecting other suggestions.
|
||||||
revertLastWord(ic);
|
revertLastWord(ic);
|
||||||
} else {
|
} else {
|
||||||
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
|
ic.deleteSurroundingText(1, 0);
|
||||||
if (mDeleteCount > DELETE_ACCELERATE_AT) {
|
if (mDeleteCount > DELETE_ACCELERATE_AT) {
|
||||||
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
|
ic.deleteSurroundingText(1, 0);
|
||||||
}
|
}
|
||||||
|
restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ic.endBatchEdit();
|
ic.endBatchEdit();
|
||||||
|
@ -2117,6 +2122,53 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
||||||
return TextUtils.equals(text, beforeText);
|
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
|
// "ic" must not be null
|
||||||
private void revertLastWord(final InputConnection ic) {
|
private void revertLastWord(final InputConnection ic) {
|
||||||
if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
|
if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
|
||||||
|
@ -2143,6 +2195,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
||||||
// Clear composing text
|
// Clear composing text
|
||||||
mComposingStringBuilder.setLength(0);
|
mComposingStringBuilder.setLength(0);
|
||||||
} else {
|
} 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;
|
mHasUncommittedTypedChars = true;
|
||||||
ic.setComposingText(mComposingStringBuilder, 1);
|
ic.setComposingText(mComposingStringBuilder, 1);
|
||||||
TextEntryState.backspace();
|
TextEntryState.backspace();
|
||||||
|
|
|
@ -146,9 +146,24 @@ public class TextEntryState {
|
||||||
} else if (sState == UNDO_COMMIT) {
|
} else if (sState == UNDO_COMMIT) {
|
||||||
setState(IN_WORD);
|
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");
|
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() {
|
public static void reset() {
|
||||||
setState(START);
|
setState(START);
|
||||||
if (DEBUG) displayState("reset");
|
if (DEBUG) displayState("reset");
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package com.android.inputmethod.latin;
|
package com.android.inputmethod.latin;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
|
import com.android.inputmethod.keyboard.Key;
|
||||||
import com.android.inputmethod.keyboard.KeyDetector;
|
import com.android.inputmethod.keyboard.KeyDetector;
|
||||||
|
import com.android.inputmethod.keyboard.LatinKeyboard;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -136,6 +138,50 @@ public class WordComposer {
|
||||||
mIsLastCharASingleQuote = Keyboard.CODE_SINGLE_QUOTE == primaryCode;
|
mIsLastCharASingleQuote = Keyboard.CODE_SINGLE_QUOTE == primaryCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* 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
|
* value in the array but the second. This happens when the preferred key is not the key that
|
||||||
|
|
|
@ -65,26 +65,9 @@ public class SuggestHelper {
|
||||||
return mSuggest.hasMainDictionary();
|
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) {
|
protected WordComposer createWordComposer(CharSequence s) {
|
||||||
WordComposer word = new WordComposer();
|
WordComposer word = new WordComposer();
|
||||||
for (int i = 0; i < s.length(); i++) {
|
word.setComposingWord(s, mKeyboard, mKeyDetector);
|
||||||
final char c = s.charAt(i);
|
|
||||||
addKeyInfo(word, c);
|
|
||||||
}
|
|
||||||
return word;
|
return word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue