2018-08-14 11:05:26 +02:00
|
|
|
package gui
|
2018-05-26 13:23:39 +10:00
|
|
|
|
|
|
|
import (
|
2023-01-03 23:07:16 +09:00
|
|
|
"context"
|
2021-10-23 11:25:37 +11:00
|
|
|
"fmt"
|
2018-08-05 22:00:02 +10:00
|
|
|
"strings"
|
2018-05-26 13:23:39 +10:00
|
|
|
|
2018-08-05 22:00:02 +10:00
|
|
|
"github.com/jesseduffield/gocui"
|
2022-08-06 18:50:52 +10:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
2022-04-16 15:52:27 +10:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
2020-11-28 19:53:39 +11: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 11:00:48 +11:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2022-05-07 16:36:20 +09:00
|
|
|
"github.com/mattn/go-runewidth"
|
2018-05-26 13:23:39 +10:00
|
|
|
)
|
|
|
|
|
2022-05-07 15:42:36 +10:00
|
|
|
// This file is for the rendering of confirmation panels along with setting and handling associated
|
|
|
|
// keybindings.
|
|
|
|
|
2023-01-03 23:07:16 +09:00
|
|
|
func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error {
|
2020-11-28 20:30:16 +11:00
|
|
|
return func() error {
|
2023-01-03 23:07:16 +09:00
|
|
|
cancel()
|
|
|
|
|
2022-08-01 21:58:49 +10:00
|
|
|
if err := gui.c.PopContext(); err != nil {
|
2022-01-15 12:04:00 +11:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-08-05 22:00:02 +10:00
|
|
|
if function != nil {
|
2020-08-15 16:36:39 +10:00
|
|
|
if err := function(); err != nil {
|
2022-01-16 14:46:53 +11:00
|
|
|
return gui.c.Error(err)
|
2020-08-15 16:36:39 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-23 13:09:36 +10:00
|
|
|
return nil
|
2020-08-15 16:36:39 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-03 23:07:16 +09:00
|
|
|
func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error {
|
2020-11-28 20:30:16 +11:00
|
|
|
return func() error {
|
2023-01-03 23:07:16 +09:00
|
|
|
cancel()
|
|
|
|
|
2022-08-01 21:58:49 +10:00
|
|
|
if err := gui.c.PopContext(); err != nil {
|
2022-01-15 12:04:00 +11:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-15 16:36:39 +10:00
|
|
|
if function != nil {
|
2020-11-28 13:14:48 +11:00
|
|
|
if err := function(getResponse()); err != nil {
|
2022-01-16 14:46:53 +11:00
|
|
|
return gui.c.Error(err)
|
2018-08-05 22:00:02 +10:00
|
|
|
}
|
|
|
|
}
|
2019-11-17 12:02:39 +11:00
|
|
|
|
2020-08-23 13:09:36 +10:00
|
|
|
return nil
|
2020-11-28 20:30:16 +11:00
|
|
|
}
|
2018-05-27 16:32:09 +10:00
|
|
|
}
|
|
|
|
|
2022-08-01 21:58:49 +10:00
|
|
|
func (gui *Gui) deactivateConfirmationPrompt() {
|
2022-02-23 20:02:40 +11:00
|
|
|
gui.Mutexes.PopupMutex.Lock()
|
|
|
|
gui.State.CurrentPopupOpts = nil
|
|
|
|
gui.Mutexes.PopupMutex.Unlock()
|
|
|
|
|
2021-04-04 23:51:59 +10:00
|
|
|
gui.Views.Confirmation.Visible = false
|
|
|
|
gui.Views.Suggestions.Visible = false
|
2020-11-28 13:14:48 +11:00
|
|
|
|
2022-08-01 21:58:49 +10:00
|
|
|
gui.clearConfirmationViewKeyBindings()
|
2018-05-27 16:32:09 +10:00
|
|
|
}
|
|
|
|
|
2019-01-15 19:33:42 +11:00
|
|
|
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
2018-08-05 22:00:02 +10:00
|
|
|
lines := strings.Split(message, "\n")
|
|
|
|
lineCount := 0
|
2018-12-26 20:39:16 +09:00
|
|
|
// if we need to wrap, calculate height to fit content within view's width
|
2019-01-15 19:33:42 +11:00
|
|
|
if wrap {
|
2018-12-26 20:39:16 +09:00
|
|
|
for _, line := range lines {
|
2022-05-07 16:36:20 +09:00
|
|
|
lineCount += runewidth.StringWidth(line)/width + 1
|
2018-12-26 20:39:16 +09:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
lineCount = len(lines)
|
2018-08-05 22:00:02 +10:00
|
|
|
}
|
|
|
|
return lineCount
|
2018-05-26 13:23:39 +10:00
|
|
|
}
|
|
|
|
|
2023-01-21 11:38:14 +00:00
|
|
|
func (gui *Gui) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
|
|
|
|
return gui.getPopupPanelDimensionsAux(panelWidth, contentHeight)
|
2022-02-05 17:04:10 +11:00
|
|
|
}
|
|
|
|
|
2023-01-21 11:38:14 +00:00
|
|
|
func (gui *Gui) getPopupPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
|
2020-08-15 17:23:16 +10:00
|
|
|
width, height := gui.g.Size()
|
2022-02-05 17:04:10 +11: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 21:48:29 +10:00
|
|
|
// we want a minimum width up to a point, then we do it based on ratio.
|
2019-11-10 22:07:45 +11:00
|
|
|
panelWidth := 4 * width / 7
|
2020-08-12 21:48:29 +10:00
|
|
|
minWidth := 80
|
|
|
|
if panelWidth < minWidth {
|
|
|
|
if width-2 < minWidth {
|
|
|
|
panelWidth = width - 2
|
|
|
|
} else {
|
|
|
|
panelWidth = minWidth
|
|
|
|
}
|
|
|
|
}
|
2022-02-05 17:04:10 +11:00
|
|
|
|
|
|
|
return panelWidth
|
2018-05-26 13:23:39 +10:00
|
|
|
}
|
|
|
|
|
2021-10-23 11:25:37 +11:00
|
|
|
func (gui *Gui) prepareConfirmationPanel(
|
2023-01-03 23:07:16 +09:00
|
|
|
ctx context.Context,
|
2022-07-30 08:10:29 +02:00
|
|
|
opts types.ConfirmOpts,
|
2021-10-23 11:25:37 +11:00
|
|
|
) error {
|
2022-07-30 08:10:29 +02:00
|
|
|
gui.Views.Confirmation.HasLoader = opts.HasLoader
|
|
|
|
if opts.HasLoader {
|
2023-01-03 23:07:16 +09:00
|
|
|
gui.g.StartTicking(ctx)
|
2018-08-05 22:00:02 +10:00
|
|
|
}
|
2022-07-30 08:10:29 +02:00
|
|
|
gui.Views.Confirmation.Title = opts.Title
|
2021-10-17 13:00:44 +11: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-05 00:06:20 +10: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 23:05:30 +09:00
|
|
|
_ = gui.Views.Confirmation.SetOrigin(0, 0)
|
2020-11-28 13:14:48 +11:00
|
|
|
|
2022-07-30 08:10:29 +02:00
|
|
|
gui.findSuggestions = opts.FindSuggestionsFunc
|
|
|
|
if opts.FindSuggestionsFunc != nil {
|
2022-05-08 13:14:24 +10:00
|
|
|
suggestionsView := gui.Views.Suggestions
|
2021-10-18 19:00:03 +11:00
|
|
|
suggestionsView.Wrap = false
|
2021-04-05 00:06:20 +10:00
|
|
|
suggestionsView.FgColor = theme.GocuiDefaultTextColor
|
2022-07-30 08:10:29 +02:00
|
|
|
gui.setSuggestions(opts.FindSuggestionsFunc(""))
|
2021-04-04 23:51:59 +10:00
|
|
|
suggestionsView.Visible = true
|
2022-01-16 14:46:53 +11:00
|
|
|
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
|
2020-11-28 13:14:48 +11:00
|
|
|
}
|
|
|
|
|
2022-08-18 23:05:30 +09:00
|
|
|
gui.resizeConfirmationPanel()
|
2021-04-05 00:06:20 +10:00
|
|
|
return nil
|
2018-05-27 16:32:09 +10:00
|
|
|
}
|
|
|
|
|
2022-02-23 19:44:48 +11:00
|
|
|
func runeForMask(mask bool) rune {
|
|
|
|
if mask {
|
|
|
|
return '*'
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2023-01-03 23:07:16 +09:00
|
|
|
func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPanelOpts) error {
|
2022-02-23 20:02:40 +11:00
|
|
|
gui.Mutexes.PopupMutex.Lock()
|
|
|
|
defer gui.Mutexes.PopupMutex.Unlock()
|
|
|
|
|
2023-01-03 23:07:16 +09:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
|
2022-02-23 20:02:40 +11: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 19:44:48 +11:00
|
|
|
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
|
2023-01-03 23:07:16 +09:00
|
|
|
cancel()
|
2022-02-23 19:44:48 +11:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-15 12:04:00 +11:00
|
|
|
// remove any previous keybindings
|
|
|
|
gui.clearConfirmationViewKeyBindings()
|
|
|
|
|
|
|
|
err := gui.prepareConfirmationPanel(
|
2023-01-03 23:07:16 +09: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 12:04:00 +11:00
|
|
|
if err != nil {
|
2023-01-03 23:07:16 +09:00
|
|
|
cancel()
|
2022-01-15 12:04:00 +11:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
confirmationView := gui.Views.Confirmation
|
2022-01-28 20:44:36 +11:00
|
|
|
confirmationView.Editable = opts.Editable
|
2023-01-21 11:38:14 +00:00
|
|
|
confirmationView.Editor = gocui.EditorFunc(gui.promptEditor)
|
2022-01-15 12:04:00 +11:00
|
|
|
|
2022-01-28 20:44:36 +11:00
|
|
|
if opts.Editable {
|
2022-01-15 12:04:00 +11:00
|
|
|
textArea := confirmationView.TextArea
|
|
|
|
textArea.Clear()
|
2022-01-28 20:44:36 +11:00
|
|
|
textArea.TypeString(opts.Prompt)
|
2022-09-01 19:31:40 +09:00
|
|
|
gui.resizeConfirmationPanel()
|
2022-01-15 12:04:00 +11:00
|
|
|
confirmationView.RenderTextArea()
|
|
|
|
} else {
|
2022-04-16 15:52:27 +10:00
|
|
|
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
|
2023-01-03 23:07:16 +09:00
|
|
|
cancel()
|
2018-08-25 15:55:49 +10:00
|
|
|
return err
|
2018-08-05 22:00:02 +10:00
|
|
|
}
|
2022-01-15 12:04:00 +11:00
|
|
|
}
|
2019-11-17 17:58:24 +11:00
|
|
|
|
2023-01-03 23:07:16 +09:00
|
|
|
if err := gui.setKeyBindings(cancel, opts); err != nil {
|
|
|
|
cancel()
|
2022-01-15 12:04:00 +11:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-02-23 20:02:40 +11:00
|
|
|
gui.State.CurrentPopupOpts = &opts
|
|
|
|
|
2022-01-16 14:46:53 +11:00
|
|
|
return gui.c.PushContext(gui.State.Contexts.Confirmation)
|
2018-06-05 18:47:57 +10:00
|
|
|
}
|
|
|
|
|
2023-01-03 23:07:16 +09:00
|
|
|
func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopupPanelOpts) error {
|
2020-10-04 11:00:48 +11:00
|
|
|
actions := utils.ResolvePlaceholderString(
|
2022-01-16 14:46:53 +11:00
|
|
|
gui.c.Tr.CloseConfirm,
|
2020-10-04 11:00:48 +11:00
|
|
|
map[string]string{
|
2018-08-15 10:53:05 +02:00
|
|
|
"keyBindClose": "esc",
|
|
|
|
"keyBindConfirm": "enter",
|
|
|
|
},
|
|
|
|
)
|
2020-03-26 21:39:59 +11:00
|
|
|
|
2022-01-15 12:04:00 +11:00
|
|
|
_ = gui.renderString(gui.Views.Options, actions)
|
2020-11-28 20:30:16 +11:00
|
|
|
var onConfirm func() error
|
2022-01-28 20:44:36 +11:00
|
|
|
if opts.HandleConfirmPrompt != nil {
|
2023-01-03 23:07:16 +09:00
|
|
|
onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
|
2020-08-15 16:36:39 +10:00
|
|
|
} else {
|
2023-01-03 23:07:16 +09:00
|
|
|
onConfirm = gui.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
|
2020-08-17 17:53:50 +10:00
|
|
|
}
|
|
|
|
|
2022-01-16 14:46:53 +11:00
|
|
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
2021-10-23 11:25:37 +11:00
|
|
|
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
|
2023-01-03 23:07:16 +09:00
|
|
|
cancel,
|
2022-01-28 20:44:36 +11:00
|
|
|
opts.HandleConfirmPrompt,
|
2021-10-23 11:25:37 +11:00
|
|
|
gui.getSelectedSuggestionValue,
|
|
|
|
)
|
2020-11-28 13:14:48 +11:00
|
|
|
|
2022-01-29 19:09:20 +11:00
|
|
|
bindings := []*types.Binding{
|
2020-11-28 20:30:16 +11:00
|
|
|
{
|
2022-01-29 19:09:20 +11:00
|
|
|
ViewName: "confirmation",
|
2022-08-06 18:50:52 +10:00
|
|
|
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
2022-01-29 19:09:20 +11:00
|
|
|
Handler: onConfirm,
|
2020-11-28 20:30:16 +11:00
|
|
|
},
|
|
|
|
{
|
2022-01-29 19:09:20 +11:00
|
|
|
ViewName: "confirmation",
|
2022-08-06 18:50:52 +10:00
|
|
|
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
2023-01-03 23:07:16 +09:00
|
|
|
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
|
2020-11-28 20:30:16 +11:00
|
|
|
},
|
|
|
|
{
|
2022-01-29 19:09:20 +11:00
|
|
|
ViewName: "confirmation",
|
2022-08-06 18:50:52 +10:00
|
|
|
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
2022-01-29 19:09:20 +11:00
|
|
|
Handler: func() error {
|
2021-10-17 17:49:32 +11:00
|
|
|
if len(gui.State.Suggestions) > 0 {
|
|
|
|
return gui.replaceContext(gui.State.Contexts.Suggestions)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
2020-11-28 20:30:16 +11:00
|
|
|
},
|
|
|
|
{
|
2022-01-29 19:09:20 +11:00
|
|
|
ViewName: "suggestions",
|
2022-08-06 18:50:52 +10:00
|
|
|
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
2022-01-29 19:09:20 +11:00
|
|
|
Handler: onSuggestionConfirm,
|
2020-11-28 20:30:16 +11:00
|
|
|
},
|
|
|
|
{
|
2022-01-29 19:09:20 +11:00
|
|
|
ViewName: "suggestions",
|
2022-08-06 18:50:52 +10:00
|
|
|
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
2023-01-03 23:07:16 +09:00
|
|
|
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
|
2020-11-28 20:30:16 +11:00
|
|
|
},
|
|
|
|
{
|
2022-01-29 19:09:20 +11:00
|
|
|
ViewName: "suggestions",
|
2022-08-06 18:50:52 +10:00
|
|
|
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
2022-01-29 19:09:20 +11:00
|
|
|
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
|
2020-11-28 20:30:16 +11:00
|
|
|
},
|
2020-11-28 13:14:48 +11:00
|
|
|
}
|
|
|
|
|
2022-01-29 19:09:20 +11:00
|
|
|
for _, binding := range bindings {
|
|
|
|
if err := gui.SetKeybinding(binding); err != nil {
|
2020-11-28 20:30:16 +11:00
|
|
|
return err
|
|
|
|
}
|
2020-11-28 13:14:48 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-08-15 16:36:39 +10:00
|
|
|
}
|
|
|
|
|
2021-10-23 11:25:37 +11:00
|
|
|
func (gui *Gui) clearConfirmationViewKeyBindings() {
|
2022-01-16 14:46:53 +11:00
|
|
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
2022-08-06 18:50:52 +10: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 11:25:37 +11:00
|
|
|
}
|
|
|
|
|
2022-01-16 14:46:53 +11:00
|
|
|
func (gui *Gui) refreshSuggestions() {
|
|
|
|
gui.suggestionsAsyncHandler.Do(func() func() {
|
2022-12-30 22:49:08 +11:00
|
|
|
findSuggestionsFn := gui.findSuggestions
|
|
|
|
if findSuggestionsFn != nil {
|
|
|
|
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
|
|
|
|
return func() { gui.setSuggestions(suggestions) }
|
|
|
|
} else {
|
|
|
|
return func() {}
|
|
|
|
}
|
2022-01-16 14:46:53 +11:00
|
|
|
})
|
|
|
|
}
|
2022-02-23 18:48:50 +11:00
|
|
|
|
|
|
|
func (gui *Gui) handleAskFocused() error {
|
|
|
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
|
|
|
|
|
|
|
message := utils.ResolvePlaceholderString(
|
|
|
|
gui.c.Tr.CloseConfirm,
|
|
|
|
map[string]string{
|
2022-08-06 18:50:52 +10:00
|
|
|
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
|
|
|
|
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
|
2022-02-23 18:48:50 +11:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return gui.renderString(gui.Views.Options, message)
|
|
|
|
}
|