Commit Graph

5 Commits

Author SHA1 Message Date
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.
2026-09-13 11:57:38 -04:00
06b1444207 gofmt: format all remaining files with the go1.27 toolchain
The tree was formatted with an older gofmt; go1.27's gofmt additionally
wants: EOF exactly one newline (no trailing blank lines), imports sorted
alphabetically within a block, mixed-precedence binary expressions
re-spaced for grouping ((a+b)/c), single-field composite literals
un-aligned, adjacent one-line method signatures aligned, and one-line
bodies containing a compound statement expanded. Applied repo-wide
(31 files under internal/); pure formatting, no semantic changes —
build and the full test suite pass.
2026-08-23 10:03:27 -04:00
3460ef3993 editor: fix viewport-on-open, chunked-buffer drift, add size guard (Phase 3 code)
- Viewport: swallow the opening tap/scroll (justOpenedAt window) and
  re-clamp ScrollOffset to [0,MaxScroll] in EditorLayout so a short file
  never opens past its content (blank viewport).
- Chunked buffer: replace the fixed i*chunkSize slot model (which drifted
  after length-changing edits and could re-read stale disk for shifted
  tail chunks) with an ordered chunk slice + prefix-sum byte offsets.
  In-range files now load fully on open (SetContent), so there is no lazy
  load and no stale-disk re-read. Edits splice only the affected chunk(s).
- Size guard: files > MaxEditableFileSize (50 MB) show a 'too large to
  edit' notice instead of loading; the browser still lists them. Edit
  handlers (KeyDown/ReplaceRange) are no-ops for too-large files.
- Tests: rewrite chunked_buffer_test.go for prefix-sum correctness (insert/
  delete across chunk boundaries, rune->byte after edit); fix the large-file
  e2e expectation to the shift-correct ground truth.
2026-08-16 10:35:10 -04:00
72b3c3f9c1 editor+ui: complete Android IME wiring (SelectionCmd, SnippetCmd, InputHintOp)
Finish Phase 1 items 1, 2, 4, on top of the range-handling done in 9b78219:

- TextField.Draw now emits, when focused:
  * key.InputHintOp{HintText}        (item 4: enable text keyboard/autocorrect)
  * key.SnippetCmd (the visible window as the snippet, Range {0,len})
                                              (item 2: swipe/autocorrect source)
  * key.SelectionCmd (caret, window-relative rune index)
                                              (item 1: IME selection sync)
  The snippet is the visible window (not the whole file), so the IME treats
  the window as the document and reports EditEvent.Range window-relative.

- HandleReplaceRange now resolves the window-relative range against
  IMEWindowText and offsets by IMEWindowStartByte to address the buffer
  (string and chunked paths). Falls back to the whole buffer when layout
  has not set the window (tests).

- EditorState gains IMEWindowStartByte / IMEWindowText, set during layout.

- Add runeCount helper (utf8 leading-byte scan) in the ui package.

- Fix a data race in three editor e2e/integration tests: they called
  OpenFile from the test goroutine while the logic goroutine ran layout;
  now wrapped in withState so the state write happens on the owner.

Tests: ime_range_test.go gains windowed-path coverage (string+chunked).
go build, go test, go vet, and go test -race are all green.
2026-08-16 02:43:58 -04:00
9b782190fa editor: honor key.EditEvent.Range in HandleReplaceRange (IME swipe/autocorrect)
The IME commit path (swipe-to-type, autocorrect replacement) sends a
key.EditEvent with a Range that the editor ignored, causing the replaced
region to be duplicated. Add Logic.HandleReplaceRange which deletes the
rune range [start,end) and inserts text, converting rune indices to byte
offsets (string path: utf8.DecodeRuneInString; chunked path: leading-byte
scan). HandleKeyDown now routes key.EditEvent through it.

Also fixes two pre-existing ChunkedBuffer correctness bugs the tests
exposed:
- Delete mis-computed the per-chunk end using a shrinking 'remaining'
  instead of the absolute end, corrupting multi-chunk deletes.
- FullContent derived its chunk bound solely from fileLen, truncating
  in-memory chunks grown past the old bound by an insert.

Adds ime_range_test.go covering insert/replace/delete, unicode, chunked,
and swapped bounds. go test -race ./... green.
2026-08-16 02:18:44 -04:00