mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-13 00:07:59 +02:00
Only render visible portion of the screen for commits view
This commit is contained in:
parent
dd2bffc278
commit
44160ef844
@ -24,6 +24,7 @@ type BaseContext struct {
|
|||||||
transient bool
|
transient bool
|
||||||
hasControlledBounds bool
|
hasControlledBounds bool
|
||||||
needsRerenderOnWidthChange bool
|
needsRerenderOnWidthChange bool
|
||||||
|
needsRerenderOnHeightChange bool
|
||||||
highlightOnFocus bool
|
highlightOnFocus bool
|
||||||
|
|
||||||
*ParentContextMgr
|
*ParentContextMgr
|
||||||
@ -46,6 +47,7 @@ type NewBaseContextOpts struct {
|
|||||||
HasUncontrolledBounds bool // negating for the sake of making false the default
|
HasUncontrolledBounds bool // negating for the sake of making false the default
|
||||||
HighlightOnFocus bool
|
HighlightOnFocus bool
|
||||||
NeedsRerenderOnWidthChange bool
|
NeedsRerenderOnWidthChange bool
|
||||||
|
NeedsRerenderOnHeightChange bool
|
||||||
|
|
||||||
OnGetOptionsMap func() map[string]string
|
OnGetOptionsMap func() map[string]string
|
||||||
}
|
}
|
||||||
@ -66,6 +68,7 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
|
|||||||
hasControlledBounds: hasControlledBounds,
|
hasControlledBounds: hasControlledBounds,
|
||||||
highlightOnFocus: opts.HighlightOnFocus,
|
highlightOnFocus: opts.HighlightOnFocus,
|
||||||
needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
|
needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
|
||||||
|
needsRerenderOnHeightChange: opts.NeedsRerenderOnHeightChange,
|
||||||
ParentContextMgr: &ParentContextMgr{},
|
ParentContextMgr: &ParentContextMgr{},
|
||||||
viewTrait: viewTrait,
|
viewTrait: viewTrait,
|
||||||
}
|
}
|
||||||
@ -197,6 +200,10 @@ func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
|
|||||||
return self.needsRerenderOnWidthChange
|
return self.needsRerenderOnWidthChange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BaseContext) NeedsRerenderOnHeightChange() bool {
|
||||||
|
return self.needsRerenderOnHeightChange
|
||||||
|
}
|
||||||
|
|
||||||
func (self *BaseContext) Title() string {
|
func (self *BaseContext) Title() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,9 @@ type ListContextTrait struct {
|
|||||||
// we should find out exactly which lines are now part of the path and refresh those.
|
// we should find out exactly which lines are now part of the path and refresh those.
|
||||||
// We should also keep track of the previous path and refresh those lines too.
|
// We should also keep track of the previous path and refresh those lines too.
|
||||||
refreshViewportOnChange bool
|
refreshViewportOnChange bool
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContextTrait) IsListContext() {}
|
func (self *ListContextTrait) IsListContext() {}
|
||||||
@ -28,6 +31,8 @@ func (self *ListContextTrait) FocusLine() {
|
|||||||
// the view could be squashed and won't how to adjust the cursor/origin.
|
// the view could be squashed and won't how to adjust the cursor/origin.
|
||||||
// Also, refreshing the viewport needs to happen after the view has been resized.
|
// Also, refreshing the viewport needs to happen after the view has been resized.
|
||||||
self.c.AfterLayout(func() error {
|
self.c.AfterLayout(func() error {
|
||||||
|
oldOrigin, _ := self.GetViewTrait().ViewPortYBounds()
|
||||||
|
|
||||||
self.GetViewTrait().FocusPoint(
|
self.GetViewTrait().FocusPoint(
|
||||||
self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx()))
|
self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx()))
|
||||||
|
|
||||||
@ -41,6 +46,11 @@ func (self *ListContextTrait) FocusLine() {
|
|||||||
|
|
||||||
if self.refreshViewportOnChange {
|
if self.refreshViewportOnChange {
|
||||||
self.refreshViewport()
|
self.refreshViewport()
|
||||||
|
} else if self.renderOnlyVisibleLines {
|
||||||
|
newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
|
||||||
|
if oldOrigin != newOrigin {
|
||||||
|
return self.HandleRender()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -83,8 +93,21 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error
|
|||||||
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
||||||
func (self *ListContextTrait) HandleRender() error {
|
func (self *ListContextTrait) HandleRender() error {
|
||||||
self.list.ClampSelection()
|
self.list.ClampSelection()
|
||||||
|
if self.renderOnlyVisibleLines {
|
||||||
|
// Rendering only the visible area can save a lot of cell memory for
|
||||||
|
// those views that support it.
|
||||||
|
totalLength := self.list.Len()
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
content := self.renderLines(-1, -1)
|
content := self.renderLines(-1, -1)
|
||||||
self.GetViewTrait().SetContent(content)
|
self.GetViewTrait().SetContent(content)
|
||||||
|
}
|
||||||
self.c.Render()
|
self.c.Render()
|
||||||
self.setFooter()
|
self.setFooter()
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
|||||||
Kind: types.SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
Focusable: true,
|
Focusable: true,
|
||||||
NeedsRerenderOnWidthChange: true,
|
NeedsRerenderOnWidthChange: true,
|
||||||
|
NeedsRerenderOnHeightChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
@ -85,6 +86,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
|||||||
},
|
},
|
||||||
c: c,
|
c: c,
|
||||||
refreshViewportOnChange: true,
|
refreshViewportOnChange: true,
|
||||||
|
renderOnlyVisibleLines: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ func NewRemoteBranchesContext(
|
|||||||
Focusable: true,
|
Focusable: true,
|
||||||
Transient: true,
|
Transient: true,
|
||||||
NeedsRerenderOnWidthChange: true,
|
NeedsRerenderOnWidthChange: true,
|
||||||
|
NeedsRerenderOnHeightChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
|
@ -122,6 +122,7 @@ func NewSubCommitsContext(
|
|||||||
Focusable: true,
|
Focusable: true,
|
||||||
Transient: true,
|
Transient: true,
|
||||||
NeedsRerenderOnWidthChange: true,
|
NeedsRerenderOnWidthChange: true,
|
||||||
|
NeedsRerenderOnHeightChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
@ -130,6 +131,7 @@ func NewSubCommitsContext(
|
|||||||
},
|
},
|
||||||
c: c,
|
c: c,
|
||||||
refreshViewportOnChange: true,
|
refreshViewportOnChange: true,
|
||||||
|
renderOnlyVisibleLines: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,15 @@ func (self *ViewTrait) SetViewPortContent(content string) {
|
|||||||
self.view.OverwriteLines(y, content)
|
self.view.OverwriteLines(y, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *ViewTrait) SetViewPortContentAndClearEverythingElse(content string) {
|
||||||
|
_, y := self.view.Origin()
|
||||||
|
self.view.OverwriteLinesAndClearEverythingElse(y, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ViewTrait) SetContentLineCount(lineCount int) {
|
||||||
|
self.view.SetContentLineCount(lineCount)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *ViewTrait) SetContent(content string) {
|
func (self *ViewTrait) SetContent(content string) {
|
||||||
self.view.SetContent(content)
|
self.view.SetContent(content)
|
||||||
}
|
}
|
||||||
|
@ -72,14 +72,26 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
frameOffset = 0
|
frameOffset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mustRerender := false
|
||||||
if context.NeedsRerenderOnWidthChange() {
|
if context.NeedsRerenderOnWidthChange() {
|
||||||
// view.Width() returns the width -1 for some reason
|
// view.Width() returns the width -1 for some reason
|
||||||
oldWidth := view.Width() + 1
|
oldWidth := view.Width() + 1
|
||||||
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
|
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
|
||||||
if oldWidth != newWidth {
|
if oldWidth != newWidth {
|
||||||
contextsToRerender = append(contextsToRerender, context)
|
mustRerender = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if context.NeedsRerenderOnHeightChange() {
|
||||||
|
// view.Height() returns the height -1 for some reason
|
||||||
|
oldHeight := view.Height() + 1
|
||||||
|
newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 2*frameOffset
|
||||||
|
if oldHeight != newHeight {
|
||||||
|
mustRerender = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mustRerender {
|
||||||
|
contextsToRerender = append(contextsToRerender, context)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = g.SetView(
|
_, err = g.SetView(
|
||||||
viewName,
|
viewName,
|
||||||
|
@ -63,6 +63,9 @@ type IBaseContext interface {
|
|||||||
// true if the view needs to be rerendered when its width changes
|
// true if the view needs to be rerendered when its width changes
|
||||||
NeedsRerenderOnWidthChange() bool
|
NeedsRerenderOnWidthChange() bool
|
||||||
|
|
||||||
|
// true if the view needs to be rerendered when its height changes
|
||||||
|
NeedsRerenderOnHeightChange() bool
|
||||||
|
|
||||||
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
|
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
|
||||||
// no title will be set
|
// no title will be set
|
||||||
Title() string
|
Title() string
|
||||||
@ -172,6 +175,8 @@ type IViewTrait interface {
|
|||||||
SetRangeSelectStart(yIdx int)
|
SetRangeSelectStart(yIdx int)
|
||||||
CancelRangeSelect()
|
CancelRangeSelect()
|
||||||
SetViewPortContent(content string)
|
SetViewPortContent(content string)
|
||||||
|
SetViewPortContentAndClearEverythingElse(content string)
|
||||||
|
SetContentLineCount(lineCount int)
|
||||||
SetContent(content string)
|
SetContent(content string)
|
||||||
SetFooter(value string)
|
SetFooter(value string)
|
||||||
SetOriginX(value int)
|
SetOriginX(value int)
|
||||||
|
@ -431,6 +431,11 @@ func (self *ViewDriver) SelectPreviousItem() *ViewDriver {
|
|||||||
return self.PressFast(self.t.keys.Universal.PrevItem)
|
return self.PressFast(self.t.keys.Universal.PrevItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// i.e. pressing '<'
|
||||||
|
func (self *ViewDriver) GotoTop() *ViewDriver {
|
||||||
|
return self.PressFast(self.t.keys.Universal.GotoTop)
|
||||||
|
}
|
||||||
|
|
||||||
// i.e. pressing space
|
// i.e. pressing space
|
||||||
func (self *ViewDriver) PressPrimaryAction() *ViewDriver {
|
func (self *ViewDriver) PressPrimaryAction() *ViewDriver {
|
||||||
return self.Press(self.t.keys.Universal.Select)
|
return self.Press(self.t.keys.Universal.Select)
|
||||||
@ -457,21 +462,15 @@ func (self *ViewDriver) PressEscape() *ViewDriver {
|
|||||||
// - the user is not in a list item
|
// - the user is not in a list item
|
||||||
// - no list item is found containing the given text
|
// - no list item is found containing the given text
|
||||||
// - multiple list items are found containing the given text in the initial page of items
|
// - multiple list items are found containing the given text in the initial page of items
|
||||||
//
|
|
||||||
// NOTE: this currently assumes that BufferLines returns all the lines that can be accessed.
|
|
||||||
// If this changes in future, we'll need to update this code to first attempt to find the item
|
|
||||||
// in the current page and failing that, jump to the top of the view and iterate through all of it,
|
|
||||||
// looking for the item.
|
|
||||||
func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
|
func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
|
||||||
self.IsFocused()
|
self.IsFocused()
|
||||||
|
|
||||||
view := self.getView()
|
view := self.getView()
|
||||||
lines := view.BufferLines()
|
lines := view.BufferLines()
|
||||||
|
|
||||||
var matchIndex int
|
matchIndex := -1
|
||||||
|
|
||||||
self.t.assertWithRetries(func() (bool, string) {
|
self.t.assertWithRetries(func() (bool, string) {
|
||||||
matchIndex = -1
|
|
||||||
var matches []string
|
var matches []string
|
||||||
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
|
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
@ -483,13 +482,19 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
|
|||||||
}
|
}
|
||||||
if len(matches) > 1 {
|
if len(matches) > 1 {
|
||||||
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching lines:\n%s", len(matches), matcher.name(), strings.Join(matches, "\n"))
|
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching lines:\n%s", len(matches), matcher.name(), strings.Join(matches, "\n"))
|
||||||
} else if len(matches) == 0 {
|
|
||||||
return false, fmt.Sprintf("Could not find item matching: %s. Lines:\n%s", matcher.name(), strings.Join(lines, "\n"))
|
|
||||||
} else {
|
|
||||||
return true, ""
|
|
||||||
}
|
}
|
||||||
|
return true, ""
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// If no match was found, it could be that this is a view that renders only
|
||||||
|
// the visible lines. In that case, we jump to the top and then press
|
||||||
|
// down-arrow until we found the match. We simply return the first match we
|
||||||
|
// find, so we have no way to assert that there are no duplicates.
|
||||||
|
if matchIndex == -1 {
|
||||||
|
self.GotoTop()
|
||||||
|
matchIndex = len(lines)
|
||||||
|
}
|
||||||
|
|
||||||
selectedLineIdx := self.getSelectedLineIdx()
|
selectedLineIdx := self.getSelectedLineIdx()
|
||||||
if selectedLineIdx == matchIndex {
|
if selectedLineIdx == matchIndex {
|
||||||
return self.SelectedLine(matcher)
|
return self.SelectedLine(matcher)
|
||||||
@ -514,12 +519,14 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
|
|||||||
for i := 0; i < maxNumKeyPresses; i++ {
|
for i := 0; i < maxNumKeyPresses; i++ {
|
||||||
keyPress()
|
keyPress()
|
||||||
idx := self.getSelectedLineIdx()
|
idx := self.getSelectedLineIdx()
|
||||||
if ok, _ := matcher.test(lines[idx]); ok {
|
// It is important to use view.BufferLines() here and not lines, because it
|
||||||
|
// could change with every keypress.
|
||||||
|
if ok, _ := matcher.test(view.BufferLines()[idx]); ok {
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.t.fail(fmt.Sprintf("Could not navigate to item matching: %s. Lines:\n%s", matcher.name(), strings.Join(lines, "\n")))
|
self.t.fail(fmt.Sprintf("Could not navigate to item matching: %s. Lines:\n%s", matcher.name(), strings.Join(view.BufferLines(), "\n")))
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user