1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/custom_commands.go

212 lines
6.7 KiB
Go
Raw Normal View History

2020-09-26 07:23:28 +02:00
package gui
import (
2020-09-26 09:15:13 +02:00
"log"
2020-09-27 01:37:22 +02:00
"strings"
2020-09-26 07:23:28 +02:00
2020-09-27 01:13:31 +02:00
"github.com/fatih/color"
2020-09-26 09:15:13 +02:00
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
2020-10-03 06:54:55 +02:00
"github.com/jesseduffield/lazygit/pkg/config"
2020-09-27 01:13:31 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
2020-09-26 07:23:28 +02:00
)
type CustomCommandObjects struct {
2021-03-31 13:08:55 +02:00
SelectedLocalCommit *models.Commit
SelectedReflogCommit *models.Commit
SelectedSubCommit *models.Commit
SelectedFile *models.File
SelectedPath string
SelectedLocalBranch *models.Branch
SelectedRemoteBranch *models.RemoteBranch
SelectedRemote *models.Remote
SelectedTag *models.Tag
SelectedStashEntry *models.StashEntry
SelectedCommitFile *models.CommitFile
SelectedCommitFilePath string
CheckedOutBranch *models.Branch
PromptResponses []string
2020-09-26 11:48:13 +02:00
}
func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (string, error) {
objects := CustomCommandObjects{
2021-03-31 13:08:55 +02:00
SelectedFile: gui.getSelectedFile(),
SelectedPath: gui.getSelectedPath(),
SelectedLocalCommit: gui.getSelectedLocalCommit(),
SelectedReflogCommit: gui.getSelectedReflogCommit(),
SelectedLocalBranch: gui.getSelectedBranch(),
SelectedRemoteBranch: gui.getSelectedRemoteBranch(),
SelectedRemote: gui.getSelectedRemote(),
SelectedTag: gui.getSelectedTag(),
SelectedStashEntry: gui.getSelectedStashEntry(),
SelectedCommitFile: gui.getSelectedCommitFile(),
SelectedCommitFilePath: gui.getSelectedCommitFilePath(),
SelectedSubCommit: gui.getSelectedSubCommit(),
CheckedOutBranch: gui.currentBranch(),
PromptResponses: promptResponses,
2020-09-26 11:48:13 +02:00
}
2020-09-30 13:12:03 +02:00
return utils.ResolveTemplate(templateStr, objects)
2020-09-26 07:23:28 +02:00
}
2020-10-03 06:54:55 +02:00
func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand) func() error {
2020-09-26 07:23:28 +02:00
return func() error {
2020-09-26 11:48:13 +02:00
promptResponses := make([]string, len(customCommand.Prompts))
2020-09-26 07:23:28 +02:00
2020-09-26 11:48:13 +02:00
f := func() error {
cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
if err != nil {
return gui.surfaceError(err)
}
2020-09-26 07:23:28 +02:00
2020-09-26 11:48:13 +02:00
if customCommand.Subprocess {
2021-04-10 03:40:42 +02:00
return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.PrepareShellSubProcess(cmdStr))
2020-09-26 11:48:13 +02:00
}
2020-09-26 07:23:28 +02:00
2020-09-27 01:21:20 +02:00
loadingText := customCommand.LoadingText
if loadingText == "" {
2020-10-04 02:00:48 +02:00
loadingText = gui.Tr.LcRunningCustomCommandStatus
2020-09-27 01:21:20 +02:00
}
return gui.WithWaitingStatus(loadingText, func() error {
2021-04-01 11:10:24 +02:00
if err := gui.OSCommand.RunShellCommand(cmdStr); err != nil {
2020-09-26 11:48:13 +02:00
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{})
})
2020-09-26 09:15:13 +02:00
}
2020-09-26 11:48:13 +02:00
// if we have prompts we'll recursively wrap our confirm handlers with more prompts
// until we reach the actual command
for reverseIdx := range customCommand.Prompts {
idx := len(customCommand.Prompts) - 1 - reverseIdx
2020-09-26 09:15:13 +02:00
2020-09-26 11:48:13 +02:00
// going backwards so the outermost prompt is the first one
prompt := customCommand.Prompts[idx]
// need to do this because f's value will change with each iteration
wrappedF := f
2020-09-26 12:32:19 +02:00
switch prompt.Type {
2020-09-27 01:13:31 +02:00
case "input":
2020-09-26 12:32:19 +02:00
f = func() error {
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
return gui.surfaceError(err)
}
initialValue, err := gui.resolveTemplate(prompt.InitialValue, promptResponses)
if err != nil {
return gui.surfaceError(err)
}
2020-11-28 04:35:58 +02:00
return gui.prompt(promptOpts{
title: title,
initialContent: initialValue,
handleConfirm: func(str string) error {
2020-09-26 12:32:19 +02:00
promptResponses[idx] = str
return wrappedF()
},
2020-11-28 04:35:58 +02:00
})
2020-09-26 12:32:19 +02:00
}
case "menu":
f = func() error {
// need to make a menu here some how
menuItems := make([]*menuItem, len(prompt.Options))
for i, option := range prompt.Options {
option := option
2020-09-27 01:11:19 +02:00
nameTemplate := option.Name
if nameTemplate == "" {
// this allows you to only pass values rather than bother with names/descriptions
nameTemplate = option.Value
}
name, err := gui.resolveTemplate(nameTemplate, promptResponses)
if err != nil {
return gui.surfaceError(err)
}
description, err := gui.resolveTemplate(option.Description, promptResponses)
if err != nil {
return gui.surfaceError(err)
}
value, err := gui.resolveTemplate(option.Value, promptResponses)
if err != nil {
return gui.surfaceError(err)
}
menuItems[i] = &menuItem{
2020-09-27 01:13:31 +02:00
displayStrings: []string{name, utils.ColoredString(description, color.FgYellow)},
onPress: func() error {
promptResponses[idx] = value
return wrappedF()
},
}
}
2020-09-26 12:32:19 +02:00
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
return gui.surfaceError(err)
2020-09-26 12:32:19 +02:00
}
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
2020-09-26 12:32:19 +02:00
}
default:
2020-09-27 01:13:31 +02:00
return gui.createErrorPanel("custom command prompt must have a type of 'input' or 'menu'")
2020-09-26 07:23:28 +02:00
}
2020-09-26 12:32:19 +02:00
2020-09-26 11:48:13 +02:00
}
return f()
2020-09-26 07:23:28 +02:00
}
}
2020-09-26 09:15:13 +02:00
func (gui *Gui) GetCustomCommandKeybindings() []*Binding {
bindings := []*Binding{}
2020-10-03 06:54:55 +02:00
customCommands := gui.Config.GetUserConfig().CustomCommands
2020-09-26 09:15:13 +02:00
for _, customCommand := range customCommands {
var viewName string
var contexts []string
2020-09-27 01:37:22 +02:00
switch customCommand.Context {
case "global":
2020-09-26 09:15:13 +02:00
viewName = ""
2020-09-27 01:37:22 +02:00
case "":
log.Fatalf("Error parsing custom command keybindings: context not provided (use context: 'global' for the global context). Key: %s, Command: %s", customCommand.Key, customCommand.Command)
default:
2021-04-04 15:51:59 +02:00
context, ok := gui.contextForContextKey(ContextKey(customCommand.Context))
// stupid golang making me build an array of strings for this.
allContextKeyStrings := make([]string, len(allContextKeys))
for i := range allContextKeys {
allContextKeyStrings[i] = string(allContextKeys[i])
}
2020-09-27 01:37:22 +02:00
if !ok {
2021-04-04 15:51:59 +02:00
log.Fatalf("Error when setting custom command keybindings: unknown context: %s. Key: %s, Command: %s.\nPermitted contexts: %s", customCommand.Context, customCommand.Key, customCommand.Command, strings.Join(allContextKeyStrings, ", "))
2020-09-26 09:15:13 +02:00
}
// 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()
contexts = []string{customCommand.Context}
2020-09-26 09:15:13 +02:00
}
description := customCommand.Description
if description == "" {
description = customCommand.Command
}
2020-09-26 09:15:13 +02:00
bindings = append(bindings, &Binding{
ViewName: viewName,
Contexts: contexts,
2020-09-26 09:15:13 +02:00
Key: gui.getKey(customCommand.Key),
Modifier: gocui.ModNone,
Handler: gui.handleCustomCommandKeybinding(customCommand),
Description: description,
2020-09-26 09:15:13 +02:00
})
}
return bindings
}