diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index a62c22cbf..5a5674f8f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -201,7 +201,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen break; case MSG_RESUME_SUGGESTIONS: latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( - latinIme.mSettings.getCurrent(), latinIme.mKeyboardSwitcher); + latinIme.mSettings.getCurrent(), 0 /* offset */, + false /* includeResumedWordInSuggestions */, latinIme.mKeyboardSwitcher); break; case MSG_REOPEN_DICTIONARIES: latinIme.initSuggest(); diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 37311acf2..4d174ddb8 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -706,45 +706,6 @@ public final class RichInputConnection { return TextUtils.equals(text, beforeText); } - /* (non-javadoc) - * Returns the word before the cursor if the cursor is at the end of a word, null otherwise - */ - public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) { - // 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|" "te|st" get rejected here - final CharSequence textAfterCursor = getTextAfterCursor(1, 0); - if (!TextUtils.isEmpty(textAfterCursor) - && !settings.isWordSeparator(textAfterCursor.charAt(0))) return null; - - // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe) - // Example: " -|" gets rejected here but "e-|" and "e|" are okay - CharSequence word = getWordAtCursor(settings.mWordSeparators); - // We don't suggest on leading single quotes, so we have to remove them from the word if - // it starts with single quotes. - while (!TextUtils.isEmpty(word) && Constants.CODE_SINGLE_QUOTE == word.charAt(0)) { - word = word.subSequence(1, word.length()); - } - if (TextUtils.isEmpty(word)) return null; - // Find the last code point of the string - final int lastCodePoint = Character.codePointBefore(word, word.length()); - // If for some reason the text field contains non-unicode binary data, or if the - // charsequence is exactly one char long and the contents is a low surrogate, return null. - if (!Character.isDefined(lastCodePoint)) return null; - // 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) : "|a" " |a" " | " all get rejected here. - if (settings.isWordSeparator(lastCodePoint)) return null; - final char firstChar = word.charAt(0); // we just tested that word is not empty - if (word.length() == 1 && !Character.isLetter(firstChar)) return null; - - // We don't restart suggestion if the first character is not a letter, because we don't - // start composing when the first character is not a letter. - if (!Character.isLetter(firstChar)) return null; - - return word; - } - public boolean revertDoubleSpacePeriod() { if (DEBUG_BATCH_NESTING) checkBatchEdit(); // Here we test whether we indeed have a period and a space before us. This should not diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index ced3f049b..b365003a5 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -680,6 +680,7 @@ public final class InputLogic { // TODO: remove these arguments final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) { mSpaceState = SpaceState.NONE; + final int deleteCountAtStart = mDeleteCount; mDeleteCount++; // In many cases, we may have to put the keyboard in auto-shift state again. However @@ -811,12 +812,13 @@ public final class InputLogic { } } } - if (settingsValues.isSuggestionsRequested() + if (settingsValues.isSuggestionStripVisible() && settingsValues.mCurrentLanguageHasSpaces) { - restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(settingsValues, keyboardSwitcher, - handler); + restartSuggestionsOnWordTouchedByCursor(settingsValues, + deleteCountAtStart - mDeleteCount /* offset */, + true /* includeResumedWordInSuggestions */, keyboardSwitcher); } - // We just removed a character. We need to update the auto-caps state. + // We just removed at least one character. We need to update the auto-caps state. keyboardSwitcher.updateShiftState(); } } @@ -1041,44 +1043,18 @@ public final class InputLogic { } } - /** - * Check if the cursor is actually at the end of a word. If so, restart suggestions on this - * word, otherwise do nothing. - * @param settingsValues the current values of the settings. - */ - private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord( - final SettingsValues settingsValues, - // TODO: remove these two arguments - final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) { - final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(settingsValues); - if (null != word) { - final String wordString = word.toString(); - mWordComposer.setComposingWord(word, - // Previous word is the 2nd word before cursor because we are restarting on the - // 1st word before cursor. - getNthPreviousWordForSuggestion(settingsValues, 2 /* nthPreviousWord */), - keyboardSwitcher.getKeyboard()); - final int length = word.length(); - mConnection.deleteSurroundingText(length, 0); - mConnection.setComposingText(word, 1); - handler.postUpdateSuggestionStrip(); - // TODO: Handle the case where the user manually moves the cursor and then backs up over - // a separator. In that case, the current log unit should not be uncommitted. - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString, - true /* dumpCurrentLogUnit */); - } - } - } - /** * Check if the cursor is touching a word. If so, restart suggestions on this word, else * do nothing. * * @param settingsValues the current values of the settings. + * @param offset how much the cursor is expected to have moved since the last updateSelection. + * @param includeResumedWordInSuggestions whether to include the word on which we resume + * suggestions in the suggestion list. */ // TODO: make this private. public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues, + final int offset, final boolean includeResumedWordInSuggestions, // TODO: Remove this argument. final KeyboardSwitcher keyboardSwitcher) { // HACK: We may want to special-case some apps that exhibit bad behavior in case of @@ -1094,6 +1070,7 @@ public final class InputLogic { if (mLastSelectionStart != mLastSelectionEnd) return; // If we don't know the cursor location, return. if (mLastSelectionStart < 0) return; + final int expectedCursorPosition = mLastSelectionStart + offset; // We know Start == End if (!mConnection.isCursorTouchingWord(settingsValues)) return; final TextRange range = mConnection.getWordRangeAtCursor( settingsValues.mWordSeparators, 0 /* additionalPrecedingWordsCount */); @@ -1102,9 +1079,16 @@ public final class InputLogic { // If for some strange reason (editor bug or so) we measure the text before the cursor as // longer than what the entire text is supposed to be, the safe thing to do is bail out. final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor(); - if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return; + if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return; final ArrayList suggestions = CollectionUtils.newArrayList(); final String typedWord = range.mWord.toString(); + if (includeResumedWordInSuggestions) { + suggestions.add(new SuggestedWordInfo(typedWord, + SuggestionStripView.MAX_SUGGESTIONS + 1, + SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, + SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, + SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); + } if (!isResumableWord(settingsValues, typedWord)) return; int i = 0; for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) { @@ -1129,8 +1113,8 @@ public final class InputLogic { keyboardSwitcher.getKeyboard()); mWordComposer.setCursorPositionWithinWord( typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor)); - mConnection.setComposingRegion(mLastSelectionStart - numberOfCharsInWordBeforeCursor, - mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor()); + mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor, + expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor()); if (suggestions.isEmpty()) { // We come here if there weren't any suggestion spans on this word. We will try to // compute suggestions for it instead. @@ -1140,7 +1124,8 @@ public final class InputLogic { public void onGetSuggestedWords( final SuggestedWords suggestedWordsIncludingTypedWord) { final SuggestedWords suggestedWords; - if (suggestedWordsIncludingTypedWord.size() > 1) { + if (suggestedWordsIncludingTypedWord.size() > 1 + && !includeResumedWordInSuggestions) { // We were able to compute new suggestions for this word. // Remove the typed word, since we don't want to display it in this // case. The #getSuggestedWordsExcludingTypedWord() method sets diff --git a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java index c4fd5a0c4..cdd8f6b85 100644 --- a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java +++ b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java @@ -61,6 +61,7 @@ public class BlueUnderlineTests extends InputTestsBase { public void testBlueUnderlineOnBackspace() { final String STRING_TO_TYPE = "tgis"; + final int typedLength = STRING_TO_TYPE.length(); final int EXPECTED_SUGGESTION_SPAN_START = -1; final int EXPECTED_UNDERLINE_SPAN_START = 0; final int EXPECTED_UNDERLINE_SPAN_END = 4; @@ -68,6 +69,8 @@ public class BlueUnderlineTests extends InputTestsBase { sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); type(Constants.CODE_SPACE); + // typedLength + 1 because we also typed a space + mLatinIME.onUpdateSelection(0, 0, typedLength + 1, typedLength + 1, -1, -1); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); type(Constants.CODE_DELETE); diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index da8627aa3..039f581a7 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -307,12 +307,14 @@ public class InputLogicTests extends InputTestsBase { } public void testResumeSuggestionOnBackspace() { - final String WORD_TO_TYPE = "and this "; - type(WORD_TO_TYPE); + final String STRING_TO_TYPE = "and this "; + final int typedLength = STRING_TO_TYPE.length(); + type(STRING_TO_TYPE); assertEquals("resume suggestion on backspace", -1, BaseInputConnection.getComposingSpanStart(mEditText.getText())); assertEquals("resume suggestion on backspace", -1, BaseInputConnection.getComposingSpanEnd(mEditText.getText())); + mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1); type(Constants.CODE_DELETE); assertEquals("resume suggestion on backspace", 4, BaseInputConnection.getComposingSpanStart(mEditText.getText()));