From 3e26be9845f06439970ee1b4af02682295dc0f99 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 1 Jun 2025 16:11:20 +0200 Subject: [PATCH] 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. --- pkg/gui/keybindings.go | 4 ++++ pkg/gui/popup/popup_handler.go | 11 +++++++++++ pkg/gui/types/common.go | 4 ++++ pkg/gui/types/keybindings.go | 12 ++++++++++++ 4 files changed, 31 insertions(+) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index fe3b88ef3..63d25b622 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -525,6 +525,10 @@ func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error { func (gui *Gui) callKeybindingHandler(binding *types.Binding) error { if binding.GetDisabledReason != nil { if disabledReason := binding.GetDisabledReason(); disabledReason != nil { + if disabledReason.AllowFurtherDispatching { + return &types.ErrKeybindingNotHandled{DisabledReason: disabledReason} + } + if disabledReason.ShowErrorInPanel { return errors.New(disabledReason.Text) } diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go index 8ea50de2b..7fe77a8da 100644 --- a/pkg/gui/popup/popup_handler.go +++ b/pkg/gui/popup/popup_handler.go @@ -2,6 +2,7 @@ package popup import ( "context" + "errors" "strings" "github.com/jesseduffield/gocui" @@ -80,6 +81,16 @@ func (self *PopupHandler) WithWaitingStatusSync(message string, f func() error) } func (self *PopupHandler) ErrorHandler(err error) error { + var notHandledError *types.ErrKeybindingNotHandled + if errors.As(err, ¬HandledError) { + 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 { diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 096f95f02..2ca016825 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -202,6 +202,10 @@ type DisabledReason struct { // error panel instead. This is useful if the text is very long, or if it is // important enough to show it more prominently, or both. ShowErrorInPanel bool + + // If true, the keybinding dispatch mechanism will continue to look for + // other handlers for the keypress. + AllowFurtherDispatching bool } type MenuWidget int diff --git a/pkg/gui/types/keybindings.go b/pkg/gui/types/keybindings.go index db21e7bc9..e0e6af58a 100644 --- a/pkg/gui/types/keybindings.go +++ b/pkg/gui/types/keybindings.go @@ -55,3 +55,15 @@ type KeybindingGuards struct { OutsideFilterMode Guard NoPopupPanel Guard } + +type ErrKeybindingNotHandled struct { + DisabledReason *DisabledReason +} + +func (e ErrKeybindingNotHandled) Error() string { + return e.DisabledReason.Text +} + +func (e ErrKeybindingNotHandled) Unwrap() error { + return gocui.ErrKeybindingNotHandled +}