diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go new file mode 100644 index 000000000..8dcff163d --- /dev/null +++ b/pkg/gui/custom_commands.go @@ -0,0 +1,59 @@ +package gui + +import ( + "bytes" + "text/template" + + "github.com/jesseduffield/lazygit/pkg/commands" +) + +type CustomCommandObjects struct { + SelectedLocalCommit *commands.Commit + SelectedReflogCommit *commands.Commit + SelectedSubCommit *commands.Commit + SelectedFile *commands.File + SelectedLocalBranch *commands.Branch + SelectedRemoteBranch *commands.RemoteBranch + SelectedRemote *commands.Remote + SelectedTag *commands.Tag + SelectedStashEntry *commands.StashEntry + SelectedCommitFile *commands.CommitFile + CurrentBranch *commands.Branch +} + +func (gui *Gui) handleCustomCommandKeybinding(templateStr string) func() error { + return func() error { + objects := CustomCommandObjects{ + SelectedFile: gui.getSelectedFile(), + SelectedLocalCommit: gui.getSelectedLocalCommit(), + SelectedReflogCommit: gui.getSelectedReflogCommit(), + SelectedLocalBranch: gui.getSelectedBranch(), + SelectedRemoteBranch: gui.getSelectedRemoteBranch(), + SelectedRemote: gui.getSelectedRemote(), + SelectedTag: gui.getSelectedTag(), + SelectedStashEntry: gui.getSelectedStashEntry(), + SelectedCommitFile: gui.getSelectedCommitFile(), + SelectedSubCommit: gui.getSelectedSubCommit(), + CurrentBranch: gui.currentBranch(), + } + + tmpl, err := template.New("custom command template").Parse(templateStr) + if err != nil { + return gui.surfaceError(err) + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, objects); err != nil { + return gui.surfaceError(err) + } + + cmdStr := buf.String() + + return gui.WithWaitingStatus(gui.Tr.SLocalize("runningCustomCommandStatus"), func() error { + if err := gui.OSCommand.RunCommand(cmdStr); err != nil { + return gui.surfaceError(err) + } + return gui.refreshSidePanels(refreshOptions{}) + }) + } +} diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 31738a419..8b6e80d20 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -181,6 +181,10 @@ func GetKeyDisplay(key interface{}) string { func (gui *Gui) getKey(name string) interface{} { key := gui.Config.GetUserConfig().GetString("keybinding." + name) + if key == "" { + // if we don't have the keybinding in our local config we'll assume it's just a plain letter from a custom command + key = name + } runeCount := utf8.RuneCountInString(key) if runeCount > 1 { binding := keymap[strings.ToLower(key)] @@ -1559,8 +1563,45 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { return bindings } +func (gui *Gui) GetCustomCommandKeybindings() []*Binding { + bindings := []*Binding{} + + ms := gui.Config.GetUserConfig().GetStringMap("customCommands") + for contextKey := range ms { + var viewName string + if contextKey == "global" { + viewName = "" + } else { + context := gui.contextForContextKey(contextKey) + if context == nil { + log.Fatalf("Error when setting custom command keybindings: unknown context: %s", contextKey) + } + // here we assume that a given context will always belong to the same view. + // Currently this is a safe bet but it's by no means guaranteed in the long term + // and we might need to make some changes in the future to support it. + viewName = context.GetViewName() + } + + keyMap := gui.Config.GetUserConfig().GetStringMapString(fmt.Sprintf("customCommands.%s", contextKey)) + for key, command := range keyMap { + bindings = append(bindings, &Binding{ + ViewName: viewName, + Contexts: []string{contextKey}, + Key: gui.getKey(key), + Modifier: gocui.ModNone, + Handler: gui.wrappedHandler(gui.handleCustomCommandKeybinding(command)), + Description: command, + }) + } + } + + return bindings +} + func (gui *Gui) keybindings() error { - bindings := gui.GetInitialKeybindings() + bindings := gui.GetCustomCommandKeybindings() + + bindings = append(bindings, gui.GetInitialKeybindings()...) for _, binding := range bindings { if err := gui.g.SetKeybinding(binding.ViewName, binding.Contexts, binding.Key, binding.Modifier, binding.Handler); err != nil { diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go index e7f9da36f..5211d34ea 100644 --- a/pkg/gui/options_menu_panel.go +++ b/pkg/gui/options_menu_panel.go @@ -12,7 +12,7 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding { bindingsGlobal, bindingsPanel []*Binding ) - bindings := gui.GetInitialKeybindings() + bindings := append(gui.GetInitialKeybindings(), gui.GetCustomCommandKeybindings()...) for _, binding := range bindings { if GetKeyDisplay(binding.Key) != "" && binding.Description != "" { @@ -39,17 +39,17 @@ func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error { menuItems := make([]*menuItem, len(bindings)) for i, binding := range bindings { - innerBinding := binding // note to self, never close over loop variables + binding := binding // note to self, never close over loop variables menuItems[i] = &menuItem{ - displayStrings: []string{GetKeyDisplay(innerBinding.Key), innerBinding.Description}, + displayStrings: []string{GetKeyDisplay(binding.Key), binding.Description}, onPress: func() error { - if innerBinding.Key == nil { + if binding.Key == nil { return nil } if err := gui.handleMenuClose(g, v); err != nil { return err } - return innerBinding.Handler(g, v) + return binding.Handler(g, v) }, } } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 2628c8fab..f774f553d 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -1188,6 +1188,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "minGitVersionError", Other: "Git version must be at least 2.0 (i.e. from 2014 onwards). Please upgrade your git version. Alternatively raise an issue at https://github.com/jesseduffield/lazygit/issues for lazygit to be more backwards compatible.", + }, &i18n.Message{ + ID: "runningCustomCommandStatus", + Other: "running custom command", }, ) }