diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2023-05-27 14:14:43 +1000 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2023-07-03 12:54:13 +1000 |
commit | a9e2c8129f6e1cdfd58446d7ce5080fcabc2ea04 (patch) | |
tree | 272c6f737052d6e06f71c6e1e9ce355410238f1a /pkg/gui/controllers/helpers | |
parent | fd861826bc11754caf4ee4651dbadf9544792d1f (diff) |
Introduce filtered list view model
We're going to start supporting filtering of list views
Diffstat (limited to 'pkg/gui/controllers/helpers')
-rw-r--r-- | pkg/gui/controllers/helpers/confirmation_helper.go | 4 | ||||
-rw-r--r-- | pkg/gui/controllers/helpers/helpers.go | 2 | ||||
-rw-r--r-- | pkg/gui/controllers/helpers/search_helper.go | 196 | ||||
-rw-r--r-- | pkg/gui/controllers/helpers/window_arrangement_helper.go | 12 |
4 files changed, 210 insertions, 4 deletions
diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index 7968933fc..c721310b2 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -292,7 +292,9 @@ func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string) } func (self *ConfirmationHelper) resizeMenu() { - itemCount := self.c.Contexts().Menu.GetList().Len() + // we want the unfiltered length here so that if we're filtering we don't + // resize the window + itemCount := self.c.Contexts().Menu.UnfilteredLen() offset := 3 panelWidth := self.getPopupPanelWidth() x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset) diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go index faf342f0a..846638249 100644 --- a/pkg/gui/controllers/helpers/helpers.go +++ b/pkg/gui/controllers/helpers/helpers.go @@ -46,6 +46,7 @@ type Helpers struct { Mode *ModeHelper AppStatus *AppStatusHelper WindowArrangement *WindowArrangementHelper + Search *SearchHelper } func NewStubHelpers() *Helpers { @@ -78,5 +79,6 @@ func NewStubHelpers() *Helpers { Mode: &ModeHelper{}, AppStatus: &AppStatusHelper{}, WindowArrangement: &WindowArrangementHelper{}, + Search: &SearchHelper{}, } } diff --git a/pkg/gui/controllers/helpers/search_helper.go b/pkg/gui/controllers/helpers/search_helper.go new file mode 100644 index 000000000..9c0e09db4 --- /dev/null +++ b/pkg/gui/controllers/helpers/search_helper.go @@ -0,0 +1,196 @@ +package helpers + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +// NOTE: this helper supports both filtering and searching. Filtering is when +// the contents of the list are filtered, whereas searching does not actually +// change the contents of the list but instead just highlights the search. +// The general term we use to capture both searching and filtering is... +// 'searching', which is unfortunate but I can't think of a better name. + +type SearchHelper struct { + c *HelperCommon +} + +func NewSearchHelper( + c *HelperCommon, +) *SearchHelper { + return &SearchHelper{ + c: c, + } +} + +func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) error { + state := self.searchState() + + state.Context = context + + self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix) + promptView := self.promptView() + promptView.ClearTextArea() + promptView.TextArea.TypeString(context.GetFilter()) + promptView.RenderTextArea() + + if err := self.c.PushContext(self.c.Contexts().Search); err != nil { + return err + } + + return nil +} + +func (self *SearchHelper) OpenSearchPrompt(context types.Context) error { + state := self.searchState() + + state.Context = context + + self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix) + promptView := self.promptView() + // TODO: should we show the currently searched thing here? Perhaps we can store that on the context + promptView.ClearTextArea() + promptView.RenderTextArea() + + if err := self.c.PushContext(self.c.Contexts().Search); err != nil { + return err + } + + return nil +} + +func (self *SearchHelper) DisplayFilterPrompt(context types.IFilterableContext) { + state := self.searchState() + + state.Context = context + searchString := context.GetFilter() + + self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix) + promptView := self.promptView() + promptView.ClearTextArea() + promptView.TextArea.TypeString(searchString) + promptView.RenderTextArea() +} + +func (self *SearchHelper) DisplaySearchPrompt(context types.ISearchableContext) { + state := self.searchState() + + state.Context = context + searchString := context.GetSearchString() + + self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix) + promptView := self.promptView() + promptView.ClearTextArea() + promptView.TextArea.TypeString(searchString) + promptView.RenderTextArea() +} + +func (self *SearchHelper) searchState() *types.SearchState { + return self.c.State().GetRepoState().GetSearchState() +} + +func (self *SearchHelper) searchPrefixView() *gocui.View { + return self.c.Views().SearchPrefix +} + +func (self *SearchHelper) promptView() *gocui.View { + return self.c.Contexts().Search.GetView() +} + +func (self *SearchHelper) promptContent() string { + return self.c.Contexts().Search.GetView().TextArea.GetContent() +} + +func (self *SearchHelper) Confirm() error { + state := self.searchState() + if self.promptContent() == "" { + return self.CancelPrompt() + } + + switch state.SearchType() { + case types.SearchTypeFilter: + return self.ConfirmFilter() + case types.SearchTypeSearch: + return self.ConfirmSearch() + case types.SearchTypeNone: + return self.c.PopContext() + } + + return nil +} + +func (self *SearchHelper) ConfirmFilter() error { + // We also do this on each keypress but we do it here again just in case + state := self.searchState() + + context, ok := state.Context.(types.IFilterableContext) + if !ok { + self.c.Log.Warnf("Context %s is not filterable", state.Context.GetKey()) + return nil + } + + context.SetFilter(self.promptContent()) + _ = self.c.PostRefreshUpdate(state.Context) + + return self.c.PopContext() +} + +func (self *SearchHelper) ConfirmSearch() error { + state := self.searchState() + + if err := self.c.PopContext(); err != nil { + return err + } + + context, ok := state.Context.(types.ISearchableContext) + if !ok { + self.c.Log.Warnf("Context %s is searchable", state.Context.GetKey()) + return nil + } + + searchString := self.promptContent() + context.SetSearchString(searchString) + + view := context.GetView() + + if err := view.Search(searchString); err != nil { + return err + } + + return nil +} + +func (self *SearchHelper) CancelPrompt() error { + self.Cancel() + + return self.c.PopContext() +} + +func (self *SearchHelper) Cancel() { + state := self.searchState() + + switch context := state.Context.(type) { + case types.IFilterableContext: + context.SetFilter("") + _ = self.c.PostRefreshUpdate(context) + case types.ISearchableContext: + context.GetView().ClearSearch() + default: + // do nothing + } + + state.Context = nil +} + +func (self *SearchHelper) OnPromptContentChanged(searchString string) { + state := self.searchState() + switch context := state.Context.(type) { + case types.IFilterableContext: + context.SetFilter(searchString) + _ = self.c.PostRefreshUpdate(context) + case types.ISearchableContext: + // do nothing + default: + // do nothing (shouldn't land here) + } +} diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper.go b/pkg/gui/controllers/helpers/window_arrangement_helper.go index 20459993f..b45586764 100644 --- a/pkg/gui/controllers/helpers/window_arrangement_helper.go +++ b/pkg/gui/controllers/helpers/window_arrangement_helper.go @@ -55,7 +55,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, self.c.Modes().Filtering.Active() showInfoSection := self.c.UserConfig.Gui.ShowBottomLine || - self.c.State().GetRepoState().IsSearching() || + self.c.State().GetRepoState().InSearchPrompt() || self.modeHelper.IsAnyModeActive() || self.appStatusHelper.HasStatus() infoSectionSize := 0 @@ -174,11 +174,17 @@ func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) { } func (self *WindowArrangementHelper) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box { - if self.c.State().GetRepoState().IsSearching() { + if self.c.State().GetRepoState().InSearchPrompt() { + var prefix string + if self.c.State().GetRepoState().GetSearchState().SearchType() == types.SearchTypeSearch { + prefix = self.c.Tr.SearchPrefix + } else { + prefix = self.c.Tr.FilterPrefix + } return []*boxlayout.Box{ { Window: "searchPrefix", - Size: runewidth.StringWidth(self.c.Tr.SearchPrefix), + Size: runewidth.StringWidth(prefix), }, { Window: "search", |