2018-05-26 05:23:39 +02:00
// lots of this has been directly ported from one of the example files, will brush up later
2018-08-14 11:05:26 +02:00
package gui
2018-05-26 05:23:39 +02:00
import (
2018-08-05 14:00:02 +02:00
"strings"
2018-05-26 05:23:39 +02:00
2018-08-05 14:00:02 +02:00
"github.com/jesseduffield/gocui"
2021-07-27 15:00:37 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/style"
2020-11-28 10:53:39 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/types"
2019-10-18 09:48:37 +02:00
"github.com/jesseduffield/lazygit/pkg/theme"
2020-10-04 02:00:48 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
2018-05-26 05:23:39 +02:00
)
2020-08-15 08:36:39 +02:00
type createPopupPanelOpts struct {
hasLoader bool
editable bool
title string
prompt string
handleConfirm func ( ) error
handleConfirmPrompt func ( string ) error
handleClose func ( ) error
2020-08-23 05:09:36 +02:00
// when handlersManageFocus is true, do not return from the confirmation context automatically. It's expected that the handlers will manage focus, whether that means switching to another context, or manually returning the context.
handlersManageFocus bool
2020-11-28 04:14:48 +02:00
2020-11-28 11:01:45 +02:00
findSuggestionsFunc func ( string ) [ ] * types . Suggestion
2020-08-15 08:36:39 +02:00
}
2020-08-15 08:38:16 +02:00
type askOpts struct {
2020-11-28 04:35:58 +02:00
title string
prompt string
handleConfirm func ( ) error
handleClose func ( ) error
handlersManageFocus bool
2020-11-28 11:01:45 +02:00
findSuggestionsFunc func ( string ) [ ] * types . Suggestion
2020-08-15 08:36:39 +02:00
}
2020-11-28 04:35:58 +02:00
type promptOpts struct {
2020-11-28 11:01:45 +02:00
title string
initialContent string
handleConfirm func ( string ) error
findSuggestionsFunc func ( string ) [ ] * types . Suggestion
2020-11-28 04:35:58 +02:00
}
func ( gui * Gui ) ask ( opts askOpts ) error {
2020-08-15 08:36:39 +02:00
return gui . createPopupPanel ( createPopupPanelOpts {
2020-11-28 04:35:58 +02:00
title : opts . title ,
prompt : opts . prompt ,
handleConfirm : opts . handleConfirm ,
handleClose : opts . handleClose ,
handlersManageFocus : opts . handlersManageFocus ,
2020-11-28 11:01:45 +02:00
findSuggestionsFunc : opts . findSuggestionsFunc ,
2020-08-15 08:36:39 +02:00
} )
}
2020-11-28 04:35:58 +02:00
func ( gui * Gui ) prompt ( opts promptOpts ) error {
2020-08-15 08:36:39 +02:00
return gui . createPopupPanel ( createPopupPanelOpts {
2020-11-28 04:35:58 +02:00
title : opts . title ,
prompt : opts . initialContent ,
editable : true ,
handleConfirmPrompt : opts . handleConfirm ,
2020-11-28 11:01:45 +02:00
findSuggestionsFunc : opts . findSuggestionsFunc ,
2020-08-15 08:36:39 +02:00
} )
}
2020-11-28 04:35:58 +02:00
func ( gui * Gui ) createLoaderPanel ( prompt string ) error {
2020-08-15 08:36:39 +02:00
return gui . createPopupPanel ( createPopupPanelOpts {
2020-11-28 04:35:58 +02:00
prompt : prompt ,
hasLoader : true ,
2020-08-15 08:36:39 +02:00
} )
}
2020-11-28 11:30:16 +02:00
func ( gui * Gui ) wrappedConfirmationFunction ( handlersManageFocus bool , function func ( ) error ) func ( ) error {
return func ( ) error {
2018-08-05 14:00:02 +02:00
if function != nil {
2020-08-15 08:36:39 +02:00
if err := function ( ) ; err != nil {
return err
}
}
2020-08-23 05:09:36 +02:00
if err := gui . closeConfirmationPrompt ( handlersManageFocus ) ; err != nil {
return err
}
return nil
2020-08-15 08:36:39 +02:00
}
}
2020-11-28 11:30:16 +02:00
func ( gui * Gui ) wrappedPromptConfirmationFunction ( handlersManageFocus bool , function func ( string ) error , getResponse func ( ) string ) func ( ) error {
return func ( ) error {
2020-08-15 08:36:39 +02:00
if function != nil {
2020-11-28 04:14:48 +02:00
if err := function ( getResponse ( ) ) ; err != nil {
2020-08-16 14:01:14 +02:00
return gui . surfaceError ( err )
2018-08-05 14:00:02 +02:00
}
}
2019-11-17 03:02:39 +02:00
2020-08-23 05:09:36 +02:00
if err := gui . closeConfirmationPrompt ( handlersManageFocus ) ; err != nil {
return err
}
return nil
2020-11-28 11:30:16 +02:00
}
2018-05-27 08:32:09 +02:00
}
2021-04-04 15:51:59 +02:00
func ( gui * Gui ) clearConfirmationViewKeyBindings ( ) {
2020-10-03 06:54:55 +02:00
keybindingConfig := gui . Config . GetUserConfig ( ) . Keybinding
_ = gui . g . DeleteKeybinding ( "confirmation" , gui . getKey ( keybindingConfig . Universal . Confirm ) , gocui . ModNone )
_ = gui . g . DeleteKeybinding ( "confirmation" , gui . getKey ( keybindingConfig . Universal . ConfirmAlt1 ) , gocui . ModNone )
_ = gui . g . DeleteKeybinding ( "confirmation" , gui . getKey ( keybindingConfig . Universal . Return ) , gocui . ModNone )
2020-08-23 05:09:36 +02:00
}
func ( gui * Gui ) closeConfirmationPrompt ( handlersManageFocus bool ) error {
2021-04-04 15:51:59 +02:00
// we've already closed it so we can just return
if ! gui . Views . Confirmation . Visible {
return nil
2018-08-05 14:00:02 +02:00
}
2020-08-17 09:53:50 +02:00
2020-08-23 05:09:36 +02:00
if ! handlersManageFocus {
if err := gui . returnFromContext ( ) ; err != nil {
return err
}
}
2020-08-17 09:53:50 +02:00
2021-04-04 15:51:59 +02:00
gui . clearConfirmationViewKeyBindings ( )
gui . Views . Confirmation . Visible = false
gui . Views . Suggestions . Visible = false
2020-11-28 04:14:48 +02:00
2020-08-23 05:09:36 +02:00
return nil
2018-05-27 08:32:09 +02:00
}
2019-01-15 10:33:42 +02:00
func ( gui * Gui ) getMessageHeight ( wrap bool , message string , width int ) int {
2018-08-05 14:00:02 +02:00
lines := strings . Split ( message , "\n" )
lineCount := 0
2018-12-26 13:39:16 +02:00
// if we need to wrap, calculate height to fit content within view's width
2019-01-15 10:33:42 +02:00
if wrap {
2018-12-26 13:39:16 +02:00
for _ , line := range lines {
lineCount += len ( line ) / width + 1
}
} else {
lineCount = len ( lines )
2018-08-05 14:00:02 +02:00
}
return lineCount
2018-05-26 05:23:39 +02:00
}
2020-08-15 09:23:16 +02:00
func ( gui * Gui ) getConfirmationPanelDimensions ( wrap bool , prompt string ) ( int , int , int , int ) {
width , height := gui . g . Size ( )
2020-08-12 13:48:29 +02:00
// we want a minimum width up to a point, then we do it based on ratio.
2019-11-10 13:07:45 +02:00
panelWidth := 4 * width / 7
2020-08-12 13:48:29 +02:00
minWidth := 80
if panelWidth < minWidth {
if width - 2 < minWidth {
panelWidth = width - 2
} else {
panelWidth = minWidth
}
}
2019-01-15 10:33:42 +02:00
panelHeight := gui . getMessageHeight ( wrap , prompt , panelWidth )
2020-03-26 12:39:59 +02:00
if panelHeight > height * 3 / 4 {
panelHeight = height * 3 / 4
}
2018-08-05 14:00:02 +02:00
return width / 2 - panelWidth / 2 ,
height / 2 - panelHeight / 2 - panelHeight % 2 - 1 ,
width / 2 + panelWidth / 2 ,
height / 2 + panelHeight / 2
2018-05-26 05:23:39 +02:00
}
2021-10-17 04:00:44 +02:00
func ( gui * Gui ) prepareConfirmationPanel ( title , prompt string , hasLoader bool , findSuggestionsFunc func ( string ) [ ] * types . Suggestion , editable bool ) error {
2020-08-15 09:23:16 +02:00
x0 , y0 , x1 , y1 := gui . getConfirmationPanelDimensions ( true , prompt )
2021-04-04 16:06:20 +02:00
// calling SetView on an existing view returns the same view, so I'm not bothering
// to reassign to gui.Views.Confirmation
_ , err := gui . g . SetView ( "confirmation" , x0 , y0 , x1 , y1 , 0 )
2018-08-25 07:55:49 +02:00
if err != nil {
2021-04-04 16:06:20 +02:00
return err
}
gui . Views . Confirmation . HasLoader = hasLoader
if hasLoader {
gui . g . StartTicking ( )
2018-08-05 14:00:02 +02:00
}
2021-04-04 16:06:20 +02:00
gui . Views . Confirmation . Title = title
2021-10-17 04:00:44 +02:00
// for now we do not support wrapping in our editor
gui . Views . Confirmation . Wrap = ! editable
2021-04-04 16:06:20 +02:00
gui . Views . Confirmation . FgColor = theme . GocuiDefaultTextColor
2020-11-28 04:14:48 +02:00
2020-11-28 11:01:45 +02:00
gui . findSuggestions = findSuggestionsFunc
if findSuggestionsFunc != nil {
2020-11-28 04:14:48 +02:00
suggestionsViewHeight := 11
suggestionsView , err := gui . g . SetView ( "suggestions" , x0 , y1 , x1 , y1 + suggestionsViewHeight , 0 )
if err != nil {
2021-04-04 16:06:20 +02:00
return err
2020-11-28 04:14:48 +02:00
}
2021-04-04 16:06:20 +02:00
suggestionsView . Wrap = true
suggestionsView . FgColor = theme . GocuiDefaultTextColor
2020-11-28 10:53:39 +02:00
gui . setSuggestions ( [ ] * types . Suggestion { } )
2021-04-04 15:51:59 +02:00
suggestionsView . Visible = true
2020-11-28 04:14:48 +02:00
}
2018-12-07 09:52:31 +02:00
gui . g . Update ( func ( g * gocui . Gui ) error {
2021-04-03 06:56:11 +02:00
return gui . pushContext ( gui . State . Contexts . Confirmation )
2018-12-07 09:52:31 +02:00
} )
2021-04-04 16:06:20 +02:00
return nil
2018-05-27 08:32:09 +02:00
}
2020-08-15 08:36:39 +02:00
func ( gui * Gui ) createPopupPanel ( opts createPopupPanelOpts ) error {
gui . g . Update ( func ( g * gocui . Gui ) error {
2021-04-04 15:51:59 +02:00
// remove any previous keybindings
gui . clearConfirmationViewKeyBindings ( )
2021-10-17 04:00:44 +02:00
err := gui . prepareConfirmationPanel (
opts . title ,
opts . prompt ,
opts . hasLoader ,
opts . findSuggestionsFunc ,
opts . editable ,
)
2018-08-25 07:55:49 +02:00
if err != nil {
return err
2018-08-05 14:00:02 +02:00
}
2021-10-17 04:00:44 +02:00
confirmationView := gui . Views . Confirmation
confirmationView . Editable = opts . editable
confirmationView . Editor = gocui . EditorFunc ( gui . defaultEditor )
2021-04-17 12:53:36 +02:00
2020-08-15 08:36:39 +02:00
if opts . editable {
2021-10-17 04:00:44 +02:00
textArea := confirmationView . TextArea
textArea . Clear ( )
textArea . TypeString ( opts . prompt )
confirmationView . RenderTextArea ( )
2021-04-17 12:53:36 +02:00
} else {
2021-10-17 04:00:44 +02:00
if err := gui . renderStringSync ( confirmationView , opts . prompt ) ; err != nil {
2021-04-17 12:53:36 +02:00
return err
}
2019-11-17 08:58:24 +02:00
}
2020-08-15 08:36:39 +02:00
return gui . setKeyBindings ( opts )
2018-08-05 14:00:02 +02:00
} )
return nil
2018-06-05 10:47:57 +02:00
}
2020-08-15 08:36:39 +02:00
func ( gui * Gui ) setKeyBindings ( opts createPopupPanelOpts ) error {
2020-10-04 02:00:48 +02:00
actions := utils . ResolvePlaceholderString (
gui . Tr . CloseConfirm ,
map [ string ] string {
2018-08-15 10:53:05 +02:00
"keyBindClose" : "esc" ,
"keyBindConfirm" : "enter" ,
} ,
)
2020-03-26 12:39:59 +02:00
2021-04-04 16:31:52 +02:00
gui . renderString ( gui . Views . Options , actions )
2020-11-28 11:30:16 +02:00
var onConfirm func ( ) error
2020-08-15 08:36:39 +02:00
if opts . handleConfirmPrompt != nil {
2021-04-04 15:51:59 +02:00
onConfirm = gui . wrappedPromptConfirmationFunction ( opts . handlersManageFocus , opts . handleConfirmPrompt , func ( ) string { return gui . Views . Confirmation . Buffer ( ) } )
2020-08-15 08:36:39 +02:00
} else {
2020-08-23 05:09:36 +02:00
onConfirm = gui . wrappedConfirmationFunction ( opts . handlersManageFocus , opts . handleConfirm )
2020-08-17 09:53:50 +02:00
}
2020-11-28 11:30:16 +02:00
type confirmationKeybinding struct {
viewName string
key interface { }
handler func ( ) error
2020-11-28 04:14:48 +02:00
}
2020-11-28 11:30:16 +02:00
keybindingConfig := gui . Config . GetUserConfig ( ) . Keybinding
2020-11-28 10:53:39 +02:00
onSuggestionConfirm := gui . wrappedPromptConfirmationFunction ( opts . handlersManageFocus , opts . handleConfirmPrompt , func ( ) string { return gui . getSelectedSuggestionValue ( ) } )
2020-11-28 04:14:48 +02:00
2020-11-28 11:30:16 +02:00
confirmationKeybindings := [ ] confirmationKeybinding {
{
viewName : "confirmation" ,
key : gui . getKey ( keybindingConfig . Universal . Confirm ) ,
handler : onConfirm ,
} ,
{
viewName : "confirmation" ,
key : gui . getKey ( keybindingConfig . Universal . ConfirmAlt1 ) ,
handler : onConfirm ,
} ,
{
viewName : "confirmation" ,
key : gui . getKey ( keybindingConfig . Universal . Return ) ,
handler : gui . wrappedConfirmationFunction ( opts . handlersManageFocus , opts . handleClose ) ,
} ,
{
viewName : "confirmation" ,
key : gui . getKey ( keybindingConfig . Universal . TogglePanel ) ,
2021-10-17 08:49:32 +02:00
handler : func ( ) error {
if len ( gui . State . Suggestions ) > 0 {
return gui . replaceContext ( gui . State . Contexts . Suggestions )
}
return nil
} ,
2020-11-28 11:30:16 +02:00
} ,
{
viewName : "suggestions" ,
key : gui . getKey ( keybindingConfig . Universal . Confirm ) ,
handler : onSuggestionConfirm ,
} ,
{
viewName : "suggestions" ,
key : gui . getKey ( keybindingConfig . Universal . ConfirmAlt1 ) ,
handler : onSuggestionConfirm ,
} ,
{
viewName : "suggestions" ,
key : gui . getKey ( keybindingConfig . Universal . Return ) ,
handler : gui . wrappedConfirmationFunction ( opts . handlersManageFocus , opts . handleClose ) ,
} ,
{
viewName : "suggestions" ,
key : gui . getKey ( keybindingConfig . Universal . TogglePanel ) ,
2021-04-03 06:56:11 +02:00
handler : func ( ) error { return gui . replaceContext ( gui . State . Contexts . Confirmation ) } ,
2020-11-28 11:30:16 +02:00
} ,
2020-11-28 04:14:48 +02:00
}
2020-11-28 11:30:16 +02:00
for _ , binding := range confirmationKeybindings {
if err := gui . g . SetKeybinding ( binding . viewName , nil , binding . key , gocui . ModNone , gui . wrappedHandler ( binding . handler ) ) ; err != nil {
return err
}
2020-11-28 04:14:48 +02:00
}
return nil
2020-08-15 08:36:39 +02:00
}
2021-04-02 10:20:40 +02:00
func ( gui * Gui ) wrappedHandler ( f func ( ) error ) func ( g * gocui . Gui , v * gocui . View ) error {
return func ( g * gocui . Gui , v * gocui . View ) error {
return f ( )
}
}
2020-08-15 08:36:39 +02:00
func ( gui * Gui ) createErrorPanel ( message string ) error {
2021-07-27 15:00:37 +02:00
coloredMessage := style . FgRed . Sprint ( strings . TrimSpace ( message ) )
2020-03-28 03:52:45 +02:00
if err := gui . refreshSidePanels ( refreshOptions { mode : ASYNC } ) ; err != nil {
return err
}
2020-08-15 08:38:16 +02:00
return gui . ask ( askOpts {
2020-10-04 02:00:48 +02:00
title : gui . Tr . Error ,
2020-08-23 04:20:49 +02:00
prompt : coloredMessage ,
2020-08-15 08:36:39 +02:00
} )
2020-03-28 02:47:54 +02:00
}
func ( gui * Gui ) surfaceError ( err error ) error {
2020-10-01 14:13:32 +02:00
if err == nil {
return nil
}
2020-03-28 02:47:54 +02:00
return gui . createErrorPanel ( err . Error ( ) )
2018-06-01 15:23:31 +02:00
}