Rerender fewer views when their width changes

In d5b4f7bb3e and 58a83b0862 we introduced a combined mechanism for rerendering
views when either their width changes (needed for the branches view which
truncates long branch names), or the screen mode (needed for those views that
display more information in half or full screen mode, e.g. the commits view).

This was a bad idea, because it unnecessarily rerenders too many views when just
their width changes, which causes a noticable lag. This is a problem, for
example, when selecting a file in the files panel that has only unstaged
changes, and then going to one that has both staged and unstaged changes; this
splits the main view, causing the side panels to become a bit narrower, and
rerendering all those views took almost 500ms on my machine. Another similar
example is entering or leaving staging mode.

Fix this by being more specific about which views need rerendering under what
conditions; this improves the time it takes to rerender in the above scenarios
from 450-500s down to about 20ms.

This reintroduces the code that was removed in 58a83b0862, but in a slightly
different way.
This commit is contained in:
Stefan Haller 2024-06-22 18:00:31 +02:00
parent 8e1464f720
commit a67eda39a5
8 changed files with 48 additions and 11 deletions

View file

@ -23,7 +23,7 @@ type BaseContext struct {
focusable bool
transient bool
hasControlledBounds bool
needsRerenderOnWidthChange bool
needsRerenderOnWidthChange types.NeedsRerenderOnWidthChangeLevel
needsRerenderOnHeightChange bool
highlightOnFocus bool
@ -46,7 +46,7 @@ type NewBaseContextOpts struct {
Transient bool
HasUncontrolledBounds bool // negating for the sake of making false the default
HighlightOnFocus bool
NeedsRerenderOnWidthChange bool
NeedsRerenderOnWidthChange types.NeedsRerenderOnWidthChangeLevel
NeedsRerenderOnHeightChange bool
OnGetOptionsMap func() map[string]string
@ -201,7 +201,7 @@ func (self *BaseContext) HasControlledBounds() bool {
return self.hasControlledBounds
}
func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
func (self *BaseContext) NeedsRerenderOnWidthChange() types.NeedsRerenderOnWidthChangeLevel {
return self.needsRerenderOnWidthChange
}

View file

@ -46,7 +46,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: true,
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES,
})),
ListRenderer: ListRenderer{
list: viewModel,

View file

@ -77,7 +77,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
Key: LOCAL_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: true,
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
NeedsRerenderOnHeightChange: true,
})),
ListRenderer: ListRenderer{

View file

@ -48,7 +48,7 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: true,
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
})),
ListRenderer: ListRenderer{
list: viewModel,

View file

@ -121,7 +121,7 @@ func NewSubCommitsContext(
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
NeedsRerenderOnWidthChange: true,
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
NeedsRerenderOnHeightChange: true,
})),
ListRenderer: ListRenderer{

View file

@ -1,6 +1,7 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -16,7 +17,7 @@ func (self *ScreenModeActions) Next() error {
),
)
return nil
return self.rerenderViewsWithScreenModeDependentContent()
}
func (self *ScreenModeActions) Prev() error {
@ -27,9 +28,33 @@ func (self *ScreenModeActions) Prev() error {
),
)
return self.rerenderViewsWithScreenModeDependentContent()
}
// these views need to be re-rendered when the screen mode changes. The commits view,
// for example, will show authorship information in half and full screen mode.
func (self *ScreenModeActions) rerenderViewsWithScreenModeDependentContent() error {
for _, context := range self.c.Context().AllList() {
if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES {
if err := self.rerenderView(context.GetView()); err != nil {
return err
}
}
}
return nil
}
func (self *ScreenModeActions) rerenderView(view *gocui.View) error {
context, ok := self.c.Helpers().View.ContextForView(view.Name())
if !ok {
self.c.Log.Errorf("no context found for view %s", view.Name())
return nil
}
return context.HandleRender()
}
func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
for i, val := range sl {
if val == current {

View file

@ -73,7 +73,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
mustRerender := false
if context.NeedsRerenderOnWidthChange() {
if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES {
// view.Width() returns the width -1 for some reason
oldWidth := view.Width() + 1
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset

View file

@ -39,6 +39,18 @@ type ParentContexter interface {
GetParentContext() (Context, bool)
}
type NeedsRerenderOnWidthChangeLevel int
const (
// view doesn't render differently when its width changes
NEEDS_RERENDER_ON_WIDTH_CHANGE_NONE NeedsRerenderOnWidthChangeLevel = iota
// view renders differently when its width changes. An example is a view
// that truncates long lines to the view width, e.g. the branches view
NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES
// view renders differently only when the screen mode changes
NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES
)
type IBaseContext interface {
HasKeybindings
ParentContexter
@ -60,8 +72,8 @@ type IBaseContext interface {
// determined independently.
HasControlledBounds() bool
// true if the view needs to be rerendered when its width changes
NeedsRerenderOnWidthChange() bool
// to what extent the view needs to be rerendered when its width changes
NeedsRerenderOnWidthChange() NeedsRerenderOnWidthChangeLevel
// true if the view needs to be rerendered when its height changes
NeedsRerenderOnHeightChange() bool