Make RefreshOptions.Then a func() error, queue it via OnUIThread

This is preparation for upcoming commits that will bounce refresh-scope
model updates (e.g. Model.Files) onto the UI thread by enqueuing the
write via OnUIThread instead of applying it directly on the worker
goroutine. Once that lands, a Then callback that reads the model must
run after that queued write has been processed, not synchronously at
wg.Wait() time — at that point the workers have returned, but a bounce
they queued may not have been processed yet.

Queuing Then via OnUIThread here, ahead of that change, guarantees the
right ordering once it lands: a bounce queued earlier in the same
refresh is already sitting in the channel by the time wg.Wait()
returns, so Then enqueued after it will always be processed after, and
see the post-refresh model.

The signature change to func() error lets Then propagate errors
through gocui's normal error handler (the same path key-handler errors
take).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-02 22:33:33 +02:00
parent b54d4c369b
commit 717448f105
6 changed files with 20 additions and 11 deletions

View file

@ -274,10 +274,11 @@ func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool)
}
func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
selectFn := func() {
selectFn := func() error {
if selectCurrent {
self.selectCurrentBisectCommit()
}
return nil
}
if waitToReselect {
@ -285,7 +286,9 @@ func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToR
return nil
}
selectFn()
if err := selectFn(); err != nil {
return err
}
self.c.Helpers().Bisect.PostBisectCommandRefresh()
return nil

View file

@ -122,9 +122,10 @@ func (self *FilteringMenuAction) setFiltering() error {
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
self.c.Refresh(types.RefreshOptions{Scope: helpers.ScopesToRefreshWhenFilteringModeChanges(), Then: func() {
self.c.Refresh(types.RefreshOptions{Scope: helpers.ScopesToRefreshWhenFilteringModeChanges(), Then: func() error {
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().LocalCommits.HandleFocus(types.OnFocusOpts{})
return nil
}})
return nil

View file

@ -191,7 +191,7 @@ func (self *ModeHelper) ClearFiltering() error {
self.c.Refresh(types.RefreshOptions{
Scope: ScopesToRefreshWhenFilteringModeChanges(),
Then: func() {
Then: func() error {
// Find the commit that was last selected in filtering mode, and select it again after refreshing
if !self.c.Contexts().LocalCommits.SelectCommitByHash(selectedCommitHash) {
// If we couldn't find it (either because no commit was selected
@ -202,6 +202,7 @@ func (self *ModeHelper) ClearFiltering() error {
}
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
return nil
},
})
return nil

View file

@ -255,7 +255,13 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) {
wg.Wait()
if options.Then != nil {
options.Then()
// Queue Then via OnUIThread so it runs *after* the refresh-scope
// functions' model-update bounces (which are already queued by
// now), not synchronously here — at this point the workers have
// returned but their bounces haven't been processed yet, so
// invoking Then synchronously would run it on a model that's
// still pre-refresh.
self.c.OnUIThread(options.Then)
}
}

View file

@ -616,7 +616,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash())
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err,
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() error {
todos := make([]*models.Commit, 0, len(commitsToEdit)-1)
for _, c := range commitsToEdit[:len(commitsToEdit)-1] {
// Merge commits can't be set to "edit", so just skip them
@ -625,11 +625,9 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
}
}
if len(todos) > 0 {
err := self.updateTodos(todo.Edit, todos)
if err != nil {
self.c.Log.Errorf("error when updating todos: %v", err)
}
return self.updateTodos(todo.Edit, todos)
}
return nil
}})
})
}

View file

@ -56,7 +56,7 @@ const (
)
type RefreshOptions struct {
Then func()
Then func() error
Scope []RefreshableView // e.g. []RefreshableView{COMMITS, BRANCHES}. Leave empty to refresh everything
Mode RefreshMode // one of SYNC (default), ASYNC, and BLOCK_UI