2023-05-27 14:14:43 +10:00
package helpers
import (
2023-06-03 13:50:26 +10:00
"fmt"
2023-05-27 14:14:43 +10:00
"github.com/jesseduffield/gocui"
2023-06-03 16:10:53 +10:00
"github.com/jesseduffield/lazygit/pkg/gui/context"
2023-06-03 13:50:26 +10:00
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
2023-05-27 14:14:43 +10:00
"github.com/jesseduffield/lazygit/pkg/gui/types"
2023-06-03 13:50:26 +10:00
"github.com/jesseduffield/lazygit/pkg/theme"
2023-05-27 14:14:43 +10:00
)
// 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
}
2023-06-03 13:15:41 +10:00
func ( self * SearchHelper ) OpenSearchPrompt ( context types . ISearchableContext ) error {
2023-05-27 14:14:43 +10:00
state := self . searchState ( )
state . Context = context
2023-06-03 13:15:41 +10:00
searchString := context . GetSearchString ( )
2023-05-27 14:14:43 +10:00
self . searchPrefixView ( ) . SetContent ( self . c . Tr . SearchPrefix )
promptView := self . promptView ( )
promptView . ClearTextArea ( )
2023-06-03 13:15:41 +10:00
promptView . TextArea . TypeString ( searchString )
2023-05-27 14:14:43 +10:00
promptView . RenderTextArea ( )
if err := self . c . PushContext ( self . c . Contexts ( ) . Search ) ; err != nil {
return err
}
return nil
}
2023-06-03 13:50:26 +10:00
func ( self * SearchHelper ) DisplayFilterStatus ( context types . IFilterableContext ) {
2023-05-27 14:14:43 +10:00
state := self . searchState ( )
state . Context = context
searchString := context . GetFilter ( )
self . searchPrefixView ( ) . SetContent ( self . c . Tr . FilterPrefix )
2023-06-03 13:50:26 +10:00
2023-05-27 14:14:43 +10:00
promptView := self . promptView ( )
2023-06-03 13:50:26 +10:00
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 ) ) )
2023-05-27 14:14:43 +10:00
}
2023-06-03 13:50:26 +10:00
func ( self * SearchHelper ) DisplaySearchStatus ( context types . ISearchableContext ) {
2023-05-27 14:14:43 +10:00
state := self . searchState ( )
state . Context = context
2023-06-03 13:50:26 +10:00
self . searchPrefixView ( ) . SetContent ( self . c . Tr . SearchPrefix )
2023-06-03 13:15:41 +10:00
_ = context . GetView ( ) . SelectCurrentSearchResult ( )
2023-05-27 14:14:43 +10:00
}
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 ( )
2023-06-03 12:12:32 +10:00
_ , ok := state . Context . ( types . IFilterableContext )
2023-05-27 14:14:43 +10:00
if ! ok {
self . c . Log . Warnf ( "Context %s is not filterable" , state . Context . GetKey ( ) )
return nil
}
2023-06-03 12:12:32 +10:00
self . OnPromptContentChanged ( self . promptContent ( ) )
2023-05-27 14:14:43 +10:00
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 )
view := context . GetView ( )
2023-05-27 20:38:49 +10:00
if err := self . c . PopContext ( ) ; err != nil {
return err
}
2023-05-27 14:14:43 +10:00
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 :
2023-05-27 20:38:49 +10:00
context . ClearFilter ( )
2023-05-27 14:14:43 +10:00
_ = self . c . PostRefreshUpdate ( context )
case types . ISearchableContext :
2023-05-27 20:38:49 +10:00
context . ClearSearchString ( )
2023-05-27 14:14:43 +10:00
context . GetView ( ) . ClearSearch ( )
default :
// do nothing
}
2023-06-03 13:15:41 +10:00
self . HidePrompt ( )
2023-05-27 14:14:43 +10:00
}
func ( self * SearchHelper ) OnPromptContentChanged ( searchString string ) {
state := self . searchState ( )
switch context := state . Context . ( type ) {
case types . IFilterableContext :
2023-06-03 12:12:32 +10:00
context . SetSelectedLineIdx ( 0 )
_ = context . GetView ( ) . SetOriginY ( 0 )
2023-05-27 14:14:43 +10:00
context . SetFilter ( searchString )
_ = self . c . PostRefreshUpdate ( context )
case types . ISearchableContext :
// do nothing
default :
// do nothing (shouldn't land here)
}
}
2023-05-28 13:25:52 +10:00
2023-06-03 16:10:53 +10:00
func ( self * SearchHelper ) RenderSearchStatus ( c types . Context ) {
if c . GetKey ( ) == context . SEARCH_CONTEXT_KEY {
return
}
2023-05-28 13:25:52 +10:00
if searchableContext , ok := c . ( types . ISearchableContext ) ; ok {
if searchableContext . IsSearching ( ) {
2023-06-03 14:11:03 +10:00
self . setSearchingFrameColor ( )
2023-06-03 13:50:26 +10:00
self . DisplaySearchStatus ( searchableContext )
2023-06-03 16:10:53 +10:00
return
2023-05-28 13:25:52 +10:00
}
}
if filterableContext , ok := c . ( types . IFilterableContext ) ; ok {
if filterableContext . IsFiltering ( ) {
2023-06-03 14:11:03 +10:00
self . setSearchingFrameColor ( )
2023-06-03 13:50:26 +10:00
self . DisplayFilterStatus ( filterableContext )
2023-06-03 16:10:53 +10:00
return
2023-05-28 13:25:52 +10:00
}
}
2023-06-03 16:10:53 +10:00
self . HidePrompt ( )
2023-05-28 13:25:52 +10:00
}
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
}
}
2023-06-03 13:15:41 +10:00
func ( self * SearchHelper ) HidePrompt ( ) {
2023-06-03 14:11:03 +10:00
self . setNonSearchingFrameColor ( )
2023-06-03 13:15:41 +10:00
state := self . searchState ( )
state . Context = nil
}
2023-06-03 14:11:03 +10:00
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
}