diff --git a/cmd/pad/main.go b/cmd/pad/main.go index eb5ad9f..0c0332c 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -29,13 +29,16 @@ func main() { } func run(w *app.Window) error { + log.Printf("run: starting") var ops op.Ops shaper := text.NewShaper(text.WithCollection(gofont.Collection())) logic := editor.NewLogic() renderer := ui.New(ui.Theme{FontSize: 14}, shaper, logic.State()) var mu sync.Mutex var elems []ui.Element + log.Printf("run: starting frameReceiver") go frameReceiver(w, &mu, &elems, logic.FrameChan()) + log.Printf("run: starting logic.Run") go logic.Run() for { @@ -49,34 +52,43 @@ func run(w *app.Window) error { PixelHeight: e.Config.Size.Y, } case app.FrameEvent: + log.Printf("run: FrameEvent") gtx := app.NewContext(&ops, e) newScale := gtx.Metric.PxPerDp curScale := logic.State().Scale() if newScale != curScale { logic.ConfigChan() <- editor.ScaleEvent{newScale} } + // Deadlock risk: ensure no channel sends while lock is held mu.Lock() currentElems := elems renderer.Draw(gtx, currentElems) - logic.DisplayLineChan() <- int(renderer.LastLineY()) + lastLineY := int(renderer.LastLineY()) // Send search query update to the logic goroutine when it changes. // The logic goroutine handles filtering and triggers a new frame. newQuery := logic.State().Browser.SearchEditor.Text() + sendQuery := false if newQuery != logic.State().Browser.Query { - logic.SearchQueryChan() <- newQuery + sendQuery = true } if events := renderer.CheckGestures(e.Source, gtx.Metric); len(events) > 0 { logic.InputChan() <- events } e.Frame(&ops) mu.Unlock() + if sendQuery { + logic.SearchQueryChan() <- newQuery + } + logic.DisplayLineChan() <- lastLineY } } } func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) { + log.Printf("frameReceiver: loop starting") for { frame := <-frameChan + log.Printf("frameReceiver: received frame") mu.Lock() *elems = frame w.Invalidate() diff --git a/internal/editor/logic.go b/internal/editor/logic.go index 0983e51..33a2140 100644 --- a/internal/editor/logic.go +++ b/internal/editor/logic.go @@ -1,6 +1,7 @@ package editor import ( + "log" "sync" "pad/internal/browser" @@ -81,8 +82,8 @@ func NewLogic() *Logic { configChan: make(chan ConfigUpdate), frameChan: make(chan []ui.Element), inputChan: make(chan []ui.InputEvent), - lastLineYChan: make(chan int), - resultChan: make(chan ResultEvent), + lastLineYChan: make(chan int), + resultChan: make(chan ResultEvent), searchQueryChan: make(chan string), openFileChan: make(chan string), // Initialized workerPool: wp, @@ -136,6 +137,7 @@ func (l *Logic) Scale() float32 { // Run runs the logic goroutine loop. func (l *Logic) Run() { // Dispatch initial directory index build on startup + log.Printf("Logic: Dispatching BuildIndexTask") l.workerPool.Dispatch(pool.NewBuildIndexTask(l.state.Browser.CurrentPath, l.mockFS)) for { @@ -143,6 +145,7 @@ func (l *Logic) Run() { case <-l.done: return case update := <-l.configChan: + log.Printf("Logic: ConfigEvent") update.apply(l.state) l.frameChan <- l.state.layout(l.browserManager) case y := <-l.lastLineYChan: @@ -151,11 +154,13 @@ func (l *Logic) Run() { l.frameChan <- l.state.layout(l.browserManager) } case events := <-l.inputChan: + log.Printf("Logic: InputEvents") for _, evt := range events { evt.Handler(evt.Data) } l.frameChan <- l.state.layout(l.browserManager) case query := <-l.searchQueryChan: + log.Printf("Logic: SearchQuery") if query != l.state.Browser.Query { l.state.Browser.Query = query if l.state.page == BrowserPage { @@ -164,9 +169,11 @@ func (l *Logic) Run() { } l.frameChan <- l.state.layout(l.browserManager) case path := <-l.openFileChan: + log.Printf("Logic: OpenFileChan %s", path) // Dispatch ReadFileTask to worker pool l.workerPool.Dispatch(pool.NewReadFileTask(path, l.mockFS)) case res := <-l.workerPool.ResultChan(): + log.Printf("Logic: WorkerResult %s", res.TaskType) l.handleWorkerResult(res) case <-l.resultChan: l.frameChan <- l.state.layout(l.browserManager) @@ -179,6 +186,7 @@ func (l *Logic) handleWorkerResult(res pool.Result) { if res.IsBrowserResult() { l.browserManager.HandleResult(res) } else if res.TaskType == pool.TypeReadFile { + log.Printf("Logic: TypeReadFile result success=%v", res.Success) if res.Success { if content, ok := res.Data.([]byte); ok { l.state.ActiveFileContent = string(content)