diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go index d2647ef84..6317a60b2 100644 --- a/pkg/gui/context/branches_context.go +++ b/pkg/gui/context/branches_context.go @@ -22,6 +22,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext { func(branch *models.Branch) []string { return []string{branch.Name} }, + func() bool { return c.AppState.LocalBranchSortOrder != "alphabetical" }, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/gui/context/filtered_list.go b/pkg/gui/context/filtered_list.go index 82d9b81c8..13b9c166a 100644 --- a/pkg/gui/context/filtered_list.go +++ b/pkg/gui/context/filtered_list.go @@ -1,6 +1,7 @@ package context import ( + "slices" "strings" "github.com/jesseduffield/lazygit/pkg/utils" @@ -16,14 +17,21 @@ type FilteredList[T any] struct { getFilterFields func(T) []string filter string + // Normally, filtered items are presented sorted by best match. If this + // function returns true, they retain their original sort order instead; + // this is useful for lists that show items sorted by date, for example. + // Leaving this nil is equivalent to returning false. + shouldRetainSortOrder func() bool + mutex *deadlock.Mutex } -func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string) *FilteredList[T] { +func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string, shouldRetainSortOrder func() bool) *FilteredList[T] { return &FilteredList[T]{ - getList: getList, - getFilterFields: getFilterFields, - mutex: &deadlock.Mutex{}, + getList: getList, + getFilterFields: getFilterFields, + shouldRetainSortOrder: shouldRetainSortOrder, + mutex: &deadlock.Mutex{}, } } @@ -92,6 +100,9 @@ func (self *FilteredList[T]) applyFilter() { self.filteredIndices = lo.Map(matches, func(match fuzzy.Match, _ int) int { return match.Index }) + if self.shouldRetainSortOrder != nil && self.shouldRetainSortOrder() { + slices.Sort(self.filteredIndices) + } } } diff --git a/pkg/gui/context/filtered_list_view_model.go b/pkg/gui/context/filtered_list_view_model.go index 2c2841964..b52fcbc0a 100644 --- a/pkg/gui/context/filtered_list_view_model.go +++ b/pkg/gui/context/filtered_list_view_model.go @@ -6,8 +6,8 @@ type FilteredListViewModel[T HasID] struct { *SearchHistory } -func NewFilteredListViewModel[T HasID](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] { - filteredList := NewFilteredList(getList, getFilterFields) +func NewFilteredListViewModel[T HasID](getList func() []T, getFilterFields func(T) []string, shouldRetainSortOrder func() bool) *FilteredListViewModel[T] { + filteredList := NewFilteredList(getList, getFilterFields, shouldRetainSortOrder) self := &FilteredListViewModel[T]{ FilteredList: filteredList, diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go index bb1060de6..32d6d7610 100644 --- a/pkg/gui/context/menu_context.go +++ b/pkg/gui/context/menu_context.go @@ -61,6 +61,10 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel { self.FilteredListViewModel = NewFilteredListViewModel( func() []*types.MenuItem { return self.menuItems }, func(item *types.MenuItem) []string { return item.LabelColumns }, + // The only menu that the user is likely to filter in is the keybindings + // menu; retain the sort order in that one because this allows us to + // keep the section headers while filtering: + func() bool { return true }, ) return self diff --git a/pkg/gui/context/reflog_commits_context.go b/pkg/gui/context/reflog_commits_context.go index 65137d633..6791932ba 100644 --- a/pkg/gui/context/reflog_commits_context.go +++ b/pkg/gui/context/reflog_commits_context.go @@ -24,6 +24,7 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext { func(commit *models.Commit) []string { return []string{commit.ShortSha(), commit.Name} }, + func() bool { return true }, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/gui/context/remote_branches_context.go b/pkg/gui/context/remote_branches_context.go index 884d3debb..9de792f27 100644 --- a/pkg/gui/context/remote_branches_context.go +++ b/pkg/gui/context/remote_branches_context.go @@ -26,6 +26,7 @@ func NewRemoteBranchesContext( func(remoteBranch *models.RemoteBranch) []string { return []string{remoteBranch.Name} }, + func() bool { return c.AppState.RemoteBranchSortOrder != "alphabetical" }, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/gui/context/remotes_context.go b/pkg/gui/context/remotes_context.go index 73ea428aa..51fc1c036 100644 --- a/pkg/gui/context/remotes_context.go +++ b/pkg/gui/context/remotes_context.go @@ -22,6 +22,7 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext { func(remote *models.Remote) []string { return []string{remote.Name} }, + nil, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/gui/context/stash_context.go b/pkg/gui/context/stash_context.go index c8d487688..c832f85ff 100644 --- a/pkg/gui/context/stash_context.go +++ b/pkg/gui/context/stash_context.go @@ -24,6 +24,7 @@ func NewStashContext( func(stashEntry *models.StashEntry) []string { return []string{stashEntry.Name} }, + func() bool { return true }, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/gui/context/submodules_context.go b/pkg/gui/context/submodules_context.go index 82deb25af..aff8f64ab 100644 --- a/pkg/gui/context/submodules_context.go +++ b/pkg/gui/context/submodules_context.go @@ -19,6 +19,7 @@ func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext { func(submodule *models.SubmoduleConfig) []string { return []string{submodule.Name} }, + nil, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/gui/context/tags_context.go b/pkg/gui/context/tags_context.go index d827564dd..c5ae2ccd5 100644 --- a/pkg/gui/context/tags_context.go +++ b/pkg/gui/context/tags_context.go @@ -24,6 +24,7 @@ func NewTagsContext( func(tag *models.Tag) []string { return []string{tag.Name, tag.Message} }, + nil, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/gui/context/worktrees_context.go b/pkg/gui/context/worktrees_context.go index 3e45f2d45..55618de85 100644 --- a/pkg/gui/context/worktrees_context.go +++ b/pkg/gui/context/worktrees_context.go @@ -19,6 +19,7 @@ func NewWorktreesContext(c *ContextCommon) *WorktreesContext { func(Worktree *models.Worktree) []string { return []string{Worktree.Name} }, + nil, ) getDisplayStrings := func(_ int, _ int) [][]string { diff --git a/pkg/integration/tests/filter_and_search/filter_updates_when_model_changes.go b/pkg/integration/tests/filter_and_search/filter_updates_when_model_changes.go index ae3c862c0..60ce9b580 100644 --- a/pkg/integration/tests/filter_and_search/filter_updates_when_model_changes.go +++ b/pkg/integration/tests/filter_and_search/filter_updates_when_model_changes.go @@ -27,9 +27,10 @@ var FilterUpdatesWhenModelChanges = NewIntegrationTest(NewIntegrationTestArgs{ ). FilterOrSearch("branch"). Lines( - Contains("branch-to-delete").IsSelected(), - Contains("checked-out-branch"), + Contains("checked-out-branch").IsSelected(), + Contains("branch-to-delete"), ). + SelectNextItem(). Press(keys.Universal.Remove). Tap(func() { t.ExpectPopup().