2020-09-26 07:23:28 +02:00
package gui
import (
"bytes"
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
"text/template"
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-09-27 01:13:31 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
2020-09-26 07:23:28 +02:00
)
type CustomCommandObjects struct {
2020-09-29 10:36:54 +02:00
SelectedLocalCommit * models . Commit
SelectedReflogCommit * models . Commit
SelectedSubCommit * models . Commit
2020-09-29 10:45:00 +02:00
SelectedFile * models . File
2020-09-29 10:34:01 +02:00
SelectedLocalBranch * models . Branch
2020-09-29 10:41:06 +02:00
SelectedRemoteBranch * models . RemoteBranch
SelectedRemote * models . Remote
2020-09-29 10:38:56 +02:00
SelectedTag * models . Tag
2020-09-29 10:46:45 +02:00
SelectedStashEntry * models . StashEntry
2020-09-29 10:48:38 +02:00
SelectedCommitFile * models . CommitFile
2020-09-29 10:34:01 +02:00
CheckedOutBranch * models . Branch
2020-09-26 11:48:13 +02:00
PromptResponses [ ] string
}
func ( gui * Gui ) resolveTemplate ( templateStr string , promptResponses [ ] string ) ( string , 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 ( ) ,
CheckedOutBranch : gui . currentBranch ( ) ,
PromptResponses : promptResponses ,
}
tmpl , err := template . New ( "template" ) . Parse ( templateStr )
if err != nil {
return "" , err
}
var buf bytes . Buffer
if err := tmpl . Execute ( & buf , objects ) ; err != nil {
return "" , err
}
cmdStr := buf . String ( )
return cmdStr , nil
2020-09-26 07:23:28 +02:00
}
2020-09-26 09:15:13 +02:00
func ( gui * Gui ) handleCustomCommandKeybinding ( customCommand 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 {
gui . PrepareSubProcess ( cmdStr )
return nil
}
2020-09-26 07:23:28 +02:00
2020-09-27 01:21:20 +02:00
loadingText := customCommand . LoadingText
if loadingText == "" {
loadingText = gui . Tr . SLocalize ( "runningCustomCommandStatus" )
}
return gui . WithWaitingStatus ( loadingText , func ( ) error {
2020-09-26 11:48:13 +02:00
gui . OSCommand . PrepareSubProcess ( cmdStr )
2020-09-26 07:23:28 +02:00
2020-09-26 11:48:13 +02:00
if err := gui . OSCommand . RunCommand ( cmdStr ) ; err != nil {
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-09-26 12:32:19 +02:00
return gui . prompt (
2020-09-26 13:47:01 +02:00
title ,
initialValue ,
2020-09-26 12:32:19 +02:00
func ( str string ) error {
promptResponses [ idx ] = str
return wrappedF ( )
} ,
)
}
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
2020-09-26 12:32:19 +02:00
type CustomCommandMenuOption struct {
Name string ` yaml:"name" `
Description string ` yaml:"description" `
Value string ` yaml:"value" `
}
2020-09-26 11:48:13 +02:00
type CustomCommandPrompt struct {
2020-09-27 01:13:31 +02:00
Type string ` yaml:"type" ` // one of 'input' and 'menu'
2020-09-26 12:32:19 +02:00
Title string ` yaml:"title" `
// this only apply to prompts
2020-09-26 11:48:13 +02:00
InitialValue string ` yaml:"initialValue" `
2020-09-26 12:32:19 +02:00
// this only applies to menus
Options [ ] CustomCommandMenuOption
2020-09-26 11:48:13 +02:00
}
2020-09-26 09:15:13 +02:00
type CustomCommand struct {
2020-09-27 01:21:20 +02:00
Key string ` yaml:"key" `
Context string ` yaml:"context" `
Command string ` yaml:"command" `
Subprocess bool ` yaml:"subprocess" `
Prompts [ ] CustomCommandPrompt ` yaml:"prompts" `
LoadingText string ` yaml:"loadingText" `
2020-09-27 03:29:10 +02:00
Description string ` yaml:"description" `
2020-09-26 09:15:13 +02:00
}
func ( gui * Gui ) GetCustomCommandKeybindings ( ) [ ] * Binding {
bindings := [ ] * Binding { }
var customCommands [ ] CustomCommand
if err := gui . Config . GetUserConfig ( ) . UnmarshalKey ( "customCommands" , & customCommands ) ; err != nil {
log . Fatalf ( "Error parsing custom command keybindings: %v" , err )
}
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 :
context , ok := gui . contextForContextKey ( customCommand . Context )
if ! ok {
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 ( allContextKeys , ", " ) )
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 ,
Handler : gui . wrappedHandler ( gui . handleCustomCommandKeybinding ( customCommand ) ) ,
2020-09-27 03:29:10 +02:00
Description : description ,
2020-09-26 09:15:13 +02:00
} )
}
return bindings
}