detached 7c1a5d9

feat(navigation): add direction-aware stack entry logic

commit
7c1a5d94194d5609fa9a13d2f993dcf875d51aaa
author
bnema · · 2025-11-13 19:05 UTC
committer
bnema · · 2025-11-13 22:18 UTC
parent
0319096
signature
unsigned
1 file · +54 -0
M internal/app/browser/workspace_pane_ops.go +54 -0
@@ -52,6 +52,60 @@ func (wm *WorkspaceManager) collectLeaves() []*paneNode {
5252 return wm.collectLeavesFrom(wm.root)
5353 }
5454
55+// collectLeavesFromWithDirection collects leaves from a subtree with direction-aware stack entry
56+func (wm *WorkspaceManager) collectLeavesFromWithDirection(node *paneNode, direction string) []*paneNode {
57+ var leaves []*paneNode
58+ visited := make(map[*paneNode]bool)
59+
60+ var walk func(*paneNode, int)
61+ walk = func(n *paneNode, depth int) {
62+ // Prevent infinite recursion and cycles
63+ const maxDepth = 50
64+ if n == nil || depth > maxDepth {
65+ return
66+ }
67+ if visited[n] {
68+ log.Printf("[workspace] collectLeavesFromWithDirection: cycle detected in tree")
69+ return
70+ }
71+ visited[n] = true
72+
73+ if n.isLeaf {
74+ leaves = append(leaves, n)
75+ return
76+ }
77+
78+ // Handle stacked panes - select entry pane based on direction
79+ if n.isStacked && len(n.stackedPanes) > 0 {
80+ var entryIndex int
81+ switch direction {
82+ case DirectionDown:
83+ // Coming from above - enter at first pane
84+ entryIndex = 0
85+ case DirectionUp:
86+ // Coming from below - enter at last pane
87+ entryIndex = len(n.stackedPanes) - 1
88+ default:
89+ // For horizontal directions, use active pane
90+ entryIndex = n.activeStackIndex
91+ if entryIndex < 0 || entryIndex >= len(n.stackedPanes) {
92+ entryIndex = 0
93+ }
94+ }
95+ if entryIndex >= 0 && entryIndex < len(n.stackedPanes) {
96+ walk(n.stackedPanes[entryIndex], depth+1)
97+ }
98+ return
99+ }
100+
101+ // Handle regular split nodes
102+ walk(n.left, depth+1)
103+ walk(n.right, depth+1)
104+ }
105+ walk(node, 0)
106+ return leaves
107+}
108+
55109 // collectLeavesFrom collects all leaf nodes from a given subtree
56110 func (wm *WorkspaceManager) collectLeavesFrom(node *paneNode) []*paneNode {
57111 var leaves []*paneNode
@@ -52,6 +52,60 @@ func (wm *WorkspaceManager) collectLeaves() []*paneNode {
52 return wm.collectLeavesFrom(wm.root)52 return wm.collectLeavesFrom(wm.root)
53 }53 }
54 54
55+// collectLeavesFromWithDirection collects leaves from a subtree with direction-aware stack entry
56+func (wm *WorkspaceManager) collectLeavesFromWithDirection(node *paneNode, direction string) []*paneNode {
57+ var leaves []*paneNode
58+ visited := make(map[*paneNode]bool)
59+
60+ var walk func(*paneNode, int)
61+ walk = func(n *paneNode, depth int) {
62+ // Prevent infinite recursion and cycles
63+ const maxDepth = 50
64+ if n == nil || depth > maxDepth {
65+ return
66+ }
67+ if visited[n] {
68+ log.Printf("[workspace] collectLeavesFromWithDirection: cycle detected in tree")
69+ return
70+ }
71+ visited[n] = true
72+
73+ if n.isLeaf {
74+ leaves = append(leaves, n)
75+ return
76+ }
77+
78+ // Handle stacked panes - select entry pane based on direction
79+ if n.isStacked && len(n.stackedPanes) > 0 {
80+ var entryIndex int
81+ switch direction {
82+ case DirectionDown:
83+ // Coming from above - enter at first pane
84+ entryIndex = 0
85+ case DirectionUp:
86+ // Coming from below - enter at last pane
87+ entryIndex = len(n.stackedPanes) - 1
88+ default:
89+ // For horizontal directions, use active pane
90+ entryIndex = n.activeStackIndex
91+ if entryIndex < 0 || entryIndex >= len(n.stackedPanes) {
92+ entryIndex = 0
93+ }
94+ }
95+ if entryIndex >= 0 && entryIndex < len(n.stackedPanes) {
96+ walk(n.stackedPanes[entryIndex], depth+1)
97+ }
98+ return
99+ }
100+
101+ // Handle regular split nodes
102+ walk(n.left, depth+1)
103+ walk(n.right, depth+1)
104+ }
105+ walk(node, 0)
106+ return leaves
107+}
108+
55 // collectLeavesFrom collects all leaf nodes from a given subtree109 // collectLeavesFrom collects all leaf nodes from a given subtree
56 func (wm *WorkspaceManager) collectLeavesFrom(node *paneNode) []*paneNode {110 func (wm *WorkspaceManager) collectLeavesFrom(node *paneNode) []*paneNode {
57 var leaves []*paneNode111 var leaves []*paneNode

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close