v0.31.0 bf0756b

browser_window_history_sidebar.go RawHistoryFolder

1package ui
2
3import (
4	"context"
5	"fmt"
6
7	"github.com/bnema/dumber/internal/application/port"
8	"github.com/bnema/dumber/internal/application/usecase"
9	"github.com/bnema/dumber/internal/logging"
10	"github.com/bnema/dumber/internal/ui/component"
11	"github.com/bnema/dumber/internal/ui/window"
12)
13
14// initHistorySidebar creates and mounts the history sidebar into the
15// browser window's sidebar container. The sidebar is hidden by default.
16func (bw *browserWindow) initHistorySidebar(ctx context.Context, a *App) {
17	if bw == nil || a == nil || bw.mainWindow == nil || a.deps == nil || a.deps.HistoryUC == nil {
18		return
19	}
20	log := logging.FromContext(ctx)
21
22	cfg := a.buildHistorySidebarConfig(bw)
23
24	sidebar := component.NewHistorySidebar(ctx, cfg)
25	if sidebar == nil {
26		log.Warn().Msg("failed to create history sidebar")
27		return
28	}
29
30	bw.historySidebar = sidebar
31
32	// Mount into the main window's sidebar box.
33	bw.mainWindow.SetSidebarWidget(sidebar.Widget())
34	bw.historySidebar.Hide()
35	bw.mainWindow.SetSidebarVisible(false)
36	bw.sidebarVisible = false
37	bw.activeSidebarKind = nativeSidebarNone
38
39	// Apply sidebar width from config, falling back to the default 320px.
40	// The width is clamped to [280, 380] by SetSidebarWidth internally.
41	bw.applySidebarWidthConfig(a)
42
43	log.Debug().Msg("history sidebar initialized")
44}
45
46// buildHistorySidebarConfig constructs the HistorySidebarConfig for the given
47// browser window. Extracted from initHistorySidebar for testability.
48//
49//nolint:dupl // History and favorites sidebar configs intentionally mirror each other for separate component types.
50func (a *App) buildHistorySidebarConfig(bw *browserWindow) component.HistorySidebarConfig {
51	var historyUC port.HistorySidebarHistory
52	if a.deps != nil {
53		historyUC = a.deps.HistoryUC
54	}
55
56	return component.HistorySidebarConfig{
57		HistoryUC: historyUC,
58		OnNavigate: func(navCtx context.Context, url string) error {
59			return a.navigateHistorySidebarSelection(navCtx, bw, url)
60		},
61		OnNavigateKeepOpen: func(navCtx context.Context, url string) error {
62			return a.navigateHistorySidebarSelection(navCtx, bw, url)
63		},
64		OnOpenInNewPane: func(splitCtx context.Context, url string) error {
65			if a.wsCoord == nil || !a.hasBrowserWindow(bw) {
66				return nil
67			}
68			a.activateBrowserWindow(bw)
69			return a.wsCoord.SplitWithURL(splitCtx, usecase.SplitRight, url)
70		},
71		OnClose: func() {
72			a.hideAndRestoreFocusForBrowserWindow(bw)
73		},
74	}
75}
76
77func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWindow, url string) error {
78	if a == nil || bw == nil || !a.hasBrowserWindow(bw) {
79		return nil
80	}
81	return a.navigateFromBrowserWindow(ctx, bw, url)
82}
83
84// toggleHistorySidebar toggles sidebar visibility.
85func (bw *browserWindow) toggleHistorySidebar() {
86	if bw == nil || bw.historySidebar == nil {
87		return
88	}
89
90	if bw.sidebarVisible && bw.activeSidebarKind == nativeSidebarHistory {
91		bw.hideHistorySidebar()
92	} else {
93		bw.showHistorySidebar()
94	}
95}
96
97// showHistorySidebar makes the sidebar visible and grabs focus for the search
98// entry.
99func (bw *browserWindow) showHistorySidebar() {
100	if bw == nil || bw.historySidebar == nil || bw.mainWindow == nil {
101		return
102	}
103	bw.mountNativeSidebar(nativeSidebarHistory)
104	bw.historySidebar.Show()
105	bw.mainWindow.SetSidebarVisible(true)
106	bw.sidebarVisible = true
107	bw.activeSidebarKind = nativeSidebarHistory
108}
109
110// hideHistorySidebar hides the sidebar. Callers should also restore focus
111// to the active content pane after calling this.
112func (bw *browserWindow) hideHistorySidebar() {
113	if bw == nil || bw.historySidebar == nil || bw.mainWindow == nil {
114		return
115	}
116	bw.historySidebar.Hide()
117	bw.mainWindow.SetSidebarVisible(false)
118	bw.sidebarVisible = false
119	if bw.activeSidebarKind == nativeSidebarHistory {
120		bw.activeSidebarKind = nativeSidebarNone
121	}
122}
123
124func (bw *browserWindow) mountNativeSidebar(kind nativeSidebarKind) {
125	if bw == nil || bw.mainWindow == nil || bw.activeSidebarKind == kind {
126		return
127	}
128	if bw.historySidebar != nil {
129		bw.historySidebar.Hide()
130	}
131	if bw.favoritesSidebar != nil {
132		bw.favoritesSidebar.Hide()
133	}
134	switch kind {
135	case nativeSidebarHistory:
136		if bw.historySidebar != nil {
137			bw.mainWindow.SetSidebarWidget(bw.historySidebar.Widget())
138		}
139	case nativeSidebarFavorites:
140		if bw.favoritesSidebar != nil {
141			bw.mainWindow.SetSidebarWidget(bw.favoritesSidebar.Widget())
142		}
143	}
144}
145
146// historySidebarWidthConfig extracts the config-backed sidebar width and
147// applies it via the MainWindow.SetSidebarWidth path. It is called during
148// initialization and can be reused if config is reloaded at runtime.
149func historySidebarWidthConfig(widthPx int) window.SidebarWidthConfig {
150	cfg := window.SidebarDefaultWidth()
151	if widthPx > 0 {
152		cfg.WidthPx = widthPx
153	}
154	return cfg
155}
156
157// applySidebarWidthConfig applies the config-backed sidebar width to the
158// main window's sidebar.
159func (bw *browserWindow) applySidebarWidthConfig(a *App) {
160	if bw == nil || bw.mainWindow == nil || a == nil {
161		return
162	}
163	bw.mainWindow.SetSidebarWidth(historySidebarWidthConfig(a.runtimeConfigSnapshot().UI.SidebarWidth))
164}
165
166// toggleHistorySidebarAction is the keyboard-action handler for toggling the
167// history sidebar on the last focused browser window.
168func (a *App) toggleHistorySidebarAction(ctx context.Context) error {
169	bw := a.lastFocusedBrowserWindow()
170	if bw == nil {
171		return fmt.Errorf("history sidebar unavailable: no focused browser window")
172	}
173	if bw.historySidebar == nil {
174		return fmt.Errorf("history sidebar unavailable: native sidebar not initialized")
175	}
176	if bw.sidebarVisible && bw.activeSidebarKind == nativeSidebarHistory {
177		a.hideAndRestoreFocusForBrowserWindow(bw)
178		return nil
179	}
180	bw.toggleHistorySidebar()
181	return nil
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