editor: route file taps to in-app editor; add gated IME commit logging

- ui.OpenFile now calls the in-app editor OpenFile(path) instead of the
  external openfunc (Android Termux bridge). The old wiring sent every
  browser-row tap through the Termux bridge, which crashed on Android 7+
  with FileUriExposedException (file:// Intent URI) and bypassed the editor.
- OpenFile no longer invokes TheState.open; it is retained as a dormant
  external hook for a future 'open externally' action.
- Add imeDebugLog (off by default) verbose per-commit IME logging in
  HandleReplaceRange, plus currentEditorText helper, to aid on-device
  IME commit/cursor-sync debugging.
This commit is contained in:
Greg Pomerantz 2026-08-16 09:44:36 -04:00
parent 92d8a6b7f0
commit c9c0d47f2a
2 changed files with 35 additions and 2 deletions

View File

@ -74,8 +74,13 @@ type Logic struct {
func NewLogic(mfs pool.FileSystem, path string, openfunc func(string)) *Logic { func NewLogic(mfs pool.FileSystem, path string, openfunc func(string)) *Logic {
state := NewState() state := NewState()
TheState = state TheState = state
// The openfunc (e.g. the Android Termux bridge) is kept on TheState.open
// as an optional external hook, but it is NOT the tap path: tapping a file
// must open it in the in-app editor (doc/spec.md). The previous wiring set
// ui.OpenFile = openfunc, which routed every tap through the external
// bridge and bypassed the editor entirely.
TheState.open = openfunc TheState.open = openfunc
ui.OpenFile = openfunc ui.OpenFile = func(path string) { OpenFile(path) }
// Initialize mock filesystem if nil // Initialize mock filesystem if nil
mockFS := mfs mockFS := mfs

View File

@ -254,7 +254,10 @@ func GoToEditor(data any) {
// data is the filename string from the browser list. // data is the filename string from the browser list.
func OpenFile(data any) { func OpenFile(data any) {
filename := data.(string) filename := data.(string)
TheState.open(filename) // NOTE: the external open hook (TheState.open, e.g. the Android Termux
// bridge) is intentionally NOT called here. Opening a file is an in-app
// action; the external bridge was the old tap path and it crashes on
// Android 7+ (FileUriExposedException from a file:// Intent URI).
// Dispatch a request to load the file // Dispatch a request to load the file
go func() { go func() {
@ -654,6 +657,10 @@ func HandleReplaceRange(startRune, endRune int, text string) {
} }
absStart := windowStart + runeIndexToByteStr(windowText, startRune) absStart := windowStart + runeIndexToByteStr(windowText, startRune)
absEnd := windowStart + runeIndexToByteStr(windowText, endRune) absEnd := windowStart + runeIndexToByteStr(windowText, endRune)
if imeDebugLog {
fmt.Printf("IME DEBUG HandleReplaceRange: startRune=%d endRune=%d text=%q windowStart=%d windowLen=%d -> absStart=%d absEnd=%d\n",
startRune, endRune, text, windowStart, len(windowText), absStart, absEnd)
}
var newCursor int var newCursor int
if buf := TheState.Editor.ChunkedBuffer; buf != nil { if buf := TheState.Editor.ChunkedBuffer; buf != nil {
@ -672,9 +679,30 @@ func HandleReplaceRange(startRune, endRune int, text string) {
newCursor = absStart + len(text) newCursor = absStart + len(text)
} }
TheState.Editor.CursorPosition = newCursor TheState.Editor.CursorPosition = newCursor
if imeDebugLog {
dbgBuf := currentEditorText()
if len(dbgBuf) > 40 {
dbgBuf = dbgBuf[:40]
}
fmt.Printf("IME DEBUG -> newCursor=%d buffer=%q\n", newCursor, dbgBuf)
}
markDirty() markDirty()
} }
// imeDebugLog enables verbose per-commit IME logging. Keep false in normal
// use; enable when debugging IME commit/cursor sync on device.
const imeDebugLog = false
// currentEditorText returns the current editor buffer contents (full for
// chunked files). Used only for debug logging.
func currentEditorText() string {
if cb := TheState.Editor.ChunkedBuffer; cb != nil {
s, _ := cb.FullContent()
return s
}
return TheState.Editor.Buffer
}
func markDirty() { func markDirty() {
// TheLogic is nil in pure unit tests (no logic goroutine). Editing the // TheLogic is nil in pure unit tests (no logic goroutine). Editing the
// buffer is still valid there; only the autosave side-effect is skipped. // buffer is still valid there; only the autosave side-effect is skipped.