mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
The keys for paging through a menu are ',' and '.' by default, and there is no non-printable alternative for them, so a menu that filters as you type would lose paging altogether as soon as the user typed anything. The same goes for confirming and cancelling if those keys are configured as printable ones. So bind the physical keys for all of it, on top of whatever is configured, and only where they aren't the configured keys anyway. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
155 lines
5 KiB
Go
155 lines
5 KiB
Go
package controllers
|
|
|
|
import (
|
|
"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/types"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type MenuController struct {
|
|
baseController
|
|
*ListControllerTrait[*types.MenuItem]
|
|
c *ControllerCommon
|
|
// for delegating navigation to, see physicalKeyBindings
|
|
listController *ListController
|
|
}
|
|
|
|
var _ types.IController = &MenuController{}
|
|
|
|
func NewMenuController(
|
|
c *ControllerCommon,
|
|
listController *ListController,
|
|
) *MenuController {
|
|
return &MenuController{
|
|
baseController: baseController{},
|
|
ListControllerTrait: NewListControllerTrait(
|
|
c,
|
|
c.Contexts().Menu,
|
|
c.Contexts().Menu.GetSelected,
|
|
c.Contexts().Menu.GetSelectedItems,
|
|
),
|
|
c: c,
|
|
listController: listController,
|
|
}
|
|
}
|
|
|
|
// NOTE: if you add a new keybinding here, you'll also need to add it to
|
|
// `reservedKeys` in `pkg/gui/context/menu_context.go`
|
|
func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
|
bindings := []*types.Binding{
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.Select),
|
|
Handler: self.withItem(self.press),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.ConfirmMenu),
|
|
Handler: self.withItem(self.press),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.Execute,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.Return),
|
|
Handler: self.close,
|
|
Description: self.c.Tr.CloseCancel,
|
|
DisplayOnScreen: true,
|
|
},
|
|
}
|
|
|
|
if self.context().FilterAsYouType() {
|
|
bindings = append(bindings, self.physicalKeyBindings(opts)...)
|
|
}
|
|
|
|
return bindings
|
|
}
|
|
|
|
// In a menu that filters as you type, the keys configured for driving the menu
|
|
// may all be printable, and printable keys become filter text once the user
|
|
// starts typing. These keys can't, so binding them on top guarantees that the
|
|
// menu stays usable no matter how the keybindings are configured.
|
|
func (self *MenuController) physicalKeyBindings(opts types.KeybindingsOpts) []*types.Binding {
|
|
candidates := []struct {
|
|
key gocui.Key
|
|
configured config.Keybinding
|
|
binding *types.Binding
|
|
}{
|
|
{
|
|
key: gocui.NewKeyName(gocui.KeyEnter),
|
|
configured: opts.Config.Universal.ConfirmMenu,
|
|
binding: &types.Binding{
|
|
Handler: self.withItem(self.press),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
},
|
|
},
|
|
{gocui.NewKeyName(gocui.KeyEsc), opts.Config.Universal.Return, &types.Binding{Handler: self.close}},
|
|
{gocui.NewKeyName(gocui.KeyArrowUp), opts.Config.Universal.PrevItem, &types.Binding{Handler: self.listController.HandlePrevLine}},
|
|
{gocui.NewKeyName(gocui.KeyArrowDown), opts.Config.Universal.NextItem, &types.Binding{Handler: self.listController.HandleNextLine}},
|
|
{gocui.NewKeyName(gocui.KeyPgup), opts.Config.Universal.PrevPage, &types.Binding{Handler: self.listController.HandlePrevPage}},
|
|
{gocui.NewKeyName(gocui.KeyPgdn), opts.Config.Universal.NextPage, &types.Binding{Handler: self.listController.HandleNextPage}},
|
|
{gocui.NewKeyName(gocui.KeyHome), opts.Config.Universal.GotoTop, &types.Binding{Handler: self.listController.HandleGotoTop}},
|
|
{gocui.NewKeyName(gocui.KeyEnd), opts.Config.Universal.GotoBottom, &types.Binding{Handler: self.listController.HandleGotoBottom}},
|
|
}
|
|
|
|
bindings := []*types.Binding{}
|
|
for _, candidate := range candidates {
|
|
if lo.Contains(opts.GetKeys(candidate.configured), candidate.key) {
|
|
// this key is the configured one, so it drives the menu already
|
|
continue
|
|
}
|
|
|
|
candidate.binding.Keys = []gocui.Key{candidate.key}
|
|
bindings = append(bindings, candidate.binding)
|
|
}
|
|
|
|
return bindings
|
|
}
|
|
|
|
func (self *MenuController) GetOnDoubleClick() func() error {
|
|
return self.withItemGraceful(self.press)
|
|
}
|
|
|
|
func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) {
|
|
return func(types.OnFocusOpts) {
|
|
selectedMenuItem := self.context().GetSelected()
|
|
if selectedMenuItem != nil {
|
|
self.c.Views().Tooltip.SetContent(self.c.Helpers().Confirmation.TooltipForMenuItem(selectedMenuItem))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (self *MenuController) press(selectedItem *types.MenuItem) error {
|
|
return self.context().OnMenuPress(selectedItem)
|
|
}
|
|
|
|
func (self *MenuController) close() error {
|
|
if self.context().FilterStarted() {
|
|
self.stopFiltering()
|
|
return nil
|
|
}
|
|
|
|
if self.context().IsFiltering() {
|
|
self.c.Helpers().Search.Cancel()
|
|
return nil
|
|
}
|
|
|
|
return self.context().OnMenuPress(nil)
|
|
}
|
|
|
|
// Hides the filter row again and puts the menu back the way it was, keeping the
|
|
// item that was selected. It takes another escape to close the menu.
|
|
func (self *MenuController) stopFiltering() {
|
|
self.c.Views().MenuFilter.ClearTextArea()
|
|
self.c.Views().MenuFilter.RenderTextArea()
|
|
|
|
self.context().SetFilterStarted(false)
|
|
self.context().ClearFilter()
|
|
self.c.PostRefreshUpdate(self.context())
|
|
}
|
|
|
|
func (self *MenuController) context() *context.MenuContext {
|
|
return self.c.Contexts().Menu
|
|
}
|