Exclude view-buffer render tasks from the busy query

The repo-switch busy query must not count view-buffer content rendering:
those tasks paint a view rather than drive a git operation, so leaving
one running across a switch is harmless (the switch's own refresh
re-renders). More importantly, they fire on nearly every focus/selection
change — including the context activation that runs right before a
menu/prompt confirmation handler (e.g. confirming worktree creation).
A synchronous busy check in such a handler would otherwise see that
render and make the very switch the handler is about to request refuse
itself.

Route ViewBufferManager's tasks through a new gocui NewBackgroundTask so
they're tracked for idle detection but excluded from the busy query. The
task "background" flag now covers two kinds of non-blocking work: the
background routines (and their refreshes) tagged earlier, and view
rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-04 07:49:04 +02:00
parent d95900ccd0
commit 8655d3f5a5
3 changed files with 23 additions and 5 deletions

View file

@ -282,6 +282,13 @@ func (g *Gui) NewTask() *TaskImpl {
return g.taskManager.NewTask(false)
}
// NewBackgroundTask creates a task that is tracked for idle detection but does
// not count towards the program being busy for repo-switch safety. See
// TaskImpl.background.
func (g *Gui) NewBackgroundTask() *TaskImpl {
return g.taskManager.NewTask(true)
}
// Busy reports whether any foreground work is in flight, ignoring the event
// currently being processed on the main goroutine (see currentTask). Background
// routines (auto-fetch etc.) don't count. It's used to decide whether it's safe

View file

@ -20,10 +20,14 @@ type TaskImpl struct {
withMutex func(func())
// Background tasks don't count towards the program being "busy" for the
// purpose of deciding whether a repo switch is safe (see
// TaskManager.hasBusyForegroundTaskExcept). They're the ongoing background
// routines (auto-fetch, files refresh, external-change detection) and the
// refreshes they trigger, whose model writes are already guarded against a
// concurrent repo switch by the repo generation.
// TaskManager.hasBusyForegroundTaskExcept). Two kinds of work are tagged
// this way: the ongoing background routines (auto-fetch, files refresh,
// external-change detection) and the refreshes they trigger, whose model
// writes are already guarded against a concurrent repo switch by the repo
// generation; and view-buffer content rendering, which only paints a view
// and so is harmless to leave running across a switch. What stays
// foreground is lazygit driving a git operation and applying its results
// to the model — exactly the work a repo switch must not run underneath.
background bool
}

View file

@ -136,7 +136,14 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
view.SetOrigin(0, 0)
},
func() gocui.Task {
return gui.c.GocuiGui().NewTask()
// A background task: rendering content into a view is display
// work, not lazygit driving a git operation, so it must not
// count towards being busy and block a repo switch. These
// renders fire on nearly every focus/selection change, including
// the context activation that happens right before a menu/prompt
// handler runs (e.g. confirming worktree creation), which would
// otherwise make the switch that handler triggers refuse itself.
return gui.c.GocuiGui().NewBackgroundTask()
},
)
gui.viewBufferManagerMap[view.Name()] = manager