lazygit/pkg/gui/context/list_context_trait.go
Stefan Haller e90a3dc666 Fix view selection running out of sync with list selection when view isn't focused
When rerendering a view at the end of a refresh, we call HandleFocus only if the
view has the focus. This is so that we rerender the main view for the new
selection.

What was missing here is to update the view selection from the list selection if
the view doesn't have the focus, so that the selection is painted properly.
Normally this is not relevant because you don't see the selection if another
side panel has the focus; however, you do see it as an inactive selection when
e.g. a popup is shown, in which case it does matter.

This will become more important when we introduce section headers for commits,
because in that case the view selection needs to change when the working copy
state changes from normal to rebasing or vice versa, even if the list selection
stays the same.

The changed test submodule/reset.go shows how this was wrong before: when
entering the submodule again after resetting, there is a refresh which keeps the
same branch selected as before (master); however, since the branches panel is
not focused, the view didn't notice and kept thinking that the detached head is
selected (which it isn't, you can tell by running the test in sandbox mode and
focusing the branches panel at the end: you'll see that master is selected). So
the change in this commit fixes that.
2025-04-20 16:06:15 +02:00

151 lines
4.7 KiB
Go

package context
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ListContextTrait struct {
types.Context
ListRenderer
c *ContextCommon
// Some contexts, like the commit context, will highlight the path from the selected commit
// to its parents, because it's ambiguous otherwise. For these, we need to refresh the viewport
// so that we show the highlighted path.
// TODO: now that we allow scrolling, we should be smarter about what gets refreshed:
// 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.
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) FocusLine() {
self.Context.FocusLine()
// Doing this at the end of the layout function because we need the view to be
// resized before we focus the line, otherwise if we're in accordion mode
// 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.
self.c.AfterLayout(func() error {
oldOrigin, _ := self.GetViewTrait().ViewPortYBounds()
self.GetViewTrait().FocusPoint(
self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx()))
selectRangeIndex, isSelectingRange := self.list.GetRangeStartIdx()
if isSelectingRange {
selectRangeIndex = self.ModelIndexToViewIndex(selectRangeIndex)
self.GetViewTrait().SetRangeSelectStart(selectRangeIndex)
} else {
self.GetViewTrait().CancelRangeSelect()
}
if self.refreshViewportOnChange {
self.refreshViewport()
} else if self.renderOnlyVisibleLines {
newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
if oldOrigin != newOrigin {
self.HandleRender()
}
}
return nil
})
self.setFooter()
}
func (self *ListContextTrait) refreshViewport() {
startIdx, length := self.GetViewTrait().ViewPortYBounds()
content := self.renderLines(startIdx, startIdx+length)
self.GetViewTrait().SetViewPortContent(content)
}
func (self *ListContextTrait) setFooter() {
self.GetViewTrait().SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.Len()))
}
func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}
func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) {
self.FocusLine()
self.GetViewTrait().SetHighlight(self.list.Len() > 0)
self.Context.HandleFocus(opts)
}
func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) {
self.GetViewTrait().SetOriginX(0)
if self.refreshViewportOnChange {
self.refreshViewport()
}
self.Context.HandleFocusLost(opts)
}
// 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() {
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)
self.GetViewTrait().SetContent(content)
}
self.c.Render()
self.setFooter()
}
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
self.GetList().SetSelection(self.ViewIndexToModelIndex(selectedLineIdx))
self.HandleFocus(types.OnFocusOpts{})
return nil
}
func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
startIdx, length := self.GetViewTrait().ViewPortYBounds()
selectionStart := self.ViewIndexToModelIndex(startIdx)
selectionEnd := self.ViewIndexToModelIndex(startIdx + length)
for i := selectionStart; i < selectionEnd; i++ {
iterItem := self.GetList().GetItem(i)
if iterItem != nil && iterItem.URN() == item.URN() {
return true
}
}
return false
}
// By default, list contexts supports range select
func (self *ListContextTrait) RangeSelectEnabled() bool {
return true
}
func (self *ListContextTrait) RenderOnlyVisibleLines() bool {
return self.renderOnlyVisibleLines
}
func (self *ListContextTrait) TotalContentHeight() int {
result := self.list.Len()
if self.getNonModelItems != nil {
result += len(self.getNonModelItems())
}
return result
}