detached 4d6f6fd

refactor(tree-renderer): pass context as first parameter in layout rendering

Remove stored ctx field from TreeRenderer and update all methods
(Build, renderNode, renderLeaf, renderSplit, renderStacked) to accept
context as first parameter, following project convention of using
log := logging.FromContext(ctx) for logging operations.
commit
4d6f6fd701502dc4ad4ef01941af6968e406e549
author
brice · · 2025-12-17 06:44 UTC
parent
39222bf
signature
unsigned
1 file · +18 -19
M internal/ui/layout/tree.go +18 -19
@@ -34,13 +34,13 @@ type TreeRenderer struct {
3434 // paneToStack maps pane IDs to their containing StackedView.
3535 // Every leaf pane is wrapped in a StackedView for easy stacking later.
3636 paneToStack map[string]*StackedView
37- ctx context.Context
3837 mu sync.RWMutex
3938 }
4039
4140 // NewTreeRenderer creates a new tree renderer.
4241 func NewTreeRenderer(ctx context.Context, factory WidgetFactory, paneViewFactory PaneViewFactory) *TreeRenderer {
4342 log := logging.FromContext(ctx)
43+ log.Debug().Msg("creating tree renderer")
4444
4545 return &TreeRenderer{
4646 factory: factory,
@@ -48,13 +48,12 @@ func NewTreeRenderer(ctx context.Context, factory WidgetFactory, paneViewFactory
4848 logger: log.With().Str("component", "tree-renderer").Logger(),
4949 nodeToWidget: make(map[string]Widget),
5050 paneToStack: make(map[string]*StackedView),
51- ctx: ctx,
5251 }
5352 }
5453
5554 // Build constructs the entire widget tree from a root PaneNode.
5655 // Returns the root widget that can be embedded in a container.
57-func (tr *TreeRenderer) Build(root *entity.PaneNode) (Widget, error) {
56+func (tr *TreeRenderer) Build(ctx context.Context, root *entity.PaneNode) (Widget, error) {
5857 if root == nil {
5958 return nil, ErrNilRoot
6059 }
@@ -66,12 +65,12 @@ func (tr *TreeRenderer) Build(root *entity.PaneNode) (Widget, error) {
6665 tr.nodeToWidget = make(map[string]Widget)
6766 tr.paneToStack = make(map[string]*StackedView)
6867
69- return tr.renderNode(root)
68+ return tr.renderNode(ctx, root)
7069 }
7170
7271 // renderNode recursively renders a single node and its children.
7372 // Must be called with lock held.
74-func (tr *TreeRenderer) renderNode(node *entity.PaneNode) (Widget, error) {
73+func (tr *TreeRenderer) renderNode(ctx context.Context, node *entity.PaneNode) (Widget, error) {
7574 if node == nil {
7675 return nil, nil
7776 }
@@ -81,15 +80,15 @@ func (tr *TreeRenderer) renderNode(node *entity.PaneNode) (Widget, error) {
8180
8281 switch {
8382 case node.IsLeaf():
84- widget = tr.renderLeaf(node)
83+ widget = tr.renderLeaf(ctx, node)
8584 case node.IsSplit():
86- widget, err = tr.renderSplit(node)
85+ widget, err = tr.renderSplit(ctx, node)
8786 case node.IsStacked:
88- widget, err = tr.renderStacked(node)
87+ widget, err = tr.renderStacked(ctx, node)
8988 default:
9089 // Unknown node type - treat as leaf if it has a pane
9190 if node.Pane != nil {
92- widget = tr.renderLeaf(node)
91+ widget = tr.renderLeaf(ctx, node)
9392 }
9493 }
9594
@@ -108,7 +107,7 @@ func (tr *TreeRenderer) renderNode(node *entity.PaneNode) (Widget, error) {
108107 // renderLeaf creates a PaneView widget wrapped in a StackedView.
109108 // Every pane is wrapped in a StackedView from the start, making stacking trivial.
110109 // When there's only 1 pane, the StackedView shows no title bar.
111-func (tr *TreeRenderer) renderLeaf(node *entity.PaneNode) Widget {
110+func (tr *TreeRenderer) renderLeaf(ctx context.Context, node *entity.PaneNode) Widget {
112111 if tr.paneViewFactory == nil {
113112 return nil
114113 }
@@ -136,7 +135,7 @@ func (tr *TreeRenderer) renderLeaf(node *entity.PaneNode) Widget {
136135 Str("title", title).
137136 Msg("TreeRenderer.renderLeaf: wrapping pane in StackedView")
138137
139- stackedView.AddPane(tr.ctx, title, "", paneWidget)
138+ stackedView.AddPane(ctx, title, "", paneWidget)
140139
141140 // Track this pane's StackedView for later stacking operations
142141 if node.Pane != nil {
@@ -147,14 +146,14 @@ func (tr *TreeRenderer) renderLeaf(node *entity.PaneNode) Widget {
147146 }
148147
149148 // renderSplit creates a SplitView for a split node.
150-func (tr *TreeRenderer) renderSplit(node *entity.PaneNode) (Widget, error) {
149+func (tr *TreeRenderer) renderSplit(ctx context.Context, node *entity.PaneNode) (Widget, error) {
151150 // Render children first
152- leftWidget, err := tr.renderNode(node.Left())
151+ leftWidget, err := tr.renderNode(ctx, node.Left())
153152 if err != nil {
154153 return nil, err
155154 }
156155
157- rightWidget, err := tr.renderNode(node.Right())
156+ rightWidget, err := tr.renderNode(ctx, node.Right())
158157 if err != nil {
159158 return nil, err
160159 }
@@ -166,13 +165,13 @@ func (tr *TreeRenderer) renderSplit(node *entity.PaneNode) (Widget, error) {
166165 }
167166
168167 // Create split view
169- splitView := NewSplitView(tr.ctx, tr.factory, orientation, leftWidget, rightWidget, node.SplitRatio)
168+ splitView := NewSplitView(ctx, tr.factory, orientation, leftWidget, rightWidget, node.SplitRatio)
170169
171170 return splitView.Widget(), nil
172171 }
173172
174173 // renderStacked creates a StackedView for a stacked node.
175-func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {
174+func (tr *TreeRenderer) renderStacked(ctx context.Context, node *entity.PaneNode) (Widget, error) {
176175 stackedView := NewStackedView(tr.factory)
177176
178177 tr.logger.Debug().
@@ -181,7 +180,7 @@ func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {
181180
182181 // Add each child pane to the stack
183182 for _, child := range node.Children {
184- childWidget, err := tr.renderNode(child)
183+ childWidget, err := tr.renderNode(ctx, child)
185184 if err != nil {
186185 return nil, err
187186 }
@@ -196,7 +195,7 @@ func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {
196195 favicon = child.Pane.FaviconURL
197196 }
198197
199- stackedView.AddPane(tr.ctx, title, favicon, childWidget)
198+ stackedView.AddPane(ctx, title, favicon, childWidget)
200199
201200 // Override paneToStack to point to THIS stacked view, not the child's wrapper.
202201 // This ensures geometric navigation and stack sync work correctly.
@@ -207,7 +206,7 @@ func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {
207206
208207 // Set active index
209208 if node.ActiveStackIndex >= 0 && node.ActiveStackIndex < stackedView.Count() {
210- _ = stackedView.SetActive(tr.ctx, node.ActiveStackIndex)
209+ _ = stackedView.SetActive(ctx, node.ActiveStackIndex)
211210 }
212211
213212 return stackedView.Widget(), nil
@@ -34,13 +34,13 @@ type TreeRenderer struct {
34 // paneToStack maps pane IDs to their containing StackedView.34 // paneToStack maps pane IDs to their containing StackedView.
35 // Every leaf pane is wrapped in a StackedView for easy stacking later.35 // Every leaf pane is wrapped in a StackedView for easy stacking later.
36 paneToStack map[string]*StackedView36 paneToStack map[string]*StackedView
37- ctx context.Context
38 mu sync.RWMutex37 mu sync.RWMutex
39 }38 }
40 39
41 // NewTreeRenderer creates a new tree renderer.40 // NewTreeRenderer creates a new tree renderer.
42 func NewTreeRenderer(ctx context.Context, factory WidgetFactory, paneViewFactory PaneViewFactory) *TreeRenderer {41 func NewTreeRenderer(ctx context.Context, factory WidgetFactory, paneViewFactory PaneViewFactory) *TreeRenderer {
43 log := logging.FromContext(ctx)42 log := logging.FromContext(ctx)
43+ log.Debug().Msg("creating tree renderer")
44 44
45 return &TreeRenderer{45 return &TreeRenderer{
46 factory: factory,46 factory: factory,
@@ -48,13 +48,12 @@ func NewTreeRenderer(ctx context.Context, factory WidgetFactory, paneViewFactory
48 logger: log.With().Str("component", "tree-renderer").Logger(),48 logger: log.With().Str("component", "tree-renderer").Logger(),
49 nodeToWidget: make(map[string]Widget),49 nodeToWidget: make(map[string]Widget),
50 paneToStack: make(map[string]*StackedView),50 paneToStack: make(map[string]*StackedView),
51- ctx: ctx,
52 }51 }
53 }52 }
54 53
55 // Build constructs the entire widget tree from a root PaneNode.54 // Build constructs the entire widget tree from a root PaneNode.
56 // Returns the root widget that can be embedded in a container.55 // Returns the root widget that can be embedded in a container.
57-func (tr *TreeRenderer) Build(root *entity.PaneNode) (Widget, error) {56+func (tr *TreeRenderer) Build(ctx context.Context, root *entity.PaneNode) (Widget, error) {
58 if root == nil {57 if root == nil {
59 return nil, ErrNilRoot58 return nil, ErrNilRoot
60 }59 }
@@ -66,12 +65,12 @@ func (tr *TreeRenderer) Build(root *entity.PaneNode) (Widget, error) {
66 tr.nodeToWidget = make(map[string]Widget)65 tr.nodeToWidget = make(map[string]Widget)
67 tr.paneToStack = make(map[string]*StackedView)66 tr.paneToStack = make(map[string]*StackedView)
68 67
69- return tr.renderNode(root)68+ return tr.renderNode(ctx, root)
70 }69 }
71 70
72 // renderNode recursively renders a single node and its children.71 // renderNode recursively renders a single node and its children.
73 // Must be called with lock held.72 // Must be called with lock held.
74-func (tr *TreeRenderer) renderNode(node *entity.PaneNode) (Widget, error) {73+func (tr *TreeRenderer) renderNode(ctx context.Context, node *entity.PaneNode) (Widget, error) {
75 if node == nil {74 if node == nil {
76 return nil, nil75 return nil, nil
77 }76 }
@@ -81,15 +80,15 @@ func (tr *TreeRenderer) renderNode(node *entity.PaneNode) (Widget, error) {
81 80
82 switch {81 switch {
83 case node.IsLeaf():82 case node.IsLeaf():
84- widget = tr.renderLeaf(node)83+ widget = tr.renderLeaf(ctx, node)
85 case node.IsSplit():84 case node.IsSplit():
86- widget, err = tr.renderSplit(node)85+ widget, err = tr.renderSplit(ctx, node)
87 case node.IsStacked:86 case node.IsStacked:
88- widget, err = tr.renderStacked(node)87+ widget, err = tr.renderStacked(ctx, node)
89 default:88 default:
90 // Unknown node type - treat as leaf if it has a pane89 // Unknown node type - treat as leaf if it has a pane
91 if node.Pane != nil {90 if node.Pane != nil {
92- widget = tr.renderLeaf(node)91+ widget = tr.renderLeaf(ctx, node)
93 }92 }
94 }93 }
95 94
@@ -108,7 +107,7 @@ func (tr *TreeRenderer) renderNode(node *entity.PaneNode) (Widget, error) {
108 // renderLeaf creates a PaneView widget wrapped in a StackedView.107 // renderLeaf creates a PaneView widget wrapped in a StackedView.
109 // Every pane is wrapped in a StackedView from the start, making stacking trivial.108 // Every pane is wrapped in a StackedView from the start, making stacking trivial.
110 // When there's only 1 pane, the StackedView shows no title bar.109 // When there's only 1 pane, the StackedView shows no title bar.
111-func (tr *TreeRenderer) renderLeaf(node *entity.PaneNode) Widget {110+func (tr *TreeRenderer) renderLeaf(ctx context.Context, node *entity.PaneNode) Widget {
112 if tr.paneViewFactory == nil {111 if tr.paneViewFactory == nil {
113 return nil112 return nil
114 }113 }
@@ -136,7 +135,7 @@ func (tr *TreeRenderer) renderLeaf(node *entity.PaneNode) Widget {
136 Str("title", title).135 Str("title", title).
137 Msg("TreeRenderer.renderLeaf: wrapping pane in StackedView")136 Msg("TreeRenderer.renderLeaf: wrapping pane in StackedView")
138 137
139- stackedView.AddPane(tr.ctx, title, "", paneWidget)138+ stackedView.AddPane(ctx, title, "", paneWidget)
140 139
141 // Track this pane's StackedView for later stacking operations140 // Track this pane's StackedView for later stacking operations
142 if node.Pane != nil {141 if node.Pane != nil {
@@ -147,14 +146,14 @@ func (tr *TreeRenderer) renderLeaf(node *entity.PaneNode) Widget {
147 }146 }
148 147
149 // renderSplit creates a SplitView for a split node.148 // renderSplit creates a SplitView for a split node.
150-func (tr *TreeRenderer) renderSplit(node *entity.PaneNode) (Widget, error) {149+func (tr *TreeRenderer) renderSplit(ctx context.Context, node *entity.PaneNode) (Widget, error) {
151 // Render children first150 // Render children first
152- leftWidget, err := tr.renderNode(node.Left())151+ leftWidget, err := tr.renderNode(ctx, node.Left())
153 if err != nil {152 if err != nil {
154 return nil, err153 return nil, err
155 }154 }
156 155
157- rightWidget, err := tr.renderNode(node.Right())156+ rightWidget, err := tr.renderNode(ctx, node.Right())
158 if err != nil {157 if err != nil {
159 return nil, err158 return nil, err
160 }159 }
@@ -166,13 +165,13 @@ func (tr *TreeRenderer) renderSplit(node *entity.PaneNode) (Widget, error) {
166 }165 }
167 166
168 // Create split view167 // Create split view
169- splitView := NewSplitView(tr.ctx, tr.factory, orientation, leftWidget, rightWidget, node.SplitRatio)168+ splitView := NewSplitView(ctx, tr.factory, orientation, leftWidget, rightWidget, node.SplitRatio)
170 169
171 return splitView.Widget(), nil170 return splitView.Widget(), nil
172 }171 }
173 172
174 // renderStacked creates a StackedView for a stacked node.173 // renderStacked creates a StackedView for a stacked node.
175-func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {174+func (tr *TreeRenderer) renderStacked(ctx context.Context, node *entity.PaneNode) (Widget, error) {
176 stackedView := NewStackedView(tr.factory)175 stackedView := NewStackedView(tr.factory)
177 176
178 tr.logger.Debug().177 tr.logger.Debug().
@@ -181,7 +180,7 @@ func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {
181 180
182 // Add each child pane to the stack181 // Add each child pane to the stack
183 for _, child := range node.Children {182 for _, child := range node.Children {
184- childWidget, err := tr.renderNode(child)183+ childWidget, err := tr.renderNode(ctx, child)
185 if err != nil {184 if err != nil {
186 return nil, err185 return nil, err
187 }186 }
@@ -196,7 +195,7 @@ func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {
196 favicon = child.Pane.FaviconURL195 favicon = child.Pane.FaviconURL
197 }196 }
198 197
199- stackedView.AddPane(tr.ctx, title, favicon, childWidget)198+ stackedView.AddPane(ctx, title, favicon, childWidget)
200 199
201 // Override paneToStack to point to THIS stacked view, not the child's wrapper.200 // Override paneToStack to point to THIS stacked view, not the child's wrapper.
202 // This ensures geometric navigation and stack sync work correctly.201 // This ensures geometric navigation and stack sync work correctly.
@@ -207,7 +206,7 @@ func (tr *TreeRenderer) renderStacked(node *entity.PaneNode) (Widget, error) {
207 206
208 // Set active index207 // Set active index
209 if node.ActiveStackIndex >= 0 && node.ActiveStackIndex < stackedView.Count() {208 if node.ActiveStackIndex >= 0 && node.ActiveStackIndex < stackedView.Count() {
210- _ = stackedView.SetActive(tr.ctx, node.ActiveStackIndex)209+ _ = stackedView.SetActive(ctx, node.ActiveStackIndex)
211 }210 }
212 211
213 return stackedView.Widget(), nil212 return stackedView.Widget(), nil

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