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
2022-03-17 19:13:40 +11:00

340 lines
9.8 KiB
Go

package gui
import (
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function func() error) func() error {
return func() error {
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
return err
}
if function != nil {
if err := function(); err != nil {
return gui.c.Error(err)
}
}
return nil
}
}
func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, function func(string) error, getResponse func() string) func() error {
return func() error {
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
return err
}
if function != nil {
if err := function(getResponse()); err != nil {
return gui.c.Error(err)
}
}
return nil
}
}
func (gui *Gui) closeConfirmationPrompt(handlersManageFocus bool) error {
gui.Mutexes.PopupMutex.Lock()
gui.State.CurrentPopupOpts = nil
gui.Mutexes.PopupMutex.Unlock()
// we've already closed it so we can just return
if !gui.Views.Confirmation.Visible {
return nil
}
if !handlersManageFocus {
if err := gui.c.PopContext(); err != nil {
return err
}
}
gui.clearConfirmationViewKeyBindings()
gui.Views.Confirmation.Visible = false
gui.Views.Suggestions.Visible = false
return nil
}
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
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)
}
return lineCount
}
func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
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) {
width, height := gui.g.Size()
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()
// we want a minimum width up to a point, then we do it based on ratio.
panelWidth := 4 * width / 7
minWidth := 80
if panelWidth < minWidth {
if width-2 < minWidth {
panelWidth = width - 2
} else {
panelWidth = minWidth
}
}
return panelWidth
}
func (gui *Gui) prepareConfirmationPanel(
title,
prompt string,
hasLoader bool,
findSuggestionsFunc func(string) []*types.Suggestion,
editable bool,
mask bool,
) error {
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(true, prompt)
// 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)
if err != nil {
return err
}
gui.Views.Confirmation.HasLoader = hasLoader
if hasLoader {
gui.g.StartTicking()
}
gui.Views.Confirmation.Title = title
// for now we do not support wrapping in our editor
gui.Views.Confirmation.Wrap = !editable
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
gui.Views.Confirmation.Mask = runeForMask(mask)
gui.findSuggestions = findSuggestionsFunc
if findSuggestionsFunc != nil {
suggestionsViewHeight := 11
suggestionsView, err := gui.g.SetView("suggestions", x0, y1+1, x1, y1+suggestionsViewHeight, 0)
if err != nil {
return err
}
suggestionsView.Wrap = false
suggestionsView.FgColor = theme.GocuiDefaultTextColor
gui.setSuggestions(findSuggestionsFunc(""))
suggestionsView.Visible = true
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
}
return nil
}
func runeForMask(mask bool) rune {
if mask {
return '*'
}
return 0
}
func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
gui.Mutexes.PopupMutex.Lock()
defer gui.Mutexes.PopupMutex.Unlock()
// 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 {
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
return nil
}
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
opts.Title,
opts.Prompt,
opts.HasLoader,
opts.FindSuggestionsFunc,
opts.Editable,
opts.Mask,
)
if err != nil {
return err
}
confirmationView := gui.Views.Confirmation
confirmationView.Editable = opts.Editable
confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor)
if opts.Editable {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.Prompt)
confirmationView.RenderTextArea()
} else {
if err := gui.renderString(confirmationView, opts.Prompt); err != nil {
return err
}
}
if err := gui.setKeyBindings(opts); err != nil {
return err
}
gui.State.CurrentPopupOpts = &opts
return gui.c.PushContext(gui.State.Contexts.Confirmation)
}
func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
actions := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": "esc",
"keyBindConfirm": "enter",
},
)
_ = gui.renderString(gui.Views.Options, actions)
var onConfirm func() error
if opts.HandleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
} else {
onConfirm = gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirm)
}
keybindingConfig := gui.c.UserConfig.Keybinding
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
opts.HandlersManageFocus,
opts.HandleConfirmPrompt,
gui.getSelectedSuggestionValue,
)
bindings := []*types.Binding{
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
return gui.replaceContext(gui.State.Contexts.Suggestions)
}
return nil
},
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
},
}
for _, binding := range bindings {
if err := gui.SetKeybinding(binding); err != nil {
return err
}
}
return nil
}
func (gui *Gui) clearConfirmationViewKeyBindings() {
keybindingConfig := gui.c.UserConfig.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)
_ = 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) }
})
}
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)
}