Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1200,40 +1200,54 @@ private void onCombiningGraceExpired() {
} else {
commitTyped(sv, LastComposedWord.NOT_A_SEPARATOR);
}
// Track whether the helper actually wrote a space (skipped for URL / e-mail / phantom).
final int beforeSpace = mConnection.getExpectedSelectionEnd();
if (!sv.mCombiningAutospaceOnlyAfterGesture || wordHadGestureFragment) {
insertAutomaticSpaceIfOptionsAndTextAllow(sv);
// #23 (PREF_SPACING_DEFER_GRACE_SPACE): defer the grace-mode space through PHANTOM
// instead of writing it eagerly, so it materializes on the NEXT input via the same path
// as the default gesture word — URL/e-mail/punctuation gates + backspace-reversibility
// are applied at materialization time, with no eager space to patch.
final boolean autospaceInserted;
if (sv.mSpacingDeferGraceSpace) {
if (!sv.mCombiningAutospaceOnlyAfterGesture || wordHadGestureFragment) {
// Arm the deferred space; the PHANTOM consumer (handleNonSeparatorEvent /
// handleSeparatorEvent) writes or suppresses it on the next input.
mSpaceState = SpaceState.PHANTOM;
} else {
clearOneShotSpaceActionAndNotifyIfChanged();
mSpaceState = SpaceState.NONE;
}
// No eager write: the cursor-delta accounting below treats this as "no space".
autospaceInserted = false;
mAutospaceJustWritten = false;
} else {
clearOneShotSpaceActionAndNotifyIfChanged();
// Eager path (default). Track whether the helper actually wrote a space (skipped for
// URL / e-mail / phantom).
final int beforeSpace = mConnection.getExpectedSelectionEnd();
if (!sv.mCombiningAutospaceOnlyAfterGesture || wordHadGestureFragment) {
insertAutomaticSpaceIfOptionsAndTextAllow(sv);
} else {
clearOneShotSpaceActionAndNotifyIfChanged();
}
autospaceInserted = mConnection.getExpectedSelectionEnd() > beforeSpace;
// If we DID insert an autospace, fix up mLastComposedWord so revertCommit (backspace +
// PREF_BACKSPACE_REVERTS_AUTOCORRECT) deletes the space along with the word. Without
// this the revert's `deleteLength = cancelLength + separatorLength` only deletes the
// word, and the DEBUG assertion (last cancelLength chars equals committedWord) throws.
if (autospaceInserted && mLastComposedWord != null
&& mLastComposedWord != LastComposedWord.NOT_A_COMPOSED_WORD
&& Constants.STRING_SPACE.equals(mLastComposedWord.mSeparatorString) == false) {
mLastComposedWord = new LastComposedWord(
mLastComposedWord.mEvents,
mLastComposedWord.mInputPointers,
mLastComposedWord.mTypedWord,
mLastComposedWord.mCommittedWord,
Constants.STRING_SPACE,
mLastComposedWord.mNgramContext,
mLastComposedWord.mCapitalizedMode);
}
// Don't set PHANTOM here — we already wrote the space; PHANTOM would make the next
// letter insert a second one.
mAutospaceJustWritten = autospaceInserted;
mSpaceState = SpaceState.NONE;
}
final boolean autospaceInserted = mConnection.getExpectedSelectionEnd() > beforeSpace;
// If we DID insert an autospace, fix up mLastComposedWord so revertCommit (backspace +
// PREF_BACKSPACE_REVERTS_AUTOCORRECT) deletes the space along with the word. Without
// this the existing revert code's `deleteLength = cancelLength + separatorLength`
// would only delete the word, and in DEBUG builds the bundled assertion against
// `getTextBeforeCursor(...).subSequence(0, cancelLength) equals committedWord` throws
// because the last cancelLength chars now include the trailing space, not the word.
if (autospaceInserted && mLastComposedWord != null
&& mLastComposedWord != LastComposedWord.NOT_A_COMPOSED_WORD
&& Constants.STRING_SPACE.equals(mLastComposedWord.mSeparatorString) == false) {
mLastComposedWord = new LastComposedWord(
mLastComposedWord.mEvents,
mLastComposedWord.mInputPointers,
mLastComposedWord.mTypedWord,
mLastComposedWord.mCommittedWord,
Constants.STRING_SPACE,
mLastComposedWord.mNgramContext,
mLastComposedWord.mCapitalizedMode);
}
// Don't set PHANTOM here — we already wrote the space to the editor. PHANTOM would
// make the next letter call insertAutomaticSpaceIfOptionsAndTextAllow AGAIN (see
// handleNonSeparatorEvent line ~1760), giving a double space. Instead, set a
// dedicated one-shot flag that handleSeparatorEvent uses to strip the autospace if
// a punctuation character follows. The flag is cleared by enterCombiningMode (next
// input took over), cancelCombiningMode (backspace etc), or once consumed.
mAutospaceJustWritten = autospaceInserted;
mSpaceState = SpaceState.NONE;
mConnection.endBatchEdit();
final int cursorAfter = mConnection.getExpectedSelectionEnd();
// The commit doesn't move the cursor for the composing text itself (it was already
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ object Defaults {
const val PREF_COMBINING_AUTOCORRECT_ON_AUTOSPACE = true
const val PREF_COMBINING_TAP_EXTRA_MS = 250
const val PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE = false
const val PREF_SPACING_DEFER_GRACE_SPACE = false
const val PREF_COMBINING_AUTOSPACE_SUGGESTIONS = "alternatives_then_next_word"
const val PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORD = true
const val PREF_COMBINING_BACKSPACE_DELETES_COMPOSING_TEXT = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
// was a tap (peck-typists need more headroom than swipers between letters). 0 = no extra.
public static final String PREF_COMBINING_TAP_EXTRA_MS = "combining_tap_extra_ms";
public static final String PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE = "combining_autospace_only_after_gesture";
// #23: route the two-thumb grace-mode auto-commit space through the deferred PHANTOM
// mechanism (like the default gesture path) instead of writing it eagerly. Experimental.
public static final String PREF_SPACING_DEFER_GRACE_SPACE = "spacing_defer_grace_space";
// What the suggestion strip shows after the combining grace timer auto-commits a word.
// Values: "keep_alternatives" (1) | "next_word" (2, default) | "alternatives_then_next_word" (3).
public static final String PREF_COMBINING_AUTOSPACE_SUGGESTIONS = "combining_autospace_suggestions";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public class SettingsValues {
public final boolean mCombiningAutocorrectOnAutospace;
public final int mCombiningTapExtraMs;
public final boolean mCombiningAutospaceOnlyAfterGesture;
public final boolean mSpacingDeferGraceSpace;
// Raw string value: "keep_alternatives" | "next_word" | "alternatives_then_next_word"
public final String mCombiningAutospaceSuggestions;
public final boolean mCombiningBackspaceDeletesGestureWord;
Expand Down Expand Up @@ -382,6 +383,9 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
mCombiningAutospaceOnlyAfterGesture = prefs.getBoolean(
Settings.PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE,
Defaults.PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE);
mSpacingDeferGraceSpace = prefs.getBoolean(
Settings.PREF_SPACING_DEFER_GRACE_SPACE,
Defaults.PREF_SPACING_DEFER_GRACE_SPACE);
mCombiningAutospaceSuggestions = prefs.getString(Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS,
Defaults.PREF_COMBINING_AUTOSPACE_SUGGESTIONS);
final boolean nonNormalTwoThumbSpacing = mGestureManualSpacing || mCombiningGraceMs > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ fun TwoThumbTypingScreen(
add(Settings.PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE)
add(Settings.PREF_COMBINING_AUTOCORRECT_ON_AUTOSPACE)
add(Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS)
add(Settings.PREF_SPACING_DEFER_GRACE_SPACE)
}
if (nonNormalSpacing) {
add(Settings.PREF_MULTIPART_FULL_WORD_SUGGESTIONS)
Expand Down Expand Up @@ -146,6 +147,11 @@ fun createTwoThumbTypingSettings(context: Context) = listOf(
R.string.combining_autospace_only_after_gesture_summary) {
SwitchPreference(it, Defaults.PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE)
},
Setting(context, Settings.PREF_SPACING_DEFER_GRACE_SPACE,
R.string.spacing_defer_grace_space,
R.string.spacing_defer_grace_space_summary) {
SwitchPreference(it, Defaults.PREF_SPACING_DEFER_GRACE_SPACE)
},
Setting(context, Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS,
R.string.combining_autospace_suggestions, R.string.combining_autospace_suggestions_summary) { def ->
val items = listOf(
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@
<string name="two_thumb_tap_autospace_grace_summary">Add this much extra time when the last input was a tapped letter, so tap-then-swipe combinations are easier to continue.</string>
<string name="combining_autospace_only_after_gesture">Only auto-space after swipes</string>
<string name="combining_autospace_only_after_gesture_summary">When enabled, tap-only words are committed without an automatic space. Words that include a swipe still auto-space.</string>
<!-- Title of the experimental toggle that defers the two-thumb grace auto-commit space. -->
<string name="spacing_defer_grace_space">Defer grace space (experimental)</string>
<!-- Description for spacing_defer_grace_space. -->
<string name="spacing_defer_grace_space_summary">Route the two-thumb grace auto-commit space through the deferred mechanism (like the default swipe path) instead of writing it immediately. The space appears on your next input and stays backspace-reversible.</string>
<!-- Title of the toggle that makes the first backspace after a gesture delete the whole word. -->
<string name="combining_backspace_deletes_gesture_word">Backspace deletes last swipe</string>
<!-- Description for combining_backspace_deletes_gesture_word. -->
Expand Down
31 changes: 31 additions & 0 deletions app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,37 @@ class InputLogicTest {
assertEquals("hello ", textBeforeCursor)
}

@Test fun deferredGraceSpaceMaterializesOnNextInput() {
// #23: with PREF_SPACING_DEFER_GRACE_SPACE on, the grace commit does NOT write the space
// eagerly (the default path gives "hello "); it arms PHANTOM so the space appears on the
// next input instead.
reset()
latinIME.prefs().edit {
putInt(Settings.PREF_COMBINING_GRACE_MS, 1000)
putBoolean(Settings.PREF_SPACING_DEFER_GRACE_SPACE, true)
}
gestureInput("hello")
expireCombiningGrace()
assertEquals("hello", textBeforeCursor) // deferred: no trailing space yet
chainInput("world")
assertEquals("hello world", textBeforeCursor) // materialized on the next letter
}

@Test fun deferredGraceCommitIsBackspaceReversible() {
// The deferred commit leaves no eager space to orphan; the first backspace deletes the
// gesture word cleanly (PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORD default on).
reset()
latinIME.prefs().edit {
putInt(Settings.PREF_COMBINING_GRACE_MS, 1000)
putBoolean(Settings.PREF_SPACING_DEFER_GRACE_SPACE, true)
}
gestureInput("hello")
expireCombiningGrace()
assertEquals("hello", textBeforeCursor)
functionalKeyPress(KeyCode.DELETE)
assertEquals("", textBeforeCursor)
}

@Test fun tapThenGestureCombiningWordStillAutospacesWhenGestureGateEnabled() {
reset()
latinIME.prefs().edit {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ class SettingsContainerTest {
container[Settings.PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE]?.key)
}

@Test
fun spacingDeferGraceSpaceSettingIsRegistered() {
assertEquals(Settings.PREF_SPACING_DEFER_GRACE_SPACE,
container[Settings.PREF_SPACING_DEFER_GRACE_SPACE]?.key)
}

@Test
fun twoThumbLowLevelBackspaceSettingIsHiddenFromSearchRegistry() {
assertNull(container[Settings.PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORD])
Expand Down
Loading