mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-21 12:16:54 +02:00
I often copy hashes in the commits panel in order to paste them into Github comments (or other places), and I can't stand it when they have the full length. I picked a default of 12 for this; I find this to be a good middle ground between being reliable in large repos (12 still works in the linux kernel repo today, but it might not be enough in really huge repos) and not being too ugly (many smaller repos can probably get away with less). We deliberately don't change this for the "Copy to clipboard" menu, since this gives users a way to copy the unabbreviated sha if they need this occasionally.
447 lines
13 KiB
Go
447 lines
13 KiB
Go
package gui
|
|
|
|
import (
|
|
"log"
|
|
|
|
"github.com/jesseduffield/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
)
|
|
|
|
func (gui *Gui) noPopupPanel(f func() error) func() error {
|
|
return func() error {
|
|
if gui.helpers.Confirmation.IsPopupPanelFocused() {
|
|
return nil
|
|
}
|
|
|
|
return f()
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) outsideFilterMode(f func() error) func() error {
|
|
return func() error {
|
|
if !gui.validateNotInFilterMode() {
|
|
return nil
|
|
}
|
|
|
|
return f()
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) validateNotInFilterMode() bool {
|
|
if gui.State.Modes.Filtering.Active() {
|
|
_ = gui.c.Confirm(types.ConfirmOpts{
|
|
Title: gui.c.Tr.MustExitFilterModeTitle,
|
|
Prompt: gui.c.Tr.MustExitFilterModePrompt,
|
|
HandleConfirm: gui.helpers.Mode.ExitFilterMode,
|
|
})
|
|
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// only to be called from the cheatsheet generate script. This mutates the Gui struct.
|
|
func (self *Gui) GetCheatsheetKeybindings() []*types.Binding {
|
|
self.g = &gocui.Gui{}
|
|
if err := self.createAllViews(); err != nil {
|
|
panic(err)
|
|
}
|
|
// need to instantiate views
|
|
self.helpers = helpers.NewStubHelpers()
|
|
self.State = &GuiRepoState{}
|
|
self.State.Contexts = self.contextTree()
|
|
self.State.ContextMgr = NewContextMgr(self, self.State.Contexts)
|
|
self.resetHelpersAndControllers()
|
|
bindings, _ := self.GetInitialKeybindings()
|
|
return bindings
|
|
}
|
|
|
|
func (self *Gui) keybindingOpts() types.KeybindingsOpts {
|
|
config := self.c.UserConfig.Keybinding
|
|
|
|
guards := types.KeybindingGuards{
|
|
OutsideFilterMode: self.outsideFilterMode,
|
|
NoPopupPanel: self.noPopupPanel,
|
|
}
|
|
|
|
return types.KeybindingsOpts{
|
|
GetKey: keybindings.GetKey,
|
|
Config: config,
|
|
Guards: guards,
|
|
}
|
|
}
|
|
|
|
// renaming receiver to 'self' to aid refactoring. Will probably end up moving all Gui handlers to this pattern eventually.
|
|
func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBinding) {
|
|
opts := self.c.KeybindingsOpts()
|
|
|
|
bindings := []*types.Binding{
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.OpenRecentRepos),
|
|
Handler: self.helpers.Repos.CreateRecentReposMenu,
|
|
Description: self.c.Tr.SwitchRepo,
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.ScrollUpMain),
|
|
Handler: self.scrollUpMain,
|
|
Alternative: "fn+up/shift+k",
|
|
Description: self.c.Tr.ScrollUpMainWindow,
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.ScrollDownMain),
|
|
Handler: self.scrollDownMain,
|
|
Alternative: "fn+down/shift+j",
|
|
Description: self.c.Tr.ScrollDownMainWindow,
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.ScrollUpMainAlt1),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpMain,
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.ScrollDownMainAlt1),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollDownMain,
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.ScrollUpMainAlt2),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpMain,
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.ScrollDownMainAlt2),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollDownMain,
|
|
},
|
|
{
|
|
ViewName: "files",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopyPathToClipboard,
|
|
},
|
|
{
|
|
ViewName: "localBranches",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopyBranchNameToClipboard,
|
|
},
|
|
{
|
|
ViewName: "remoteBranches",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopyBranchNameToClipboard,
|
|
},
|
|
{
|
|
ViewName: "commits",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemCommitHashToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopyCommitShaToClipboard,
|
|
},
|
|
{
|
|
ViewName: "commits",
|
|
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
|
|
Handler: self.helpers.CherryPick.Reset,
|
|
Description: self.c.Tr.ResetCherryPick,
|
|
},
|
|
{
|
|
ViewName: "reflogCommits",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopyCommitShaToClipboard,
|
|
},
|
|
{
|
|
ViewName: "subCommits",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemCommitHashToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopyCommitShaToClipboard,
|
|
},
|
|
{
|
|
ViewName: "information",
|
|
Key: gocui.MouseLeft,
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.handleInfoClick,
|
|
},
|
|
{
|
|
ViewName: "commitFiles",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopyPathToClipboard,
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.ExtrasMenu),
|
|
Handler: self.handleCreateExtrasMenuPanel,
|
|
Description: self.c.Tr.OpenCommandLogMenu,
|
|
Tooltip: self.c.Tr.OpenCommandLogMenuTooltip,
|
|
OpensMenu: true,
|
|
},
|
|
{
|
|
ViewName: "secondary",
|
|
Key: gocui.MouseWheelUp,
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpSecondary,
|
|
},
|
|
{
|
|
ViewName: "secondary",
|
|
Key: gocui.MouseWheelDown,
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollDownSecondary,
|
|
},
|
|
{
|
|
ViewName: "main",
|
|
Key: gocui.MouseWheelDown,
|
|
Handler: self.scrollDownMain,
|
|
Description: self.c.Tr.ScrollDown,
|
|
Alternative: "fn+up",
|
|
},
|
|
{
|
|
ViewName: "main",
|
|
Key: gocui.MouseWheelUp,
|
|
Handler: self.scrollUpMain,
|
|
Description: self.c.Tr.ScrollUp,
|
|
Alternative: "fn+down",
|
|
},
|
|
{
|
|
ViewName: "secondary",
|
|
Key: gocui.MouseWheelUp,
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpSecondary,
|
|
},
|
|
{
|
|
ViewName: "confirmation",
|
|
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpConfirmationPanel,
|
|
},
|
|
{
|
|
ViewName: "confirmation",
|
|
Key: opts.GetKey(opts.Config.Universal.NextItem),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollDownConfirmationPanel,
|
|
},
|
|
{
|
|
ViewName: "confirmation",
|
|
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpConfirmationPanel,
|
|
},
|
|
{
|
|
ViewName: "confirmation",
|
|
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollDownConfirmationPanel,
|
|
},
|
|
{
|
|
ViewName: "confirmation",
|
|
Key: gocui.MouseWheelUp,
|
|
Handler: self.scrollUpConfirmationPanel,
|
|
},
|
|
{
|
|
ViewName: "confirmation",
|
|
Key: gocui.MouseWheelDown,
|
|
Handler: self.scrollDownConfirmationPanel,
|
|
},
|
|
{
|
|
ViewName: "submodules",
|
|
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
|
Handler: self.handleCopySelectedSideContextItemToClipboard,
|
|
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
|
|
Description: self.c.Tr.CopySubmoduleNameToClipboard,
|
|
},
|
|
{
|
|
ViewName: "extras",
|
|
Key: gocui.MouseWheelUp,
|
|
Handler: self.scrollUpExtra,
|
|
},
|
|
{
|
|
ViewName: "extras",
|
|
Key: gocui.MouseWheelDown,
|
|
Handler: self.scrollDownExtra,
|
|
},
|
|
{
|
|
ViewName: "extras",
|
|
Tag: "navigation",
|
|
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpExtra,
|
|
},
|
|
{
|
|
ViewName: "extras",
|
|
Tag: "navigation",
|
|
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollUpExtra,
|
|
},
|
|
{
|
|
ViewName: "extras",
|
|
Tag: "navigation",
|
|
Key: opts.GetKey(opts.Config.Universal.NextItem),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollDownExtra,
|
|
},
|
|
{
|
|
ViewName: "extras",
|
|
Tag: "navigation",
|
|
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.scrollDownExtra,
|
|
},
|
|
{
|
|
ViewName: "extras",
|
|
Tag: "navigation",
|
|
Key: gocui.MouseLeft,
|
|
Modifier: gocui.ModNone,
|
|
Handler: self.handleFocusCommandLog,
|
|
},
|
|
}
|
|
|
|
mouseKeybindings := []*gocui.ViewMouseBinding{}
|
|
for _, c := range self.State.Contexts.Flatten() {
|
|
viewName := c.GetViewName()
|
|
for _, binding := range c.GetKeybindings(opts) {
|
|
// TODO: move all mouse keybindings into the mouse keybindings approach below
|
|
binding.ViewName = viewName
|
|
bindings = append(bindings, binding)
|
|
}
|
|
|
|
mouseKeybindings = append(mouseKeybindings, c.GetMouseKeybindings(opts)...)
|
|
}
|
|
|
|
bindings = append(bindings, []*types.Binding{
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.NextTab),
|
|
Handler: self.handleNextTab,
|
|
Description: self.c.Tr.NextTab,
|
|
Tag: "navigation",
|
|
},
|
|
{
|
|
ViewName: "",
|
|
Key: opts.GetKey(opts.Config.Universal.PrevTab),
|
|
Handler: self.handlePrevTab,
|
|
Description: self.c.Tr.PrevTab,
|
|
Tag: "navigation",
|
|
},
|
|
}...)
|
|
|
|
return bindings, mouseKeybindings
|
|
}
|
|
|
|
func (self *Gui) GetInitialKeybindingsWithCustomCommands() ([]*types.Binding, []*gocui.ViewMouseBinding) {
|
|
bindings, mouseBindings := self.GetInitialKeybindings()
|
|
customBindings, err := self.CustomCommandsClient.GetCustomCommandKeybindings()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// prepending because we want to give our custom keybindings precedence over default keybindings
|
|
bindings = append(customBindings, bindings...)
|
|
return bindings, mouseBindings
|
|
}
|
|
|
|
func (gui *Gui) resetKeybindings() error {
|
|
gui.g.DeleteAllKeybindings()
|
|
|
|
bindings, mouseBindings := gui.GetInitialKeybindingsWithCustomCommands()
|
|
|
|
for _, binding := range bindings {
|
|
if err := gui.SetKeybinding(binding); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, binding := range mouseBindings {
|
|
if err := gui.SetMouseKeybinding(binding); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, values := range gui.viewTabMap() {
|
|
for _, value := range values {
|
|
viewName := value.ViewName
|
|
tabClickCallback := func(tabIndex int) error {
|
|
return gui.onViewTabClick(gui.helpers.Window.WindowForView(viewName), tabIndex)
|
|
}
|
|
|
|
if err := gui.g.SetTabClickBinding(viewName, tabClickCallback); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View) error {
|
|
return func(g *gocui.Gui, v *gocui.View) error {
|
|
return f()
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) SetKeybinding(binding *types.Binding) error {
|
|
handler := func() error {
|
|
return gui.callKeybindingHandler(binding)
|
|
}
|
|
|
|
// TODO: move all mouse-ey stuff into new mouse approach
|
|
if gocui.IsMouseKey(binding.Key) {
|
|
handler = func() error {
|
|
// we ignore click events on views that aren't popup panels, when a popup panel is focused
|
|
if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName {
|
|
return nil
|
|
}
|
|
|
|
return binding.Handler()
|
|
}
|
|
}
|
|
|
|
return gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, gui.wrappedHandler(handler))
|
|
}
|
|
|
|
// warning: mutates the binding
|
|
func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error {
|
|
baseHandler := binding.Handler
|
|
newHandler := func(opts gocui.ViewMouseBindingOpts) error {
|
|
// we ignore click events on views that aren't popup panels, when a popup panel is focused
|
|
if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName {
|
|
return nil
|
|
}
|
|
|
|
return baseHandler(opts)
|
|
}
|
|
binding.Handler = newHandler
|
|
|
|
return gui.g.SetViewClickBinding(binding)
|
|
}
|
|
|
|
func (gui *Gui) callKeybindingHandler(binding *types.Binding) error {
|
|
var disabledReason *types.DisabledReason
|
|
if binding.GetDisabledReason != nil {
|
|
disabledReason = binding.GetDisabledReason()
|
|
}
|
|
if disabledReason != nil {
|
|
if disabledReason.ShowErrorInPanel {
|
|
return gui.c.ErrorMsg(disabledReason.Text)
|
|
}
|
|
|
|
gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason.Text)
|
|
return nil
|
|
}
|
|
return binding.Handler()
|
|
}
|