Give a parent view's keybindings the same precedence as a view's own

When a key matches several bindings of the same view, the first one wins;
when it matches several of the view's parent, the last one did.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-08-25 09:16:12 +02:00
parent b2a684bec1
commit 28c5f5748c
2 changed files with 77 additions and 1 deletions

View file

@ -1979,7 +1979,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
matchingParentViewKb = nil
break
}
if v != nil && g.matchView(v.ParentView, kb) {
if matchingParentViewKb == nil && v != nil && g.matchView(v.ParentView, kb) {
matchingParentViewKb = kb
}
if globalKb == nil && kb.viewName == "" {

View file

@ -0,0 +1,76 @@
package gocui
import (
"testing"
"github.com/stretchr/testify/assert"
)
// A view and its parent view, with the child holding the focus.
func setupParentAndChildView(t *testing.T, g *Gui) (*View, *View) {
t.Helper()
parent, _ := g.SetView("parent", 0, 0, 20, 10, 0)
child, _ := g.SetView("child", 0, 10, 20, 12, 0)
child.ParentView = parent
_, err := g.SetCurrentView(child.Name())
assert.NoError(t, err)
return parent, child
}
func TestKeybindingOfParentViewIsUsedWhenChildHasNone(t *testing.T) {
g := newTestGui(t)
parent, child := setupParentAndChildView(t, g)
pressed := []string{}
g.SetKeybinding(parent.Name(), NewKeyName(KeyArrowDown), func(*Gui, *View) error {
pressed = append(pressed, "parent")
return nil
})
g.SetKeybinding(child.Name(), NewKeyName(KeyEnter), func(*Gui, *View) error {
pressed = append(pressed, "child")
return nil
})
assert.NoError(t, g.onKey(&GocuiEvent{Type: eventKey, Key: NewKeyName(KeyArrowDown)}))
assert.NoError(t, g.onKey(&GocuiEvent{Type: eventKey, Key: NewKeyName(KeyEnter)}))
assert.Equal(t, []string{"parent", "child"}, pressed)
}
func TestFirstMatchingKeybindingOfParentViewWins(t *testing.T) {
g := newTestGui(t)
parent, _ := setupParentAndChildView(t, g)
pressed := []string{}
for _, name := range []string{"first", "second"} {
g.SetKeybinding(parent.Name(), NewKeyName(KeyArrowDown), func(*Gui, *View) error {
pressed = append(pressed, name)
return nil
})
}
assert.NoError(t, g.onKey(&GocuiEvent{Type: eventKey, Key: NewKeyName(KeyArrowDown)}))
assert.Equal(t, []string{"first"}, pressed)
}
func TestUnhandledKeybindingOfParentViewFallsThroughToEditor(t *testing.T) {
g := newTestGui(t)
parent, child := setupParentAndChildView(t, g)
edited := []Key{}
child.Editable = true
child.Editor = EditorFunc(func(_ *View, key Key) bool {
edited = append(edited, key)
return true
})
g.SetKeybinding(parent.Name(), NewKeyName(KeyArrowDown), func(*Gui, *View) error {
return ErrKeybindingNotHandled
})
assert.NoError(t, g.onKey(&GocuiEvent{Type: eventKey, Key: NewKeyName(KeyArrowDown)}))
assert.Equal(t, []Key{NewKeyName(KeyArrowDown)}, edited)
}