mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-08-24 19:39:16 +02:00
Allow filtering the keybindings menu by keybinding (#4821)
- **PR Description** When filtering the keybindings menu using `/`, typing `@` filters by keybinding. Closes #4739.
This commit is contained in:
@@ -14,6 +14,7 @@ type FilteredList[T any] struct {
|
|||||||
|
|
||||||
getList func() []T
|
getList func() []T
|
||||||
getFilterFields func(T) []string
|
getFilterFields func(T) []string
|
||||||
|
preprocessFilter func(string) string
|
||||||
filter string
|
filter string
|
||||||
|
|
||||||
mutex deadlock.Mutex
|
mutex deadlock.Mutex
|
||||||
@@ -26,6 +27,10 @@ func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *FilteredList[T]) SetPreprocessFilterFunc(preprocessFilter func(string) string) {
|
||||||
|
self.preprocessFilter = preprocessFilter
|
||||||
|
}
|
||||||
|
|
||||||
func (self *FilteredList[T]) GetFilter() string {
|
func (self *FilteredList[T]) GetFilter() string {
|
||||||
return self.filter
|
return self.filter
|
||||||
}
|
}
|
||||||
@@ -79,7 +84,12 @@ func (self *FilteredList[T]) applyFilter(useFuzzySearch bool) {
|
|||||||
self.mutex.Lock()
|
self.mutex.Lock()
|
||||||
defer self.mutex.Unlock()
|
defer self.mutex.Unlock()
|
||||||
|
|
||||||
if self.filter == "" {
|
filter := self.filter
|
||||||
|
if self.preprocessFilter != nil {
|
||||||
|
filter = self.preprocessFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter == "" {
|
||||||
self.filteredIndices = nil
|
self.filteredIndices = nil
|
||||||
} else {
|
} else {
|
||||||
source := &fuzzySource[T]{
|
source := &fuzzySource[T]{
|
||||||
@@ -87,7 +97,7 @@ func (self *FilteredList[T]) applyFilter(useFuzzySearch bool) {
|
|||||||
getFilterFields: self.getFilterFields,
|
getFilterFields: self.getFilterFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := utils.FindFrom(self.filter, source, useFuzzySearch)
|
matches := utils.FindFrom(filter, source, useFuzzySearch)
|
||||||
self.filteredIndices = lo.Map(matches, func(match fuzzy.Match, _ int) int {
|
self.filteredIndices = lo.Map(matches, func(match fuzzy.Match, _ int) int {
|
||||||
return match.Index
|
return match.Index
|
||||||
})
|
})
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
|
import "github.com/jesseduffield/lazygit/pkg/i18n"
|
||||||
|
|
||||||
type FilteredListViewModel[T HasID] struct {
|
type FilteredListViewModel[T HasID] struct {
|
||||||
*FilteredList[T]
|
*FilteredList[T]
|
||||||
*ListViewModel[T]
|
*ListViewModel[T]
|
||||||
@@ -33,3 +35,8 @@ func (self *FilteredListViewModel[T]) ClearFilter() {
|
|||||||
|
|
||||||
self.SetSelection(unfilteredIndex)
|
self.SetSelection(unfilteredIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default implementation of most filterable contexts. Can be overridden if needed.
|
||||||
|
func (self *FilteredListViewModel[T]) FilterPrefix(tr *i18n.TranslationSet) string {
|
||||||
|
return tr.FilterPrefix
|
||||||
|
}
|
||||||
|
@@ -2,10 +2,12 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
@@ -53,6 +55,7 @@ type MenuViewModel struct {
|
|||||||
prompt string
|
prompt string
|
||||||
promptLines []string
|
promptLines []string
|
||||||
columnAlignment []utils.Alignment
|
columnAlignment []utils.Alignment
|
||||||
|
allowFilteringKeybindings bool
|
||||||
*FilteredListViewModel[*types.MenuItem]
|
*FilteredListViewModel[*types.MenuItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +65,29 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
|||||||
c: c,
|
c: c,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterKeybindings := false
|
||||||
|
|
||||||
self.FilteredListViewModel = NewFilteredListViewModel(
|
self.FilteredListViewModel = NewFilteredListViewModel(
|
||||||
func() []*types.MenuItem { return self.menuItems },
|
func() []*types.MenuItem { return self.menuItems },
|
||||||
func(item *types.MenuItem) []string { return item.LabelColumns },
|
func(item *types.MenuItem) []string {
|
||||||
|
if filterKeybindings {
|
||||||
|
return []string{keybindings.LabelFromKey(item.Key)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.LabelColumns
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.FilteredListViewModel.SetPreprocessFilterFunc(func(filter string) string {
|
||||||
|
if self.allowFilteringKeybindings && strings.HasPrefix(filter, "@") {
|
||||||
|
filterKeybindings = true
|
||||||
|
return filter[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
filterKeybindings = false
|
||||||
|
return filter
|
||||||
|
})
|
||||||
|
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +113,10 @@ func (self *MenuViewModel) SetPromptLines(promptLines []string) {
|
|||||||
self.promptLines = promptLines
|
self.promptLines = promptLines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *MenuViewModel) SetAllowFilteringKeybindings(allow bool) {
|
||||||
|
self.allowFilteringKeybindings = allow
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: move into presentation package
|
// TODO: move into presentation package
|
||||||
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
|
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
|
||||||
menuItems := self.FilteredListViewModel.GetItems()
|
menuItems := self.FilteredListViewModel.GetItems()
|
||||||
@@ -214,3 +239,11 @@ func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
|
|||||||
func (self *MenuContext) RangeSelectEnabled() bool {
|
func (self *MenuContext) RangeSelectEnabled() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *MenuContext) FilterPrefix(tr *i18n.TranslationSet) string {
|
||||||
|
if self.allowFilteringKeybindings {
|
||||||
|
return tr.FilterPrefixMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.FilteredListViewModel.FilterPrefix(tr)
|
||||||
|
}
|
||||||
|
@@ -35,7 +35,7 @@ func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) err
|
|||||||
|
|
||||||
state.Context = context
|
state.Context = context
|
||||||
|
|
||||||
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
|
self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr))
|
||||||
promptView := self.promptView()
|
promptView := self.promptView()
|
||||||
promptView.ClearTextArea()
|
promptView.ClearTextArea()
|
||||||
self.OnPromptContentChanged("")
|
self.OnPromptContentChanged("")
|
||||||
@@ -69,7 +69,7 @@ func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext)
|
|||||||
state.Context = context
|
state.Context = context
|
||||||
searchString := context.GetFilter()
|
searchString := context.GetFilter()
|
||||||
|
|
||||||
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
|
self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr))
|
||||||
|
|
||||||
promptView := self.promptView()
|
promptView := self.promptView()
|
||||||
keybindingConfig := self.c.UserConfig().Keybinding
|
keybindingConfig := self.c.UserConfig().Keybinding
|
||||||
|
@@ -79,10 +79,10 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
|||||||
repoState := self.c.State().GetRepoState()
|
repoState := self.c.State().GetRepoState()
|
||||||
|
|
||||||
var searchPrefix string
|
var searchPrefix string
|
||||||
if repoState.GetSearchState().SearchType() == types.SearchTypeSearch {
|
if filterableContext, ok := repoState.GetSearchState().Context.(types.IFilterableContext); ok {
|
||||||
searchPrefix = self.c.Tr.SearchPrefix
|
searchPrefix = filterableContext.FilterPrefix(self.c.Tr)
|
||||||
} else {
|
} else {
|
||||||
searchPrefix = self.c.Tr.FilterPrefix
|
searchPrefix = self.c.Tr.SearchPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
args := WindowArrangementArgs{
|
args := WindowArrangementArgs{
|
||||||
|
@@ -50,6 +50,7 @@ func (self *OptionsMenuAction) Call() error {
|
|||||||
Items: menuItems,
|
Items: menuItems,
|
||||||
HideCancel: true,
|
HideCancel: true,
|
||||||
ColumnAlignment: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
|
ColumnAlignment: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
|
||||||
|
AllowFilteringKeybindings: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,6 +43,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
|
|||||||
|
|
||||||
gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment)
|
gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment)
|
||||||
gui.State.Contexts.Menu.SetPrompt(opts.Prompt)
|
gui.State.Contexts.Menu.SetPrompt(opts.Prompt)
|
||||||
|
gui.State.Contexts.Menu.SetAllowFilteringKeybindings(opts.AllowFilteringKeybindings)
|
||||||
gui.State.Contexts.Menu.SetSelection(0)
|
gui.State.Contexts.Menu.SetSelection(0)
|
||||||
|
|
||||||
gui.Views.Menu.Title = opts.Title
|
gui.Views.Menu.Title = opts.Title
|
||||||
|
@@ -152,6 +152,7 @@ type CreateMenuOptions struct {
|
|||||||
Items []*MenuItem
|
Items []*MenuItem
|
||||||
HideCancel bool
|
HideCancel bool
|
||||||
ColumnAlignment []utils.Alignment
|
ColumnAlignment []utils.Alignment
|
||||||
|
AllowFilteringKeybindings bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatePopupPanelOpts struct {
|
type CreatePopupPanelOpts struct {
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
"github.com/sasha-s/go-deadlock"
|
"github.com/sasha-s/go-deadlock"
|
||||||
)
|
)
|
||||||
@@ -130,6 +131,7 @@ type IFilterableContext interface {
|
|||||||
ReApplyFilter(bool)
|
ReApplyFilter(bool)
|
||||||
IsFiltering() bool
|
IsFiltering() bool
|
||||||
IsFilterableContext()
|
IsFilterableContext()
|
||||||
|
FilterPrefix(tr *i18n.TranslationSet) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ISearchableContext interface {
|
type ISearchableContext interface {
|
||||||
|
@@ -816,6 +816,7 @@ type TranslationSet struct {
|
|||||||
SearchKeybindings string
|
SearchKeybindings string
|
||||||
SearchPrefix string
|
SearchPrefix string
|
||||||
FilterPrefix string
|
FilterPrefix string
|
||||||
|
FilterPrefixMenu string
|
||||||
ExitSearchMode string
|
ExitSearchMode string
|
||||||
ExitTextFilterMode string
|
ExitTextFilterMode string
|
||||||
Switch string
|
Switch string
|
||||||
@@ -1881,6 +1882,7 @@ func EnglishTranslationSet() *TranslationSet {
|
|||||||
SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
|
SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
|
||||||
SearchPrefix: "Search: ",
|
SearchPrefix: "Search: ",
|
||||||
FilterPrefix: "Filter: ",
|
FilterPrefix: "Filter: ",
|
||||||
|
FilterPrefixMenu: "Filter (prepend '@' to filter keybindings): ",
|
||||||
WorktreesTitle: "Worktrees",
|
WorktreesTitle: "Worktrees",
|
||||||
WorktreeTitle: "Worktree",
|
WorktreeTitle: "Worktree",
|
||||||
Switch: "Switch",
|
Switch: "Switch",
|
||||||
|
@@ -48,7 +48,7 @@ var FilterMenu = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().Menu().
|
t.ExpectPopup().Menu().
|
||||||
Title(Equals("Keybindings")).
|
Title(Equals("Keybindings")).
|
||||||
LineCount(GreaterThan(1))
|
LineCount(GreaterThan(2))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
package filter_and_search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FilterMenuByKeybinding = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Filtering the keybindings menu by keybinding",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Files().
|
||||||
|
Press(keys.Universal.OptionMenu).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().Menu().
|
||||||
|
Title(Equals("Keybindings")).
|
||||||
|
Filter("@+").
|
||||||
|
Lines(
|
||||||
|
// menu has filtered down to the one item that matches the filter
|
||||||
|
Contains("--- Global ---"),
|
||||||
|
Contains("+ Next screen mode").IsSelected(),
|
||||||
|
).
|
||||||
|
Confirm()
|
||||||
|
}).
|
||||||
|
|
||||||
|
// Upon opening the menu again, the filter should have been reset
|
||||||
|
Press(keys.Universal.OptionMenu).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().Menu().
|
||||||
|
Title(Equals("Keybindings")).
|
||||||
|
LineCount(GreaterThan(1))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
@@ -220,6 +220,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
filter_and_search.FilterFiles,
|
filter_and_search.FilterFiles,
|
||||||
filter_and_search.FilterFuzzy,
|
filter_and_search.FilterFuzzy,
|
||||||
filter_and_search.FilterMenu,
|
filter_and_search.FilterMenu,
|
||||||
|
filter_and_search.FilterMenuByKeybinding,
|
||||||
filter_and_search.FilterMenuCancelFilterWithEscape,
|
filter_and_search.FilterMenuCancelFilterWithEscape,
|
||||||
filter_and_search.FilterMenuWithNoKeybindings,
|
filter_and_search.FilterMenuWithNoKeybindings,
|
||||||
filter_and_search.FilterRemoteBranches,
|
filter_and_search.FilterRemoteBranches,
|
||||||
|
Reference in New Issue
Block a user