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

333 lines
9.8 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 (
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"
2022-01-29 10:15:46 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/context"
"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-11-28 11:30:16 +02:00
func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function func() error) func() error {
return func() error {
2022-01-15 03:04:00 +02:00
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
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
}
}
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 {
2022-01-15 03:04:00 +02:00
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
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
}
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 {
2022-02-22 11:17:26 +02:00
if err := gui.c.PopContext(); err != nil {
2020-08-23 05:09:36 +02:00
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) {
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(contentHeight int) (int, int, int, int) {
panelWidth := gui.getConfirmationPanelWidth()
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(
title,
prompt string,
hasLoader bool,
findSuggestionsFunc func(string) []*types.Suggestion,
editable bool,
2022-02-23 10:44:48 +02:00
mask bool,
2021-10-23 02:25:37 +02:00
) 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
2021-10-17 04:00:44 +02:00
// for now we do not support wrapping in our editor
gui.Views.Confirmation.Wrap = !editable
2021-04-04 16:06:20 +02:00
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
2022-02-23 10:44:48 +02:00
gui.Views.Confirmation.Mask = runeForMask(mask)
2020-11-28 11:01:45 +02:00
gui.findSuggestions = findSuggestionsFunc
if findSuggestionsFunc != nil {
suggestionsViewHeight := 11
2021-10-23 02:25:37 +02:00
suggestionsView, err := gui.g.SetView("suggestions", x0, y1+1, x1, y1+suggestionsViewHeight, 0)
if err != nil {
2021-04-04 16:06:20 +02:00
return err
}
suggestionsView.Wrap = false
2021-04-04 16:06:20 +02:00
suggestionsView.FgColor = theme.GocuiDefaultTextColor
2021-10-23 02:25:37 +02:00
gui.setSuggestions(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)
}
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
}
2022-01-29 10:15:46 +02:00
func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
2022-02-23 10:44:48 +02:00
// if a popup panel already appears we must ignore this current one. This is
// not great but it prevents lost state. The proper solution is to have a stack of
// popups. We could have a queue of types.CreatePopupPanelOpts so that if you
// close a popup and there's another one in the queue we show that.
// One important popup we don't want to interrupt is the credentials popup
// or a process might get stuck waiting on user input.
if gui.currentContext().GetKey() == context.CONFIRMATION_CONTEXT_KEY {
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
return nil
}
2022-01-15 03:04:00 +02:00
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
2022-01-28 11:44:36 +02:00
opts.Title,
opts.Prompt,
opts.HasLoader,
opts.FindSuggestionsFunc,
opts.Editable,
2022-02-23 10:44:48 +02:00
opts.Mask,
2022-01-15 03:04:00 +02:00
)
if err != nil {
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)
2022-01-15 03:04:00 +02:00
confirmationView.RenderTextArea()
} else {
2022-01-28 11:44:36 +02:00
if err := gui.renderString(confirmationView, opts.Prompt); err != nil {
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
2022-01-15 03:04:00 +02:00
if err := gui.setKeyBindings(opts); err != nil {
return err
}
return gui.c.PushContext(gui.State.Contexts.Confirmation)
}
2022-01-29 10:15:46 +02:00
func (gui *Gui) setKeyBindings(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 {
onConfirm = gui.wrappedPromptConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
2020-08-15 08:36:39 +02:00
} else {
2022-01-28 11:44:36 +02:00
onConfirm = gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirm)
}
keybindingConfig := gui.c.UserConfig.Keybinding
2021-10-23 02:25:37 +02:00
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
2022-01-28 11:44:36 +02:00
opts.HandlersManageFocus,
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",
2022-01-29 10:15:46 +02:00
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "confirmation",
2022-01-29 10:15:46 +02:00
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "confirmation",
2022-01-29 10:15:46 +02:00
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "confirmation",
2022-01-29 10:15:46 +02:00
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
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",
2022-01-30 11:34:59 +02:00
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "suggestions",
2022-01-30 11:34:59 +02:00
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onSuggestionConfirm,
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "suggestions",
2022-01-30 11:34:59 +02:00
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
2020-11-28 11:30:16 +02:00
},
{
2022-01-29 10:09:20 +02:00
ViewName: "suggestions",
2022-01-30 11:34:59 +02:00
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
2022-01-29 10:09:20 +02:00
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
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
2021-10-23 02:25:37 +02:00
_ = 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)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
}
func (gui *Gui) refreshSuggestions() {
gui.suggestionsAsyncHandler.Do(func() func() {
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
return func() { gui.setSuggestions(suggestions) }
})
}
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": gui.getKeyDisplay(keybindingConfig.Universal.Return),
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
},
)
return gui.renderString(gui.Views.Options, message)
}