From b7edcbad3a805990dbc4198c44e4066543121bb4 Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Tue, 5 May 2026 18:39:07 -0400 Subject: [PATCH] Improve performance of spinner in Synchronized events Instead of redrawing the two views on each tick, only redraw the spinner --- pkg/gocui/flush_test.go | 4 +- pkg/gocui/gui.go | 38 +++++++------------ .../controllers/helpers/app_status_helper.go | 2 +- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/pkg/gocui/flush_test.go b/pkg/gocui/flush_test.go index 2447901f5..a27943d7c 100644 --- a/pkg/gocui/flush_test.go +++ b/pkg/gocui/flush_test.go @@ -64,7 +64,7 @@ func TestFlushContentOnly_SkipsUntaintedViews(t *testing.T) { assert.False(t, main.IsTainted(), "main view should not be tainted (was not modified)") // flushContentOnly should succeed and clear status tainted flag - assert.NoError(t, g.flushContentOnly()) + assert.NoError(t, g.flushContentOnly(g.views)) assert.False(t, status.IsTainted(), "status view should not be tainted after flushContentOnly") assert.False(t, main.IsTainted(), "main view should not be tainted after flushContentOnly") @@ -75,7 +75,7 @@ func TestFlushContentOnly_WritesCorrectContent(t *testing.T) { status, _ := setupViews(t, g) status.SetContent("Fetching |") - assert.NoError(t, g.flushContentOnly()) + assert.NoError(t, g.flushContentOnly(g.views)) assert.Equal(t, "Fetching |", status.Buffer()) } diff --git a/pkg/gocui/gui.go b/pkg/gocui/gui.go index 73e358f55..dac7295a3 100644 --- a/pkg/gocui/gui.go +++ b/pkg/gocui/gui.go @@ -779,7 +779,7 @@ func (g *Gui) processEvent() error { contentOnly = contentOnly && remainingContentOnly if contentOnly { - return g.flushContentOnly() + return g.flushContentOnly(g.views) } return g.flush() } @@ -1167,32 +1167,11 @@ func (g *Gui) flush() error { return nil } -func (g *Gui) ForceLayoutAndRedraw() error { - return g.flush() -} - -// force redrawing one or more views outside of the normal main loop. Useful during longer -// operations that block the main thread, to update a spinner in a status view. -func (g *Gui) ForceRedrawViews(views ...*View) error { - for _, m := range g.managers { - if err := m.Layout(g); err != nil { - return err - } - } - - for _, v := range views { - v.draw() - } - - Screen.Show() - return nil -} - // Redraws only tainted views and skips the layout pass. // tcell's cell-level dirty tracking ensures only // actually-changed cells are emitted to the terminal. -func (g *Gui) flushContentOnly() error { - for _, v := range g.views { +func (g *Gui) flushContentOnly(views []*View) error { + for _, v := range views { if !v.tainted { continue } @@ -1205,6 +1184,17 @@ func (g *Gui) flushContentOnly() error { return nil } +func (g *Gui) ForceLayoutAndRedraw() error { + return g.flush() +} + +// Redraws only tainted views outside of the normal main +// loop, without a layout pass. Useful during longer operations that block the +// main thread, e.g. to update a spinner in a status view. +func (g *Gui) ForceFlushViewsContentOnly(views []*View) error { + return g.flushContentOnly(views) +} + // draw manages the cursor and calls the draw function of a view. func (g *Gui) draw(v *View) error { if g.suspended { diff --git a/pkg/gui/controllers/helpers/app_status_helper.go b/pkg/gui/controllers/helpers/app_status_helper.go index 4a6e7726e..d71faeb65 100644 --- a/pkg/gui/controllers/helpers/app_status_helper.go +++ b/pkg/gui/controllers/helpers/app_status_helper.go @@ -136,7 +136,7 @@ func (self *AppStatusHelper) renderAppStatusSync(stop chan struct{}) { self.c.Views().AppStatus, self.c.Views().Options, self.c.Views().Information, self.c.Views().StatusSpacer1, self.c.Views().StatusSpacer2, } - _ = self.c.GocuiGui().ForceRedrawViews(bottomLineViews...) + _ = self.c.GocuiGui().ForceFlushViewsContentOnly(bottomLineViews) case <-stop: break outer }