mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-21 12:16:54 +02:00
Previously there was no way to render a view's search status without also moving the cursor to the current search match. This caused issues where we wanted to display the status after leaving the view and coming back, or when beginning a new search from within the view. This commit separates the two use cases so we only move the cursor when we're actually selecting the next search match
289 lines
7.1 KiB
Go
289 lines
7.1 KiB
Go
package helpers
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jesseduffield/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
|
)
|
|
|
|
// 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()
|
|
self.OnPromptContentChanged("")
|
|
promptView.RenderTextArea()
|
|
|
|
if err := self.c.PushContext(self.c.Contexts().Search); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *SearchHelper) OpenSearchPrompt(context types.ISearchableContext) error {
|
|
state := self.searchState()
|
|
|
|
state.PrevSearchIndex = -1
|
|
|
|
state.Context = context
|
|
|
|
self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix)
|
|
promptView := self.promptView()
|
|
promptView.ClearTextArea()
|
|
promptView.RenderTextArea()
|
|
|
|
if err := self.c.PushContext(self.c.Contexts().Search); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext) {
|
|
state := self.searchState()
|
|
|
|
state.Context = context
|
|
searchString := context.GetFilter()
|
|
|
|
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
|
|
|
|
promptView := self.promptView()
|
|
keybindingConfig := self.c.UserConfig.Keybinding
|
|
promptView.SetContent(fmt.Sprintf("matches for '%s' ", searchString) + theme.OptionsFgColor.Sprintf(self.c.Tr.ExitTextFilterMode, keybindings.Label(keybindingConfig.Universal.Return)))
|
|
}
|
|
|
|
func (self *SearchHelper) DisplaySearchStatus(context types.ISearchableContext) {
|
|
state := self.searchState()
|
|
|
|
state.Context = context
|
|
|
|
self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix)
|
|
index, totalCount := context.GetView().GetSearchStatus()
|
|
context.RenderSearchStatus(index, totalCount)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
self.OnPromptContentChanged(self.promptContent())
|
|
filterString := self.promptContent()
|
|
if filterString != "" {
|
|
context.GetSearchHistory().Push(filterString)
|
|
}
|
|
|
|
return self.c.PopContext()
|
|
}
|
|
|
|
func (self *SearchHelper) ConfirmSearch() error {
|
|
state := self.searchState()
|
|
|
|
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)
|
|
if searchString != "" {
|
|
context.GetSearchHistory().Push(searchString)
|
|
}
|
|
|
|
view := context.GetView()
|
|
|
|
if err := self.c.PopContext(); err != nil {
|
|
return err
|
|
}
|
|
|
|
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) ScrollHistory(scrollIncrement int) {
|
|
state := self.searchState()
|
|
|
|
context, ok := state.Context.(types.ISearchHistoryContext)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
states := context.GetSearchHistory()
|
|
|
|
if val, err := states.PeekAt(state.PrevSearchIndex + scrollIncrement); err == nil {
|
|
state.PrevSearchIndex += scrollIncrement
|
|
promptView := self.promptView()
|
|
promptView.ClearTextArea()
|
|
promptView.TextArea.TypeString(val)
|
|
promptView.RenderTextArea()
|
|
self.OnPromptContentChanged(val)
|
|
}
|
|
}
|
|
|
|
func (self *SearchHelper) Cancel() {
|
|
state := self.searchState()
|
|
|
|
switch context := state.Context.(type) {
|
|
case types.IFilterableContext:
|
|
context.ClearFilter()
|
|
_ = self.c.PostRefreshUpdate(context)
|
|
case types.ISearchableContext:
|
|
context.ClearSearchString()
|
|
context.GetView().ClearSearch()
|
|
default:
|
|
// do nothing
|
|
}
|
|
|
|
self.HidePrompt()
|
|
}
|
|
|
|
func (self *SearchHelper) OnPromptContentChanged(searchString string) {
|
|
state := self.searchState()
|
|
switch context := state.Context.(type) {
|
|
case types.IFilterableContext:
|
|
context.SetSelectedLineIdx(0)
|
|
_ = context.GetView().SetOriginY(0)
|
|
context.SetFilter(searchString)
|
|
_ = self.c.PostRefreshUpdate(context)
|
|
case types.ISearchableContext:
|
|
// do nothing
|
|
default:
|
|
// do nothing (shouldn't land here)
|
|
}
|
|
}
|
|
|
|
func (self *SearchHelper) RenderSearchStatus(c types.Context) {
|
|
if c.GetKey() == context.SEARCH_CONTEXT_KEY {
|
|
return
|
|
}
|
|
|
|
if searchableContext, ok := c.(types.ISearchableContext); ok {
|
|
if searchableContext.IsSearching() {
|
|
self.setSearchingFrameColor()
|
|
self.DisplaySearchStatus(searchableContext)
|
|
return
|
|
}
|
|
}
|
|
if filterableContext, ok := c.(types.IFilterableContext); ok {
|
|
if filterableContext.IsFiltering() {
|
|
self.setSearchingFrameColor()
|
|
self.DisplayFilterStatus(filterableContext)
|
|
return
|
|
}
|
|
}
|
|
|
|
self.HidePrompt()
|
|
}
|
|
|
|
func (self *SearchHelper) CancelSearchIfSearching(c types.Context) {
|
|
if searchableContext, ok := c.(types.ISearchableContext); ok {
|
|
view := searchableContext.GetView()
|
|
if view != nil && view.IsSearching() {
|
|
view.ClearSearch()
|
|
searchableContext.ClearSearchString()
|
|
self.Cancel()
|
|
}
|
|
return
|
|
}
|
|
|
|
if filterableContext, ok := c.(types.IFilterableContext); ok {
|
|
if filterableContext.IsFiltering() {
|
|
filterableContext.ClearFilter()
|
|
self.Cancel()
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func (self *SearchHelper) HidePrompt() {
|
|
self.setNonSearchingFrameColor()
|
|
|
|
state := self.searchState()
|
|
state.Context = nil
|
|
}
|
|
|
|
func (self *SearchHelper) setSearchingFrameColor() {
|
|
self.c.GocuiGui().SelFgColor = theme.SearchingActiveBorderColor
|
|
self.c.GocuiGui().SelFrameColor = theme.SearchingActiveBorderColor
|
|
}
|
|
|
|
func (self *SearchHelper) setNonSearchingFrameColor() {
|
|
self.c.GocuiGui().SelFgColor = theme.ActiveBorderColor
|
|
self.c.GocuiGui().SelFrameColor = theme.ActiveBorderColor
|
|
}
|