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"
2020-09-29 12:28:39 +02:00
"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-10 09:31:23 +02:00
if err := gui . OSCommand . WithSpan ( "Custom command" ) . 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 ]
2020-09-26 13:47:01 +02:00
// 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 {
2020-09-26 13:47:01 +02:00
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" :
2020-09-26 13:47:01 +02:00
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 )
2020-09-26 13:47:01 +02:00
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 ) } ,
2020-09-26 13:47:01 +02:00
onPress : func ( ) error {
promptResponses [ idx ] = value
return wrappedF ( )
} ,
}
}
2020-09-26 12:32:19 +02:00
2020-09-26 13:47:01 +02:00
title , err := gui . resolveTemplate ( prompt . Title , promptResponses )
if err != nil {
return gui . surfaceError ( err )
2020-09-26 12:32:19 +02:00
}
2020-09-26 13:47:01 +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
2020-09-27 03:04:57 +02:00
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 ( )
2020-09-27 03:04:57 +02:00
contexts = [ ] string { customCommand . Context }
2020-09-26 09:15:13 +02:00
}
2020-09-27 03:29:10 +02:00
description := customCommand . Description
if description == "" {
description = customCommand . Command
}
2020-09-26 09:15:13 +02:00
bindings = append ( bindings , & Binding {
ViewName : viewName ,
2020-09-27 03:04:57 +02:00
Contexts : contexts ,
2020-09-26 09:15:13 +02:00
Key : gui . getKey ( customCommand . Key ) ,
Modifier : gocui . ModNone ,
2021-04-02 10:20:40 +02:00
Handler : gui . handleCustomCommandKeybinding ( customCommand ) ,
2020-09-27 03:29:10 +02:00
Description : description ,
2020-09-26 09:15:13 +02:00
} )
}
return bindings
}