Loading java/src/com/android/inputmethod/latin/RichInputConnection.java +14 −6 Original line number Original line Diff line number Diff line Loading @@ -623,14 +623,24 @@ public final class RichInputConnection { return Arrays.binarySearch(sortedSeparators, code) >= 0; return Arrays.binarySearch(sortedSeparators, code) >= 0; } } private static boolean isPartOfCompositionForScript(final int codePoint, final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { // We always consider word connectors part of compositions. return spacingAndPunctuations.isWordConnector(codePoint) // Otherwise, it's part of composition if it's part of script and not a separator. || (!spacingAndPunctuations.isWordSeparator(codePoint) && ScriptUtils.isLetterPartOfScript(codePoint, scriptId)); } /** /** * Returns the text surrounding the cursor. * Returns the text surrounding the cursor. * * * @param sortedSeparators a sorted array of code points that split words. * @param spacingAndPunctuations the rules for spacing and punctuation * @param scriptId the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* * @param scriptId the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* * @return a range containing the text surrounding the cursor * @return a range containing the text surrounding the cursor */ */ public TextRange getWordRangeAtCursor(final int[] sortedSeparators, final int scriptId) { public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { mIC = mParent.getCurrentInputConnection(); mIC = mParent.getCurrentInputConnection(); if (mIC == null) { if (mIC == null) { return null; return null; Loading @@ -647,8 +657,7 @@ public final class RichInputConnection { int startIndexInBefore = before.length(); int startIndexInBefore = before.length(); while (startIndexInBefore > 0) { while (startIndexInBefore > 0) { final int codePoint = Character.codePointBefore(before, startIndexInBefore); final int codePoint = Character.codePointBefore(before, startIndexInBefore); if (isSeparator(codePoint, sortedSeparators) if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { || !ScriptUtils.isLetterPartOfScript(codePoint, scriptId)) { break; break; } } --startIndexInBefore; --startIndexInBefore; Loading @@ -661,8 +670,7 @@ public final class RichInputConnection { int endIndexInAfter = -1; int endIndexInAfter = -1; while (++endIndexInAfter < after.length()) { while (++endIndexInAfter < after.length()) { final int codePoint = Character.codePointAt(after, endIndexInAfter); final int codePoint = Character.codePointAt(after, endIndexInAfter); if (isSeparator(codePoint, sortedSeparators) if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { || !ScriptUtils.isLetterPartOfScript(codePoint, scriptId)) { break; break; } } if (Character.isSupplementaryCodePoint(codePoint)) { if (Character.isSupplementaryCodePoint(codePoint)) { Loading java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +1 −2 Original line number Original line Diff line number Diff line Loading @@ -1478,8 +1478,7 @@ public final class InputLogic { return; return; } } final TextRange range = mConnection.getWordRangeAtCursor( final TextRange range = mConnection.getWordRangeAtCursor( settingsValues.mSpacingAndPunctuations.mSortedWordSeparators, settingsValues.mSpacingAndPunctuations, currentKeyboardScriptId); currentKeyboardScriptId); if (null == range) return; // Happens if we don't have an input connection at all if (null == range) return; // Happens if we don't have an input connection at all if (range.length() <= 0) { if (range.length() <= 0) { // Race condition, or touching a word in a non-supported script. // Race condition, or touching a word in a non-supported script. Loading java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java +17 −0 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.settings; import android.content.res.Resources; import android.content.res.Resources; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.PunctuationSuggestions; import com.android.inputmethod.latin.PunctuationSuggestions; Loading Loading @@ -68,6 +69,22 @@ public final class SpacingAndPunctuations { mSuggestPuncList = PunctuationSuggestions.newPunctuationSuggestions(suggestPuncsSpec); mSuggestPuncList = PunctuationSuggestions.newPunctuationSuggestions(suggestPuncsSpec); } } @UsedForTesting public SpacingAndPunctuations(final SpacingAndPunctuations model, final int[] overrideSortedWordSeparators) { mSortedSymbolsPrecededBySpace = model.mSortedSymbolsPrecededBySpace; mSortedSymbolsFollowedBySpace = model.mSortedSymbolsFollowedBySpace; mSortedSymbolsClusteringTogether = model.mSortedSymbolsClusteringTogether; mSortedWordConnectors = model.mSortedWordConnectors; mSortedWordSeparators = overrideSortedWordSeparators; mSuggestPuncList = model.mSuggestPuncList; mSentenceSeparator = model.mSentenceSeparator; mSentenceSeparatorAndSpace = model.mSentenceSeparatorAndSpace; mCurrentLanguageHasSpaces = model.mCurrentLanguageHasSpaces; mUsesAmericanTypography = model.mUsesAmericanTypography; mUsesGermanRules = model.mUsesGermanRules; } public boolean isWordSeparator(final int code) { public boolean isWordSeparator(final int code) { return Arrays.binarySearch(mSortedWordSeparators, code) >= 0; return Arrays.binarySearch(mSortedWordSeparators, code) >= 0; } } Loading tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java +22 −18 Original line number Original line Diff line number Diff line Loading @@ -215,18 +215,23 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { "abc 'def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); "abc 'def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); } } public void testGetWordRangeAtCursor() { /** /** * Test logic in getting the word range at the cursor. * Test logic in getting the word range at the cursor. */ */ private static final int[] SPACE = { Constants.CODE_SPACE }; final SpacingAndPunctuations SPACE = new SpacingAndPunctuations( static final int[] TAB = { Constants.CODE_TAB }; mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE }); private static final int[] SPACE_TAB = StringUtils.toSortedCodePointArray(" \t"); final SpacingAndPunctuations TAB = new SpacingAndPunctuations( mSpacingAndPunctuations, new int[] { Constants.CODE_TAB }); final int[] SPACE_TAB = StringUtils.toSortedCodePointArray(" \t"); // A character that needs surrogate pair to represent its code point (U+2008A). // A character that needs surrogate pair to represent its code point (U+2008A). private static final String SUPPLEMENTARY_CHAR = "\uD840\uDC8A"; final String SUPPLEMENTARY_CHAR_STRING = "\uD840\uDC8A"; private static final String HIRAGANA_WORD = "\u3042\u3044\u3046\u3048\u304A"; // あいうえお final SpacingAndPunctuations SUPPLEMENTARY_CHAR = new SpacingAndPunctuations( private static final String GREEK_WORD = "\u03BA\u03B1\u03B9"; // και mSpacingAndPunctuations, StringUtils.toSortedCodePointArray( SUPPLEMENTARY_CHAR_STRING)); final String HIRAGANA_WORD = "\u3042\u3044\u3046\u3048\u304A"; // あいうえお final String GREEK_WORD = "\u03BA\u03B1\u03B9"; // και public void testGetWordRangeAtCursor() { ExtractedText et = new ExtractedText(); ExtractedText et = new ExtractedText(); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); Loading @@ -249,10 +254,9 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { // splitting on supplementary character // splitting on supplementary character mockInputMethodService.setInputConnection( mockInputMethodService.setInputConnection( new MockConnection("one word" + SUPPLEMENTARY_CHAR + "wo", "rd", et)); new MockConnection("one word" + SUPPLEMENTARY_CHAR_STRING + "wo", "rd", et)); ic.beginBatchEdit(); ic.beginBatchEdit(); r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN); ScriptUtils.SCRIPT_LATIN); ic.endBatchEdit(); ic.endBatchEdit(); assertTrue(TextUtils.equals("word", r.mWord)); assertTrue(TextUtils.equals("word", r.mWord)); Loading @@ -260,8 +264,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { mockInputMethodService.setInputConnection( mockInputMethodService.setInputConnection( new MockConnection(HIRAGANA_WORD + "wo", "rd" + GREEK_WORD, et)); new MockConnection(HIRAGANA_WORD + "wo", "rd" + GREEK_WORD, et)); ic.beginBatchEdit(); ic.beginBatchEdit(); r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN); ScriptUtils.SCRIPT_LATIN); ic.endBatchEdit(); ic.endBatchEdit(); assertTrue(TextUtils.equals("word", r.mWord)); assertTrue(TextUtils.equals("word", r.mWord)); Loading @@ -269,8 +272,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { mockInputMethodService.setInputConnection( mockInputMethodService.setInputConnection( new MockConnection("text" + GREEK_WORD, "text", et)); new MockConnection("text" + GREEK_WORD, "text", et)); ic.beginBatchEdit(); ic.beginBatchEdit(); r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_GREEK); ScriptUtils.SCRIPT_GREEK); ic.endBatchEdit(); ic.endBatchEdit(); assertTrue(TextUtils.equals(GREEK_WORD, r.mWord)); assertTrue(TextUtils.equals(GREEK_WORD, r.mWord)); } } Loading @@ -286,6 +288,8 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { } } private void helpTestGetSuggestionSpansAtWord(final int cursorPos) { private void helpTestGetSuggestionSpansAtWord(final int cursorPos) { final SpacingAndPunctuations SPACE = new SpacingAndPunctuations( mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE }); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); Loading Loading
java/src/com/android/inputmethod/latin/RichInputConnection.java +14 −6 Original line number Original line Diff line number Diff line Loading @@ -623,14 +623,24 @@ public final class RichInputConnection { return Arrays.binarySearch(sortedSeparators, code) >= 0; return Arrays.binarySearch(sortedSeparators, code) >= 0; } } private static boolean isPartOfCompositionForScript(final int codePoint, final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { // We always consider word connectors part of compositions. return spacingAndPunctuations.isWordConnector(codePoint) // Otherwise, it's part of composition if it's part of script and not a separator. || (!spacingAndPunctuations.isWordSeparator(codePoint) && ScriptUtils.isLetterPartOfScript(codePoint, scriptId)); } /** /** * Returns the text surrounding the cursor. * Returns the text surrounding the cursor. * * * @param sortedSeparators a sorted array of code points that split words. * @param spacingAndPunctuations the rules for spacing and punctuation * @param scriptId the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* * @param scriptId the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* * @return a range containing the text surrounding the cursor * @return a range containing the text surrounding the cursor */ */ public TextRange getWordRangeAtCursor(final int[] sortedSeparators, final int scriptId) { public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { mIC = mParent.getCurrentInputConnection(); mIC = mParent.getCurrentInputConnection(); if (mIC == null) { if (mIC == null) { return null; return null; Loading @@ -647,8 +657,7 @@ public final class RichInputConnection { int startIndexInBefore = before.length(); int startIndexInBefore = before.length(); while (startIndexInBefore > 0) { while (startIndexInBefore > 0) { final int codePoint = Character.codePointBefore(before, startIndexInBefore); final int codePoint = Character.codePointBefore(before, startIndexInBefore); if (isSeparator(codePoint, sortedSeparators) if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { || !ScriptUtils.isLetterPartOfScript(codePoint, scriptId)) { break; break; } } --startIndexInBefore; --startIndexInBefore; Loading @@ -661,8 +670,7 @@ public final class RichInputConnection { int endIndexInAfter = -1; int endIndexInAfter = -1; while (++endIndexInAfter < after.length()) { while (++endIndexInAfter < after.length()) { final int codePoint = Character.codePointAt(after, endIndexInAfter); final int codePoint = Character.codePointAt(after, endIndexInAfter); if (isSeparator(codePoint, sortedSeparators) if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { || !ScriptUtils.isLetterPartOfScript(codePoint, scriptId)) { break; break; } } if (Character.isSupplementaryCodePoint(codePoint)) { if (Character.isSupplementaryCodePoint(codePoint)) { Loading
java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +1 −2 Original line number Original line Diff line number Diff line Loading @@ -1478,8 +1478,7 @@ public final class InputLogic { return; return; } } final TextRange range = mConnection.getWordRangeAtCursor( final TextRange range = mConnection.getWordRangeAtCursor( settingsValues.mSpacingAndPunctuations.mSortedWordSeparators, settingsValues.mSpacingAndPunctuations, currentKeyboardScriptId); currentKeyboardScriptId); if (null == range) return; // Happens if we don't have an input connection at all if (null == range) return; // Happens if we don't have an input connection at all if (range.length() <= 0) { if (range.length() <= 0) { // Race condition, or touching a word in a non-supported script. // Race condition, or touching a word in a non-supported script. Loading
java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java +17 −0 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.settings; import android.content.res.Resources; import android.content.res.Resources; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.PunctuationSuggestions; import com.android.inputmethod.latin.PunctuationSuggestions; Loading Loading @@ -68,6 +69,22 @@ public final class SpacingAndPunctuations { mSuggestPuncList = PunctuationSuggestions.newPunctuationSuggestions(suggestPuncsSpec); mSuggestPuncList = PunctuationSuggestions.newPunctuationSuggestions(suggestPuncsSpec); } } @UsedForTesting public SpacingAndPunctuations(final SpacingAndPunctuations model, final int[] overrideSortedWordSeparators) { mSortedSymbolsPrecededBySpace = model.mSortedSymbolsPrecededBySpace; mSortedSymbolsFollowedBySpace = model.mSortedSymbolsFollowedBySpace; mSortedSymbolsClusteringTogether = model.mSortedSymbolsClusteringTogether; mSortedWordConnectors = model.mSortedWordConnectors; mSortedWordSeparators = overrideSortedWordSeparators; mSuggestPuncList = model.mSuggestPuncList; mSentenceSeparator = model.mSentenceSeparator; mSentenceSeparatorAndSpace = model.mSentenceSeparatorAndSpace; mCurrentLanguageHasSpaces = model.mCurrentLanguageHasSpaces; mUsesAmericanTypography = model.mUsesAmericanTypography; mUsesGermanRules = model.mUsesGermanRules; } public boolean isWordSeparator(final int code) { public boolean isWordSeparator(final int code) { return Arrays.binarySearch(mSortedWordSeparators, code) >= 0; return Arrays.binarySearch(mSortedWordSeparators, code) >= 0; } } Loading
tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java +22 −18 Original line number Original line Diff line number Diff line Loading @@ -215,18 +215,23 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { "abc 'def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); "abc 'def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); } } public void testGetWordRangeAtCursor() { /** /** * Test logic in getting the word range at the cursor. * Test logic in getting the word range at the cursor. */ */ private static final int[] SPACE = { Constants.CODE_SPACE }; final SpacingAndPunctuations SPACE = new SpacingAndPunctuations( static final int[] TAB = { Constants.CODE_TAB }; mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE }); private static final int[] SPACE_TAB = StringUtils.toSortedCodePointArray(" \t"); final SpacingAndPunctuations TAB = new SpacingAndPunctuations( mSpacingAndPunctuations, new int[] { Constants.CODE_TAB }); final int[] SPACE_TAB = StringUtils.toSortedCodePointArray(" \t"); // A character that needs surrogate pair to represent its code point (U+2008A). // A character that needs surrogate pair to represent its code point (U+2008A). private static final String SUPPLEMENTARY_CHAR = "\uD840\uDC8A"; final String SUPPLEMENTARY_CHAR_STRING = "\uD840\uDC8A"; private static final String HIRAGANA_WORD = "\u3042\u3044\u3046\u3048\u304A"; // あいうえお final SpacingAndPunctuations SUPPLEMENTARY_CHAR = new SpacingAndPunctuations( private static final String GREEK_WORD = "\u03BA\u03B1\u03B9"; // και mSpacingAndPunctuations, StringUtils.toSortedCodePointArray( SUPPLEMENTARY_CHAR_STRING)); final String HIRAGANA_WORD = "\u3042\u3044\u3046\u3048\u304A"; // あいうえお final String GREEK_WORD = "\u03BA\u03B1\u03B9"; // και public void testGetWordRangeAtCursor() { ExtractedText et = new ExtractedText(); ExtractedText et = new ExtractedText(); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); Loading @@ -249,10 +254,9 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { // splitting on supplementary character // splitting on supplementary character mockInputMethodService.setInputConnection( mockInputMethodService.setInputConnection( new MockConnection("one word" + SUPPLEMENTARY_CHAR + "wo", "rd", et)); new MockConnection("one word" + SUPPLEMENTARY_CHAR_STRING + "wo", "rd", et)); ic.beginBatchEdit(); ic.beginBatchEdit(); r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN); ScriptUtils.SCRIPT_LATIN); ic.endBatchEdit(); ic.endBatchEdit(); assertTrue(TextUtils.equals("word", r.mWord)); assertTrue(TextUtils.equals("word", r.mWord)); Loading @@ -260,8 +264,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { mockInputMethodService.setInputConnection( mockInputMethodService.setInputConnection( new MockConnection(HIRAGANA_WORD + "wo", "rd" + GREEK_WORD, et)); new MockConnection(HIRAGANA_WORD + "wo", "rd" + GREEK_WORD, et)); ic.beginBatchEdit(); ic.beginBatchEdit(); r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN); ScriptUtils.SCRIPT_LATIN); ic.endBatchEdit(); ic.endBatchEdit(); assertTrue(TextUtils.equals("word", r.mWord)); assertTrue(TextUtils.equals("word", r.mWord)); Loading @@ -269,8 +272,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { mockInputMethodService.setInputConnection( mockInputMethodService.setInputConnection( new MockConnection("text" + GREEK_WORD, "text", et)); new MockConnection("text" + GREEK_WORD, "text", et)); ic.beginBatchEdit(); ic.beginBatchEdit(); r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_GREEK); ScriptUtils.SCRIPT_GREEK); ic.endBatchEdit(); ic.endBatchEdit(); assertTrue(TextUtils.equals(GREEK_WORD, r.mWord)); assertTrue(TextUtils.equals(GREEK_WORD, r.mWord)); } } Loading @@ -286,6 +288,8 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { } } private void helpTestGetSuggestionSpansAtWord(final int cursorPos) { private void helpTestGetSuggestionSpansAtWord(final int cursorPos) { final SpacingAndPunctuations SPACE = new SpacingAndPunctuations( mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE }); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); final RichInputConnection ic = new RichInputConnection(mockInputMethodService); Loading