1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-06 23:46:13 +02:00
lazygit/pkg/gui/popup/popup_handler.go
Stefan Haller 3e26be9845 Optionally pass disabled commands on to next handler
If a DisabledReason has its AllowFurtherDispatching flag set, it is returned as
a ErrKeybindingNotHandled error, instead of shown as a toast right away. This
allows gocui to continue to dispatch the keybinding, and we can unwrap the error
at the other end (in our global ErrorHandler) and display it then.

This allows having keybindings for the same key at the local and global levels,
and they will continue to be dispatched even if the first one returns a
DisabledReason. It is opt-in, so we only use it for cases where we know that a
local and a global handler share the same (default) keybinding.
2025-06-05 13:20:38 +02:00

137 lines
4.3 KiB
Go

package popup
import (
"context"
"errors"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type PopupHandler struct {
*common.Common
createPopupPanelFn func(context.Context, types.CreatePopupPanelOpts)
onErrorFn func() error
popContextFn func()
currentContextFn func() types.Context
createMenuFn func(types.CreateMenuOptions) error
withWaitingStatusFn func(message string, f func(gocui.Task) error)
withWaitingStatusSyncFn func(message string, f func() error) error
toastFn func(message string, kind types.ToastKind)
getPromptInputFn func() string
inDemo func() bool
}
var _ types.IPopupHandler = &PopupHandler{}
func NewPopupHandler(
common *common.Common,
createPopupPanelFn func(context.Context, types.CreatePopupPanelOpts),
onErrorFn func() error,
popContextFn func(),
currentContextFn func() types.Context,
createMenuFn func(types.CreateMenuOptions) error,
withWaitingStatusFn func(message string, f func(gocui.Task) error),
withWaitingStatusSyncFn func(message string, f func() error) error,
toastFn func(message string, kind types.ToastKind),
getPromptInputFn func() string,
inDemo func() bool,
) *PopupHandler {
return &PopupHandler{
Common: common,
createPopupPanelFn: createPopupPanelFn,
onErrorFn: onErrorFn,
popContextFn: popContextFn,
currentContextFn: currentContextFn,
createMenuFn: createMenuFn,
withWaitingStatusFn: withWaitingStatusFn,
withWaitingStatusSyncFn: withWaitingStatusSyncFn,
toastFn: toastFn,
getPromptInputFn: getPromptInputFn,
inDemo: inDemo,
}
}
func (self *PopupHandler) Menu(opts types.CreateMenuOptions) error {
return self.createMenuFn(opts)
}
func (self *PopupHandler) Toast(message string) {
self.toastFn(message, types.ToastKindStatus)
}
func (self *PopupHandler) ErrorToast(message string) {
self.toastFn(message, types.ToastKindError)
}
func (self *PopupHandler) SetToastFunc(f func(string, types.ToastKind)) {
self.toastFn = f
}
func (self *PopupHandler) WithWaitingStatus(message string, f func(gocui.Task) error) error {
self.withWaitingStatusFn(message, f)
return nil
}
func (self *PopupHandler) WithWaitingStatusSync(message string, f func() error) error {
return self.withWaitingStatusSyncFn(message, f)
}
func (self *PopupHandler) ErrorHandler(err error) error {
var notHandledError *types.ErrKeybindingNotHandled
if errors.As(err, &notHandledError) {
if !notHandledError.DisabledReason.ShowErrorInPanel {
if msg := notHandledError.DisabledReason.Text; len(msg) > 0 {
self.ErrorToast(self.Tr.DisabledMenuItemPrefix + msg)
}
return nil
}
}
// Need to set bold here explicitly; otherwise it gets cancelled by the red colouring.
coloredMessage := style.FgRed.SetBold().Sprint(strings.TrimSpace(err.Error()))
if err := self.onErrorFn(); err != nil {
return err
}
self.Alert(self.Tr.Error, coloredMessage)
return nil
}
func (self *PopupHandler) Alert(title string, message string) {
self.Confirm(types.ConfirmOpts{Title: title, Prompt: message})
}
func (self *PopupHandler) Confirm(opts types.ConfirmOpts) {
self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{
Title: opts.Title,
Prompt: opts.Prompt,
HandleConfirm: opts.HandleConfirm,
HandleClose: opts.HandleClose,
})
}
func (self *PopupHandler) Prompt(opts types.PromptOpts) {
self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{
Title: opts.Title,
Prompt: opts.InitialContent,
Editable: true,
HandleConfirmPrompt: opts.HandleConfirm,
HandleClose: opts.HandleClose,
HandleDeleteSuggestion: opts.HandleDeleteSuggestion,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
AllowEditSuggestion: opts.AllowEditSuggestion,
Mask: opts.Mask,
})
}
// returns the content that has currently been typed into the prompt. Useful for
// asynchronously updating the suggestions list under the prompt.
func (self *PopupHandler) GetPromptInput() string {
return self.getPromptInputFn()
}