Skip to content

Commit 0c0f53f

Browse files
authored
Allow filtering the keybindings menu by keybinding (#4821)
- **PR Description** When filtering the keybindings menu using `/`, typing `@` filters by keybinding. Closes #4739.
2 parents 389ff29 + a08799a commit 0c0f53f

File tree

13 files changed

+122
-26
lines changed

13 files changed

+122
-26
lines changed

pkg/gui/context/filtered_list.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import (
1212
type FilteredList[T any] struct {
1313
filteredIndices []int // if nil, we are not filtering
1414

15-
getList func() []T
16-
getFilterFields func(T) []string
17-
filter string
15+
getList func() []T
16+
getFilterFields func(T) []string
17+
preprocessFilter func(string) string
18+
filter string
1819

1920
mutex deadlock.Mutex
2021
}
@@ -26,6 +27,10 @@ func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string
2627
}
2728
}
2829

30+
func (self *FilteredList[T]) SetPreprocessFilterFunc(preprocessFilter func(string) string) {
31+
self.preprocessFilter = preprocessFilter
32+
}
33+
2934
func (self *FilteredList[T]) GetFilter() string {
3035
return self.filter
3136
}
@@ -79,15 +84,20 @@ func (self *FilteredList[T]) applyFilter(useFuzzySearch bool) {
7984
self.mutex.Lock()
8085
defer self.mutex.Unlock()
8186

82-
if self.filter == "" {
87+
filter := self.filter
88+
if self.preprocessFilter != nil {
89+
filter = self.preprocessFilter(filter)
90+
}
91+
92+
if filter == "" {
8393
self.filteredIndices = nil
8494
} else {
8595
source := &fuzzySource[T]{
8696
list: self.getList(),
8797
getFilterFields: self.getFilterFields,
8898
}
8999

90-
matches := utils.FindFrom(self.filter, source, useFuzzySearch)
100+
matches := utils.FindFrom(filter, source, useFuzzySearch)
91101
self.filteredIndices = lo.Map(matches, func(match fuzzy.Match, _ int) int {
92102
return match.Index
93103
})

pkg/gui/context/filtered_list_view_model.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package context
22

3+
import "github.com/jesseduffield/lazygit/pkg/i18n"
4+
35
type FilteredListViewModel[T HasID] struct {
46
*FilteredList[T]
57
*ListViewModel[T]
@@ -33,3 +35,8 @@ func (self *FilteredListViewModel[T]) ClearFilter() {
3335

3436
self.SetSelection(unfilteredIndex)
3537
}
38+
39+
// Default implementation of most filterable contexts. Can be overridden if needed.
40+
func (self *FilteredListViewModel[T]) FilterPrefix(tr *i18n.TranslationSet) string {
41+
return tr.FilterPrefix
42+
}

pkg/gui/context/menu_context.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package context
22

33
import (
44
"errors"
5+
"strings"
56

67
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
78
"github.com/jesseduffield/lazygit/pkg/gui/style"
89
"github.com/jesseduffield/lazygit/pkg/gui/types"
10+
"github.com/jesseduffield/lazygit/pkg/i18n"
911
"github.com/jesseduffield/lazygit/pkg/utils"
1012
"github.com/samber/lo"
1113
)
@@ -48,11 +50,12 @@ func NewMenuContext(
4850
}
4951

5052
type MenuViewModel struct {
51-
c *ContextCommon
52-
menuItems []*types.MenuItem
53-
prompt string
54-
promptLines []string
55-
columnAlignment []utils.Alignment
53+
c *ContextCommon
54+
menuItems []*types.MenuItem
55+
prompt string
56+
promptLines []string
57+
columnAlignment []utils.Alignment
58+
allowFilteringKeybindings bool
5659
*FilteredListViewModel[*types.MenuItem]
5760
}
5861

@@ -62,11 +65,29 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
6265
c: c,
6366
}
6467

68+
filterKeybindings := false
69+
6570
self.FilteredListViewModel = NewFilteredListViewModel(
6671
func() []*types.MenuItem { return self.menuItems },
67-
func(item *types.MenuItem) []string { return item.LabelColumns },
72+
func(item *types.MenuItem) []string {
73+
if filterKeybindings {
74+
return []string{keybindings.LabelFromKey(item.Key)}
75+
}
76+
77+
return item.LabelColumns
78+
},
6879
)
6980

81+
self.FilteredListViewModel.SetPreprocessFilterFunc(func(filter string) string {
82+
if self.allowFilteringKeybindings && strings.HasPrefix(filter, "@") {
83+
filterKeybindings = true
84+
return filter[1:]
85+
}
86+
87+
filterKeybindings = false
88+
return filter
89+
})
90+
7091
return self
7192
}
7293

@@ -92,6 +113,10 @@ func (self *MenuViewModel) SetPromptLines(promptLines []string) {
92113
self.promptLines = promptLines
93114
}
94115

116+
func (self *MenuViewModel) SetAllowFilteringKeybindings(allow bool) {
117+
self.allowFilteringKeybindings = allow
118+
}
119+
95120
// TODO: move into presentation package
96121
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
97122
menuItems := self.FilteredListViewModel.GetItems()
@@ -214,3 +239,11 @@ func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
214239
func (self *MenuContext) RangeSelectEnabled() bool {
215240
return false
216241
}
242+
243+
func (self *MenuContext) FilterPrefix(tr *i18n.TranslationSet) string {
244+
if self.allowFilteringKeybindings {
245+
return tr.FilterPrefixMenu
246+
}
247+
248+
return self.FilteredListViewModel.FilterPrefix(tr)
249+
}

pkg/gui/controllers/helpers/search_helper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) err
3535

3636
state.Context = context
3737

38-
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
38+
self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr))
3939
promptView := self.promptView()
4040
promptView.ClearTextArea()
4141
self.OnPromptContentChanged("")
@@ -69,7 +69,7 @@ func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext)
6969
state.Context = context
7070
searchString := context.GetFilter()
7171

72-
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
72+
self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr))
7373

7474
promptView := self.promptView()
7575
keybindingConfig := self.c.UserConfig().Keybinding

pkg/gui/controllers/helpers/window_arrangement_helper.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
7979
repoState := self.c.State().GetRepoState()
8080

8181
var searchPrefix string
82-
if repoState.GetSearchState().SearchType() == types.SearchTypeSearch {
83-
searchPrefix = self.c.Tr.SearchPrefix
82+
if filterableContext, ok := repoState.GetSearchState().Context.(types.IFilterableContext); ok {
83+
searchPrefix = filterableContext.FilterPrefix(self.c.Tr)
8484
} else {
85-
searchPrefix = self.c.Tr.FilterPrefix
85+
searchPrefix = self.c.Tr.SearchPrefix
8686
}
8787

8888
args := WindowArrangementArgs{

pkg/gui/controllers/options_menu_action.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ func (self *OptionsMenuAction) Call() error {
4646
appendBindings(navigation, &types.MenuSection{Title: self.c.Tr.KeybindingsMenuSectionNavigation, Column: 1})
4747

4848
return self.c.Menu(types.CreateMenuOptions{
49-
Title: self.c.Tr.Keybindings,
50-
Items: menuItems,
51-
HideCancel: true,
52-
ColumnAlignment: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
49+
Title: self.c.Tr.Keybindings,
50+
Items: menuItems,
51+
HideCancel: true,
52+
ColumnAlignment: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
53+
AllowFilteringKeybindings: true,
5354
})
5455
}
5556

pkg/gui/menu_panel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
4343

4444
gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment)
4545
gui.State.Contexts.Menu.SetPrompt(opts.Prompt)
46+
gui.State.Contexts.Menu.SetAllowFilteringKeybindings(opts.AllowFilteringKeybindings)
4647
gui.State.Contexts.Menu.SetSelection(0)
4748

4849
gui.Views.Menu.Title = opts.Title

pkg/gui/types/common.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,12 @@ const (
147147
)
148148

149149
type CreateMenuOptions struct {
150-
Title string
151-
Prompt string // a message that will be displayed above the menu options
152-
Items []*MenuItem
153-
HideCancel bool
154-
ColumnAlignment []utils.Alignment
150+
Title string
151+
Prompt string // a message that will be displayed above the menu options
152+
Items []*MenuItem
153+
HideCancel bool
154+
ColumnAlignment []utils.Alignment
155+
AllowFilteringKeybindings bool
155156
}
156157

157158
type CreatePopupPanelOpts struct {

pkg/gui/types/context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/jesseduffield/gocui"
55
"github.com/jesseduffield/lazygit/pkg/config"
66
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
7+
"github.com/jesseduffield/lazygit/pkg/i18n"
78
"github.com/jesseduffield/lazygit/pkg/utils"
89
"github.com/sasha-s/go-deadlock"
910
)
@@ -130,6 +131,7 @@ type IFilterableContext interface {
130131
ReApplyFilter(bool)
131132
IsFiltering() bool
132133
IsFilterableContext()
134+
FilterPrefix(tr *i18n.TranslationSet) string
133135
}
134136

135137
type ISearchableContext interface {

pkg/i18n/english.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ type TranslationSet struct {
816816
SearchKeybindings string
817817
SearchPrefix string
818818
FilterPrefix string
819+
FilterPrefixMenu string
819820
ExitSearchMode string
820821
ExitTextFilterMode string
821822
Switch string
@@ -1881,6 +1882,7 @@ func EnglishTranslationSet() *TranslationSet {
18811882
SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
18821883
SearchPrefix: "Search: ",
18831884
FilterPrefix: "Filter: ",
1885+
FilterPrefixMenu: "Filter (prepend '@' to filter keybindings): ",
18841886
WorktreesTitle: "Worktrees",
18851887
WorktreeTitle: "Worktree",
18861888
Switch: "Switch",

0 commit comments

Comments
 (0)