1
0
Fork 0

improved the quick delete feature not to erase whole paragraphs of text in languages without spaces

This commit is contained in:
sspanak 2025-04-15 14:54:52 +03:00 committed by Dimo Karaivanov
parent 0efc84cd51
commit 9686251345
5 changed files with 51 additions and 27 deletions

View file

@ -133,18 +133,7 @@ public abstract class TypingHandler extends KeyPadHandler {
} else { } else {
suggestionOps.commitCurrent(false); suggestionOps.commitCurrent(false);
mInputMode.reset(); mInputMode.reset();
deleteText(settings.getBackspaceAcceleration() && repeat > 0);
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);
} }
if (settings.getBackspaceRecomposing() && repeat == 0 && suggestionOps.isEmpty() && !DictionaryLoader.getInstance(this).isRunning()) { 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 * determineLanguage
* Restore the last language or auto-select a more appropriate one, if the application hints so. * Restore the last language or auto-select a more appropriate one, if the application hints so.

View file

@ -166,7 +166,7 @@ public class TextField extends InputField {
return 0; 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)); return Math.min(before.length() - whitespaceShift, (int) (SettingsStore.BACKSPACE_ACCELERATION_MAX_CHARS * 1.5));
} }

View file

@ -7,7 +7,8 @@ public class SettingsStore extends SettingsHotkeys {
public SettingsStore(Context context) { super(context); } public SettingsStore(Context context) { super(context); }
/************* internal settings *************/ /************* 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 static final int BACKSPACE_ACCELERATION_REPEAT_DEBOUNCE = 5;
public final static int CLIPBOARD_PREVIEW_LENGTH = 20; public final static int CLIPBOARD_PREVIEW_LENGTH = 20;
public final static int CUSTOM_WORDS_IMPORT_MAX_LINES = 250; public final static int CUSTOM_WORDS_IMPORT_MAX_LINES = 250;

View file

@ -4,6 +4,8 @@ import androidx.annotation.NonNull;
import java.util.List; import java.util.List;
import java.util.Locale; 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.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
@ -14,6 +16,8 @@ public class Text extends TextTools {
private final Language language; private final Language language;
private final String text; 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) { public Text(Language language, String text) {
this.language = language; 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) { if (text == null || text.length() < 2) {
return -1; return -1;
} }
char lastChar = text.charAt(text.length() - 1); Matcher matcher = QUICK_DELETE_GROUP.matcher(text);
char penultimateChar = text.charAt(text.length() - 2); 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); if (nonLetterGroup == 1) {
boolean endsWithPunctuationBlock = (lastChar == '.' || lastChar == ',') && (penultimateChar == '.' || penultimateChar == ','); return matcher.start();
}
for (int i = text.length() - 1, firstChar = 1; i >= 0; i--, firstChar = 0) { Text gr = new Text(group);
char currentChar = text.charAt(i); int codePoints = gr.codePointLength();
if ( // In the writing systems that do not use spaces, the last group is not the last word, but
(endsWithPunctuationBlock && currentChar != '.' && currentChar != ',') // it could be an entire sentence or a paragraph. That's why we assume an average word length
|| (endsWithWhitespaceBlock && !Character.isWhitespace(currentChar)) // of N letters
|| (!endsWithWhitespaceBlock && !endsWithPunctuationBlock && firstChar == 0 && Character.isWhitespace(currentChar)) 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();
return i + 1; } else {
return matcher.start();
} }
} }

View file

@ -17,6 +17,7 @@ public class TextTools {
private static final Pattern IS_CHINESE = Pattern.compile("\\p{script=Han}+"); 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_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_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 NEXT_TO_WORD = Pattern.compile("\\b$");
private static final Pattern PREVIOUS_IS_LETTER = Pattern.compile("[\\p{L}\\p{M}](?!\\n)$"); private static final Pattern PREVIOUS_IS_LETTER = Pattern.compile("[\\p{L}\\p{M}](?!\\n)$");
private static final Pattern START_OF_SENTENCE = Pattern.compile("(?<!\\.)(^|[.?!؟¿¡])\\s+$"); 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) { public static int indexOfIgnoreCase(List<String> list, String str) {
for (int i = 0, size = list != null && str != null ? list.size() : 0; i < size; i++) { for (int i = 0, size = list != null && str != null ? list.size() : 0; i < size; i++) {
if (list.get(i).equalsIgnoreCase(str)) { if (list.get(i).equalsIgnoreCase(str)) {