Skip to content

Floor batch-edit depth at zero for unbalanced endBatchEdit - #35

Merged
Wavesonics merged 1 commit into
mainfrom
fix/unbalanced-endbatchedit
Jul 21, 2026
Merged

Floor batch-edit depth at zero for unbalanced endBatchEdit#35
Wavesonics merged 1 commit into
mainfrom
fix/unbalanced-endbatchedit

Conversation

@Wavesonics

Copy link
Copy Markdown
Collaborator

Root cause

Some IMEs call endBatchEdit() without a matching beginBatchEdit() — Huawei Celia does this consistently, Microsoft SwiftKey intermittently. TextEditorInputConnection.endBatchEditInternal decremented batchDepth unconditionally, so a stray end drove the depth to -1. After that:

  • Every bare edit runs through addEditCommandWithBatch (begin: -1 → 0, queue, end: 0 → -1), so the drain condition batchDepth == 0 is never true at the end of the wrapper. The edit command is queued and never applied — typed characters silently vanish, and the IME rebuilds its composition from the wrong buffer state, swallowing the first character of words (downstream report: Strange Android IME behavior hammer-editor#759).
  • The platform-side counter (PlatformTextEditorExtensions.batchEditDepth) already floors at 0, so the two counters permanently desynced, breaking the "drain runs with isInBatchEdit == false" invariant that ImeCursorSync depends on.

This is the same IME misbehavior AndroidX guards against in StatelessInputConnection, which floors its batch depth at zero.

Fix

batchDepth = (batchDepth - 1).coerceAtLeast(0) in endBatchEditInternal, keeping the connection counter in lockstep with the platform counter. An unbalanced end at depth 0 with an empty queue is now a harmless no-op, and endBatchEdit()'s return contract (true iff a batch is still in progress) is unchanged.

Tests

Added an androidHostTest source set (via withHostTestBuilder) and made TextEditorInputConnection internal (@VisibleForTesting) so the real class is testable. New TextEditorInputConnectionBatchTest covers:

  • Stray endBatchEdit() followed by setComposingText/commitText — all text lands in the document (fails without the fix)
  • Repeated stray ends are no-ops (fails without the fix)
  • Nested begin/begin/end/end still defers edits until the outermost end
  • Stray end doesn't desync PlatformTextEditorExtensions.isInBatchEdit
  • endBatchEdit() return value contract (fails without the fix)
  • Per-keystroke batched composition still lands after a stray end

Verified: 3 of the 6 new tests fail without the one-line fix and all pass with it; desktopTest still green.

Fixes #33

🤖 Generated with Claude Code

Some IMEs (Huawei Celia, SwiftKey) call endBatchEdit without a matching
beginBatchEdit, driving the InputConnection batch depth negative. Once
negative, the drain condition batchDepth == 0 never fires and queued edit
commands are silently dropped, swallowing typed characters.

Floor the depth at zero, matching PlatformTextEditorExtensions which
already floors, and add Android host tests for the InputConnection
batch-edit behavior.

Fixes #33
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 8 complexity · 1 duplication

Metric Results
Complexity 8
Duplication 1

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@Wavesonics
Wavesonics merged commit 456ab56 into main Jul 21, 2026
2 checks passed
@Wavesonics
Wavesonics deleted the fix/unbalanced-endbatchedit branch July 21, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dropped characters: InputConnection batchDepth goes negative on unbalanced endBatchEdit (Android)

1 participant