dd9493bdbd
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| dd9493bdbd |
IME: fix random mid-word capitalization (stable snippet window + commit in-flight handling)
Gboard capitalizes the first character of a fresh input session and
derives its word context from the text it holds locally; it never
queries the app for context while typing. The editor was resetting that
session on essentially every event, so Gboard re-anchored to a fresh
session mid-word and caps went random:
- The pushed snippet WAS the render window. Any viewport change (keyboard
show/hide animation, tap re-centering, scroll) changed the snippet, and
gioui turns every snippet change into imm.restartInput — a full IME
session reset. The snippet is now a hysteresis window around the caret
(32 KB, re-anchor only when the caret is within 4 KB of an edge),
decoupled from the render window: typing, taps within range, keyboard
animation and flings never re-push it.
- gioui's EditEvent callback applies the commit to its own window state
directly, so the op queue lags it by one commit; any frame event in
the gap regressed the state and sent a restartInput with pre-commit
text per keystroke. The drained commit is now applied to the pushed
model immediately (Renderer.ApplyIMECommitToModel), and a short hold
keeps stale pre-commit frames out of FlushIME until the logic's
post-commit frame arrives.
- The layout feedback loop re-emitted on exact float equality of the
derived last-line Y, spinning a re-emit -> shape -> re-emit loop
(float-sum noise) that re-drew the editor and re-pushed IME state;
gate it with a 0.5 dp epsilon.
- The render window bottom mapped through the WrapIndex whose
in-viewport counts land while this very window is being shaped: the
bottom oscillated frame to frame, resizing the window (and the old
snippet) every frame. Use a fixed line span from the stable top
instead (each logical line yields >= 1 visual line, so the viewport is
always covered).
Result: zero snippet re-pushes during typing or flings (one re-anchor at
a far tap/file switch); emulator typing tests show all-lowercase
mid-word commits ('thaaaaaaaae', 'vapoaaaaaaaar') and the stress suite
(7 scenarios) passes clean with contiguous insertions only.
|
|||
| f54ca2f81a |
IME: map commits against the whole buffer; drop the renderer-side model
The renderer kept a mirror of the pushed IME snippet (the 'IME model') to translate commit positions, but it transiently desynced from the buffer on fling/tap sequences (observed as a few-byte mapping drift on both the x86_64 emulator and the ARM phone), corrupting text. The model string also sat on the main goroutine next to the JNI render path, where the app observed states that were impossible for Go memory (string contents changing between reads microseconds apart), pointing at corruption in the native bridge layer. Restructure along the lines of the Android InputConnection contract and Gio's own reference editor (widget/editor.go): - Commits carry absolute file runes (the pushed snippet's coordinate space) straight to the logic goroutine, which maps them to bytes against the WHOLE buffer (runeToByteWhole, an 8 KiB-step scan). Scrolling moves the window, not the buffer, so the mapping is exact mid-fling by construction — no mirror to desync. - Drift guard in HandleIMECommit: a small commit (range <= 2 runes) is always anchored at the caret the IME was last told about; if the IME reports it ending elsewhere, its snippet text is stale (a dropped restartInput, as Gboard does during flings) and its position is in the stale text's coordinates — snap the commit to the cursor, the only position it cannot drift from. - FlushIME simplifies to: push the snippet when the frame's (context+window) text differs from the last push (gioui dedupes against its own cache), force the selection re-push in the same frame. After a commit the frame text equals what the IME already holds locally, so the restart is naturally suppressed; a fling re-anchors the IME once per text change. - Remove the renderer model (adoptFrame/ModelTranslate/ ApplyIMEEdit/ApplyIMEKey/IMECaret), the IME freeze/settle machinery (IMEFrozen, markIMEScrollActive, imeSettleChan), and the window-relative imeRuneToByte. Also fixed along the way (both found while chasing the corruption): - real.ReadFileAt: loop over short reads. A single ReadAt on Android FUSE can return a short read, silently truncating a chunk and shifting every byte offset after it. - logic: a late lazy-chunk result no longer clobbers a buffer that SetContent has already fully loaded. - e2e: large-file IME test (1.6 MB file, fling + commit). - app icon (scripts/make_icon.py + cmd/pad/appicon.png) so gogio builds the mipmap/adaptive icon set. Verified: go vet + staticcheck, go test -race (all packages), and the emulator scenario loop (open moby excerpt, fling to mid-file, tap, type 'a', byte-compare the saved file) 75/75 clean. |