github.com/andrewrech/lazygit@v0.8.1/pkg/gui/confirmation_panel.go (about)

     1  // lots of this has been directly ported from one of the example files, will brush up later
     2  
     3  // Copyright 2014 The gocui Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  package gui
     8  
     9  import (
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/fatih/color"
    14  	"github.com/jesseduffield/gocui"
    15  )
    16  
    17  func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error {
    18  	return func(g *gocui.Gui, v *gocui.View) error {
    19  		if function != nil {
    20  			if err := function(g, v); err != nil {
    21  				return err
    22  			}
    23  		}
    24  		return gui.closeConfirmationPrompt(g)
    25  	}
    26  }
    27  
    28  func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error {
    29  	view, err := g.View("confirmation")
    30  	if err != nil {
    31  		return nil // if it's already been closed we can just return
    32  	}
    33  	if err := gui.returnFocus(g, view); err != nil {
    34  		panic(err)
    35  	}
    36  	g.DeleteKeybindings("confirmation")
    37  	return g.DeleteView("confirmation")
    38  }
    39  
    40  func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
    41  	lines := strings.Split(message, "\n")
    42  	lineCount := 0
    43  	// if we need to wrap, calculate height to fit content within view's width
    44  	if wrap {
    45  		for _, line := range lines {
    46  			lineCount += len(line)/width + 1
    47  		}
    48  	} else {
    49  		lineCount = len(lines)
    50  	}
    51  	return lineCount
    52  }
    53  
    54  func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt string) (int, int, int, int) {
    55  	width, height := g.Size()
    56  	panelWidth := width / 2
    57  	panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
    58  	return width/2 - panelWidth/2,
    59  		height/2 - panelHeight/2 - panelHeight%2 - 1,
    60  		width/2 + panelWidth/2,
    61  		height/2 + panelHeight/2
    62  }
    63  
    64  func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
    65  	gui.onNewPopupPanel()
    66  	confirmationView, err := gui.prepareConfirmationPanel(currentView, title, "", false)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	confirmationView.Editable = true
    71  	return gui.setKeyBindings(g, handleConfirm, nil)
    72  }
    73  
    74  func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string, hasLoader bool) (*gocui.View, error) {
    75  	x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, true, prompt)
    76  	confirmationView, err := gui.g.SetView("confirmation", x0, y0, x1, y1, 0)
    77  	if err != nil {
    78  		if err.Error() != "unknown view" {
    79  			return nil, err
    80  		}
    81  		confirmationView.HasLoader = hasLoader
    82  		confirmationView.Title = title
    83  		confirmationView.Wrap = true
    84  		confirmationView.FgColor = gocui.ColorWhite
    85  	}
    86  	gui.g.Update(func(g *gocui.Gui) error {
    87  		return gui.switchFocus(gui.g, currentView, confirmationView)
    88  	})
    89  	return confirmationView, nil
    90  }
    91  
    92  func (gui *Gui) onNewPopupPanel() {
    93  	viewNames := []string{"commitMessage",
    94  		"credentials",
    95  		"menu"}
    96  	for _, viewName := range viewNames {
    97  		_, _ = gui.g.SetViewOnBottom(viewName)
    98  	}
    99  }
   100  
   101  func (gui *Gui) createLoaderPanel(g *gocui.Gui, currentView *gocui.View, prompt string) error {
   102  	return gui.createPopupPanel(g, currentView, "", prompt, true, nil, nil)
   103  }
   104  
   105  // 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
   106  func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
   107  	return gui.createPopupPanel(g, currentView, title, prompt, false, handleConfirm, handleClose)
   108  }
   109  
   110  func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, hasLoader bool, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
   111  	gui.onNewPopupPanel()
   112  	g.Update(func(g *gocui.Gui) error {
   113  		// delete the existing confirmation panel if it exists
   114  		if view, _ := g.View("confirmation"); view != nil {
   115  			if err := gui.closeConfirmationPrompt(g); err != nil {
   116  				errMessage := gui.Tr.TemplateLocalize(
   117  					"CantCloseConfirmationPrompt",
   118  					Teml{
   119  						"error": err.Error(),
   120  					},
   121  				)
   122  				gui.Log.Error(errMessage)
   123  			}
   124  		}
   125  		confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt, hasLoader)
   126  		if err != nil {
   127  			return err
   128  		}
   129  		confirmationView.Editable = false
   130  		if err := gui.renderString(g, "confirmation", prompt); err != nil {
   131  			return err
   132  		}
   133  		return gui.setKeyBindings(g, handleConfirm, handleClose)
   134  	})
   135  	return nil
   136  }
   137  
   138  func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
   139  	actions := gui.Tr.TemplateLocalize(
   140  		"CloseConfirm",
   141  		Teml{
   142  			"keyBindClose":   "esc",
   143  			"keyBindConfirm": "enter",
   144  		},
   145  	)
   146  	if err := gui.renderString(g, "options", actions); err != nil {
   147  		return err
   148  	}
   149  	if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm)); err != nil {
   150  		return err
   151  	}
   152  	return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose))
   153  }
   154  
   155  func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
   156  	return gui.createPopupPanel(g, currentView, title, prompt, false, nil, nil)
   157  }
   158  
   159  // createSpecificErrorPanel allows you to create an error popup, specifying the
   160  //  view to be focused when the user closes the popup, and a boolean specifying
   161  // whether we will log the error. If the message may include a user password,
   162  // this function is to be used over the more generic createErrorPanel, with
   163  // willLog set to false
   164  func (gui *Gui) createSpecificErrorPanel(message string, nextView *gocui.View, willLog bool) error {
   165  	if willLog {
   166  		go func() {
   167  			// when reporting is switched on this log call sometimes introduces
   168  			// a delay on the error panel popping up. Here I'm adding a second wait
   169  			// so that the error is logged while the user is reading the error message
   170  			time.Sleep(time.Second)
   171  			gui.Log.Error(message)
   172  		}()
   173  	}
   174  
   175  	colorFunction := color.New(color.FgRed).SprintFunc()
   176  	coloredMessage := colorFunction(strings.TrimSpace(message))
   177  	return gui.createConfirmationPanel(gui.g, nextView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
   178  }
   179  
   180  func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
   181  	return gui.createSpecificErrorPanel(message, g.CurrentView(), true)
   182  }