am 6d1c8b2c: Merge "Try to figure out whether d.quotes open or close."

* commit '6d1c8b2c3f78969866db7f718e7e8d2be240f674':
  Try to figure out whether d.quotes open or close.
main
Jean Chalard 2014-01-27 02:02:44 -08:00 committed by Android Git Automerger
commit 7f1a5c4d9b
4 changed files with 103 additions and 6 deletions

View File

@ -814,6 +814,17 @@ public final class RichInputConnection {
return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText); return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText);
} }
/**
* Looks at the text just before the cursor to find out if we are inside a double quote.
*
* As with #textBeforeCursorLooksLikeURL, this is dependent on how much text we have cached.
* However this won't be a concrete problem in most situations, as the cache is almost always
* long enough for this use.
*/
public boolean isInsideDoubleQuoteOrAfterDigit() {
return StringUtils.isInsideDoubleQuoteOrAfterDigit(mCommittedTextBeforeComposingText);
}
/** /**
* Try to get the text from the editor to expose lies the framework may have been * Try to get the text from the editor to expose lies the framework may have been
* telling us. Concretely, when the device rotates, the frameworks tells us about where the * telling us. Concretely, when the device rotates, the frameworks tells us about where the

View File

@ -670,8 +670,21 @@ public final class InputLogic {
final boolean swapWeakSpace = maybeStripSpace(settingsValues, codePoint, spaceState, final boolean swapWeakSpace = maybeStripSpace(settingsValues, codePoint, spaceState,
isFromSuggestionStrip); isFromSuggestionStrip);
if (SpaceState.PHANTOM == spaceState && final boolean isInsideDoubleQuoteOrAfterDigit = Constants.CODE_DOUBLE_QUOTE == codePoint
settingsValues.isUsuallyPrecededBySpace(codePoint)) { && mConnection.isInsideDoubleQuoteOrAfterDigit();
final boolean needsPrecedingSpace;
if (SpaceState.PHANTOM != spaceState) {
needsPrecedingSpace = false;
} else if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
// Double quotes behave like they are usually preceded by space iff we are
// not inside a double quote or after a digit.
needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit;
} else {
needsPrecedingSpace = settingsValues.isUsuallyPrecededBySpace(codePoint);
}
if (needsPrecedingSpace) {
promotePhantomSpace(settingsValues); promotePhantomSpace(settingsValues);
} }
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@ -698,14 +711,17 @@ public final class InputLogic {
if (swapWeakSpace) { if (swapWeakSpace) {
swapSwapperAndSpace(keyboardSwitcher); swapSwapperAndSpace(keyboardSwitcher);
mSpaceState = SpaceState.SWAP_PUNCTUATION; mSpaceState = SpaceState.SWAP_PUNCTUATION;
} else if (SpaceState.PHANTOM == spaceState } else if ((SpaceState.PHANTOM == spaceState
&& settingsValues.isUsuallyFollowedBySpace(codePoint)) { && settingsValues.isUsuallyFollowedBySpace(codePoint))
|| (Constants.CODE_DOUBLE_QUOTE == codePoint
&& isInsideDoubleQuoteOrAfterDigit)) {
// If we are in phantom space state, and the user presses a separator, we want to // If we are in phantom space state, and the user presses a separator, we want to
// stay in phantom space state so that the next keypress has a chance to add the // stay in phantom space state so that the next keypress has a chance to add the
// space. For example, if I type "Good dat", pick "day" from the suggestion strip // space. For example, if I type "Good dat", pick "day" from the suggestion strip
// then insert a comma and go on to typing the next word, I want the space to be // then insert a comma and go on to typing the next word, I want the space to be
// inserted automatically before the next word, the same way it is when I don't // inserted automatically before the next word, the same way it is when I don't
// input the comma. // input the comma. A double quote behaves like it's usually followed by space if
// we're inside a double quote.
// The case is a little different if the separator is a space stripper. Such a // The case is a little different if the separator is a space stripper. Such a
// separator does not normally need a space on the right (that's the difference // separator does not normally need a space on the right (that's the difference
// between swappers and strippers), so we should not stay in phantom space state if // between swappers and strippers), so we should not stay in phantom space state if

View File

@ -348,7 +348,7 @@ public final class StringUtils {
boolean hasPeriod = false; boolean hasPeriod = false;
int codePoint = 0; int codePoint = 0;
while (i > 0) { while (i > 0) {
codePoint = Character.codePointBefore(text, i); codePoint = Character.codePointBefore(text, i);
if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') { if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') {
// Handwavy heuristic to see if that's a URL character. Anything between period // Handwavy heuristic to see if that's a URL character. Anything between period
// and z. This includes all lower- and upper-case ascii letters, period, // and z. This includes all lower- and upper-case ascii letters, period,
@ -387,6 +387,48 @@ public final class StringUtils {
return false; return false;
} }
/**
* Examines the string and returns whether we're inside a double quote.
*
* This is used to decide whether we should put an automatic space before or after a double
* quote character. If we're inside a quotation, then we want to close it, so we want a space
* after and not before. Otherwise, we want to open the quotation, so we want a space before
* and not after. Exception: after a digit, we never want a space because the "inch" or
* "minutes" use cases is dominant after digits.
* In the practice, we determine whether we are in a quotation or not by finding the previous
* double quote character, and looking at whether it's followed by whitespace. If so, that
* was a closing quotation mark, so we're not inside a double quote. If it's not followed
* by whitespace, then it was an opening quotation mark, and we're inside a quotation.
*
* @param text the text to examine.
* @return whether we're inside a double quote.
*/
public static boolean isInsideDoubleQuoteOrAfterDigit(final CharSequence text) {
int i = text.length();
if (0 == i) return false;
int codePoint = Character.codePointBefore(text, i);
if (Character.isDigit(codePoint)) return true;
int prevCodePoint = 0;
while (i > 0) {
codePoint = Character.codePointBefore(text, i);
if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
// If we see a double quote followed by whitespace, then that
// was a closing quote.
if (Character.isWhitespace(prevCodePoint)) return false;
}
if (Character.isWhitespace(codePoint) && Constants.CODE_DOUBLE_QUOTE == prevCodePoint) {
// If we see a double quote preceded by whitespace, then that
// was an opening quote. No need to continue seeking.
return true;
}
i -= Character.charCount(codePoint);
prevCodePoint = codePoint;
}
// We reached the start of text. If the first char is a double quote, then we're inside
// a double quote. Otherwise we're not.
return Constants.CODE_DOUBLE_QUOTE == codePoint;
}
public static boolean isEmptyStringOrWhiteSpaces(final String s) { public static boolean isEmptyStringOrWhiteSpaces(final String s) {
final int N = codePointCount(s); final int N = codePointCount(s);
for (int i = 0; i < N; ++i) { for (int i = 0; i < N; ++i) {

View File

@ -169,4 +169,32 @@ public class PunctuationTests extends InputTestsBase {
+ " ; Suggestions = " + mLatinIME.getSuggestedWords(), + " ; Suggestions = " + mLatinIME.getSuggestedWords(),
EXPECTED_RESULT, mEditText.getText().toString()); EXPECTED_RESULT, mEditText.getText().toString());
} }
public void testAutoSpaceWithDoubleQuotes() {
final String STRING_TO_TYPE = "He said\"hello\"to me. I replied,\"hi\"."
+ "Then, 5\"passed. He said\"bye\"and left.";
final String EXPECTED_RESULT = "He said \"hello\" to me. I replied, \"hi\". "
+ "Then, 5\" passed. He said \"bye\" and left. \"";
// Split by double quote, so that we can type the double quotes individually.
for (final String partToType : STRING_TO_TYPE.split("\"")) {
// Split at word boundaries. This regexp means "anywhere that is preceded
// by a word character but not followed by a word character, OR that is not
// preceded by a word character but followed by a word character".
// We need to input word by word because auto-spaces are only active when
// manually picking or gesturing (which we can't simulate yet), but only words
// can be picked.
final String[] wordsToType = partToType.split("(?<=\\w)(?!\\w)|(?<!\\w)(?=\\w)");
for (final String wordToType : wordsToType) {
type(wordToType);
if (wordToType.matches("^\\w+$")) {
// Only pick selection if that was a word, because if that was not a word,
// then we don't have a composition.
pickSuggestionManually(0, wordToType);
}
}
type("\"");
}
assertEquals("auto-space with double quotes",
EXPECTED_RESULT, mEditText.getText().toString());
}
} }