1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-02 23:27:32 +02:00

Show files filter status (#4230)

- **PR Description**

This PR contains three improvements to the Files panel filtering:
- it allows the user to switch to a different filter type (or reset the
filter) when we are auto-showing only conflicting files
- it shows the filter menu as radio buttons
- it displays the current filter in the top-right corner of the Files
panel's frame

See the individual commits for details.
This commit is contained in:
Stefan Haller 2025-02-07 09:27:23 +01:00 committed by GitHub
commit fcf30caf40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 51 additions and 16 deletions

View File

@ -2,6 +2,7 @@ package controllers
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
@ -753,6 +754,7 @@ func (self *FilesController) isResolvingConflicts() bool {
} }
func (self *FilesController) handleStatusFilterPressed() error { func (self *FilesController) handleStatusFilterPressed() error {
currentFilter := self.context().GetFilter()
return self.c.Menu(types.CreateMenuOptions{ return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.FilteringMenuTitle, Title: self.c.Tr.FilteringMenuTitle,
Items: []*types.MenuItem{ Items: []*types.MenuItem{
@ -761,44 +763,69 @@ func (self *FilesController) handleStatusFilterPressed() error {
OnPress: func() error { OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayStaged) return self.setStatusFiltering(filetree.DisplayStaged)
}, },
Key: 's', Key: 's',
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayStaged),
}, },
{ {
Label: self.c.Tr.FilterUnstagedFiles, Label: self.c.Tr.FilterUnstagedFiles,
OnPress: func() error { OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayUnstaged) return self.setStatusFiltering(filetree.DisplayUnstaged)
}, },
Key: 'u', Key: 'u',
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUnstaged),
}, },
{ {
Label: self.c.Tr.FilterTrackedFiles, Label: self.c.Tr.FilterTrackedFiles,
OnPress: func() error { OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayTracked) return self.setStatusFiltering(filetree.DisplayTracked)
}, },
Key: 't', Key: 't',
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayTracked),
}, },
{ {
Label: self.c.Tr.FilterUntrackedFiles, Label: self.c.Tr.FilterUntrackedFiles,
OnPress: func() error { OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayUntracked) return self.setStatusFiltering(filetree.DisplayUntracked)
}, },
Key: 'T', Key: 'T',
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUntracked),
}, },
{ {
Label: self.c.Tr.ResetFilter, Label: self.c.Tr.NoFilter,
OnPress: func() error { OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayAll) return self.setStatusFiltering(filetree.DisplayAll)
}, },
Key: 'r', Key: 'r',
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayAll),
}, },
}, },
}) })
} }
func (self *FilesController) filteringLabel(filter filetree.FileTreeDisplayFilter) string {
switch filter {
case filetree.DisplayAll:
return ""
case filetree.DisplayStaged:
return self.c.Tr.FilterLabelStagedFiles
case filetree.DisplayUnstaged:
return self.c.Tr.FilterLabelUnstagedFiles
case filetree.DisplayTracked:
return self.c.Tr.FilterLabelTrackedFiles
case filetree.DisplayUntracked:
return self.c.Tr.FilterLabelUntrackedFiles
case filetree.DisplayConflicted:
return self.c.Tr.FilterLabelConflictingFiles
}
panic(fmt.Sprintf("Unexpected files display filter: %d", filter))
}
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error { func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
previousFilter := self.context().GetFilter() previousFilter := self.context().GetFilter()
self.context().FileTreeViewModel.SetStatusFilter(filter) self.context().FileTreeViewModel.SetStatusFilter(filter)
self.c.Contexts().Files.GetView().Subtitle = self.filteringLabel(filter)
// Whenever we switch between untracked and other filters, we need to refresh the files view // Whenever we switch between untracked and other filters, we need to refresh the files view
// because the untracked files filter applies when running `git status`. // because the untracked files filter applies when running `git status`.

View File

@ -588,16 +588,14 @@ func (self *RefreshHelper) refreshStateFiles() error {
fileTreeViewModel.RWMutex.Lock() fileTreeViewModel.RWMutex.Lock()
// only taking over the filter if it hasn't already been set by the user. // only taking over the filter if it hasn't already been set by the user.
// Though this does make it impossible for the user to actually say they want to display all if if conflictFileCount > 0 && prevConflictFileCount == 0 {
// conflicts are currently being shown. Hmm. Worth it I reckon. If we need to add some
// extra state here to see if the user's set the filter themselves we can do that, but
// I'd prefer to maintain as little state as possible.
if conflictFileCount > 0 {
if fileTreeViewModel.GetFilter() == filetree.DisplayAll { if fileTreeViewModel.GetFilter() == filetree.DisplayAll {
fileTreeViewModel.SetStatusFilter(filetree.DisplayConflicted) fileTreeViewModel.SetStatusFilter(filetree.DisplayConflicted)
self.c.Contexts().Files.GetView().Subtitle = self.c.Tr.FilterLabelConflictingFiles
} }
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted { } else if conflictFileCount == 0 && fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
fileTreeViewModel.SetStatusFilter(filetree.DisplayAll) fileTreeViewModel.SetStatusFilter(filetree.DisplayAll)
self.c.Contexts().Files.GetView().Subtitle = ""
} }
self.c.Model().Files = files self.c.Model().Files = files

View File

@ -89,7 +89,12 @@ type TranslationSet struct {
FilterUnstagedFiles string FilterUnstagedFiles string
FilterTrackedFiles string FilterTrackedFiles string
FilterUntrackedFiles string FilterUntrackedFiles string
ResetFilter string NoFilter string
FilterLabelStagedFiles string
FilterLabelUnstagedFiles string
FilterLabelTrackedFiles string
FilterLabelUntrackedFiles string
FilterLabelConflictingFiles string
MergeConflictsTitle string MergeConflictsTitle string
Checkout string Checkout string
CheckoutTooltip string CheckoutTooltip string
@ -1115,7 +1120,12 @@ func EnglishTranslationSet() *TranslationSet {
FilterUnstagedFiles: "Show only unstaged files", FilterUnstagedFiles: "Show only unstaged files",
FilterTrackedFiles: "Show only tracked files", FilterTrackedFiles: "Show only tracked files",
FilterUntrackedFiles: "Show only untracked files", FilterUntrackedFiles: "Show only untracked files",
ResetFilter: "Reset filter", NoFilter: "No filter",
FilterLabelStagedFiles: "(only staged)",
FilterLabelUnstagedFiles: "(only unstaged)",
FilterLabelTrackedFiles: "(only tracked)",
FilterLabelUntrackedFiles: "(only untracked)",
FilterLabelConflictingFiles: "(only conflicting)",
NoChangedFiles: "No changed files", NoChangedFiles: "No changed files",
SoftReset: "Soft reset", SoftReset: "Soft reset",
AlreadyCheckedOutBranch: "You have already checked out this branch", AlreadyCheckedOutBranch: "You have already checked out this branch",

View File

@ -25,7 +25,7 @@ var Filter = NewIntegrationTest(NewIntegrationTestArgs{
Tap(func() { Tap(func() {
t.ExpectPopup().Menu(). t.ExpectPopup().Menu().
Title(Equals("Filtering")). Title(Equals("Filtering")).
Select(Contains("Reset filter")). Select(Contains("No filter")).
Confirm() Confirm()
}). }).
Lines( Lines(

View File

@ -52,7 +52,7 @@ var FilterByFileStatus = NewIntegrationTest(NewIntegrationTestArgs{
Tap(func() { Tap(func() {
t.ExpectPopup().Menu(). t.ExpectPopup().Menu().
Title(Equals("Filtering")). Title(Equals("Filtering")).
Select(Contains("Reset filter")). Select(Contains("No filter")).
Confirm() Confirm()
}). }).
Lines( Lines(