v0.23.1 6c872d5

stacked_pane_manager.go RawHistoryFolder

1// Package component provides UI components for the browser.
2package component
3
4import (
5	"context"
6	"errors"
7
8	"github.com/bnema/dumber/internal/domain/entity"
9	"github.com/bnema/dumber/internal/logging"
10	"github.com/bnema/dumber/internal/ui/layout"
11)
12
13// ErrStackNotFound is returned when a StackedView cannot be found for a pane.
14var ErrStackNotFound = errors.New("stacked view not found for pane")
15
16// StackedPaneManager handles stacked pane operations.
17// With the new architecture, every pane is wrapped in a StackedView from the start,
18// making stacking operations trivial - we just add panes to the existing StackedView.
19type StackedPaneManager struct {
20	factory layout.WidgetFactory
21}
22
23// NewStackedPaneManager creates a new stacked pane manager.
24func NewStackedPaneManager(factory layout.WidgetFactory) *StackedPaneManager {
25	return &StackedPaneManager{
26		factory: factory,
27	}
28}
29
30// AddPaneToStack adds a new pane to the stack containing the active pane.
31// The StackedView is looked up from the TreeRenderer.
32func (spm *StackedPaneManager) AddPaneToStack(
33	ctx context.Context,
34	wsView *WorkspaceView,
35	activePaneID entity.PaneID,
36	newPaneView *PaneView,
37	title string,
38) error {
39	log := logging.FromContext(ctx)
40
41	// Get the StackedView containing the active pane
42	tr := wsView.TreeRenderer()
43	if tr == nil {
44		return errors.New("tree renderer not available")
45	}
46
47	stackedView := tr.GetStackedViewForPane(string(activePaneID))
48	if stackedView == nil {
49		return ErrStackNotFound
50	}
51
52	// Get the new pane's widget
53	widget := newPaneView.Widget()
54	if widget == nil {
55		return errors.New("new pane has no widget")
56	}
57
58	// Ensure widget is unparented
59	if widget.GetParent() != nil {
60		widget.Unparent()
61	}
62
63	// Add to the stack after the current active pane (not at end)
64	if title == "" {
65		title = "Untitled"
66	}
67
68	// Get current active index to insert after it
69	currentActiveIndex := stackedView.ActiveIndex()
70
71	log.Debug().
72		Str("active_pane", string(activePaneID)).
73		Str("new_pane", string(newPaneView.PaneID())).
74		Str("title", title).
75		Int("current_active_index", currentActiveIndex).
76		Int("stack_size_before", stackedView.Count()).
77		Msg("StackedPaneManager: inserting pane after active position")
78
79	stackedView.InsertPaneAfter(ctx, currentActiveIndex, string(newPaneView.PaneID()), title, "", widget)
80
81	// Register the new pane in the TreeRenderer's tracking
82	tr.RegisterPaneInStack(string(newPaneView.PaneID()), stackedView)
83
84	log.Info().
85		Str("active_pane", string(activePaneID)).
86		Str("new_pane", string(newPaneView.PaneID())).
87		Int("stack_size", stackedView.Count()).
88		Msg("added pane to stack")
89
90	return nil
91}
92
93// NavigateStack moves to the next or previous pane in a stack.
94// Returns the pane ID that became active.
95func (spm *StackedPaneManager) NavigateStack(
96	ctx context.Context,
97	wsView *WorkspaceView,
98	currentPaneID entity.PaneID,
99	direction string,
100) (entity.PaneID, error) {
101	log := logging.FromContext(ctx)
102
103	tr := wsView.TreeRenderer()
104	if tr == nil {
105		return "", errors.New("tree renderer not available")
106	}
107
108	stackedView := tr.GetStackedViewForPane(string(currentPaneID))
109	if stackedView == nil {
110		return "", ErrStackNotFound
111	}
112
113	// Only navigate if there are multiple panes
114	if stackedView.Count() <= 1 {
115		log.Debug().Msg("only one pane in stack, nothing to navigate")
116		return currentPaneID, nil
117	}
118
119	var err error
120	switch direction {
121	case "up":
122		err = stackedView.NavigatePrevious(ctx)
123	case "down":
124		err = stackedView.NavigateNext(ctx)
125	default:
126		return "", errors.New("invalid direction: use 'up' or 'down'")
127	}
128	if err != nil {
129		return "", err
130	}
131
132	// Get the newly active pane's widget to find its ID
133	// We need to find which pane is now at the active index
134	activeIndex := stackedView.ActiveIndex()
135
136	// Find the pane ID at this index by looking through registered panes
137	// This is a bit indirect but works with the current architecture
138	log.Debug().
139		Str("current_pane", string(currentPaneID)).
140		Str("direction", direction).
141		Int("new_index", activeIndex).
142		Msg("navigated stack")
143
144	// Return empty - caller should update based on domain model
145	return "", nil
146}
147
148// SetStackActiveCallback sets up the callback for when a stack pane is clicked.
149func (spm *StackedPaneManager) SetStackActiveCallback(
150	wsView *WorkspaceView,
151	paneID entity.PaneID,
152	callback func(index int),
153) {
154	tr := wsView.TreeRenderer()
155	if tr == nil {
156		return
157	}
158
159	stackedView := tr.GetStackedViewForPane(string(paneID))
160	if stackedView == nil {
161		return
162	}
163
164	stackedView.SetOnActivate(callback)
165}
166
167// GetStackedView returns the StackedView for a pane.
168func (spm *StackedPaneManager) GetStackedView(wsView *WorkspaceView, paneID entity.PaneID) *layout.StackedView {
169	tr := wsView.TreeRenderer()
170	if tr == nil {
171		return nil
172	}
173	return tr.GetStackedViewForPane(string(paneID))
174}
175
176// IsStacked returns true if the pane is in a stack with multiple panes.
177func (spm *StackedPaneManager) IsStacked(wsView *WorkspaceView, paneID entity.PaneID) bool {
178	sv := spm.GetStackedView(wsView, paneID)
179	return sv != nil && sv.Count() > 1
180}

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