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

330 lines
9.4 KiB
Go
Raw Normal View History

2018-08-14 11:05:26 +02:00
package gui
2018-05-26 05:23:39 +02:00
import (
2023-01-03 16:07:16 +02:00
"context"
2021-10-23 02:25:37 +02:00
"fmt"
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/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
2022-04-16 07:52:27 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/style"
"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"
2022-05-07 09:36:20 +02:00
"github.com/mattn/go-runewidth"
2018-05-26 05:23:39 +02:00
)
2022-05-07 07:42:36 +02:00
// This file is for the rendering of confirmation panels along with setting and handling associated
// keybindings.
2023-01-03 16:07:16 +02:00
func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error {
2020-11-28 11:30:16 +02:00
return func() error {
2023-01-03 16:07:16 +02:00
cancel()
2022-08-01 13:58:49 +02:00
if err := gui.c.PopContext(); err != nil {
2022-01-15 03:04:00 +02:00
return err
}
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 gui.c.Error(err)
2020-08-15 08:36:39 +02:00
}
}
2020-08-23 05:09:36 +02:00
return nil
2020-08-15 08:36:39 +02:00
}
}
2023-01-03 16:07:16 +02:00
func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error {
2020-11-28 11:30:16 +02:00
return func() error {
2023-01-03 16:07:16 +02:00
cancel()
2022-08-01 13:58:49 +02:00
if err := gui.c.PopContext(); err != nil {
2022-01-15 03:04:00 +02:00
return err
}
2020-08-15 08:36:39 +02:00
if function != nil {
if err := function(getResponse()); err != nil {
return gui.c.Error(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
return nil
2020-11-28 11:30:16 +02:00
}
2018-05-27 08:32:09 +02:00
}
2022-08-01 13:58:49 +02:00
func (gui *Gui) deactivateConfirmationPrompt() {
gui.Mutexes.PopupMutex.Lock()
gui.State.CurrentPopupOpts = nil
gui.Mutexes.PopupMutex.Unlock()
2021-04-04 15:51:59 +02:00
gui.Views.Confirmation.Visible = false
gui.Views.Suggestions.Visible = false
2022-08-01 13:58:49 +02:00
gui.clearConfirmationViewKeyBindings()
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 {
2022-05-07 09:36:20 +02:00
lineCount += runewidth.StringWidth(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) {
2022-02-05 08:04:10 +02:00
panelWidth := gui.getConfirmationPanelWidth()
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
}
func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
2022-02-05 08:04:10 +02:00
return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight)
}
func (gui *Gui) getConfirmationPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
2020-08-15 09:23:16 +02:00
width, height := gui.g.Size()
2022-02-05 08:04:10 +02:00
if panelHeight > height*3/4 {
panelHeight = height * 3 / 4
}
return width/2 - panelWidth/2,
height/2 - panelHeight/2 - panelHeight%2 - 1,
width/2 + panelWidth/2,
height/2 + panelHeight/2
}
func (gui *Gui) getConfirmationPanelWidth() int {
width, _ := 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
}
}
2022-02-05 08:04:10 +02:00
return panelWidth
2018-05-26 05:23:39 +02:00
}
2021-10-23 02:25:37 +02:00
func (gui *Gui) prepareConfirmationPanel(
2023-01-03 16:07:16 +02:00
ctx context.Context,
2022-07-30 08:10:29 +02:00
opts types.ConfirmOpts,
2021-10-23 02:25:37 +02:00
) error {
2022-07-30 08:10:29 +02:00
gui.Views.Confirmation.HasLoader = opts.HasLoader
if opts.HasLoader {
2023-01-03 16:07:16 +02:00
gui.g.StartTicking(ctx)
2018-08-05 14:00:02 +02:00
}
2022-07-30 08:10:29 +02:00
gui.Views.Confirmation.Title = opts.Title
2021-10-17 04:00:44 +02:00
// for now we do not support wrapping in our editor
2022-07-30 08:10:29 +02:00
gui.Views.Confirmation.Wrap = !opts.Editable
2021-04-04 16:06:20 +02:00
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
2022-07-30 08:10:29 +02:00
gui.Views.Confirmation.Mask = runeForMask(opts.Mask)
_ = gui.Views.Confirmation.SetOrigin(0, 0)
2022-07-30 08:10:29 +02:00
gui.findSuggestions = opts.FindSuggestionsFunc
if opts.FindSuggestionsFunc != nil {
2022-05-08 05:14:24 +02:00
suggestionsView := gui.Views.Suggestions
suggestionsView.Wrap = false
2021-04-04 16:06:20 +02:00
suggestionsView.FgColor = theme.GocuiDefaultTextColor
2022-07-30 08:10:29 +02:00
gui.setSuggestions(opts.FindSuggestionsFunc(""))
2021-04-04 15:51:59 +02:00
suggestionsView.Visible = true
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
}
gui.resizeConfirmationPanel()
2021-04-04 16:06:20 +02:00
return nil
2018-05-27 08:32:09 +02:00
}
2022-02-23 10:44:48 +02:00
func runeForMask(mask bool) rune {
if mask {
return '*'
}
return 0
}
2023-01-03 16:07:16 +02:00
func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPanelOpts) error {
gui.Mutexes.PopupMutex.Lock()
defer gui.Mutexes.PopupMutex.Unlock()
2023-01-03 16:07:16 +02:00
ctx, cancel := context.WithCancel(ctx)
// we don't allow interruptions of non-loader popups in case we get stuck somehow
// e.g. a credentials popup never gets its required user input so a process hangs
// forever.
// The proper solution is to have a queue of popup options
if gui.State.CurrentPopupOpts != nil && !gui.State.CurrentPopupOpts.HasLoader {
2022-02-23 10:44:48 +02:00
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
2023-01-03 16:07:16 +02:00
cancel()
2022-02-23 10:44:48 +02:00
return nil
}
2022-01-15 03:04:00 +02:00
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
2023-01-03 16:07:16 +02:00
ctx,
2022-07-30 08:10:29 +02:00
types.ConfirmOpts{
Title: opts.Title,
Prompt: opts.Prompt,
HasLoader: opts.HasLoader,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
Editable: opts.Editable,
Mask: opts.Mask,
})
2022-01-15 03:04:00 +02:00
if err != nil {
2023-01-03 16:07:16 +02:00
cancel()
2022-01-15 03:04:00 +02:00
return err
}
confirmationView := gui.Views.Confirmation
2022-01-28 11:44:36 +02:00
confirmationView.Editable = opts.Editable
2022-01-15 03:04:00 +02:00
confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor)
2022-01-28 11:44:36 +02:00
if opts.Editable {
2022-01-15 03:04:00 +02:00
textArea := confirmationView.TextArea
textArea.Clear()
2022-01-28 11:44:36 +02:00
textArea.TypeString(opts.Prompt)
gui.resizeConfirmationPanel()
2022-01-15 03:04:00 +02:00
confirmationView.RenderTextArea()
} else {
2022-04-16 07:52:27 +02:00
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
2023-01-03 16:07:16 +02:00
cancel()
2018-08-25 07:55:49 +02:00
return err
2018-08-05 14:00:02 +02:00
}
2022-01-15 03:04:00 +02:00
}
2019-11-17 08:58:24 +02:00
2023-01-03 16:07:16 +02:00
if err := gui.setKeyBindings(cancel, opts); err != nil {
cancel()
2022-01-15 03:04:00 +02:00
return err
}
gui.State.CurrentPopupOpts = &opts
return gui.c.PushContext(gui.State.Contexts.Confirmation)
}
2023-01-03 16:07:16 +02:00
func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopupPanelOpts) error {
2020-10-04 02:00:48 +02:00
actions := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
2020-10-04 02:00:48 +02:00
map[string]string{
"keyBindClose": "esc",
"keyBindConfirm": "enter",
},
)
2020-03-26 12:39:59 +02:00
2022-01-15 03:04:00 +02:00
_ = gui.renderString(gui.Views.Options, actions)
2020-11-28 11:30:16 +02:00
var onConfirm func() error
2022-01-28 11:44:36 +02:00
if opts.HandleConfirmPrompt != nil {
2023-01-03 16:07:16 +02:00
onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
2020-08-15 08:36:39 +02:00
} else {
2023-01-03 16:07:16 +02:00
onConfirm = gui.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
}
keybindingConfig := gui.c.UserConfig.Keybinding
2021-10-23 02:25:37 +02:00
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
2023-01-03 16:07:16 +02:00
cancel,
2022-01-28 11:44:36 +02:00
opts.HandleConfirmPrompt,
2021-10-23 02:25:37 +02:00
gui.getSelectedSuggestionValue,
)
2022-01-29 10:09:20 +02:00
bindings := []*types.Binding{
2020-11-28 11:30:16 +02:00
{
2022-01-29 10:09:20 +02:00
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
2022-01-29 10:09:20 +02:00
Handler: onConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
2022-01-29 10:09:20 +02:00
Handler: onConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
2023-01-03 16:07:16 +02:00
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
2022-01-29 10:09:20 +02:00
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
return gui.replaceContext(gui.State.Contexts.Suggestions)
}
return nil
},
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
2022-01-29 10:09:20 +02:00
Handler: onSuggestionConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
2022-01-29 10:09:20 +02:00
Handler: onSuggestionConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
2023-01-03 16:07:16 +02:00
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
2022-01-29 10:09:20 +02:00
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
2020-11-28 11:30:16 +02:00
},
}
2022-01-29 10:09:20 +02:00
for _, binding := range bindings {
if err := gui.SetKeybinding(binding); err != nil {
2020-11-28 11:30:16 +02:00
return err
}
}
return nil
2020-08-15 08:36:39 +02:00
}
2021-10-23 02:25:37 +02:00
func (gui *Gui) clearConfirmationViewKeyBindings() {
keybindingConfig := gui.c.UserConfig.Keybinding
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
2021-10-23 02:25:37 +02:00
}
func (gui *Gui) refreshSuggestions() {
gui.suggestionsAsyncHandler.Do(func() func() {
2022-12-30 13:49:08 +02:00
findSuggestionsFn := gui.findSuggestions
if findSuggestionsFn != nil {
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
return func() { gui.setSuggestions(suggestions) }
} else {
return func() {}
}
})
}
2022-02-23 09:48:50 +02:00
func (gui *Gui) handleAskFocused() error {
keybindingConfig := gui.c.UserConfig.Keybinding
message := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
2022-02-23 09:48:50 +02:00
},
)
return gui.renderString(gui.Views.Options, message)
}