1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/confirmation_panel.go

260 lines
7.3 KiB
Go
Raw Normal View History

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"
"github.com/jesseduffield/lazygit/pkg/theme"
2018-05-26 05:23:39 +02:00
)
2020-08-15 08:36:39 +02:00
type createPopupPanelOpts struct {
returnToView *gocui.View
hasLoader bool
returnFocusOnClose bool
editable bool
title string
prompt string
handleConfirm func() error
handleConfirmPrompt func(string) error
handleClose func() error
}
2020-08-15 08:38:16 +02:00
type askOpts struct {
2020-08-15 08:36:39 +02:00
returnToView *gocui.View
returnFocusOnClose bool
title string
prompt string
handleConfirm func() error
handleClose func() error
}
func (gui *Gui) createLoaderPanel(currentView *gocui.View, prompt string) error {
return gui.createPopupPanel(createPopupPanelOpts{
returnToView: currentView,
prompt: prompt,
hasLoader: true,
returnFocusOnClose: true,
})
}
2020-08-15 08:38:16 +02:00
func (gui *Gui) ask(opts askOpts) error {
2020-08-15 08:36:39 +02:00
return gui.createPopupPanel(createPopupPanelOpts{
returnToView: opts.returnToView,
title: opts.title,
prompt: opts.prompt,
returnFocusOnClose: opts.returnFocusOnClose,
handleConfirm: opts.handleConfirm,
handleClose: opts.handleClose,
})
}
2020-08-15 08:38:16 +02:00
func (gui *Gui) prompt(currentView *gocui.View, title string, initialContent string, handleConfirm func(string) error) error {
2020-08-15 08:36:39 +02:00
return gui.createPopupPanel(createPopupPanelOpts{
returnToView: currentView,
title: title,
prompt: initialContent,
returnFocusOnClose: true,
editable: true,
handleConfirmPrompt: handleConfirm,
})
}
func (gui *Gui) wrappedConfirmationFunction(function func() 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 {
2019-11-17 03:02:39 +02:00
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-15 09:23:16 +02:00
return gui.closeConfirmationPrompt(returnFocusOnClose)
2020-08-15 08:36:39 +02:00
}
}
func (gui *Gui) wrappedPromptConfirmationFunction(function func(string) error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
if function != nil {
if err := function(v.Buffer()); err != nil {
return gui.surfaceError(err)
2018-08-05 14:00:02 +02:00
}
}
2019-11-17 03:02:39 +02:00
2020-08-15 09:23:16 +02:00
return gui.closeConfirmationPrompt(returnFocusOnClose)
2018-08-05 14:00:02 +02:00
}
2018-05-27 08:32:09 +02:00
}
2020-08-15 09:23:16 +02:00
func (gui *Gui) closeConfirmationPrompt(returnFocusOnClose bool) error {
view, err := gui.g.View("confirmation")
2018-08-05 14:00:02 +02:00
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-17 08:58:24 +02:00
view.Editable = false
if returnFocusOnClose {
2020-08-15 09:23:16 +02:00
if err := gui.returnFocus(view); err != nil {
panic(err)
}
2018-08-05 14:00:02 +02:00
}
for _, key := range gui.menuConfirmationKeys() {
if err := gui.g.DeleteKeybinding("confirmation", key, gocui.ModNone); err != nil {
return err
}
}
gui.g.DeleteKeybinding("confirmation", gui.getKey("universal.return"), gocui.ModNone)
2020-08-15 09:23:16 +02:00
return gui.g.DeleteView("confirmation")
2018-05-27 08:32:09 +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
// if we need to wrap, calculate height to fit content within view's width
if wrap {
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
}
}
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
}
2019-02-16 03:03:22 +02:00
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string, hasLoader bool) (*gocui.View, error) {
2020-08-15 09:23:16 +02:00
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(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
if hasLoader {
gui.g.StartTicking()
}
2018-08-05 14:00:02 +02:00
confirmationView.Title = title
2018-12-05 10:33:46 +02:00
confirmationView.Wrap = true
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 {
2020-08-15 08:36:39 +02:00
return gui.switchFocus(currentView, confirmationView)
2018-12-07 09:52:31 +02:00
})
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",
"credentials",
"menu"}
for _, viewName := range viewNames {
_, _ = gui.g.SetViewOnBottom(viewName)
}
2018-08-15 13:49:38 +02:00
}
2020-08-15 08:36:39 +02:00
func (gui *Gui) createPopupPanel(opts createPopupPanelOpts) error {
2018-08-15 13:49:38 +02:00
gui.onNewPopupPanel()
2020-08-15 08:36:39 +02:00
gui.g.Update(func(g *gocui.Gui) error {
2018-08-05 14:00:02 +02:00
// delete the existing confirmation panel if it exists
if view, _ := g.View("confirmation"); view != nil {
2020-08-15 09:23:16 +02:00
if err := gui.closeConfirmationPrompt(true); err != nil {
2019-11-17 08:58:24 +02:00
gui.Log.Error(err)
2018-08-05 14:00:02 +02:00
}
}
2020-08-15 08:36:39 +02:00
confirmationView, err := gui.prepareConfirmationPanel(opts.returnToView, opts.title, opts.prompt, opts.hasLoader)
2018-08-25 07:55:49 +02:00
if err != nil {
return err
2018-08-05 14:00:02 +02:00
}
2020-08-15 08:36:39 +02:00
confirmationView.Editable = opts.editable
if opts.editable {
2019-11-17 08:58:24 +02:00
go func() {
// TODO: remove this wait (right now if you remove it the EditGotoToEndOfLine method doesn't seem to work)
time.Sleep(time.Millisecond)
gui.g.Update(func(g *gocui.Gui) error {
confirmationView.EditGotoToEndOfLine()
return nil
})
}()
}
2020-08-15 08:36:39 +02:00
gui.renderString("confirmation", opts.prompt)
return gui.setKeyBindings(opts)
2018-08-05 14:00:02 +02:00
})
return nil
}
2020-08-15 08:36:39 +02:00
func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
actions := gui.Tr.TemplateLocalize(
"CloseConfirm",
Teml{
"keyBindClose": "esc",
"keyBindConfirm": "enter",
},
)
2020-03-26 12:39:59 +02:00
2020-08-15 08:36:39 +02:00
gui.renderString("options", actions)
var onConfirm func(*gocui.Gui, *gocui.View) error
2020-08-15 08:36:39 +02:00
if opts.handleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(opts.handleConfirmPrompt, opts.returnFocusOnClose)
2020-08-15 08:36:39 +02:00
} else {
onConfirm = gui.wrappedConfirmationFunction(opts.handleConfirm, opts.returnFocusOnClose)
}
for _, key := range gui.menuConfirmationKeys() {
if err := gui.g.SetKeybinding("confirmation", nil, key, gocui.ModNone, onConfirm); err != nil {
2020-08-15 08:36:39 +02:00
return err
}
}
return gui.g.SetKeybinding("confirmation", nil, gui.getKey("universal.return"), gocui.ModNone, gui.wrappedConfirmationFunction(opts.handleClose, opts.returnFocusOnClose))
2020-08-15 08:36:39 +02:00
}
func (gui *Gui) createErrorPanel(message string) error {
2018-08-05 14:00:02 +02:00
colorFunction := color.New(color.FgRed).SprintFunc()
coloredMessage := colorFunction(strings.TrimSpace(message))
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
return err
}
2020-08-15 08:38:16 +02:00
return gui.ask(askOpts{
2020-08-15 08:36:39 +02:00
returnToView: gui.g.CurrentView(),
title: gui.Tr.SLocalize("Error"),
prompt: coloredMessage,
returnFocusOnClose: true,
})
2020-03-28 02:47:54 +02:00
}
func (gui *Gui) surfaceError(err error) error {
return gui.createErrorPanel(err.Error())
2018-06-01 15:23:31 +02:00
}