v0.11.0 dc50343

pane.go RawHistoryFolder

1package browser
2
3import (
4	"log"
5	"time"
6
7	"github.com/bnema/dumber/internal/app/control"
8	"github.com/bnema/dumber/internal/app/messaging"
9	"github.com/bnema/dumber/pkg/webkit"
10)
11
12// BrowserPane bundles the per-pane WebView and its controllers.
13type BrowserPane struct {
14	webView              *webkit.WebView
15	navigationController *control.NavigationController
16	zoomController       *control.ZoomController
17	clipboardController  *control.ClipboardController
18	messageHandler       *messaging.Handler
19	shortcutHandler      *ShortcutHandler
20
21	// Multi-pane support
22	id            string
23	hasGUI        bool
24	guiComponents map[string]bool
25	lastFocusTime time.Time
26}
27
28func (p *BrowserPane) WebView() *webkit.WebView { return p.webView }
29
30func (p *BrowserPane) MessageHandler() *messaging.Handler { return p.messageHandler }
31
32func (p *BrowserPane) NavigationController() *control.NavigationController {
33	return p.navigationController
34}
35
36func (p *BrowserPane) ZoomController() *control.ZoomController { return p.zoomController }
37
38func (p *BrowserPane) ClipboardController() *control.ClipboardController {
39	return p.clipboardController
40}
41
42func (p *BrowserPane) ShortcutHandler() *ShortcutHandler { return p.shortcutHandler }
43
44// ID returns the unique pane identifier
45func (p *BrowserPane) ID() string { return p.id }
46
47// HasGUI returns whether GUI components are injected
48func (p *BrowserPane) HasGUI() bool { return p.hasGUI }
49
50// SetHasGUI marks GUI components as injected
51func (p *BrowserPane) SetHasGUI(has bool) { p.hasGUI = has }
52
53// SetID sets the pane identifier
54func (p *BrowserPane) SetID(id string) { p.id = id }
55
56// UpdateLastFocus updates the last focus time
57func (p *BrowserPane) UpdateLastFocus() { p.lastFocusTime = time.Now() }
58
59// Initialize GUI component tracking
60func (p *BrowserPane) initializeGUITracking() {
61	if p.guiComponents == nil {
62		p.guiComponents = make(map[string]bool)
63	}
64}
65
66// HasGUIComponent checks if a specific GUI component is loaded
67func (p *BrowserPane) HasGUIComponent(component string) bool {
68	if p.guiComponents == nil {
69		return false
70	}
71	return p.guiComponents[component]
72}
73
74// SetGUIComponent marks a GUI component as loaded
75func (p *BrowserPane) SetGUIComponent(component string, loaded bool) {
76	p.initializeGUITracking()
77	p.guiComponents[component] = loaded
78}
79
80// Cleanup releases pane resources
81func (p *BrowserPane) Cleanup() {
82	if p == nil {
83		return
84	}
85
86	if p.shortcutHandler != nil {
87		p.shortcutHandler.Detach()
88		log.Printf("[pane-%s] Cleaned up shortcuts", p.id)
89	}
90
91	if p.navigationController != nil {
92		p.navigationController.Detach()
93	}
94
95	if p.zoomController != nil {
96		p.zoomController.DetachWebView()
97	}
98
99	if p.clipboardController != nil {
100		p.clipboardController.Detach()
101	}
102
103	if p.messageHandler != nil {
104		p.messageHandler.SetWebView(nil)
105	}
106
107	// Notify JS to cleanup GUI
108	if p.webView != nil && p.hasGUI && !p.webView.IsDestroyed() {
109		cleanupScript := `try {
110	if (typeof window.__dumber_teardown === 'function') {
111		window.__dumber_teardown();
112	}
113	if (window.__dumber_gui_manager) {
114		window.__dumber_gui_manager.destroy();
115	}
116} catch (err) {
117	console.warn('[dumber] GUI teardown failed', err);
118}`
119		if err := p.webView.InjectScript(cleanupScript); err != nil {
120			log.Printf("[pane-%s] failed to inject cleanup script: %v", p.id, err)
121		}
122	}
123
124	// Clear tracking
125	p.guiComponents = nil
126	p.hasGUI = false
127}
128
129// CleanupFromWorkspace removes this pane from workspace tracking maps and app.panes slice
130// This should be called before Cleanup() to ensure proper workspace state management
131func (p *BrowserPane) CleanupFromWorkspace(wm interface{}) {
132	if p == nil || p.webView == nil {
133		return
134	}
135
136	// Type assert to avoid circular import - workspace manager will pass itself
137	if workspaceManager, ok := wm.(interface {
138		removeFromMaps(*webkit.WebView)
139		removeFromAppPanes(*BrowserPane)
140	}); ok {
141		workspaceManager.removeFromMaps(p.webView)
142		workspaceManager.removeFromAppPanes(p)
143		log.Printf("[pane-%s] Cleaned up from workspace tracking", p.id)
144	}
145}

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