diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java index 735404dc..9108ba9a 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java @@ -172,7 +172,6 @@ public abstract class TypingHandler extends KeyPadHandler { protected boolean onNumber(int key, boolean hold, int repeat) { suggestionOps.cancelDelayedAccept(); - // In Korean, the next char may "steal" components from the previous one, in which case, // we must replace the previous char with a one containing less strokes. if (mInputMode.shouldReplaceLastLetter(key, hold)) { @@ -183,7 +182,7 @@ public abstract class TypingHandler extends KeyPadHandler { // First pass, analyze the incoming key press and decide whether it could be the start of // a new word. In case we do accept it, we preserve the suggestion list instead of clearing, // to prevent flashing while the next suggestions are being loaded. - else if (mInputMode.shouldAcceptPreviousSuggestion(key, hold)) { + else if (mInputMode.shouldAcceptPreviousSuggestion(suggestionOps.getCurrent(), key, hold)) { // WARNING! Ensure the code after "acceptIncompleteAndKeepList()" does not depend on // the suggestions in SuggestionOps, since we don't clear that list. String lastWord = suggestionOps.acceptIncompleteAndKeepList(); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java index 1361becf..6f7a8e61 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java @@ -153,7 +153,7 @@ abstract public class InputMode { // Interaction with the IME. Return "true" if it should perform the respective action. public boolean shouldAcceptPreviousSuggestion(String unacceptedText) { return false; } - public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { return false; } + public boolean shouldAcceptPreviousSuggestion(String currentWord, int nextKey, boolean hold) { return false; } public boolean shouldReplacePreviousSuggestion(@Nullable String currentWord) { return Characters.PLACEHOLDER.equals(currentWord); } public boolean shouldAddTrailingSpace(boolean isWordAcceptedManually, int nextKey) { return false; } public boolean shouldAddPrecedingSpace() { return false; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java index 6c2295c3..6343a72d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java @@ -16,7 +16,7 @@ class Mode123 extends ModePassthrough { @Override @NonNull public String toString() { return "123"; } @Override public int getSequenceLength() { return digitSequence.length(); } - @Override public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { return true; } + @Override public boolean shouldAcceptPreviousSuggestion(String currentWord, int nextKey, boolean hold) { return true; } private final ArrayList> KEY_CHARACTERS = new ArrayList<>(); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java index 473a0cfc..dcca1b8e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java @@ -94,10 +94,10 @@ public class ModeBopomofo extends ModePinyin { * In Bopomofo mode, the 0-key is not Spacebar, so we do not rely on the parents to handle accepting */ @Override - public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { - String newSequence = digitSequence + (char)(nextKey + '0'); + public boolean shouldAcceptPreviousSuggestion(String currentWord, int nextDigit, boolean hold) { + String newSequence = digitSequence + (char)(nextDigit + '0'); return hold || newSequence.startsWith(seq.CHARS_0_SEQUENCE) - || (newSequence.startsWith(seq.CHARS_1_SEQUENCE) && nextKey != Sequences.CHARS_1_KEY); + || (newSequence.startsWith(seq.CHARS_1_SEQUENCE) && nextDigit != Sequences.CHARS_1_KEY); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java index a51edfbe..353062da 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java @@ -340,7 +340,7 @@ class ModeCheonjiin extends InputMode { * Used for analysis before processing the incoming pressed key. */ @Override - public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { + public boolean shouldAcceptPreviousSuggestion(String currentWord, int nextKey, boolean hold) { return (hold && !digitSequence.isEmpty()) || (nextKey != Sequences.CHARS_0_KEY && digitSequence.startsWith(seq.CHARS_0_SEQUENCE)) diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java index d90e7042..6dd7f8bb 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java @@ -11,7 +11,6 @@ import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Logger; import io.github.sspanak.tt9.util.Text; -import io.github.sspanak.tt9.util.TextTools; public class ModeIdeograms extends ModeWords { private static final String LOG_TAG = ModeIdeograms.class.getSimpleName(); @@ -107,7 +106,7 @@ public class ModeIdeograms extends ModeWords { final Text text = new Text(currentWord); if (text.isEmpty() || text.startsWithWhitespace() || text.isNumeric() || !language.isValidWord(currentWord)) { reset(); - Logger.i(LOG_TAG, "Current word is empty, numeric or invalid. Nothing to accept."); + Logger.i(LOG_TAG, "Current word: '" + currentWord + "' is empty, numeric or invalid. Nothing to accept."); return; } @@ -122,7 +121,7 @@ public class ModeIdeograms extends ModeWords { boolean lastDigitBelongsToNewWord = preserveWords && initialLength >= 2; try { - if (!digitSequence.equals(seq.CHARS_0_SEQUENCE) && !digitSequence.equals(seq.CHARS_1_SEQUENCE)) { + if (!seq.isAnySpecialCharSequence(digitSequence)) { lastAcceptedWord = currentWord; lastAcceptedSequence = lastDigitBelongsToNewWord ? digitSequence.substring(0, initialLength - 1) : digitSequence; @@ -148,30 +147,8 @@ public class ModeIdeograms extends ModeWords { return digitSequence.length() > 1 && predictions.noDbWords() - && !digitSequence.equals(seq.EMOJI_SEQUENCE) - && !digitSequence.equals(seq.CHARS_1_SEQUENCE) - && !digitSequence.equals(seq.CHARS_0_SEQUENCE); - } - - - @Override - public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { - if (digitSequence.isEmpty()) { - return false; - } - - if (super.shouldAcceptPreviousSuggestion(nextKey, hold)) { - return true; - } - - String nextSequence = digitSequence + (char)(nextKey + '0'); - - return - TextTools.containsOtherThan1(nextSequence) - && ( - nextSequence.endsWith(seq.EMOJI_SEQUENCE) || nextSequence.startsWith(seq.EMOJI_SEQUENCE) || - nextSequence.endsWith(seq.CHARS_1_SEQUENCE) || nextSequence.startsWith(seq.CHARS_1_SEQUENCE) - ); + && !seq.isAnySpecialCharSequence(digitSequence) + && !digitSequence.startsWith(seq.EMOJI_SEQUENCE); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java index 059c35cf..3ea95de1 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java @@ -40,21 +40,6 @@ public class ModeKanji extends ModePinyin { } - @Override - public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { - if (digitSequence.isEmpty()) { - return false; - } - - String nextSequence = digitSequence + (char)(nextKey + '0'); - if (seq.isAnySpecialCharSequence(nextSequence)) { - return false; - } - - return super.shouldAcceptPreviousSuggestion(nextKey, hold); - } - - @Override public boolean validateLanguage(@Nullable Language newLanguage) { return LanguageKind.isJapanese(newLanguage); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java index 744e84af..6d30f2ae 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java @@ -56,14 +56,14 @@ public class ModePinyin extends ModeIdeograms { @Override - public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { + public boolean shouldAcceptPreviousSuggestion(String currentWord, int nextDigit, boolean hold) { // In East Asian languages, Space must accept the current word, or type a space when there is no word. // Here, we handle the case when 0-key is Space, unlike the Space hotkey in HotkeyHandler, // which could be a different key, assigned by the user. - if (!digitSequence.isEmpty() && !digitSequence.equals(seq.CHARS_0_SEQUENCE) && nextKey == Sequences.CHARS_0_KEY) { + if (nextDigit == Sequences.CHARS_0_KEY && !digitSequence.isEmpty() && !seq.isAnySpecialCharSequence(digitSequence)) { ignoreNextSpace = true; } - return super.shouldAcceptPreviousSuggestion(nextKey, hold); + return super.shouldAcceptPreviousSuggestion(currentWord, nextDigit, hold); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java index 5c11879a..c13d8081 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java @@ -395,25 +395,36 @@ class ModeWords extends ModeCheonjiin { * automatically. This is used for analysis before processing the incoming pressed key. */ @Override - public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { + public boolean shouldAcceptPreviousSuggestion(String currentWord, int nextDigit, boolean hold) { if (hold) { return true; } + if (digitSequence.isEmpty()) { + return false; + } else if ( + digitSequence.equals(seq.CUSTOM_EMOJI_SEQUENCE) || + (seq.startsWithEmojiSequence(digitSequence) && nextDigit != Sequences.CHARS_1_KEY && nextDigit != Sequences.CUSTOM_EMOJI_KEY) + ) { + return true; + } + // Prevent typing the preferred character when the user has scrolled the special char suggestions. // For example, it makes more sense to allow typing "+ " with 0 + scroll + 0, instead of clearing // the "+" and replacing it with the preferred character. - boolean specialOrCurrency = digitSequence.equals(seq.CHARS_GROUP_0_SEQUENCE) || digitSequence.equals(seq.CHARS_GROUP_1_SEQUENCE); - boolean isWhitespaceAndScrolled = digitSequence.equals(seq.CHARS_0_SEQUENCE) && !stem.isEmpty(); - if (nextKey == Sequences.CHARS_0_KEY && (isWhitespaceAndScrolled || specialOrCurrency)) { + // Also don't type the preferred character when viewing a group. In that case we obviously want to + // type a space after the character from the group. + boolean inGroup = digitSequence.equals(seq.CHARS_GROUP_0_SEQUENCE) || digitSequence.equals(seq.CHARS_GROUP_1_SEQUENCE); + boolean isWhitespaceAndScrolled = digitSequence.equals(seq.CHARS_0_SEQUENCE) && !suggestions.isEmpty() && !suggestions.get(0).equals(currentWord); + if (nextDigit == Sequences.CHARS_0_KEY && (isWhitespaceAndScrolled || inGroup)) { return true; } + final char lastDigit = digitSequence.charAt(digitSequence.length() - 1); + return - !digitSequence.isEmpty() && ( - (nextKey == Sequences.CHARS_0_KEY && digitSequence.charAt(digitSequence.length() - 1) != Sequences.CHARS_0_CODE) - || (nextKey != Sequences.CHARS_0_KEY && digitSequence.charAt(digitSequence.length() - 1) == Sequences.CHARS_0_CODE) - ); + (nextDigit == Sequences.CHARS_0_KEY && lastDigit != Sequences.CHARS_0_CODE) + || (nextDigit != Sequences.CHARS_0_KEY && lastDigit == Sequences.CHARS_0_CODE); } @@ -432,13 +443,15 @@ class ModeWords extends ModeCheonjiin { return false; } - // punctuation breaks words, unless there are database matches ('s, qu', по-, etc...) return !digitSequence.isEmpty() && predictions.noDbWords() - && digitSequence.contains(seq.CHARS_1_SEQUENCE) - && !digitSequence.startsWith(seq.EMOJI_SEQUENCE) - && Text.containsOtherThan1(digitSequence); + && ( + // when no custom emoji, assume the last digit is the beginning of a new word + digitSequence.equals(seq.CUSTOM_EMOJI_SEQUENCE) + // punctuation breaks words, unless there are database matches ('s, qu', по-, etc...) + || (digitSequence.contains(seq.CHARS_1_SEQUENCE) && !digitSequence.equals(seq.CHARS_1_SEQUENCE)) + ); } diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/TranscribedLanguage.java b/app/src/main/java/io/github/sspanak/tt9/languages/TranscribedLanguage.java index af0b706f..3a28cecc 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/TranscribedLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/TranscribedLanguage.java @@ -11,7 +11,7 @@ abstract class TranscribedLanguage extends Language implements Comparable MAX_WORD_LENGTH_NO_SPACE && (TextTools.isChinese(group) || TextTools.isJapanese(group) || TextTools.isThai(group))) { + if (codePoints > MAX_WORD_LENGTH_NO_SPACE && (TextTools.isChineseText(group) || TextTools.isJapaneseText(group) || TextTools.isThaiText(group))) { return text.length() - gr.substringCodePoints(codePoints - 4, codePoints).length(); } else { return matcher.start(); diff --git a/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java b/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java index b36e3e53..16b5977a 100644 --- a/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java +++ b/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java @@ -11,28 +11,27 @@ import java.util.regex.Pattern; import io.github.sspanak.tt9.util.chars.Characters; public class TextTools { - private static final Pattern CONTAINS_OTHER_THAN_1 = Pattern.compile("[02-9]"); private static final Pattern COMBINING_STRING = Pattern.compile("^\\p{M}+$"); private static final Pattern NEXT_IS_PUNCTUATION = Pattern.compile("^\\p{Punct}"); - private static final Pattern IS_CHINESE = Pattern.compile("\\p{script=Han}+"); - private static final Pattern IS_JAPANESE = Pattern.compile("\\p{script=Hiragana}+|\\p{script=Katakana}+|\\p{script=Han}+"); - private static final Pattern IS_HANGUL = Pattern.compile("[\u1100-\u11FF\u302E-\u302F\u3131-\u318F\u3200-\u321F\u3260-\u327E\uA960-\uA97F\uAC00-\uD7FB\uFFA0-\uFFDF]+"); - private static final Pattern IS_THAI = Pattern.compile("[\\u0E00-\\u0E7F]+"); + private static final Pattern IS_CHINESE = Pattern.compile("[\\p{script=Han}" + String.join("", Characters.PunctuationChinese) + "]+"); + private static final Pattern IS_HANGUL_TEXT = Pattern.compile("[\u1100-\u11FF\u302E-\u302F\u3131-\u318F\u3200-\u321F\u3260-\u327E\uA960-\uA97F\uAC00-\uD7FB\uFFA0-\uFFDF]+"); + private static final Pattern IS_JAPANESE = Pattern.compile("[\\p{script=Hiragana}\\p{script=Katakana}\\p{script=Han}" + String.join("", Characters.PunctuationChinese) + "]+"); + private static final Pattern IS_CHINESE_TEXT = Pattern.compile("\\p{script=Han}+"); + private static final Pattern IS_JAPANESE_TEXT = Pattern.compile("[\\p{script=Hiragana}\\p{script=Katakana}\\p{script=Han}]+"); + private static final Pattern IS_THAI_TEXT = Pattern.compile("[\\u0E00-\\u0E7F]+"); private static final Pattern NEXT_TO_WORD = Pattern.compile("\\b$"); private static final Pattern PREVIOUS_IS_LETTER = Pattern.compile("[\\p{L}\\p{M}](?!\\n)$"); private static final Pattern START_OF_SENTENCE = Pattern.compile("(?