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
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
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-12-05 10:33:46 +02:00
"time"
2018-05-26 05:23:39 +02:00
2018-08-05 14:00:02 +02:00
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
2019-10-18 09:48:37 +02:00
"github.com/jesseduffield/lazygit/pkg/theme"
2018-05-26 05:23:39 +02:00
)
2019-11-05 06:19:43 +02:00
func ( gui * Gui ) wrappedConfirmationFunction ( function func ( * gocui . Gui , * gocui . View ) error , returnFocusOnClose bool ) func ( * gocui . Gui , * gocui . View ) error {
2018-08-05 14:00:02 +02:00
return func ( g * gocui . Gui , v * gocui . View ) error {
if function != nil {
if err := function ( g , v ) ; err != nil {
2018-08-23 10:43:16 +02:00
return err
2018-08-05 14:00:02 +02:00
}
}
2019-11-05 06:19:43 +02:00
return gui . closeConfirmationPrompt ( g , returnFocusOnClose )
2018-08-05 14:00:02 +02:00
}
2018-05-27 08:32:09 +02:00
}
2019-11-05 06:19:43 +02:00
func ( gui * Gui ) closeConfirmationPrompt ( g * gocui . Gui , returnFocusOnClose bool ) error {
2018-08-05 14:00:02 +02:00
view , err := g . View ( "confirmation" )
if err != nil {
2018-12-07 09:52:31 +02:00
return nil // if it's already been closed we can just return
2018-08-05 14:00:02 +02:00
}
2019-11-05 06:19:43 +02:00
if returnFocusOnClose {
if err := gui . returnFocus ( g , view ) ; err != nil {
panic ( err )
}
2018-08-05 14:00:02 +02:00
}
g . DeleteKeybindings ( "confirmation" )
return g . DeleteView ( "confirmation" )
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
}
2019-01-15 10:33:42 +02:00
func ( gui * Gui ) getConfirmationPanelDimensions ( g * gocui . Gui , wrap bool , prompt string ) ( int , int , int , int ) {
2018-08-05 14:00:02 +02:00
width , height := g . Size ( )
2019-11-10 13:07:45 +02:00
panelWidth := 4 * width / 7
2019-01-15 10:33:42 +02:00
panelHeight := gui . getMessageHeight ( wrap , prompt , panelWidth )
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
}
2019-11-11 14:22:09 +02:00
func ( gui * Gui ) createPromptPanel ( g * gocui . Gui , currentView * gocui . View , title string , initialContent string , handleConfirm func ( * gocui . Gui , * gocui . View ) error ) error {
2018-08-15 13:49:38 +02:00
gui . onNewPopupPanel ( )
2019-11-11 14:22:09 +02:00
confirmationView , err := gui . prepareConfirmationPanel ( currentView , title , initialContent , false )
2018-08-25 07:55:49 +02:00
if err != nil {
return err
}
confirmationView . Editable = true
2019-11-11 14:22:09 +02:00
if err := gui . renderString ( g , "confirmation" , initialContent ) ; err != nil {
return err
}
2019-11-05 06:19:43 +02:00
// in the future we might want to give createPromptPanel the returnFocusOnClose arg too, but for now we're always setting it to true
return gui . setKeyBindings ( g , handleConfirm , nil , true )
2018-08-25 07:55:49 +02:00
}
2019-02-16 03:03:22 +02:00
func ( gui * Gui ) prepareConfirmationPanel ( currentView * gocui . View , title , prompt string , hasLoader bool ) ( * gocui . View , error ) {
2019-01-15 10:33:42 +02:00
x0 , y0 , x1 , y1 := gui . getConfirmationPanelDimensions ( gui . g , true , prompt )
2018-08-25 07:55:49 +02:00
confirmationView , err := gui . g . SetView ( "confirmation" , x0 , y0 , x1 , y1 , 0 )
if err != nil {
2019-02-16 03:03:22 +02:00
if err . Error ( ) != "unknown view" {
2018-08-25 07:55:49 +02:00
return nil , err
2018-08-05 14:00:02 +02:00
}
2019-02-16 03:03:22 +02:00
confirmationView . HasLoader = hasLoader
2018-08-05 14:00:02 +02:00
confirmationView . Title = title
2018-12-05 10:33:46 +02:00
confirmationView . Wrap = true
2019-10-18 09:48:37 +02:00
confirmationView . FgColor = theme . GocuiDefaultTextColor
2018-08-05 14:00:02 +02:00
}
2018-12-07 09:52:31 +02:00
gui . g . Update ( func ( g * gocui . Gui ) error {
return gui . switchFocus ( gui . g , currentView , confirmationView )
} )
2018-08-25 07:55:49 +02:00
return confirmationView , nil
2018-05-27 08:32:09 +02:00
}
2018-08-15 13:49:38 +02:00
func ( gui * Gui ) onNewPopupPanel ( ) {
2019-02-11 12:07:12 +02:00
viewNames := [ ] string { "commitMessage" ,
2019-02-11 12:02:53 +02:00
"credentials" ,
"menu" }
for _ , viewName := range viewNames {
_ , _ = gui . g . SetViewOnBottom ( viewName )
}
2018-08-15 13:49:38 +02:00
}
2019-02-16 03:03:22 +02:00
func ( gui * Gui ) createLoaderPanel ( g * gocui . Gui , currentView * gocui . View , prompt string ) error {
2019-11-05 06:19:43 +02:00
return gui . createPopupPanel ( g , currentView , "" , prompt , true , true , nil , nil )
2019-02-16 03:03:22 +02:00
}
2018-12-16 08:05:34 +02:00
// it is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password
2019-11-05 06:19:43 +02:00
func ( gui * Gui ) createConfirmationPanel ( g * gocui . Gui , currentView * gocui . View , returnFocusOnClose bool , title , prompt string , handleConfirm , handleClose func ( * gocui . Gui , * gocui . View ) error ) error {
return gui . createPopupPanel ( g , currentView , title , prompt , false , returnFocusOnClose , handleConfirm , handleClose )
2019-02-16 03:03:22 +02:00
}
2019-11-05 06:19:43 +02:00
func ( gui * Gui ) createPopupPanel ( g * gocui . Gui , currentView * gocui . View , title , prompt string , hasLoader bool , returnFocusOnClose bool , handleConfirm , handleClose func ( * gocui . Gui , * gocui . View ) error ) error {
2018-08-15 13:49:38 +02:00
gui . onNewPopupPanel ( )
2018-08-05 14:00:02 +02:00
g . Update ( func ( g * gocui . Gui ) error {
// delete the existing confirmation panel if it exists
if view , _ := g . View ( "confirmation" ) ; view != nil {
2019-11-05 06:19:43 +02:00
if err := gui . closeConfirmationPrompt ( g , true ) ; err != nil {
2018-08-15 15:12:55 +02:00
errMessage := gui . Tr . TemplateLocalize (
"CantCloseConfirmationPrompt" ,
2018-08-16 11:31:03 +02:00
Teml {
2018-08-15 15:12:55 +02:00
"error" : err . Error ( ) ,
} ,
)
gui . Log . Error ( errMessage )
2018-08-05 14:00:02 +02:00
}
}
2019-02-16 03:03:22 +02:00
confirmationView , err := gui . prepareConfirmationPanel ( currentView , title , prompt , hasLoader )
2018-08-25 07:55:49 +02:00
if err != nil {
return err
2018-08-05 14:00:02 +02:00
}
2018-08-25 07:55:49 +02:00
confirmationView . Editable = false
if err := gui . renderString ( g , "confirmation" , prompt ) ; err != nil {
return err
}
2019-11-05 06:19:43 +02:00
return gui . setKeyBindings ( g , handleConfirm , handleClose , returnFocusOnClose )
2018-08-05 14:00:02 +02:00
} )
return nil
2018-06-05 10:47:57 +02:00
}
2019-11-05 06:19:43 +02:00
func ( gui * Gui ) setKeyBindings ( g * gocui . Gui , handleConfirm , handleClose func ( * gocui . Gui , * gocui . View ) error , returnFocusOnClose bool ) error {
2018-08-15 10:53:05 +02:00
actions := gui . Tr . TemplateLocalize (
"CloseConfirm" ,
2018-08-16 11:31:03 +02:00
Teml {
2018-08-15 10:53:05 +02:00
"keyBindClose" : "esc" ,
"keyBindConfirm" : "enter" ,
} ,
)
2018-08-25 07:55:49 +02:00
if err := gui . renderString ( g , "options" , actions ) ; err != nil {
return err
}
2019-11-05 06:19:43 +02:00
if err := g . SetKeybinding ( "confirmation" , gocui . KeyEnter , gocui . ModNone , gui . wrappedConfirmationFunction ( handleConfirm , returnFocusOnClose ) ) ; err != nil {
2018-08-05 14:00:02 +02:00
return err
}
2019-11-05 06:19:43 +02:00
return g . SetKeybinding ( "confirmation" , gocui . KeyEsc , gocui . ModNone , gui . wrappedConfirmationFunction ( handleClose , returnFocusOnClose ) )
2018-05-26 05:23:39 +02:00
}
2018-05-27 08:32:09 +02:00
2018-08-14 11:05:26 +02:00
func ( gui * Gui ) createMessagePanel ( g * gocui . Gui , currentView * gocui . View , title , prompt string ) error {
2019-11-05 06:19:43 +02:00
return gui . createPopupPanel ( g , currentView , title , prompt , false , true , nil , nil )
2018-05-27 08:32:09 +02:00
}
2018-06-01 15:23:31 +02:00
2018-12-16 08:05:34 +02:00
// createSpecificErrorPanel allows you to create an error popup, specifying the
// view to be focused when the user closes the popup, and a boolean specifying
// whether we will log the error. If the message may include a user password,
// this function is to be used over the more generic createErrorPanel, with
// willLog set to false
func ( gui * Gui ) createSpecificErrorPanel ( message string , nextView * gocui . View , willLog bool ) error {
if willLog {
go func ( ) {
// when reporting is switched on this log call sometimes introduces
// a delay on the error panel popping up. Here I'm adding a second wait
// so that the error is logged while the user is reading the error message
time . Sleep ( time . Second )
gui . Log . Error ( message )
} ( )
}
2018-08-05 14:00:02 +02:00
colorFunction := color . New ( color . FgRed ) . SprintFunc ( )
coloredMessage := colorFunction ( strings . TrimSpace ( message ) )
2019-11-05 06:19:43 +02:00
return gui . createConfirmationPanel ( gui . g , nextView , true , gui . Tr . SLocalize ( "Error" ) , coloredMessage , nil , nil )
2018-12-16 08:05:34 +02:00
}
func ( gui * Gui ) createErrorPanel ( g * gocui . Gui , message string ) error {
return gui . createSpecificErrorPanel ( message , g . CurrentView ( ) , true )
2018-06-01 15:23:31 +02:00
}