From 3d18ee8f91c7b33a66a5fc964e46837e13a7c957 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 3 May 2026 22:44:53 +0200 Subject: [PATCH] Use a slice of keys for each binding This is a pure refactor in preparation for letting users configure multiple alternate bindings for a single command. Every Binding still has exactly one key, so nothing changes visibly: the cheatsheet, the on-screen options bar, and the keybindings menu all render identically. When a Binding ends up with multiple keys, the on-screen options bar will show only the first (to avoid clutter); the cheatsheet will show all of them (in a later commit). For now both paths take Key[0]. MenuItem.Key is changed in the same way, it also has a slice of keys now. In this commit we keep the name `Key` in Binding, KeybindingOpts and MenuItem, instead of renaming them to `Keys` right away, in order to keep the diff a bit more readable. We'll do the rename separately in the next commit. --- pkg/cheatsheet/generate.go | 6 +- pkg/cheatsheet/generate_test.go | 58 +++++++++---------- pkg/config/keynames.go | 8 +++ pkg/gui/context/menu_context.go | 13 +++-- pkg/gui/controllers/helpers/menu_key.go | 10 ++-- .../helpers/merge_and_rebase_helper.go | 2 +- pkg/gui/controllers/helpers/refs_helper.go | 6 +- pkg/gui/controllers/menu_key.go | 10 ++-- pkg/gui/controllers/prompt_controller.go | 2 +- .../controllers/search_prompt_controller.go | 2 +- pkg/gui/extras_panel.go | 4 +- pkg/gui/keybindings.go | 26 +++++---- pkg/gui/menu_panel.go | 6 +- pkg/gui/options_map.go | 6 +- pkg/gui/services/custom_commands/client.go | 7 ++- .../custom_commands/handler_creator.go | 2 +- .../custom_commands/keybinding_creator.go | 3 +- pkg/gui/types/common.go | 7 ++- pkg/gui/types/context.go | 2 +- pkg/gui/types/keybindings.go | 2 +- 20 files changed, 101 insertions(+), 81 deletions(-) diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go index 3b5c490b8..22bc0c505 100644 --- a/pkg/cheatsheet/generate.go +++ b/pkg/cheatsheet/generate.go @@ -145,7 +145,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b return false } - return (binding.Description != "" || binding.Alternative != "") && binding.Key.IsSet() + return (binding.Description != "" || binding.Alternative != "") && len(binding.Key) > 0 }) bindingsByHeader := lo.GroupBy(bindingsToDisplay, func(binding *types.Binding) header { @@ -156,7 +156,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b bindingsByHeader, func(header header, hBindings []*types.Binding) headerWithBindings { uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string { - return binding.Description + config.LabelForKey(binding.Key) + return binding.Description + config.LabelForKey(binding.Key[0]) }) return headerWithBindings{ @@ -214,7 +214,7 @@ func formatTitle(title string) string { } func formatBinding(binding *types.Binding) string { - action := config.LabelForKey(binding.Key) + action := config.LabelForKey(binding.Key[0]) description := binding.Description if binding.Alternative != "" { action += fmt.Sprintf(" (%s)", binding.Alternative) diff --git a/pkg/cheatsheet/generate_test.go b/pkg/cheatsheet/generate_test.go index bae2ec498..3ff066244 100644 --- a/pkg/cheatsheet/generate_test.go +++ b/pkg/cheatsheet/generate_test.go @@ -28,7 +28,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, expected: []*bindingSection{ @@ -38,7 +38,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, @@ -50,7 +50,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "", Description: "quit", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, expected: []*bindingSection{ @@ -60,7 +60,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "", Description: "quit", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, @@ -72,17 +72,17 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "unstage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "submodules", Description: "drop submodule", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, expected: []*bindingSection{ @@ -92,12 +92,12 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "unstage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, @@ -107,7 +107,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "submodules", Description: "drop submodule", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, @@ -119,23 +119,23 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "unstage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "scroll", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, Tag: "navigation", }, { ViewName: "commits", Description: "revert commit", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, expected: []*bindingSection{ @@ -145,7 +145,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "scroll", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, Tag: "navigation", }, }, @@ -156,7 +156,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "commits", Description: "revert commit", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, @@ -166,12 +166,12 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "unstage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, @@ -183,34 +183,34 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "unstage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "scroll", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, Tag: "navigation", }, { ViewName: "commits", Description: "revert commit", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "commits", Description: "scroll", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, Tag: "navigation", }, { ViewName: "commits", Description: "page up", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, Tag: "navigation", }, }, @@ -221,13 +221,13 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "scroll", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, Tag: "navigation", }, { ViewName: "commits", Description: "page up", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, Tag: "navigation", }, }, @@ -238,7 +238,7 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "commits", Description: "revert commit", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, @@ -248,12 +248,12 @@ func TestGetBindingSections(t *testing.T) { { ViewName: "files", Description: "stage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, { ViewName: "files", Description: "unstage file", - Key: gocui.NewKeyRune('a'), + Key: []gocui.Key{gocui.NewKeyRune('a')}, }, }, }, diff --git a/pkg/config/keynames.go b/pkg/config/keynames.go index 943a2f37a..7f361dec8 100644 --- a/pkg/config/keynames.go +++ b/pkg/config/keynames.go @@ -205,3 +205,11 @@ func GetValidatedKeyBindingKey(label string) gocui.Key { return key } + +func GetValidatedKeyBindingKeys(label string) []gocui.Key { + k := GetValidatedKeyBindingKey(label) + if !k.IsSet() { + return nil + } + return []gocui.Key{k} +} diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go index e9fece612..03b8d51c4 100644 --- a/pkg/gui/context/menu_context.go +++ b/pkg/gui/context/menu_context.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/i18n" @@ -73,7 +74,11 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel { func() []*types.MenuItem { return self.menuItems }, func(item *types.MenuItem) []string { if filterKeybindings { - return []string{config.LabelForKey(item.Key)} + // Allow searching all configured keybindings of each item, even though only the + // first one is shown in the menu. + return lo.Map(item.Key, func(k gocui.Key, _ int) string { + return config.LabelForKey(k) + }) } return item.LabelColumns @@ -138,8 +143,8 @@ func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string { } keyLabel := "" - if item.Key.IsSet() { - keyLabel = style.FgCyan.Sprint(config.LabelForKey(item.Key)) + if len(item.Key) > 0 { + keyLabel = style.FgCyan.Sprint(config.LabelForKey(item.Key[0])) } checkMark := "" @@ -205,7 +210,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem { func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { basicBindings := self.ListContextTrait.GetKeybindings(opts) menuItemsWithKeys := lo.Filter(self.menuItems, func(item *types.MenuItem, _ int) bool { - return item.Key.IsSet() + return len(item.Key) > 0 }) menuItemBindings := lo.Map(menuItemsWithKeys, func(item *types.MenuItem, _ int) *types.Binding { diff --git a/pkg/gui/controllers/helpers/menu_key.go b/pkg/gui/controllers/helpers/menu_key.go index 7b43a66fc..d4271fa5b 100644 --- a/pkg/gui/controllers/helpers/menu_key.go +++ b/pkg/gui/controllers/helpers/menu_key.go @@ -3,9 +3,9 @@ package helpers import "github.com/jesseduffield/lazygit/pkg/gocui" // menuKey is a shorthand for constructing a key value for a menu item from a single rune literal, -// avoiding the noise of `gocui.NewKeyRune('a')` at every call site. There is an intentionally -// identical helper in the controllers package so that callers in either package can use the -// unqualified form. -func menuKey(r rune) gocui.Key { - return gocui.NewKeyRune(r) +// avoiding the noise of `[]gocui.Key{gocui.NewKeyRune('a')}` at every call site. There is an +// intentionally identical helper in the controllers package so that callers in either package can +// use the unqualified form. +func menuKey(r rune) []gocui.Key { + return []gocui.Key{gocui.NewKeyRune(r)} } diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index ae042b8cb..48c342446 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -39,7 +39,7 @@ const ( func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error { type optionAndKey struct { option string - key gocui.Key + key []gocui.Key } options := []optionAndKey{ diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go index 6771bf301..ae560941f 100644 --- a/pkg/gui/controllers/helpers/refs_helper.go +++ b/pkg/gui/controllers/helpers/refs_helper.go @@ -216,7 +216,7 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, menuPrompt string, onSelected func(sortOrder string) error, currentValue string) error { type sortMenuOption struct { - key gocui.Key + key []gocui.Key label string description string sortOrder string @@ -260,7 +260,7 @@ func (self *RefsHelper) CreateGitResetMenu(name string, ref string) error { type strengthWithKey struct { strength string label string - key gocui.Key + key []gocui.Key tooltip string } strengths := []strengthWithKey{ @@ -318,7 +318,7 @@ func (self *RefsHelper) CreateCheckoutMenu(commit *models.Commit) error { if len(branches) > 0 { menuItems = append(menuItems, lo.Map(branches, func(branch *models.Branch, index int) *types.MenuItem { - var key gocui.Key + var key []gocui.Key if index < 9 { key = menuKey(rune(index + 1 + '0')) // Convert 1-based index to key } diff --git a/pkg/gui/controllers/menu_key.go b/pkg/gui/controllers/menu_key.go index afafe1c0b..95993801b 100644 --- a/pkg/gui/controllers/menu_key.go +++ b/pkg/gui/controllers/menu_key.go @@ -3,9 +3,9 @@ package controllers import "github.com/jesseduffield/lazygit/pkg/gocui" // menuKey is a shorthand for constructing a key value for a menu item from a single rune literal, -// avoiding the noise of `gocui.NewKeyRune('a')` at every call site. There is an intentionally -// identical helper in the helpers package so that callers in either package can use the unqualified -// form. -func menuKey(r rune) gocui.Key { - return gocui.NewKeyRune(r) +// avoiding the noise of `[]gocui.Key{gocui.NewKeyRune('a')}` at every call site. There is an +// intentionally identical helper in the helpers package so that callers in either package can use +// the unqualified form. +func menuKey(r rune) []gocui.Key { + return []gocui.Key{gocui.NewKeyRune(r)} } diff --git a/pkg/gui/controllers/prompt_controller.go b/pkg/gui/controllers/prompt_controller.go index 1ff40a0ef..9a1fd63c9 100644 --- a/pkg/gui/controllers/prompt_controller.go +++ b/pkg/gui/controllers/prompt_controller.go @@ -27,7 +27,7 @@ func NewPromptController( func (self *PromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { bindings := []*types.Binding{ { - Key: gocui.NewKeyName(gocui.KeyEnter), + Key: []gocui.Key{gocui.NewKeyName(gocui.KeyEnter)}, Handler: func() error { return self.context().State.OnConfirm() }, Description: self.c.Tr.Confirm, DisplayOnScreen: true, diff --git a/pkg/gui/controllers/search_prompt_controller.go b/pkg/gui/controllers/search_prompt_controller.go index d5c2f5c3c..b3b9918e6 100644 --- a/pkg/gui/controllers/search_prompt_controller.go +++ b/pkg/gui/controllers/search_prompt_controller.go @@ -24,7 +24,7 @@ func NewSearchPromptController( func (self *SearchPromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { return []*types.Binding{ { - Key: gocui.NewKeyName(gocui.KeyEnter), + Key: []gocui.Key{gocui.NewKeyName(gocui.KeyEnter)}, Handler: self.confirm, }, { diff --git a/pkg/gui/extras_panel.go b/pkg/gui/extras_panel.go index 03e82345a..468d8f290 100644 --- a/pkg/gui/extras_panel.go +++ b/pkg/gui/extras_panel.go @@ -15,7 +15,7 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error { Items: []*types.MenuItem{ { Label: gui.c.Tr.ToggleShowCommandLog, - Key: gocui.NewKeyRune('t'), + Key: []gocui.Key{gocui.NewKeyRune('t')}, OnPress: func() error { currentContext := gui.c.Context().CurrentStatic() if gui.c.State().GetShowExtrasWindow() && currentContext.GetKey() == context.COMMAND_LOG_CONTEXT_KEY { @@ -30,7 +30,7 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error { }, { Label: gui.c.Tr.FocusCommandLog, - Key: gocui.NewKeyRune('f'), + Key: []gocui.Key{gocui.NewKeyRune('f')}, OnPress: gui.handleFocusCommandLog, }, }, diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 11a3fbc6a..038ae5d49 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -69,7 +69,7 @@ func (gui *Gui) keybindingOpts() types.KeybindingsOpts { } return types.KeybindingsOpts{ - GetKey: config.GetValidatedKeyBindingKey, + GetKey: config.GetValidatedKeyBindingKeys, Config: keybindingConfig, Guards: guards, } @@ -176,7 +176,7 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin }, { ViewName: "information", - Key: gocui.NewKeyName(gocui.MouseLeft), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseLeft)}, Handler: gui.handleInfoClick, }, { @@ -196,26 +196,26 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin }, { ViewName: "main", - Key: gocui.NewKeyName(gocui.MouseWheelDown), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelDown)}, Handler: gui.scrollDownMain, Description: gui.c.Tr.ScrollDown, Alternative: "fn+up", }, { ViewName: "main", - Key: gocui.NewKeyName(gocui.MouseWheelUp), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelUp)}, Handler: gui.scrollUpMain, Description: gui.c.Tr.ScrollUp, Alternative: "fn+down", }, { ViewName: "secondary", - Key: gocui.NewKeyName(gocui.MouseWheelDown), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelDown)}, Handler: gui.scrollDownSecondary, }, { ViewName: "secondary", - Key: gocui.NewKeyName(gocui.MouseWheelUp), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelUp)}, Handler: gui.scrollUpSecondary, }, { @@ -240,12 +240,12 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin }, { ViewName: "confirmation", - Key: gocui.NewKeyName(gocui.MouseWheelUp), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelUp)}, Handler: gui.scrollUpConfirmationPanel, }, { ViewName: "confirmation", - Key: gocui.NewKeyName(gocui.MouseWheelDown), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelDown)}, Handler: gui.scrollDownConfirmationPanel, }, { @@ -287,12 +287,12 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin }, { ViewName: "extras", - Key: gocui.NewKeyName(gocui.MouseWheelUp), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelUp)}, Handler: gui.scrollUpExtra, }, { ViewName: "extras", - Key: gocui.NewKeyName(gocui.MouseWheelDown), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseWheelDown)}, Handler: gui.scrollDownExtra, }, { @@ -352,7 +352,7 @@ func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBin { ViewName: "extras", Tag: "navigation", - Key: gocui.NewKeyName(gocui.MouseLeft), + Key: []gocui.Key{gocui.NewKeyName(gocui.MouseLeft)}, Handler: gui.handleFocusCommandLog, }, } @@ -448,7 +448,9 @@ func (gui *Gui) SetKeybinding(binding *types.Binding) { return gui.callKeybindingHandler(binding) } - gui.g.SetKeybinding(binding.ViewName, binding.Key, handler) + for _, key := range binding.Key { + gui.g.SetKeybinding(binding.ViewName, key, handler) + } } func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error { diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 248da40fb..20079700c 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -46,8 +46,10 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error { maxColumnSize = max(maxColumnSize, len(item.LabelColumns)) // Remove all item keybindings that are the same as one of the essential bindings - if !opts.KeepConflictingKeybindings && lo.Contains(essentialKeys, item.Key) { - item.Key = gocui.Key{} + if !opts.KeepConflictingKeybindings { + item.Key = lo.Filter(item.Key, func(k gocui.Key, _ int) bool { + return !lo.Contains(essentialKeys, k) + }) } } diff --git a/pkg/gui/options_map.go b/pkg/gui/options_map.go index c6360e27c..2771a6c25 100644 --- a/pkg/gui/options_map.go +++ b/pkg/gui/options_map.go @@ -41,12 +41,12 @@ func (self *OptionsMapMgr) renderContextOptionsMap() { globalBindings := self.c.Contexts().Global.GetKeybindings(self.c.KeybindingsOpts()) currentContextKeys := set.NewFromSlice( - lo.Map(currentContextBindings, func(binding *types.Binding, _ int) gocui.Key { + lo.FlatMap(currentContextBindings, func(binding *types.Binding, _ int) []gocui.Key { return binding.Key })) allBindings := append(currentContextBindings, lo.Filter(globalBindings, func(b *types.Binding, _ int) bool { - return !currentContextKeys.Includes(b.Key) + return len(b.Key) == 0 || !currentContextKeys.Includes(b.Key[0]) })...) bindingsToDisplay := lo.Filter(allBindings, func(binding *types.Binding, _ int) bool { @@ -60,7 +60,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() { } return bindingInfo{ - key: config.LabelForKey(binding.Key), + key: config.LabelForKey(binding.Key[0]), description: binding.GetShortDescription(), style: displayStyle, } diff --git a/pkg/gui/services/custom_commands/client.go b/pkg/gui/services/custom_commands/client.go index 4b927cf68..6d30ab310 100644 --- a/pkg/gui/services/custom_commands/client.go +++ b/pkg/gui/services/custom_commands/client.go @@ -2,6 +2,7 @@ package custom_commands import ( "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/i18n" @@ -45,7 +46,7 @@ func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) { } bindings = append(bindings, &types.Binding{ ViewName: "", // custom commands menus are global; we filter the commands inside by context - Key: config.GetValidatedKeyBindingKey(customCommand.Key), + Key: []gocui.Key{config.GetValidatedKeyBindingKey(customCommand.Key)}, Handler: handler, Description: getCustomCommandsMenuDescription(customCommand, self.c.Tr), OpensMenu: true, @@ -72,7 +73,7 @@ func (self *Client) showCustomCommandsMenu(customCommand config.CustomCommand) e } menuItems = append(menuItems, &types.MenuItem{ Label: subCommand.GetDescription(), - Key: config.GetValidatedKeyBindingKey(subCommand.Key), + Key: []gocui.Key{config.GetValidatedKeyBindingKey(subCommand.Key)}, OnPress: handler, OpensMenu: true, }) @@ -92,7 +93,7 @@ func (self *Client) showCustomCommandsMenu(customCommand config.CustomCommand) e menuItems = append(menuItems, &types.MenuItem{ Label: subCommand.GetDescription(), - Key: config.GetValidatedKeyBindingKey(subCommand.Key), + Key: []gocui.Key{config.GetValidatedKeyBindingKey(subCommand.Key)}, OnPress: self.handlerCreator.call(subCommand), }) } diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go index 412fc7de6..60a4555d8 100644 --- a/pkg/gui/services/custom_commands/handler_creator.go +++ b/pkg/gui/services/custom_commands/handler_creator.go @@ -232,7 +232,7 @@ func (self *HandlerCreator) menuPrompt(prompt *config.CustomCommandPrompt, wrapp OnPress: func() error { return wrappedF(option.Value) }, - Key: config.GetValidatedKeyBindingKey(option.Key), + Key: []gocui.Key{config.GetValidatedKeyBindingKey(option.Key)}, } }) diff --git a/pkg/gui/services/custom_commands/keybinding_creator.go b/pkg/gui/services/custom_commands/keybinding_creator.go index 644e6af45..52456323a 100644 --- a/pkg/gui/services/custom_commands/keybinding_creator.go +++ b/pkg/gui/services/custom_commands/keybinding_creator.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/types" @@ -35,7 +36,7 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler return lo.Map(viewNames, func(viewName string, _ int) *types.Binding { return &types.Binding{ ViewName: viewName, - Key: config.GetValidatedKeyBindingKey(customCommand.Key), + Key: []gocui.Key{config.GetValidatedKeyBindingKey(customCommand.Key)}, Handler: handler, Description: customCommand.GetDescription(), } diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 5856bb5e0..475cafeab 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -259,9 +259,10 @@ type MenuItem struct { // Only applies when Label is used OpensMenu bool - // If Key is defined it allows the user to press the key to invoke the menu - // item, as opposed to having to navigate to it - Key gocui.Key + // If Key is non-empty, the user can press any of these keys to invoke the + // menu item, as opposed to having to navigate to it. Only the first key is + // shown in the menu; the alternates are matched silently. + Key []gocui.Key // A widget to show in front of the menu item. Supported widget types are // checkboxes and radio buttons, diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index 35201c6cf..69d76ea78 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -239,7 +239,7 @@ type OnFocusLostOpts struct { type ContextKey string type KeybindingsOpts struct { - GetKey func(key string) gocui.Key + GetKey func(key string) []gocui.Key Config config.KeybindingConfig Guards KeybindingGuards } diff --git a/pkg/gui/types/keybindings.go b/pkg/gui/types/keybindings.go index 63079ccc7..3b3e512d8 100644 --- a/pkg/gui/types/keybindings.go +++ b/pkg/gui/types/keybindings.go @@ -11,7 +11,7 @@ import ( type Binding struct { ViewName string Handler func() error - Key gocui.Key + Key []gocui.Key Description string // DescriptionFunc is used instead of Description if non-nil, and is useful for dynamic // descriptions that change depending on context. Important: this must not be an expensive call.