diff --git a/container/container.go b/container/container.go index ab60218..ba69898 100644 --- a/container/container.go +++ b/container/container.go @@ -278,8 +278,11 @@ func (c *Container) updateFocusFromMouse(m *terminalapi.Mouse) { // changes the focused container. // Caller must hold c.mu. func (c *Container) updateFocusFromKeyboard(k *terminalapi.Keyboard) { - if c.opts.global.keyFocusNext != nil && *c.opts.global.keyFocusNext == k.Key { + switch { + case c.opts.global.keyFocusNext != nil && *c.opts.global.keyFocusNext == k.Key: c.focusTracker.next() + case c.opts.global.keyFocusPrevious != nil && *c.opts.global.keyFocusPrevious == k.Key: + c.focusTracker.previous() } } diff --git a/container/focus.go b/container/focus.go index 6b0b229..1e8c1f9 100644 --- a/container/focus.go +++ b/container/focus.go @@ -82,20 +82,20 @@ func (ft *focusTracker) setActive(c *Container) { func (ft *focusTracker) next() { var ( errStr string - first *Container - cont *Container + firstCont *Container + nextCont *Container focusNext bool ) - postOrder(rootCont(ft.container), &errStr, visitFunc(func(c *Container) error { - if cont != nil { + preOrder(rootCont(ft.container), &errStr, visitFunc(func(c *Container) error { + if nextCont != nil { // Already found the next container, nothing to do. return nil } - if c.isLeaf() && first == nil { + if c.isLeaf() && firstCont == nil { // Remember the first eligible container in case we "wrap" over, // i.e. finish the iteration before finding the next container. - first = c + firstCont = c } if ft.container == c { @@ -106,18 +106,49 @@ func (ft *focusTracker) next() { } if c.isLeaf() && focusNext { - cont = c + nextCont = c } return nil })) - if cont == nil && first != nil { + if nextCont == nil && firstCont != nil { // If the traversal finishes without finding the next container, move // focus back to the first container. - cont = first + nextCont = firstCont } - if cont != nil { - ft.setActive(cont) + if nextCont != nil { + ft.setActive(nextCont) + } +} + +// previous moves focus to the previous container. +func (ft *focusTracker) previous() { + var ( + errStr string + prevCont *Container + lastCont *Container + visitedCurr bool + ) + preOrder(rootCont(ft.container), &errStr, visitFunc(func(c *Container) error { + if ft.container == c { + visitedCurr = true + } + + if c.isLeaf() { + if !visitedCurr { + // Remember the last eligible container closest to the one + // currently focused. + prevCont = c + } + lastCont = c + } + return nil + })) + + if prevCont != nil { + ft.setActive(prevCont) + } else if lastCont != nil { + ft.setActive(lastCont) } } diff --git a/container/focus_test.go b/container/focus_test.go index 451efde..5f3a5a4 100644 --- a/container/focus_test.go +++ b/container/focus_test.go @@ -624,6 +624,120 @@ func TestFocusTrackerNextAndPrevious(t *testing.T) { wantFocused: contLocLeft, wantProcessed: 5, }, + { + desc: "keyPrevious does nothing when only root exists", + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + KeyFocusPrevious(keyPrevious), + ) + }, + events: []*terminalapi.Keyboard{ + {Key: keyPrevious}, + }, + wantFocused: contLocRoot, + wantProcessed: 1, + }, + { + desc: "keyPrevious focuses the last container", + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left(), + Right(), + ), + KeyFocusPrevious(keyPrevious), + ) + }, + events: []*terminalapi.Keyboard{ + {Key: keyPrevious}, + }, + wantFocused: contLocRight, + wantProcessed: 1, + }, + { + desc: "two keyPrevious presses focuses the first container", + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left(), + Right(), + ), + KeyFocusPrevious(keyPrevious), + ) + }, + events: []*terminalapi.Keyboard{ + {Key: keyPrevious}, + {Key: keyPrevious}, + }, + wantFocused: contLocLeft, + wantProcessed: 2, + }, + { + desc: "three keyPrevious presses focuses the second container again", + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left(), + Right(), + ), + KeyFocusPrevious(keyPrevious), + ) + }, + events: []*terminalapi.Keyboard{ + {Key: keyPrevious}, + {Key: keyPrevious}, + {Key: keyPrevious}, + }, + wantFocused: contLocRight, + wantProcessed: 3, + }, + { + desc: "four keyPrevious presses focuses the first container again", + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left(), + Right(), + ), + KeyFocusPrevious(keyPrevious), + ) + }, + events: []*terminalapi.Keyboard{ + {Key: keyPrevious}, + {Key: keyPrevious}, + {Key: keyPrevious}, + {Key: keyPrevious}, + }, + wantFocused: contLocLeft, + wantProcessed: 4, + }, + { + desc: "five keyPrevious presses focuses the second container again", + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left(), + Right(), + ), + KeyFocusPrevious(keyPrevious), + ) + }, + events: []*terminalapi.Keyboard{ + {Key: keyPrevious}, + {Key: keyPrevious}, + {Key: keyPrevious}, + {Key: keyPrevious}, + {Key: keyPrevious}, + }, + wantFocused: contLocRight, + wantProcessed: 5, + }, } for _, tc := range tests {