Backspace appeared dead at the start of a paragraph in rich-text editors
(MS Word, Samsung/Google Notes, Docs). Those editors model paragraphs as
separate blocks and silently ignore a deleteSurroundingText that would
merge two of them, while honouring a real KEYCODE_DEL - which is what
other keyboards send.
HeliBoard already had a KEYCODE_DEL fallback (8f80c2a) but scoped it to
browsers only, so plain multi-line fields never reached it. Upstream
v4.1.2 carries the same logic, so this is not fixed by merging upstream.
Changes in handleBackspaceEvent:
- Route deletion of a '\n' through a key event instead of
deleteSurroundingText. Equivalent in a plain EditText.
- Broaden the NOT_A_CODE fallback to any editor that hides context, but
only when the expected cursor position is > 0, so a genuine document
start stays a no-op and we never dispatch key events at apps that react
to them (recipient chips, search boxes, web views).
- Never chain the accelerated second delete onto a key event: key events
take an asynchronous route that ignores batch edits, so a following
deleteSurroundingText could race ahead and act on pre-merge text.
- Fix a pre-existing bug in the accelerated path, which tested the first
code point instead of codePointBeforeCursorToDeleteAgain and could
split an emoji. Also present upstream; worth reporting.
Tests: the fake InputConnection can now simulate a block-based editor
(paragraph-scoped reads, cross-paragraph deletes that report success but
do nothing). Also fixes two latent harness bugs: deleteSurroundingText
did not clamp to available text (drove selectionStart negative), and
setText ignored its own requireIdle parameter.
The tap-into-paragraph variant is added as an @ignore'd test: it measures
expectedSelectionStart == 0, making it indistinguishable from an empty
field, so it needs a device trace before it can be fixed safely.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
Warning
Draft, parked — do not merge. This asserts a failure mode that device testing did not reproduce. It exists so the work and the evidence against it stay together.
Relates to #133. The provable half of this work shipped separately in #134.
The theory
Block-based editors (MS Word, Samsung/Google Notes, Docs) model paragraphs as separate blocks. The theory was that they silently ignore an
InputConnection.deleteSurroundingTextthat would merge two blocks — and still returntrue, so the failure is invisible to the IME — while honouring a realKEYCODE_DEL, which is what other keyboards send.HeliBoard already had a
KEYCODE_DELfallback (8f80c2a11) but scoped it to browsers (TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), so ordinary multi-line fields never reach it. Upstream v4.1.2 carries identical code.Why it's parked
Tested on the reporter's device (Samsung SM-S936B, Android 16), driving the real on-screen backspace key against the unfixed build:
MOVE_HOMEBoth apps honour
deleteSurroundingTextacross a paragraph boundary. The reporter's settings are all defaults (gesture_method=fallback), so no exotic fork backspace path was armed either.The change is not harmful — the same matrix passes on the fixed build, machine-checked by reading the editor contents back via
uiautomator. But "harmless" is not a reason to ship a behaviour change to the input path.What's here
'\n'deletion routed throughsendDownUpKeyEvent(KEYCODE_DEL)instead ofdeleteSurroundingText.NOT_A_CODEfallback broadened beyond browsers, gated ongetExpectedSelectionStart() > 0so a genuine document start never dispatches a key event at the app (recipient chips, search boxes and web views react to those).deleteSurroundingTextcould race ahead and act on pre-merge text.What would un-park it
A concrete reproduction: which app, the exact sequence, and whether the text was RTL. The reporter's real content is Hebrew and I could not drive an RTL repro reliably through
adb, so that's the most likely gap.If it reproduces, un-ignore the two parked tests in
InputLogicTest.kt(backspace merges paragraphs when the editor refuses deleteSurroundingText, and the tap-into-paragraph variant) and rebase onto whateverdevlooks like then.