diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 03de03d25..db8f269eb 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1118,7 +1118,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Note: getCursorCapsMode() returns the current capitalization mode that is any // combination of CAP_MODE_CHARACTERS, CAP_MODE_WORDS, and CAP_MODE_SENTENCES. 0 means none // of them. - return mConnection.getCursorCapsMode(inputType); + return mConnection.getCursorCapsMode(inputType, mSubtypeSwitcher.getCurrentSubtypeLocale()); } // Factor in auto-caps and manual caps and compute the current caps mode. diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 43b9ba7a9..b85f9dcd7 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -30,6 +30,7 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.research.ResearchLogger; +import java.util.Locale; import java.util.regex.Pattern; /** @@ -189,7 +190,7 @@ public class RichInputConnection { } } - public int getCursorCapsMode(final int inputType) { + public int getCursorCapsMode(final int inputType, final Locale locale) { mIC = mParent.getCurrentInputConnection(); if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF; if (!TextUtils.isEmpty(mComposingText)) return Constants.TextUtils.CAP_MODE_OFF; @@ -204,7 +205,7 @@ public class RichInputConnection { } // This never calls InputConnection#getCapsMode - in fact, it's a static method that // never blocks or initiates IPC. - return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType); + return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale); } public CharSequence getTextBeforeCursor(final int i, final int j) { diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index 0fc6c32d7..6dc1ea807 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -196,13 +196,14 @@ public final class StringUtils { * @param reqModes The modes to be checked: may be any combination of * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and * {@link TextUtils#CAP_MODE_SENTENCES}. + * @param locale The locale to consider for capitalization rules * * @return Returns the actual capitalization modes that can be in effect * at the current position, which is any combination of * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and * {@link TextUtils#CAP_MODE_SENTENCES}. */ - public static int getCapsMode(final CharSequence cs, final int reqModes) { + public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale) { // Quick description of what we want to do: // CAP_MODE_CHARACTERS is always on. // CAP_MODE_WORDS is on if there is some whitespace before the cursor. @@ -270,19 +271,24 @@ public final class StringUtils { // we know that MODE_SENTENCES is being requested. // Step 4 : Search for MODE_SENTENCES. - for (; j > 0; j--) { - // Here we look to go over any closing punctuation. This is because in dominant variants - // of English, the final period is placed within double quotes and maybe other closing - // punctuation signs. - // TODO: this is wrong for almost everything except American typography rules for - // English. It's wrong for British typography rules for English, it's wrong for French, - // it's wrong for German, it's wrong for Spanish, and possibly everything else. - // (note that American rules and British rules have nothing to do with en_US and en_GB, - // as both rules are used in both countries - it's merely a name for the set of rules) - final char c = cs.charAt(j - 1); - if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE - && Character.getType(c) != Character.END_PUNCTUATION) { - break; + // English is a special case in that "American typography" rules, which are the most common + // in English, state that a sentence terminator immediately following a quotation mark + // should be swapped with it and de-duplicated (included in the quotation mark), + // e.g. <> + // No other language has such a rule as far as I know, instead putting inside the quotation + // mark as the exact thing quoted and handling the surrounding punctuation independently, + // e.g. <> + // Hence, specifically for English, we treat this special case here. + if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) { + for (; j > 0; j--) { + // Here we look to go over any closing punctuation. This is because in dominant + // variants of English, the final period is placed within double quotes and maybe + // other closing punctuation signs. This is generally not true in other languages. + final char c = cs.charAt(j - 1); + if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE + && Character.getType(c) != Character.END_PUNCTUATION) { + break; + } } } diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java index c3d9c0616..00cca9d3b 100644 --- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java @@ -19,6 +19,8 @@ package com.android.inputmethod.latin; import android.test.AndroidTestCase; import android.text.TextUtils; +import java.util.Locale; + public class StringUtilsTests extends AndroidTestCase { public void testContainsInArray() { assertFalse("empty array", StringUtils.containsInArray("key", new String[0])); @@ -90,43 +92,50 @@ public class StringUtilsTests extends AndroidTestCase { StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5")); } - private void onePathForCaps(final CharSequence cs, final int expectedResult, final int mask) { + private void onePathForCaps(final CharSequence cs, final int expectedResult, final int mask, + final Locale l) { int oneTimeResult = expectedResult & mask; - assertEquals("After >" + cs + "<", oneTimeResult, StringUtils.getCapsMode(cs, mask)); + assertEquals("After >" + cs + "<", oneTimeResult, StringUtils.getCapsMode(cs, mask, l)); } - private void allPathsForCaps(final CharSequence cs, final int expectedResult) { + private void allPathsForCaps(final CharSequence cs, final int expectedResult, final Locale l) { final int c = TextUtils.CAP_MODE_CHARACTERS; final int w = TextUtils.CAP_MODE_WORDS; final int s = TextUtils.CAP_MODE_SENTENCES; - onePathForCaps(cs, expectedResult, c | w | s); - onePathForCaps(cs, expectedResult, w | s); - onePathForCaps(cs, expectedResult, c | s); - onePathForCaps(cs, expectedResult, c | w); - onePathForCaps(cs, expectedResult, c); - onePathForCaps(cs, expectedResult, w); - onePathForCaps(cs, expectedResult, s); + onePathForCaps(cs, expectedResult, c | w | s, l); + onePathForCaps(cs, expectedResult, w | s, l); + onePathForCaps(cs, expectedResult, c | s, l); + onePathForCaps(cs, expectedResult, c | w, l); + onePathForCaps(cs, expectedResult, c, l); + onePathForCaps(cs, expectedResult, w, l); + onePathForCaps(cs, expectedResult, s, l); } public void testGetCapsMode() { final int c = TextUtils.CAP_MODE_CHARACTERS; final int w = TextUtils.CAP_MODE_WORDS; final int s = TextUtils.CAP_MODE_SENTENCES; - allPathsForCaps("", c | w | s); - allPathsForCaps("Word", c); - allPathsForCaps("Word.", c); - allPathsForCaps("Word ", c | w); - allPathsForCaps("Word. ", c | w | s); - allPathsForCaps("Word..", c); - allPathsForCaps("Word.. ", c | w | s); - allPathsForCaps("Word... ", c | w | s); - allPathsForCaps("Word ... ", c | w | s); - allPathsForCaps("Word . ", c | w); - allPathsForCaps("In the U.S ", c | w); - allPathsForCaps("In the U.S. ", c | w); - allPathsForCaps("Some stuff (e.g. ", c | w); - allPathsForCaps("In the U.S.. ", c | w | s); - allPathsForCaps("\"Word.\" ", c | w | s); - allPathsForCaps("\"Word\" ", c | w); + Locale l = Locale.ENGLISH; + allPathsForCaps("", c | w | s, l); + allPathsForCaps("Word", c, l); + allPathsForCaps("Word.", c, l); + allPathsForCaps("Word ", c | w, l); + allPathsForCaps("Word. ", c | w | s, l); + allPathsForCaps("Word..", c, l); + allPathsForCaps("Word.. ", c | w | s, l); + allPathsForCaps("Word... ", c | w | s, l); + allPathsForCaps("Word ... ", c | w | s, l); + allPathsForCaps("Word . ", c | w, l); + allPathsForCaps("In the U.S ", c | w, l); + allPathsForCaps("In the U.S. ", c | w, l); + allPathsForCaps("Some stuff (e.g. ", c | w, l); + allPathsForCaps("In the U.S.. ", c | w | s, l); + allPathsForCaps("\"Word.\" ", c | w | s, l); + allPathsForCaps("\"Word\". ", c | w | s, l); + allPathsForCaps("\"Word\" ", c | w, l); + l = Locale.FRENCH; + allPathsForCaps("\"Word.\" ", c | w, l); + allPathsForCaps("\"Word\". ", c | w | s, l); + allPathsForCaps("\"Word\" ", c | w, l); } }