summaryrefslogtreecommitdiffstats
path: root/pkg/gui/controllers/helpers
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-05-27 14:14:43 +1000
committerJesse Duffield <jessedduffield@gmail.com>2023-07-03 12:54:13 +1000
commita9e2c8129f6e1cdfd58446d7ce5080fcabc2ea04 (patch)
tree272c6f737052d6e06f71c6e1e9ce355410238f1a /pkg/gui/controllers/helpers
parentfd861826bc11754caf4ee4651dbadf9544792d1f (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.go4
-rw-r--r--pkg/gui/controllers/helpers/helpers.go2
-rw-r--r--pkg/gui/controllers/helpers/search_helper.go196
-rw-r--r--pkg/gui/controllers/helpers/window_arrangement_helper.go12
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",