v0.23.2 bfd5eb4

tab_bar.go RawHistoryFolder

1package component
2
3import (
4	"sync"
5
6	"github.com/bnema/dumber/internal/domain/entity"
7	"github.com/jwijenbergh/puregotk/v4/gtk"
8)
9
10// TabBar represents the horizontal tab bar widget.
11type TabBar struct {
12	box     *gtk.Box
13	buttons map[entity.TabID]*TabButton
14
15	// Active tab tracking
16	activeTabID entity.TabID
17
18	// Callbacks
19	onSwitch func(tabID entity.TabID)
20	onClose  func(tabID entity.TabID)
21	onCreate func()
22
23	mu sync.RWMutex
24}
25
26// NewTabBar creates a new tab bar widget.
27func NewTabBar() *TabBar {
28	tb := &TabBar{
29		buttons: make(map[entity.TabID]*TabButton),
30	}
31
32	// Create horizontal box container
33	tb.box = gtk.NewBox(gtk.OrientationHorizontalValue, 0)
34	if tb.box == nil {
35		return nil
36	}
37
38	// Configure the box
39	tb.box.SetHexpand(true)
40	tb.box.SetVexpand(false)
41	tb.box.SetVisible(true)
42	tb.box.AddCssClass("tab-bar")
43
44	return tb
45}
46
47// Widget returns the underlying GTK widget for embedding.
48func (tb *TabBar) Widget() *gtk.Widget {
49	return &tb.box.Widget
50}
51
52// Box returns the underlying GTK box.
53func (tb *TabBar) Box() *gtk.Box {
54	return tb.box
55}
56
57// AddTab adds a new tab button to the bar.
58func (tb *TabBar) AddTab(tab *entity.Tab) {
59	tb.mu.Lock()
60	defer tb.mu.Unlock()
61
62	// Check if already exists
63	if _, exists := tb.buttons[tab.ID]; exists {
64		return
65	}
66
67	// Create the button
68	button := NewTabButton(tab)
69	if button == nil {
70		return
71	}
72
73	// Set click handler
74	button.SetOnClick(func(tabID entity.TabID) {
75		if tb.onSwitch != nil {
76			tb.onSwitch(tabID)
77		}
78	})
79
80	// Add to container
81	tb.box.Append(button.Widget())
82	tb.buttons[tab.ID] = button
83}
84
85// RemoveTab removes a tab button from the bar.
86func (tb *TabBar) RemoveTab(tabID entity.TabID) {
87	tb.mu.Lock()
88	defer tb.mu.Unlock()
89
90	button, exists := tb.buttons[tabID]
91	if !exists {
92		return
93	}
94
95	// Remove from container
96	tb.box.Remove(button.Widget())
97	button.Destroy()
98	delete(tb.buttons, tabID)
99}
100
101// SetActive updates which tab is shown as active.
102func (tb *TabBar) SetActive(tabID entity.TabID) {
103	tb.mu.Lock()
104	defer tb.mu.Unlock()
105
106	// Deactivate previous
107	if tb.activeTabID != "" && tb.activeTabID != tabID {
108		if prevButton, exists := tb.buttons[tb.activeTabID]; exists {
109			prevButton.SetActive(false)
110		}
111	}
112
113	// Activate new
114	if button, exists := tb.buttons[tabID]; exists {
115		button.SetActive(true)
116	}
117
118	tb.activeTabID = tabID
119}
120
121// UpdateTitle updates the title of a specific tab button.
122func (tb *TabBar) UpdateTitle(tabID entity.TabID, title string) {
123	tb.mu.RLock()
124	defer tb.mu.RUnlock()
125
126	if button, exists := tb.buttons[tabID]; exists {
127		button.SetTitle(title)
128	}
129}
130
131// SetOnSwitch sets the callback for tab switch events.
132func (tb *TabBar) SetOnSwitch(fn func(tabID entity.TabID)) {
133	tb.onSwitch = fn
134}
135
136// SetOnClose sets the callback for tab close events.
137func (tb *TabBar) SetOnClose(fn func(tabID entity.TabID)) {
138	tb.onClose = fn
139}
140
141// SetOnCreate sets the callback for new tab creation.
142func (tb *TabBar) SetOnCreate(fn func()) {
143	tb.onCreate = fn
144}
145
146// Count returns the number of tabs in the bar.
147func (tb *TabBar) Count() int {
148	tb.mu.RLock()
149	defer tb.mu.RUnlock()
150	return len(tb.buttons)
151}
152
153// ActiveTabID returns the currently active tab ID.
154func (tb *TabBar) ActiveTabID() entity.TabID {
155	tb.mu.RLock()
156	defer tb.mu.RUnlock()
157	return tb.activeTabID
158}
159
160// SetVisible shows or hides the tab bar.
161func (tb *TabBar) SetVisible(visible bool) {
162	if tb.box == nil {
163		return
164	}
165	tb.box.SetVisible(visible)
166}
167
168// Destroy cleans up all tab bar resources.
169func (tb *TabBar) Destroy() {
170	tb.mu.Lock()
171	defer tb.mu.Unlock()
172
173	for _, button := range tb.buttons {
174		button.Destroy()
175	}
176	tb.buttons = nil
177
178	if tb.box != nil {
179		tb.box.Unref()
180		tb.box = nil
181	}
182}

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