improved the quick delete feature not to erase whole paragraphs of text in languages without spaces
This commit is contained in:
parent
0efc84cd51
commit
9686251345
5 changed files with 51 additions and 27 deletions
|
|
@ -133,18 +133,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
|||
} else {
|
||||
suggestionOps.commitCurrent(false);
|
||||
mInputMode.reset();
|
||||
|
||||
int charsToDelete;
|
||||
|
||||
if (settings.getBackspaceAcceleration() && repeat > 0) {
|
||||
charsToDelete = Math.max(textField.getPaddedWordBeforeCursorLength(), 1);
|
||||
} else if (!textSelection.isEmpty()) {
|
||||
charsToDelete = textSelection.length();
|
||||
textSelection.clear(false);
|
||||
} else {
|
||||
charsToDelete = 1;
|
||||
}
|
||||
textField.deleteChars(charsToDelete);
|
||||
deleteText(settings.getBackspaceAcceleration() && repeat > 0);
|
||||
}
|
||||
|
||||
if (settings.getBackspaceRecomposing() && repeat == 0 && suggestionOps.isEmpty() && !DictionaryLoader.getInstance(this).isRunning()) {
|
||||
|
|
@ -251,6 +240,18 @@ public abstract class TypingHandler extends KeyPadHandler {
|
|||
}
|
||||
|
||||
|
||||
private void deleteText(boolean deleteMany) {
|
||||
int charsToDelete = 1;
|
||||
if (deleteMany) {
|
||||
charsToDelete = Math.max(textField.getPaddedWordBeforeCursorLength(), 1);
|
||||
} else if (!textSelection.isEmpty()) {
|
||||
charsToDelete = textSelection.length();
|
||||
textSelection.clear(false);
|
||||
}
|
||||
textField.deleteChars(charsToDelete);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* determineLanguage
|
||||
* Restore the last language or auto-select a more appropriate one, if the application hints so.
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ public class TextField extends InputField {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int whitespaceShift = Math.max(before.lastBoundaryIndex(), 0);
|
||||
int whitespaceShift = Math.max(before.lastBoundaryIndex(SettingsStore.BACKSPACE_ACCELERATION_MAX_CHARS_NO_SPACE), 0);
|
||||
return Math.min(before.length() - whitespaceShift, (int) (SettingsStore.BACKSPACE_ACCELERATION_MAX_CHARS * 1.5));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ public class SettingsStore extends SettingsHotkeys {
|
|||
public SettingsStore(Context context) { super(context); }
|
||||
|
||||
/************* internal settings *************/
|
||||
public static final int BACKSPACE_ACCELERATION_MAX_CHARS = 20;
|
||||
public static final int BACKSPACE_ACCELERATION_MAX_CHARS = 20; // maximum chars to be deleted at once in very long words
|
||||
public static final int BACKSPACE_ACCELERATION_MAX_CHARS_NO_SPACE = 4; // maximum chars to be deleted at once for languages with no spaces
|
||||
public static final int BACKSPACE_ACCELERATION_REPEAT_DEBOUNCE = 5;
|
||||
public final static int CLIPBOARD_PREVIEW_LENGTH = 20;
|
||||
public final static int CUSTOM_WORDS_IMPORT_MAX_LINES = 250;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
|
|
@ -14,6 +16,8 @@ public class Text extends TextTools {
|
|||
private final Language language;
|
||||
private final String text;
|
||||
|
||||
private final static Pattern QUICK_DELETE_GROUP = Pattern.compile("(?:([\\s\\u3000]{2,})|([.,、。,،]{2,})|([^、。,\\s\\u3000]*.))$");
|
||||
|
||||
|
||||
public Text(Language language, String text) {
|
||||
this.language = language;
|
||||
|
|
@ -189,26 +193,38 @@ public class Text extends TextTools {
|
|||
}
|
||||
|
||||
|
||||
public int lastBoundaryIndex() {
|
||||
/**
|
||||
* Returns the starting index of the last word boundary. This could be start of a word, of a whitespace
|
||||
* block or of a punctuation block (e.g. "..."). In the case of languages that do not use spaces
|
||||
* it is not possible to determine where a word starts, so in case the text ends with letters only,
|
||||
* we assume the last word is at most MAX_WORD_LENGTH_NO_SPACE letters long.
|
||||
*/
|
||||
public int lastBoundaryIndex(final int MAX_WORD_LENGTH_NO_SPACE) {
|
||||
if (text == null || text.length() < 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char lastChar = text.charAt(text.length() - 1);
|
||||
char penultimateChar = text.charAt(text.length() - 2);
|
||||
Matcher matcher = QUICK_DELETE_GROUP.matcher(text);
|
||||
for (int i = matcher.find() ? matcher.groupCount() : 0, nonLetterGroup = 0; i >= 0; i--, nonLetterGroup = 1) {
|
||||
String group = matcher.group(i);
|
||||
if (group == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean endsWithWhitespaceBlock = Character.isWhitespace(lastChar) && Character.isWhitespace(penultimateChar);
|
||||
boolean endsWithPunctuationBlock = (lastChar == '.' || lastChar == ',') && (penultimateChar == '.' || penultimateChar == ',');
|
||||
if (nonLetterGroup == 1) {
|
||||
return matcher.start();
|
||||
}
|
||||
|
||||
for (int i = text.length() - 1, firstChar = 1; i >= 0; i--, firstChar = 0) {
|
||||
char currentChar = text.charAt(i);
|
||||
Text gr = new Text(group);
|
||||
int codePoints = gr.codePointLength();
|
||||
|
||||
if (
|
||||
(endsWithPunctuationBlock && currentChar != '.' && currentChar != ',')
|
||||
|| (endsWithWhitespaceBlock && !Character.isWhitespace(currentChar))
|
||||
|| (!endsWithWhitespaceBlock && !endsWithPunctuationBlock && firstChar == 0 && Character.isWhitespace(currentChar))
|
||||
) {
|
||||
return i + 1;
|
||||
// In the writing systems that do not use spaces, the last group is not the last word, but
|
||||
// it could be an entire sentence or a paragraph. That's why we assume an average word length
|
||||
// of N letters
|
||||
if (codePoints > MAX_WORD_LENGTH_NO_SPACE && (TextTools.isChinese(group) || TextTools.isJapanese(group) || TextTools.isThai(group))) {
|
||||
return text.length() - gr.substringCodePoints(codePoints - 4, codePoints).length();
|
||||
} else {
|
||||
return matcher.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public class TextTools {
|
|||
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 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("(?<!\\.)(^|[.?!؟¿¡])\\s+$");
|
||||
|
|
@ -62,6 +63,11 @@ public class TextTools {
|
|||
}
|
||||
|
||||
|
||||
public static boolean isThai(String str) {
|
||||
return str != null && IS_THAI.matcher(str).find();
|
||||
}
|
||||
|
||||
|
||||
public static int indexOfIgnoreCase(List<String> list, String str) {
|
||||
for (int i = 0, size = list != null && str != null ? list.size() : 0; i < size; i++) {
|
||||
if (list.get(i).equalsIgnoreCase(str)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue