jesseduffield.lazygit/pkg/gui/controllers/global_controller.go
Stefan Haller 348224a96e Add a global keybinding for editing the config file
The status panel already binds the universal edit key to "edit config
file", but that's only reachable while the status panel is focused. Add
a dedicated global binding (alt+shift+c) so the config file can be
opened from anywhere.
2026-06-24 22:10:52 +02:00

288 lines
8.7 KiB
Go

package controllers
import (
"strconv"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type GlobalController struct {
baseController
c *ControllerCommon
}
func NewGlobalController(
c *ControllerCommon,
) *GlobalController {
return &GlobalController{
baseController: baseController{},
c: c,
}
}
func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Keys: opts.GetKeys(opts.Config.Universal.ExecuteShellCommand),
Handler: self.shellCommand,
Description: self.c.Tr.ExecuteShellCommand,
Tooltip: self.c.Tr.ExecuteShellCommandTooltip,
OpensMenu: true,
},
{
Keys: opts.GetKeys(opts.Config.Universal.CreatePatchOptionsMenu),
Handler: self.createCustomPatchOptionsMenu,
Description: self.c.Tr.ViewPatchOptions,
OpensMenu: true,
},
{
Keys: opts.GetKeys(opts.Config.Universal.CreateRebaseOptionsMenu),
Handler: opts.Guards.NoPopupPanel(self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu),
Description: self.c.Tr.ViewMergeRebaseOptions,
Tooltip: self.c.Tr.ViewMergeRebaseOptionsTooltip,
OpensMenu: true,
GetDisabledReason: self.canShowRebaseOptions,
},
{
Keys: opts.GetKeys(opts.Config.Universal.Refresh),
Handler: opts.Guards.NoPopupPanel(self.refresh),
Description: self.c.Tr.Refresh,
Tooltip: self.c.Tr.RefreshTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Universal.NextScreenMode),
Handler: opts.Guards.NoPopupPanel(self.nextScreenMode),
Description: self.c.Tr.NextScreenMode,
},
{
Keys: opts.GetKeys(opts.Config.Universal.PrevScreenMode),
Handler: opts.Guards.NoPopupPanel(self.prevScreenMode),
Description: self.c.Tr.PrevScreenMode,
},
{
Keys: opts.GetKeys(opts.Config.Universal.CyclePagers),
Handler: opts.Guards.NoPopupPanel(self.cyclePagers),
GetDisabledReason: self.canCyclePagers,
Description: self.c.Tr.CyclePagers,
Tooltip: self.c.Tr.CyclePagersTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Universal.CyclePagersReverse),
Handler: opts.Guards.NoPopupPanel(self.cyclePagersBackward),
GetDisabledReason: self.canCyclePagers,
Description: self.c.Tr.CyclePagersReverse,
Tooltip: self.c.Tr.CyclePagersReverseTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Universal.Return),
Handler: self.escape,
Description: self.c.Tr.Cancel,
DescriptionFunc: self.escapeDescription,
GetDisabledReason: self.escapeEnabled,
DisplayOnScreen: true,
},
{
ViewName: "",
Keys: opts.GetKeys(opts.Config.Universal.OptionMenu),
Description: self.c.Tr.OpenKeybindingsMenu,
ShortDescription: self.c.Tr.Keybindings,
Handler: self.createOptionsMenu,
GetDisabledReason: self.optionsMenuDisabledReason,
OpensMenu: true,
DisplayOnScreen: true,
},
{
ViewName: "",
Keys: opts.GetKeys(opts.Config.Universal.FilteringMenu),
Handler: opts.Guards.NoPopupPanel(self.createFilteringMenu),
Description: self.c.Tr.OpenFilteringMenu,
Tooltip: self.c.Tr.OpenFilteringMenuTooltip,
OpensMenu: true,
},
{
Keys: opts.GetKeys(opts.Config.Universal.DiffingMenu),
Handler: opts.Guards.NoPopupPanel(self.createDiffingMenu),
Description: self.c.Tr.ViewDiffingOptions,
Tooltip: self.c.Tr.ViewDiffingOptionsTooltip,
OpensMenu: true,
},
{
Keys: opts.GetKeys(opts.Config.Universal.Quit),
Description: self.c.Tr.Quit,
Handler: self.quit,
},
{
Keys: opts.GetKeys(opts.Config.Universal.QuitWithoutChangingDirectory),
Handler: self.quitWithoutChangingDirectory,
},
{
Keys: opts.GetKeys(opts.Config.Universal.SuspendApp),
Handler: self.c.Helpers().SuspendResume.SuspendApp,
Description: self.c.Tr.SuspendApp,
GetDisabledReason: func() *types.DisabledReason {
if !self.c.Helpers().SuspendResume.CanSuspendApp() {
return &types.DisabledReason{
Text: self.c.Tr.CannotSuspendApp,
}
}
return nil
},
},
{
Keys: opts.GetKeys(opts.Config.Universal.ToggleWhitespaceInDiffView),
Handler: self.toggleWhitespace,
Description: self.c.Tr.ToggleWhitespaceInDiffView,
Tooltip: self.c.Tr.ToggleWhitespaceInDiffViewTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Universal.EditConfig),
Handler: self.editConfig,
Description: self.c.Tr.EditConfig,
Tooltip: self.c.Tr.EditFileTooltip,
},
}
}
func (self *GlobalController) Context() types.Context {
return nil
}
func (self *GlobalController) shellCommand() error {
return (&ShellCommandAction{c: self.c}).Call()
}
func (self *GlobalController) createCustomPatchOptionsMenu() error {
return (&CustomPatchOptionsMenuAction{c: self.c}).Call()
}
func (self *GlobalController) refresh() error {
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
func (self *GlobalController) nextScreenMode() error {
return (&ScreenModeActions{c: self.c}).Next()
}
func (self *GlobalController) prevScreenMode() error {
return (&ScreenModeActions{c: self.c}).Prev()
}
func (self *GlobalController) cyclePagers() error {
self.c.State().GetPagerConfig().CyclePagers()
self.onPagerChanged()
return nil
}
func (self *GlobalController) cyclePagersBackward() error {
self.c.State().GetPagerConfig().CyclePagersBackward()
self.onPagerChanged()
return nil
}
// onPagerChanged re-renders the main view so the newly selected pager takes
// effect, and shows a toast naming it.
func (self *GlobalController) onPagerChanged() {
currentSide := self.c.Context().CurrentSide()
currentKey := self.c.Context().Current().GetKey()
if currentSide.GetKey() == currentKey ||
currentKey == context.NORMAL_MAIN_CONTEXT_KEY ||
currentKey == context.NORMAL_SECONDARY_CONTEXT_KEY {
currentSide.HandleRenderToMain()
}
pagerConfig := self.c.State().GetPagerConfig()
current, total := pagerConfig.CurrentPagerIndex()
name := pagerConfig.CurrentPagerName()
if name == "" {
if pagerConfig.CurrentPagerUsesGitConfigDiff() {
name = self.c.Tr.ExternalDiffPagerName
} else {
name = self.c.Tr.DefaultPagerName
}
}
self.c.Toast(utils.ResolvePlaceholderString(self.c.Tr.SelectedPager, map[string]string{
"name": name,
"current": strconv.Itoa(current + 1),
"total": strconv.Itoa(total),
}))
}
func (self *GlobalController) canCyclePagers() *types.DisabledReason {
_, total := self.c.State().GetPagerConfig().CurrentPagerIndex()
if total <= 1 {
return &types.DisabledReason{
Text: self.c.Tr.CyclePagersDisabledReason,
}
}
return nil
}
func (self *GlobalController) createOptionsMenu() error {
return (&OptionsMenuAction{c: self.c}).Call()
}
func (self *GlobalController) optionsMenuDisabledReason() *types.DisabledReason {
ctx := self.c.Context().Current()
// Don't show options menu while displaying popup.
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
// The empty error text is intentional. We don't want to show an error
// toast for this, but only hide it from the options map.
return &types.DisabledReason{Text: ""}
}
return nil
}
func (self *GlobalController) createFilteringMenu() error {
return (&FilteringMenuAction{c: self.c}).Call()
}
func (self *GlobalController) createDiffingMenu() error {
return (&DiffingMenuAction{c: self.c}).Call()
}
func (self *GlobalController) quit() error {
return (&QuitActions{c: self.c}).Quit()
}
func (self *GlobalController) quitWithoutChangingDirectory() error {
return (&QuitActions{c: self.c}).QuitWithoutChangingDirectory()
}
func (self *GlobalController) escape() error {
return (&QuitActions{c: self.c}).Escape()
}
func (self *GlobalController) escapeDescription() string {
return (&QuitActions{c: self.c}).EscapeDescription()
}
func (self *GlobalController) escapeEnabled() *types.DisabledReason {
if (&QuitActions{c: self.c}).EscapeEnabled() {
return nil
}
// The empty error text is intentional. We don't want to show an error
// toast for this, but only hide it from the options map.
return &types.DisabledReason{Text: ""}
}
func (self *GlobalController) toggleWhitespace() error {
return (&ToggleWhitespaceAction{c: self.c}).Call()
}
func (self *GlobalController) editConfig() error {
return (&EditConfigAction{c: self.c}).Call()
}
func (self *GlobalController) canShowRebaseOptions() *types.DisabledReason {
if self.c.Model().WorkingTreeStateAtLastCommitRefresh.None() {
return &types.DisabledReason{
Text: self.c.Tr.NotMergingOrRebasing,
}
}
return nil
}