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

338 lines
9.7 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-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/gui/types"
"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 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 {
if err := function(getResponse()); err != nil {
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-23 05:09:36 +02:00
if !handlersManageFocus {
if err := gui.returnFromContext(); err != nil {
return err
}
}
2021-04-04 15:51:59 +02:00
gui.clearConfirmationViewKeyBindings()
gui.Views.Confirmation.Visible = false
gui.Views.Suggestions.Visible = false
2020-08-23 05:09:36 +02:00
return nil
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
}
2021-04-04 16:06:20 +02:00
func (gui *Gui) prepareConfirmationPanel(title, prompt string, hasLoader bool, findSuggestionsFunc func(string) []*types.Suggestion) 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
gui.Views.Confirmation.Wrap = true
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
2020-11-28 11:01:45 +02:00
gui.findSuggestions = findSuggestionsFunc
if findSuggestionsFunc != nil {
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
}
2021-04-04 16:06:20 +02:00
suggestionsView.Wrap = true
suggestionsView.FgColor = theme.GocuiDefaultTextColor
gui.setSuggestions([]*types.Suggestion{})
2021-04-04 15:51:59 +02:00
suggestionsView.Visible = true
}
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-04-04 16:06:20 +02:00
err := gui.prepareConfirmationPanel(opts.title, opts.prompt, opts.hasLoader, opts.findSuggestionsFunc)
2018-08-25 07:55:49 +02:00
if err != nil {
return err
2018-08-05 14:00:02 +02:00
}
2021-04-04 16:06:20 +02:00
gui.Views.Confirmation.Editable = opts.editable
gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.defaultEditor)
2020-08-15 08:36:39 +02:00
if opts.editable {
if err := gui.Views.Confirmation.SetEditorContent(opts.prompt); err != nil {
return err
}
} else {
if err := gui.renderStringSync(gui.Views.Confirmation, opts.prompt); err != nil {
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
}
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{
"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-11-28 11:30:16 +02:00
type confirmationKeybinding struct {
viewName string
key interface{}
handler func() error
}
2020-11-28 11:30:16 +02:00
keybindingConfig := gui.Config.GetUserConfig().Keybinding
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.getSelectedSuggestionValue() })
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-04-03 06:56:11 +02:00
handler: func() error { return gui.replaceContext(gui.State.Contexts.Suggestions) },
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 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
}
}
return nil
2020-08-15 08:36:39 +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 {
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-10-04 02:00:48 +02:00
title: gui.Tr.Error,
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
}