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 }