summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-07-03 12:57:11 +1000
committerGitHub <noreply@github.com>2023-07-03 12:57:11 +1000
commit1a36cb9f3fbcf07353b8ae73e8b055d2e0e1d794 (patch)
treef486f588c72676322c341cb9aae209a02cbf9c61 /pkg
parent2be4359e87bee6c737e19b02e4c7896fbc2ebf91 (diff)
parent5d982e1d70aa9a07645a5665130f2e499b7b56c8 (diff)
View filtering (#2680)
Diffstat (limited to 'pkg')
-rw-r--r--pkg/commands/models/commit_file.go4
-rw-r--r--pkg/config/user_config.go38
-rw-r--r--pkg/gui/command_log_panel.go4
-rw-r--r--pkg/gui/context.go32
-rw-r--r--pkg/gui/context/branches_context.go13
-rw-r--r--pkg/gui/context/commit_files_context.go11
-rw-r--r--pkg/gui/context/filtered_list.go93
-rw-r--r--pkg/gui/context/filtered_list_view_model.go33
-rw-r--r--pkg/gui/context/list_view_model.go (renamed from pkg/gui/context/basic_view_model.go)14
-rw-r--r--pkg/gui/context/local_commits_context.go15
-rw-r--r--pkg/gui/context/menu_context.go13
-rw-r--r--pkg/gui/context/patch_explorer_context.go14
-rw-r--r--pkg/gui/context/reflog_commits_context.go13
-rw-r--r--pkg/gui/context/remote_branches_context.go15
-rw-r--r--pkg/gui/context/remotes_context.go13
-rw-r--r--pkg/gui/context/search_trait.go74
-rw-r--r--pkg/gui/context/stash_context.go13
-rw-r--r--pkg/gui/context/sub_commits_context.go18
-rw-r--r--pkg/gui/context/submodules_context.go13
-rw-r--r--pkg/gui/context/suggestions_context.go8
-rw-r--r--pkg/gui/context/tags_context.go13
-rw-r--r--pkg/gui/context/working_tree_context.go11
-rw-r--r--pkg/gui/controllers.go15
-rw-r--r--pkg/gui/controllers/files_controller.go4
-rw-r--r--pkg/gui/controllers/filter_controller.go48
-rw-r--r--pkg/gui/controllers/helpers/confirmation_helper.go4
-rw-r--r--pkg/gui/controllers/helpers/helpers.go2
-rw-r--r--pkg/gui/controllers/helpers/refresh_helper.go4
-rw-r--r--pkg/gui/controllers/helpers/search_helper.go260
-rw-r--r--pkg/gui/controllers/helpers/window_arrangement_helper.go12
-rw-r--r--pkg/gui/controllers/list_controller.go13
-rw-r--r--pkg/gui/controllers/local_commits_controller.go4
-rw-r--r--pkg/gui/controllers/patch_explorer_controller.go6
-rw-r--r--pkg/gui/controllers/quit_actions.go13
-rw-r--r--pkg/gui/controllers/remote_branches_controller.go9
-rw-r--r--pkg/gui/controllers/remotes_controller.go10
-rw-r--r--pkg/gui/controllers/search_controller.go48
-rw-r--r--pkg/gui/controllers/search_prompt_controller.go53
-rw-r--r--pkg/gui/controllers/switch_to_diff_files_controller.go1
-rw-r--r--pkg/gui/controllers/switch_to_sub_commits_controller.go15
-rw-r--r--pkg/gui/editors.go11
-rw-r--r--pkg/gui/filetree/file_tree.go6
-rw-r--r--pkg/gui/filetree/file_tree_view_model.go4
-rw-r--r--pkg/gui/gui.go20
-rw-r--r--pkg/gui/gui_common.go4
-rw-r--r--pkg/gui/gui_driver.go4
-rw-r--r--pkg/gui/keybindings.go12
-rw-r--r--pkg/gui/layout.go14
-rw-r--r--pkg/gui/menu_panel.go3
-rw-r--r--pkg/gui/searching.go103
-rw-r--r--pkg/gui/types/common.go5
-rw-r--r--pkg/gui/types/context.go24
-rw-r--r--pkg/gui/types/search_state.go31
-rw-r--r--pkg/gui/views.go13
-rw-r--r--pkg/i18n/dutch.go2
-rw-r--r--pkg/i18n/english.go101
-rw-r--r--pkg/i18n/japanese.go2
-rw-r--r--pkg/i18n/korean.go2
-rw-r--r--pkg/i18n/polish.go2
-rw-r--r--pkg/i18n/russian.go2
-rw-r--r--pkg/i18n/traditional_chinese.go2
-rw-r--r--pkg/integration/components/int_matcher.go12
-rw-r--r--pkg/integration/components/menu_driver.go12
-rw-r--r--pkg/integration/components/view_driver.go55
-rw-r--r--pkg/integration/tests/commit/search.go1
-rw-r--r--pkg/integration/tests/file/discard_all_dir_changes.go117
-rw-r--r--pkg/integration/tests/file/discard_unstaged_dir_changes.go56
-rw-r--r--pkg/integration/tests/file/discard_unstaged_file_changes.go41
-rw-r--r--pkg/integration/tests/filter_and_search/filter_commit_files.go84
-rw-r--r--pkg/integration/tests/filter_and_search/filter_files.go76
-rw-r--r--pkg/integration/tests/filter_and_search/filter_menu.go48
-rw-r--r--pkg/integration/tests/filter_and_search/filter_remote_branches.go59
-rw-r--r--pkg/integration/tests/filter_and_search/nested_filter.go151
-rw-r--r--pkg/integration/tests/filter_and_search/nested_filter_transient.go106
-rw-r--r--pkg/integration/tests/test_list.go10
-rw-r--r--pkg/theme/theme.go4
-rw-r--r--pkg/utils/fuzzy_search.go21
-rw-r--r--pkg/utils/fuzzy_search_test.go53
-rw-r--r--pkg/utils/search.go48
-rw-r--r--pkg/utils/search_test.go80
-rw-r--r--pkg/utils/slice.go11
81 files changed, 1975 insertions, 433 deletions
diff --git a/pkg/commands/models/commit_file.go b/pkg/commands/models/commit_file.go
index 45b56d2dd..d05b5b3fd 100644
--- a/pkg/commands/models/commit_file.go
+++ b/pkg/commands/models/commit_file.go
@@ -23,3 +23,7 @@ func (f *CommitFile) Added() bool {
func (f *CommitFile) Deleted() bool {
return f.ChangeStatus == "D"
}
+
+func (f *CommitFile) GetPath() string {
+ return f.Name
+}
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index c3e4fc07f..8faff4326 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -60,15 +60,16 @@ type GuiConfig struct {
}
type ThemeConfig struct {
- ActiveBorderColor []string `yaml:"activeBorderColor"`
- InactiveBorderColor []string `yaml:"inactiveBorderColor"`
- OptionsTextColor []string `yaml:"optionsTextColor"`
- SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
- SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
- CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
- CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
- UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
- DefaultFgColor []string `yaml:"defaultFgColor"`
+ ActiveBorderColor []string `yaml:"activeBorderColor"`
+ InactiveBorderColor []string `yaml:"inactiveBorderColor"`
+ SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
+ OptionsTextColor []string `yaml:"optionsTextColor"`
+ SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
+ SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
+ CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
+ CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
+ UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
+ DefaultFgColor []string `yaml:"defaultFgColor"`
}
type CommitLengthConfig struct {
@@ -409,15 +410,16 @@ func GetDefaultConfig() *UserConfig {
TimeFormat: "02 Jan 06",
ShortTimeFormat: time.Kitchen,
Theme: ThemeConfig{
- ActiveBorderColor: []string{"green", "bold"},
- InactiveBorderColor: []string{"default"},
- OptionsTextColor: []string{"blue"},
- SelectedLineBgColor: []string{"blue"},
- SelectedRangeBgColor: []string{"blue"},
- CherryPickedCommitBgColor: []string{"cyan"},
- CherryPickedCommitFgColor: []string{"blue"},
- UnstagedChangesColor: []string{"red"},
- DefaultFgColor: []string{"default"},
+ ActiveBorderColor: []string{"green", "bold"},
+ SearchingActiveBorderColor: []string{"cyan", "bold"},
+ InactiveBorderColor: []string{"default"},
+ OptionsTextColor: []string{"blue"},
+ SelectedLineBgColor: []string{"blue"},
+ SelectedRangeBgColor: []string{"blue"},
+ CherryPickedCommitBgColor: []string{"cyan"},
+ CherryPickedCommitFgColor: []string{"blue"},
+ UnstagedChangesColor: []string{"red"},
+ DefaultFgColor: []string{"default"},
},
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
diff --git a/pkg/gui/command_log_panel.go b/pkg/gui/command_log_panel.go
index 0a5ccfae3..2faee3572 100644
--- a/pkg/gui/command_log_panel.go
+++ b/pkg/gui/command_log_panel.go
@@ -136,10 +136,6 @@ func (gui *Gui) getRandomTip() string {
formattedKey(config.Universal.Return),
),
fmt.Sprintf(
- "To search for a string in your panel, press '%s'",
- formattedKey(config.Universal.StartSearch),
- ),
- fmt.Sprintf(
"You can page through the items of a panel using '%s' and '%s'",
formattedKey(config.Universal.PrevPage),
formattedKey(config.Universal.NextPage),
diff --git a/pkg/gui/context.go b/pkg/gui/context.go
index b55713f27..38e1212c7 100644
--- a/pkg/gui/context.go
+++ b/pkg/gui/context.go
@@ -200,9 +200,9 @@ func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
- if view != nil && view.IsSearching() {
- if err := self.gui.onSearchEscape(); err != nil {
- return err
+ if opts.NewContextKey != context.SEARCH_CONTEXT_KEY {
+ if c.GetKind() == types.MAIN_CONTEXT || c.GetKind() == types.TEMPORARY_POPUP {
+ self.gui.helpers.Search.CancelSearchIfSearching(c)
}
}
@@ -234,6 +234,8 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
return err
}
+ self.gui.helpers.Search.RenderSearchStatus(c)
+
desiredTitle := c.Title()
if desiredTitle != "" {
v.Title = desiredTitle
@@ -326,6 +328,30 @@ func (self *ContextMgr) IsCurrent(c types.Context) bool {
return self.Current().GetKey() == c.GetKey()
}
+func (self *ContextMgr) AllFilterable() []types.IFilterableContext {
+ var result []types.IFilterableContext
+
+ for _, context := range self.allContexts.Flatten() {
+ if ctx, ok := context.(types.IFilterableContext); ok {
+ result = append(result, ctx)
+ }
+ }
+
+ return result
+}
+
+func (self *ContextMgr) AllSearchable() []types.ISearchableContext {
+ var result []types.ISearchableContext
+
+ for _, context := range self.allContexts.Flatten() {
+ if ctx, ok := context.(types.ISearchableContext); ok {
+ result = append(result, ctx)
+ }
+ }
+
+ return result
+}
+
// all list contexts
func (self *ContextMgr) AllList() []types.IListContext {
var listContexts []types.IListContext
diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go
index c2463ad20..497b3a2c4 100644
--- a/pkg/gui/context/branches_context.go
+++ b/pkg/gui/context/branches_context.go
@@ -7,7 +7,7 @@ import (
)
type BranchesContext struct {
- *BasicViewModel[*models.Branch]
+ *FilteredListViewModel[*models.Branch]
*ListContextTrait
}
@@ -17,11 +17,16 @@ var (
)
func NewBranchesContext(c *ContextCommon) *BranchesContext {
- viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
+ viewModel := NewFilteredListViewModel(
+ func() []*models.Branch { return c.Model().Branches },
+ func(branch *models.Branch) []string {
+ return []string{branch.Name}
+ },
+ )
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetBranchListDisplayStrings(
- c.Model().Branches,
+ viewModel.GetItems(),
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Tr,
@@ -30,7 +35,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
}
self := &BranchesContext{
- BasicViewModel: viewModel,
+ FilteredListViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Branches,
diff --git a/pkg/gui/context/commit_files_context.go b/pkg/gui/context/commit_files_context.go
index 96b6f2fcf..5cb11d9cc 100644
--- a/pkg/gui/context/commit_files_context.go
+++ b/pkg/gui/context/commit_files_context.go
@@ -13,6 +13,7 @@ type CommitFilesContext struct {
*filetree.CommitFileTreeViewModel
*ListContextTrait
*DynamicTitleBuilder
+ *SearchTrait
}
var (
@@ -38,9 +39,10 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
})
}
- return &CommitFilesContext{
+ ctx := &CommitFilesContext{
CommitFileTreeViewModel: viewModel,
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.CommitFilesDynamicTitle),
+ SearchTrait: NewSearchTrait(c),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
@@ -57,6 +59,13 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
c: c,
},
}
+
+ ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
+ ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
+ return ctx.HandleFocus(types.OnFocusOpts{})
+ }))
+
+ return ctx
}
func (self *CommitFilesContext) GetSelectedItemId() string {
diff --git a/pkg/gui/context/filtered_list.go b/pkg/gui/context/filtered_list.go
new file mode 100644
index 000000000..298d3d615
--- /dev/null
+++ b/pkg/gui/context/filtered_list.go
@@ -0,0 +1,93 @@
+package context
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/sasha-s/go-deadlock"
+)
+
+type FilteredList[T any] struct {
+ filteredIndices []int // if nil, we are not filtering
+
+ getList func() []T
+ getFilterFields func(T) []string
+ filter string
+
+ mutex *deadlock.Mutex
+}
+
+func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string) *FilteredList[T] {
+ return &FilteredList[T]{
+ getList: getList,
+ getFilterFields: getFilterFields,
+ mutex: &deadlock.Mutex{},
+ }
+}
+
+func (self *FilteredList[T]) GetFilter() string {
+ return self.filter
+}
+
+func (self *FilteredList[T]) SetFilter(filter string) {
+ self.filter = filter
+
+ self.applyFilter()
+}
+
+func (self *FilteredList[T]) ClearFilter() {
+ self.SetFilter("")
+}
+
+func (self *FilteredList[T]) IsFiltering() bool {
+ return self.filter != ""
+}
+
+func (self *FilteredList[T]) GetFilteredList() []T {
+ if self.filteredIndices == nil {
+ return self.getList()
+ }
+ return utils.ValuesAtIndices(self.getList(), self.filteredIndices)
+}
+
+// TODO: update to just 'Len'
+func (self *FilteredList[T]) UnfilteredLen() int {
+ return len(self.getList())
+}
+
+func (self *FilteredList[T]) applyFilter() {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ if self.filter == "" {
+ self.filteredIndices = nil
+ } else {
+ self.filteredIndices = []int{}
+ for i, item := range self.getList() {
+ for _, field := range self.getFilterFields(item) {
+ if self.match(field, self.filter) {
+ self.filteredIndices = append(self.filteredIndices, i)
+ break
+ }
+ }
+ }
+ }
+}
+
+func (self *FilteredList[T]) match(haystack string, needle string) bool {
+ return utils.CaseAwareContains(haystack, needle)
+}
+
+func (self *FilteredList[T]) UnfilteredIndex(index int) int {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ if self.filteredIndices == nil {
+ return index
+ }