Rework the logic that tells if the cursor touches words

Bug: 13312942
Change-Id: I6be6a558bbc6c88508150f9c25cadbd0240ff88e
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 965518e..606bb77 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -687,13 +687,23 @@
     }
 
     public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations) {
-        final int codePointBeforeCursor = getCodePointBeforeCursor();
-        if (Constants.NOT_A_CODE == codePointBeforeCursor
-                || spacingAndPunctuations.isWordSeparator(codePointBeforeCursor)
-                || spacingAndPunctuations.isWordConnector(codePointBeforeCursor)) {
-            return isCursorFollowedByWordCharacter(spacingAndPunctuations);
+        if (isCursorFollowedByWordCharacter(spacingAndPunctuations)) {
+            // If what's after the cursor is a word character, then we're touching a word.
+            return true;
         }
-        return true;
+        final String textBeforeCursor = mCommittedTextBeforeComposingText.toString();
+        int indexOfCodePointInJavaChars = textBeforeCursor.length();
+        int consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
+                : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
+        // Search for the first non word-connector char
+        if (spacingAndPunctuations.isWordConnector(consideredCodePoint)) {
+            indexOfCodePointInJavaChars -= Character.charCount(consideredCodePoint);
+            consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
+                    : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
+        }
+        return !(Constants.NOT_A_CODE == consideredCodePoint
+                || spacingAndPunctuations.isWordSeparator(consideredCodePoint)
+                || spacingAndPunctuations.isWordConnector(consideredCodePoint));
     }
 
     public boolean isCursorFollowedByWordCharacter(
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index 7f07435..842f3f3 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -89,6 +89,10 @@
             mExtractedText = extractedText;
         }
 
+        public int cursorPos() {
+            return mTextBefore.length();
+        }
+
         /* (non-Javadoc)
          * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int)
          */
@@ -131,13 +135,16 @@
     }
 
     private class MockInputMethodService extends InputMethodService {
-        InputConnection mInputConnection;
-        public void setInputConnection(final InputConnection inputConnection) {
-            mInputConnection = inputConnection;
+        private MockConnection mMockConnection;
+        public void setInputConnection(final MockConnection mockConnection) {
+            mMockConnection = mockConnection;
+        }
+        public int cursorPos() {
+            return mMockConnection.cursorPos();
         }
         @Override
         public InputConnection getCurrentInputConnection() {
-            return mInputConnection;
+            return mMockConnection;
         }
     }
 
@@ -336,4 +343,82 @@
         suggestions = r.getSuggestionSpansAtWord();
         assertEquals(suggestions.length, 0);
     }
+
+    public void testCursorTouchingWord() {
+        final MockInputMethodService ims = new MockInputMethodService();
+        final RichInputConnection ic = new RichInputConnection(ims);
+        final SpacingAndPunctuations sap = mSpacingAndPunctuations;
+
+        ims.setInputConnection(new MockConnection("users", 5));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users'", 5));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users'", 6));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("'users'", 6));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("'users'", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users '", 6));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users '", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("re-", 3));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("re--", 4));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("-", 1));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("--", 2));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" -", 2));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" --", 3));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users '", 1));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users '", 3));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users '", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users are", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users 'are", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+    }
 }