1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2026-01-26 01:41:35 +03:00

Fix annoying UI stalls after refresh (e.g. background fetch) when the reflog is very long (#5135)

Draw only the visible lines of the reflogs panel, like we do for
commits.

Since the reflog can get very long, this saves some memory but
especially some UI thread lag. In one of my repos I had over 11'000
reflog entries (I guess I should prune them more regularly...), and
rendering them took ~600ms; since this happens on the UI thread, there
was an annoying stall for half a second after every background fetch,
for example (even if the reflog panel is not visible).
This commit is contained in:
Stefan Haller
2025-12-23 16:23:00 +01:00
committed by GitHub
9 changed files with 46 additions and 27 deletions

2
go.mod
View File

@@ -18,7 +18,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd
github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a
github.com/jesseduffield/gocui v0.3.1-0.20251223144240-29fe12e8d53f
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3

4
go.sum
View File

@@ -194,8 +194,8 @@ github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2Paiis
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE=
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q=
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA=
github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a h1:XRsyqrSljes4TlaPczQViIAA4xqdnB0fKEEpZdqWWTA=
github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
github.com/jesseduffield/gocui v0.3.1-0.20251223144240-29fe12e8d53f h1:5ArylWehV98WTxJM7AcSa53YNskEFpHHv+VePONQn58=
github.com/jesseduffield/gocui v0.3.1-0.20251223144240-29fe12e8d53f/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=

View File

@@ -21,6 +21,9 @@ type ListContextTrait struct {
// If this is true, we only render the visible lines of the list. Useful for lists that can
// get very long, because it can save a lot of memory
renderOnlyVisibleLines bool
// If renderOnlyVisibleLines is true, needRerenderVisibleLines indicates whether we need to
// rerender the visible lines e.g. because the scroll position changed
needRerenderVisibleLines bool
}
func (self *ListContextTrait) IsListContext() {}
@@ -50,8 +53,8 @@ func (self *ListContextTrait) FocusLine(scrollIntoView bool) {
self.refreshViewport()
} else if self.renderOnlyVisibleLines {
newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
if oldOrigin != newOrigin {
self.HandleRender()
if oldOrigin != newOrigin || self.needRerenderVisibleLines {
self.refreshViewport()
}
}
return nil
@@ -102,10 +105,10 @@ func (self *ListContextTrait) HandleRender() {
if self.getNonModelItems != nil {
totalLength += len(self.getNonModelItems())
}
self.GetViewTrait().SetContentLineCount(totalLength)
startIdx, length := self.GetViewTrait().ViewPortYBounds()
content := self.renderLines(startIdx, startIdx+length)
self.GetViewTrait().SetViewPortContentAndClearEverythingElse(content)
self.GetViewTrait().SetViewPortContentAndClearEverythingElse(totalLength, content)
self.needRerenderVisibleLines = false
} else {
content := self.renderLines(-1, -1)
self.GetViewTrait().SetContent(content)
@@ -142,6 +145,10 @@ func (self *ListContextTrait) RenderOnlyVisibleLines() bool {
return self.renderOnlyVisibleLines
}
func (self *ListContextTrait) SetNeedRerenderVisibleLines() {
self.needRerenderVisibleLines = true
}
func (self *ListContextTrait) TotalContentHeight() int {
result := self.list.Len()
if self.getNonModelItems != nil {

View File

@@ -26,9 +26,14 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
},
)
getDisplayStrings := func(_ int, _ int) [][]string {
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
commits := viewModel.GetItems()
if startIdx >= len(commits) {
return nil
}
return presentation.GetReflogCommitListDisplayStrings(
viewModel.GetItems(),
commits[startIdx:endIdx],
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
@@ -43,18 +48,20 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
FilteredListViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().ReflogCommits,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
View: c.Views().ReflogCommits,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
NeedsRerenderOnHeightChange: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
getDisplayStrings: getDisplayStrings,
},
c: c,
c: c,
renderOnlyVisibleLines: true,
},
}
}

View File

@@ -34,13 +34,9 @@ func (self *ViewTrait) SetViewPortContent(content string) {
self.view.OverwriteLines(y, content)
}
func (self *ViewTrait) SetViewPortContentAndClearEverythingElse(content string) {
func (self *ViewTrait) SetViewPortContentAndClearEverythingElse(lineCount int, content string) {
_, y := self.view.Origin()
self.view.OverwriteLinesAndClearEverythingElse(y, content)
}
func (self *ViewTrait) SetContentLineCount(lineCount int) {
self.view.SetContentLineCount(lineCount)
self.view.OverwriteLinesAndClearEverythingElse(lineCount, y, content)
}
func (self *ViewTrait) SetContent(content string) {

View File

@@ -178,6 +178,13 @@ func (self *ListController) handlePageChange(delta int) error {
}
}
// Since we already scrolled the view above, the normal mechanism that
// ListContextTrait.FocusLine uses for deciding whether rerendering is needed won't work. It is
// based on checking whether the origin was changed by the call to FocusPoint in that function,
// but since we scrolled the view directly above, the origin has already been updated. So we
// must tell it explicitly to rerender.
self.context.SetNeedRerenderVisibleLines()
// Since we are maintaining the scroll position ourselves above, there's no point in passing
// ScrollSelectionIntoView=true here.
self.context.HandleFocus(types.OnFocusOpts{})

View File

@@ -182,6 +182,7 @@ type IListContext interface {
IsListContext() // used for type switch
RangeSelectEnabled() bool
RenderOnlyVisibleLines() bool
SetNeedRerenderVisibleLines()
IndexForGotoBottom() int
}
@@ -205,8 +206,7 @@ type IViewTrait interface {
SetRangeSelectStart(yIdx int)
CancelRangeSelect()
SetViewPortContent(content string)
SetViewPortContentAndClearEverythingElse(content string)
SetContentLineCount(lineCount int)
SetViewPortContentAndClearEverythingElse(lineCount int, content string)
SetContent(content string)
SetFooter(value string)
SetOriginX(value int)

View File

@@ -1728,10 +1728,12 @@ func (v *View) OverwriteLines(y int, content string) {
}
// only call this function if you don't care where v.wx and v.wy end up
func (v *View) OverwriteLinesAndClearEverythingElse(y int, content string) {
func (v *View) OverwriteLinesAndClearEverythingElse(lineCount int, y int, content string) {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.setContentLineCount(lineCount)
v.overwriteLines(y, content)
for i := 0; i < y; i += 1 {
@@ -1743,7 +1745,7 @@ func (v *View) OverwriteLinesAndClearEverythingElse(y int, content string) {
}
}
func (v *View) SetContentLineCount(lineCount int) {
func (v *View) setContentLineCount(lineCount int) {
if lineCount > 0 {
v.makeWriteable(0, lineCount-1)
}

2
vendor/modules.txt vendored
View File

@@ -221,7 +221,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
github.com/jesseduffield/go-git/v5/utils/sync
github.com/jesseduffield/go-git/v5/utils/trace
# github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a
# github.com/jesseduffield/gocui v0.3.1-0.20251223144240-29fe12e8d53f
## explicit; go 1.12
github.com/jesseduffield/gocui
# github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5