diff --git a/pkg/gui/context/list_view_model.go b/pkg/gui/context/list_view_model.go index bf8c80e23..1cb2ee648 100644 --- a/pkg/gui/context/list_view_model.go +++ b/pkg/gui/context/list_view_model.go @@ -20,15 +20,11 @@ func NewListViewModel[T HasID](getModel func() []T) *ListViewModel[T] { getModel: getModel, } - self.ListCursor = traits.NewListCursor(self) + self.ListCursor = traits.NewListCursor(func() int { return len(getModel()) }) return self } -func (self *ListViewModel[T]) Len() int { - return len(self.getModel()) -} - func (self *ListViewModel[T]) GetSelected() T { if self.Len() == 0 { return Zero[T]() diff --git a/pkg/gui/context/traits/list_cursor.go b/pkg/gui/context/traits/list_cursor.go index e42f1f5e5..3b4ab1c3d 100644 --- a/pkg/gui/context/traits/list_cursor.go +++ b/pkg/gui/context/traits/list_cursor.go @@ -5,10 +5,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -type HasLength interface { - Len() int -} - type RangeSelectMode int const ( @@ -27,15 +23,17 @@ type ListCursor struct { rangeSelectMode RangeSelectMode // value is ignored when rangeSelectMode is RangeSelectModeNone rangeStartIdx int - list HasLength + // Get the length of the list. We use this to clamp the selection so that + // the selected index is always valid + getLength func() int } -func NewListCursor(list HasLength) *ListCursor { +func NewListCursor(getLength func() int) *ListCursor { return &ListCursor{ selectedIdx: 0, rangeStartIdx: 0, rangeSelectMode: RangeSelectModeNone, - list: list, + getLength: getLength, } } @@ -81,8 +79,9 @@ func (self *ListCursor) GetSelectionRangeAndMode() (int, int, RangeSelectMode) { func (self *ListCursor) clampValue(value int) int { clampedValue := -1 - if self.list.Len() > 0 { - clampedValue = utils.Clamp(value, 0, self.list.Len()-1) + length := self.getLength() + if length > 0 { + clampedValue = utils.Clamp(value, 0, length-1) } return clampedValue @@ -114,7 +113,12 @@ func (self *ListCursor) ClampSelection() { } func (self *ListCursor) Len() int { - return self.list.Len() + // The length of the model slice can change at any time, so the selection may + // become out of bounds. To reduce the likelihood of this, we clamp the selection + // whenever we obtain the length of the model. + self.ClampSelection() + + return self.getLength() } func (self *ListCursor) GetRangeStartIdx() (int, bool) { diff --git a/pkg/gui/filetree/commit_file_tree_view_model.go b/pkg/gui/filetree/commit_file_tree_view_model.go index f5cba0edd..99ed8d477 100644 --- a/pkg/gui/filetree/commit_file_tree_view_model.go +++ b/pkg/gui/filetree/commit_file_tree_view_model.go @@ -22,8 +22,8 @@ type ICommitFileTreeViewModel interface { type CommitFileTreeViewModel struct { sync.RWMutex - ICommitFileTree types.IListCursor + ICommitFileTree // this is e.g. the commit for which we're viewing the files ref types.Ref @@ -37,7 +37,7 @@ var _ ICommitFileTreeViewModel = &CommitFileTreeViewModel{} func NewCommitFileTreeViewModel(getFiles func() []*models.CommitFile, log *logrus.Entry, showTree bool) *CommitFileTreeViewModel { fileTree := NewCommitFileTree(getFiles, log, showTree) - listCursor := traits.NewListCursor(fileTree) + listCursor := traits.NewListCursor(fileTree.Len) return &CommitFileTreeViewModel{ ICommitFileTree: fileTree, IListCursor: listCursor, diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go index 439471b01..25b3d0edc 100644 --- a/pkg/gui/filetree/file_tree_view_model.go +++ b/pkg/gui/filetree/file_tree_view_model.go @@ -21,15 +21,15 @@ type IFileTreeViewModel interface { // after the files are refreshed type FileTreeViewModel struct { sync.RWMutex - IFileTree types.IListCursor + IFileTree } var _ IFileTreeViewModel = &FileTreeViewModel{} func NewFileTreeViewModel(getFiles func() []*models.File, log *logrus.Entry, showTree bool) *FileTreeViewModel { fileTree := NewFileTree(getFiles, log, showTree) - listCursor := traits.NewListCursor(fileTree) + listCursor := traits.NewListCursor(fileTree.Len) return &FileTreeViewModel{ IFileTree: fileTree, IListCursor: listCursor,