From c9c0d47f2a8ffbb926c6ff6b822b59d384ec1db9 Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Sun, 16 Aug 2026 09:44:36 -0400 Subject: [PATCH] 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. --- internal/editor/logic.go | 7 ++++++- internal/editor/state.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/internal/editor/logic.go b/internal/editor/logic.go index bcf669c..f308fd3 100644 --- a/internal/editor/logic.go +++ b/internal/editor/logic.go @@ -74,8 +74,13 @@ type Logic struct { func NewLogic(mfs pool.FileSystem, path string, openfunc func(string)) *Logic { state := NewState() 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 - ui.OpenFile = openfunc + ui.OpenFile = func(path string) { OpenFile(path) } // Initialize mock filesystem if nil mockFS := mfs diff --git a/internal/editor/state.go b/internal/editor/state.go index 038c6a8..695c766 100644 --- a/internal/editor/state.go +++ b/internal/editor/state.go @@ -254,7 +254,10 @@ func GoToEditor(data any) { // data is the filename string from the browser list. func OpenFile(data any) { 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 go func() { @@ -654,6 +657,10 @@ func HandleReplaceRange(startRune, endRune int, text string) { } absStart := windowStart + runeIndexToByteStr(windowText, startRune) 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 if buf := TheState.Editor.ChunkedBuffer; buf != nil { @@ -672,9 +679,30 @@ func HandleReplaceRange(startRune, endRune int, text string) { newCursor = absStart + len(text) } 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() } +// 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() { // TheLogic is nil in pure unit tests (no logic goroutine). Editing the // buffer is still valid there; only the autosave side-effect is skipped.