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 9b27ca6f..d79e9f83 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
@@ -1,6 +1,5 @@
package io.github.sspanak.tt9.ime;
-import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -132,10 +131,13 @@ public abstract class TypingHandler extends KeyPadHandler {
suggestionOps.commitCurrent(false);
mInputMode.reset();
- int repeats = hold ? Math.min(Math.max(textField.getWordBeforeCursorLength(), 1), SettingsStore.BACKSPACE_ACCELERATION_MAX_CHARS) : 1;
- for (int i = repeats; i > 0; i--) {
- super.sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
- }
+ int prevChars = hold ? Math.min(Math.max(textField.getPaddedWordBeforeCursorLength(), 1), SettingsStore.BACKSPACE_ACCELERATION_MAX_CHARS) : 1;
+ textField.deleteChars(prevChars);
+ }
+
+ if (!hold && suggestionOps.isEmpty() && mInputMode.recompose(textField.getWordBeforeCursor())) {
+ textField.deleteChars(mInputMode.getSequenceLength());
+ getSuggestions();
}
return true;
@@ -289,7 +291,7 @@ public abstract class TypingHandler extends KeyPadHandler {
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);
textSelection.onSelectionUpdate(newSelStart, newSelEnd);
- // in case the app has modified the InputField and moved the cursor without notifiying us...
+ // in case the app has modified the InputField and moved the cursor without notifying us...
if (appHacks.onUpdateSelection(mInputMode, suggestionOps, oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd)) {
return;
}
diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java
index 4ffa12de..545cb351 100644
--- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java
+++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java
@@ -104,12 +104,29 @@ public class TextField extends InputField {
}
+ @NonNull public String getWordBeforeCursor() {
+ if (getTextAfterCursor(1).startsWithWord()) {
+ return "";
+ }
+
+ String before = getStringBeforeCursor();
+ if (before.isEmpty() || !Character.isAlphabetic(before.charAt(before.length() - 1))) {
+ return "";
+ }
+
+ int lastSpace = Math.max(before.lastIndexOf(' ') + 1, 0);
+ int length = Math.max(before.length(), lastSpace);
+
+ return before.substring(lastSpace, length);
+ }
+
+
/**
- * Returns the length of the word before the cursor. If the cursor is inside a word, 0 is returned,
- * because there is no full word before it. The scanning length is up to the maximum returned by
- * getTextBeforeCursor().
+ * Returns the length of the first word before the cursor including any whitespace after it.
+ * If the cursor is inside a word, 0 is returned, because there is no full word before it.
+ * The scanning length is up to the maximum returned by getTextBeforeCursor().
*/
- public int getWordBeforeCursorLength() {
+ public int getPaddedWordBeforeCursorLength() {
if (getTextAfterCursor(1).startsWithWord()) {
return 0;
}
@@ -124,6 +141,20 @@ public class TextField extends InputField {
}
+ /**
+ * Deletes one character by emulating a backspace key event (useful for deleting emoji and Unicode
+ * characters); Or deletes a region of text at once (faster, but may leave half a surrogate pair).
+ */
+ public void deleteChars(int numberOfChars) {
+ if (numberOfChars == 1) {
+ connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
+ } else if (numberOfChars > 1) {
+ connection.deleteSurroundingText(numberOfChars, 0);
+ }
+ }
+
+
/**
* deletePrecedingSpace
* Deletes the preceding space before the given word. The word must be before the cursor.
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 62903891..79533d39 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
@@ -102,6 +102,7 @@ abstract public class InputMode {
public boolean shouldDeletePrecedingSpace(InputType inputType) { return false; }
public boolean shouldIgnoreText(String text) { return text == null || text.isEmpty(); }
public boolean shouldSelectNextSuggestion() { return false; }
+ public boolean recompose(String word) { return false; }
public void reset() {
autoAcceptTimeout = -1;
diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java
index 0b5ab6d4..6511c3fb 100644
--- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java
+++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java
@@ -14,6 +14,7 @@ import io.github.sspanak.tt9.languages.EmojiLanguage;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageKind;
import io.github.sspanak.tt9.languages.NaturalLanguage;
+import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.util.Characters;
import io.github.sspanak.tt9.util.Logger;
@@ -123,6 +124,27 @@ public class ModePredictive extends InputMode {
}
+ @Override
+ public boolean recompose(String word) {
+ if (!settings.getBackspaceRecomposing() || word == null || word.length() < 2 || word.contains(" ")) {
+ Logger.d(LOG_TAG, "Not recomposing invalid word: '" + word + "'");
+ textCase = CASE_CAPITALIZE;
+ return false;
+ }
+
+ try {
+ reset();
+ digitSequence = language.getDigitSequenceForWord(word);
+ textCase = new Text(language, word).getTextCase();
+ setWordStem(word, true);
+ } catch (InvalidLanguageCharactersException e) {
+ Logger.d(LOG_TAG, "Not recomposing word: '" + word + "'. " + e.getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
@Override
public void reset() {
super.reset();
@@ -246,7 +268,7 @@ public class ModePredictive extends InputMode {
.setIsStemFuzzy(isStemFuzzy)
.setStem(stem)
.setLanguage(searchLanguage)
- .setInputWord(currentWord)
+ .setInputWord(currentWord.isEmpty() ? stem : currentWord)
.setWordsChangedHandler(this::onPredictions)
.load();
}
diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsTyping.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsTyping.java
index fcfa6cae..5d490b9f 100644
--- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsTyping.java
+++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsTyping.java
@@ -19,6 +19,10 @@ class SettingsTyping extends SettingsInput {
return prefs.getBoolean("backspace_acceleration", false);
}
+ public boolean getBackspaceRecomposing() {
+ return prefs.getBoolean("backspace_recomposing", true);
+ }
+
public String getDoubleZeroChar() {
String character = prefs.getString("pref_double_zero_char", ".");
diff --git a/app/src/main/java/io/github/sspanak/tt9/util/Text.java b/app/src/main/java/io/github/sspanak/tt9/util/Text.java
index 91bfe6ee..94270dae 100644
--- a/app/src/main/java/io/github/sspanak/tt9/util/Text.java
+++ b/app/src/main/java/io/github/sspanak/tt9/util/Text.java
@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import java.util.Locale;
+import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language;
public class Text extends TextTools {
@@ -36,11 +37,39 @@ public class Text extends TextTools {
return text != null && !text.isEmpty() && Characters.isGraphic(text.charAt(text.length() - 1));
}
+ public int getTextCase() {
+ if (isUpperCase()) {
+ return InputMode.CASE_UPPER;
+ } else if (isCapitalized()) {
+ return InputMode.CASE_CAPITALIZE;
+ } else if (isMixedCase()) {
+ return InputMode.CASE_DICTIONARY;
+ } else {
+ return InputMode.CASE_LOWER;
+ }
+ }
+
public boolean isEmpty() {
return text == null || text.isEmpty();
}
+ private boolean isCapitalized() {
+ if (text == null || text.length() < 2) {
+ return false;
+ }
+ if (!Character.isUpperCase(text.charAt(0))) {
+ return false;
+ }
+
+ for (int i = 1, end = text.length(); i < end; i++) {
+ if (Character.isUpperCase(text.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
public boolean isMixedCase() {
return
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index b94a00ab..d40b9871 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -23,6 +23,8 @@
Тъмен облик
Автоматични главни букви на всеки ред
Започвай всеки ред с главна буква, дори и да е в средата на изречение.
+ Редактиране на думи
+ Връщай списъка с предложения при натискане на Backspace на края на дума.\n(Не работи в някои приложения.)
Езици
Изтрий всички
Икона за състояние
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 7ac929aa..0b0e063d 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -37,8 +37,9 @@
Automatisch Sätze mit einem Großbuchstaben beginnen.
Automatische Großbuchstaben auf jeder Zeile
Jede Zeile mit einem Großbuchstaben beginnen, auch wenn es mitten im Satz ist.
+ Wortnachbearbeitung
+ Die Vorschlagsliste zurückbringen, indem Sie am Ende eines Wortes die Rücktaste drücken.\n(Nicht in allen Apps unterstützt)
Sprachen
-
Statusicon
Ein Icon anzeigen, wenn die Tastatureingabe aktiv ist.
Die Reihenfolge der Tasten umkehren
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index fed9419d..bd04d071 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -30,6 +30,8 @@
Teclado
Mayúsculas automáticas en cada línea
Comenzar cada línea con una letra mayúscula, incluso si está en medio de una oración.
+ Recomposición de palabras
+ Recuperar la lista de sugerencias presionando la tecla Retroceso al final de una palabra.\n(No compatible con algunas aplicaciones)
Idiomas
Tema oscuro
Espacio
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index bc7c6244..540b59f8 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -24,6 +24,8 @@
Thème sombre
Majuscules automatiques sur chaque ligne
Commencer chaque ligne avec une majuscule, même si elle est au milieu d\'une phrase.
+ Recomposition de mots
+ Ramener la liste de suggestions en appuyant sur Retour arrière à la fin d\'un mot.\n(Non pris en charge dans certaines applications)
Langues
Supprimer tous
Icône d\'état
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 0522a7d5..ba594cc8 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -39,6 +39,8 @@
Iniziare automaticamente le frasi con una lettera maiuscola.
Maiuscole automatiche su ogni riga
Iniziare ogni riga con una lettera maiuscola, anche se è nel mezzo di una frase.
+ Ricomposizione delle parole
+ Riportare l\'elenco dei suggerimenti premendo Backspace alla fine di una parola.\n(Non supportato in alcune app)
Lingue
Icona di stato
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index cd6ea0e6..2fa14f34 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -41,6 +41,8 @@
התחל אוטומטית משפטים באות גדולה.
רישיות אוטומטיות בכל שורה
להתחיל כל שורה באות ראשונה גדולה, גם אם היא באמצע משפט.
+ עריכת מילים
+ להחזיר את רשימת ההצעות על ידי לחיצה על מקש Backspace בסוף מילה.\n(לא נתמך בחלק מהאפליקציות)
שפות
ערכת נושא שחורה
תו ללחיצה מרובה על מקש 0
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index bccba867..1b4efed0 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -41,6 +41,8 @@
Automatiškai pradėti sakinius didžiąja raide.
Automatiniai didžiosios raidės kiekvienoje eilutėje
Pradėti kiekvieną eilutę didžiąja raide, net jei ji yra sakinio viduryje.
+ Žodžių redagavimas
+ Sugrąžinti siūlymų sąrašą paspaudžiant klavišą Backspace žodžio pabaigoje.\n(Nepalaikoma kai kuriose programėlėse)
Kalbos
Tamsi išvaizda
Taip
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index d9ef5d90..a8889bb5 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -36,6 +36,8 @@
Automatisch zinnen beginnen met een hoofdletter.
Automatische hoofdletters op elke regel
Elke regel beginnen met een hoofdletter, zelfs als het midden van een zin is.
+ Woordherbewerking
+ Breng de suggestielijst terug door Backspace te drukken aan het einde van een woord.\n(Niet ondersteund in sommige apps)
Talen
Statusicoon
Een icoon tonen wanneer toetsenbordinvoer actief is.
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 89a7174b..c4769923 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -42,6 +42,8 @@
Iniciar automaticamente as frases com letras maiúsculas.
Letras maiúsculas automáticas em cada linha
Começar cada linha com uma letra maiúscula, mesmo que esteja no meio de uma frase.
+ Recomposição de palavras
+ Trazer de volta a lista de sugestões pressionando Backspace no final de uma palavra.\n(Não suportado em alguns aplicativos)
Idiomas
Modo Escuro
Caractere para toque duplo na tecla 0
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 6b4bb56b..b49a3ba5 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -24,6 +24,8 @@
Темная тема
Автоматические заглавные буквы на каждой строке
Начинать каждую строку с заглавной буквы, даже если она в середине предложения.
+ Редактирование слов
+ Вернуть список предложений, нажав клавишу Backspace в конце слова.\n(Не поддерживается в некоторых приложениях)
Языки
Удалить все
Иконка состояния
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index cb310f61..ce44adbb 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -37,6 +37,8 @@
Cümlelere otomatik olarak büyük harfle başlar.
Her Satıra Büyük Harf ile Başlama
Cümlenin ortasında olsa bile her yeni satıra büyük harf ile başlar.
+ Kelime Yeniden Düzenleme
+ Kelimenin sonunda Geri tuşuna basarak öneri listesini geri getirmek.\n(Bazı uygulamalarda desteklenmez)
Diller
Durum
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index e9e06144..63a2d283 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -43,6 +43,8 @@
Автоматично починати речення з великої букви
Автоматичні великі літери на кожному рядку
Починати кожен рядок з великої літери, навіть якщо він у середині речення.
+ Редагування слів
+ Повернути список пропозицій, натиснувши клавішу Backspace в кінці слова.\n(Не підтримується в деяких додатках)
Мови
Темна тема
Так
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c950f751..fa47e596 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -55,6 +55,8 @@
Start every line with a capital letter, even it is in the middle of a sentence.
Fast Delete
Erase entire words by holding Backspace.\n(Not supported in some apps)
+ Word Re-composition
+ Bring back the suggestion list by pressing Backspace at the end of a word. (Not supported in some apps)
Languages
Dark Theme
Yes
diff --git a/app/src/main/res/xml/prefs_screen_keypad.xml b/app/src/main/res/xml/prefs_screen_keypad.xml
index 37c38253..b61840a0 100644
--- a/app/src/main/res/xml/prefs_screen_keypad.xml
+++ b/app/src/main/res/xml/prefs_screen_keypad.xml
@@ -9,19 +9,17 @@
app:key="screen_hotkeys"
app:title="@string/pref_category_function_keys" />
+
+
-
-
-
-
+