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"
|
2022-08-06 10:50:52 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
2022-04-16 07:52:27 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
2020-11-28 10:53:39 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
2019-10-18 09:48:37 +02:00
|
|
|
"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 {
|
2022-01-16 05:46:53 +02:00
|
|
|
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 {
|
2020-11-28 04:14:48 +02:00
|
|
|
if err := function(getResponse()); err != nil {
|
2022-01-16 05:46:53 +02:00
|
|
|
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() {
|
2022-02-23 11:02:40 +02:00
|
|
|
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
|
2020-11-28 04:14:48 +02:00
|
|
|
|
2022-08-01 13:58:49 +02:00
|
|
|
gui.clearConfirmationViewKeyBindings()
|
2018-05-27 08:32:09 +02:00
|
|
|
}
|
|
|
|
|
2019-01-15 10:33:42 +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
|
2018-12-26 13:39:16 +02:00
|
|
|
// if we need to wrap, calculate height to fit content within view's width
|
2019-01-15 10:33:42 +02:00
|
|
|
if wrap {
|
2018-12-26 13:39:16 +02:00
|
|
|
for _, line := range lines {
|
2022-05-07 09:36:20 +02:00
|
|
|
lineCount += runewidth.StringWidth(line)/width + 1
|
2018-12-26 13:39:16 +02:00
|
|
|
}
|
|
|
|
} 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)
|
|
|
|
}
|
|
|
|
|
2022-05-08 04:46:48 +02:00
|
|
|
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)
|
2022-08-18 16:05:30 +02:00
|
|
|
_ = gui.Views.Confirmation.SetOrigin(0, 0)
|
2020-11-28 04:14:48 +02:00
|
|
|
|
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
|
2021-10-18 10:00:03 +02:00
|
|
|
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
|
2022-01-16 05:46:53 +02:00
|
|
|
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
|
2020-11-28 04:14:48 +02:00
|
|
|
}
|
|
|
|
|
2022-08-18 16:05:30 +02:00
|
|
|
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 {
|
2022-02-23 11:02:40 +02:00
|
|
|
gui.Mutexes.PopupMutex.Lock()
|
|
|
|
defer gui.Mutexes.PopupMutex.Unlock()
|
|
|
|
|
2023-01-03 16:07:16 +02:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
|
2022-02-23 11:02:40 +02:00
|
|
|
// 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)
|
2022-09-01 12:31:40 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-02-23 11:02:40 +02:00
|
|
|
gui.State.CurrentPopupOpts = &opts
|
|
|
|
|
2022-01-16 05:46:53 +02:00
|
|
|
return gui.c.PushContext(gui.State.Contexts.Confirmation)
|
2018-06-05 10:47:57 +02:00
|
|
|
}
|
|
|
|
|
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(
|
2022-01-16 05:46:53 +02:00
|
|
|
gui.c.Tr.CloseConfirm,
|
2020-10-04 02:00:48 +02:00
|
|
|
map[string]string{
|
2018-08-15 10:53:05 +02:00
|
|
|
"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)
|
2020-08-17 09:53:50 +02:00
|
|
|
}
|
|
|
|
|
2022-01-16 05:46:53 +02:00
|
|
|
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,
|
|
|
|
)
|
2020-11-28 04:14:48 +02:00
|
|
|
|
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-08-06 10:50:52 +02:00
|
|
|
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",
|
2022-08-06 10:50:52 +02:00
|
|
|
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",
|
2022-08-06 10:50:52 +02:00
|
|
|
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
2022-01-29 10:09:20 +02:00
|
|
|
Handler: func() error {
|
2021-10-17 08:49:32 +02:00
|
|
|
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-08-06 10:50:52 +02:00
|
|
|
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",
|
2022-08-06 10:50:52 +02:00
|
|
|
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",
|
2022-08-06 10:50:52 +02:00
|
|
|
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
|
|
|
},
|
2020-11-28 04:14:48 +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
|
|
|
|
}
|
2020-11-28 04:14:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-08-15 08:36:39 +02:00
|
|
|
}
|
|
|
|
|
2021-10-23 02:25:37 +02:00
|
|
|
func (gui *Gui) clearConfirmationViewKeyBindings() {
|
2022-01-16 05:46:53 +02:00
|
|
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
2022-08-06 10:50:52 +02:00
|
|
|
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), 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.Return), gocui.ModNone)
|
2021-10-23 02:25:37 +02:00
|
|
|
}
|
|
|
|
|
2022-01-16 05:46:53 +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-01-16 05:46:53 +02:00
|
|
|
})
|
|
|
|
}
|
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{
|
2022-08-06 10:50:52 +02:00
|
|
|
"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)
|
|
|
|
}
|