v0.32.0 0617278

app_window_lifecycle.go RawHistoryFolder

1package ui
2
3import (
4	"context"
5	"fmt"
6
7	"github.com/bnema/dumber/internal/application/usecase"
8	"github.com/bnema/dumber/internal/domain/entity"
9	"github.com/bnema/dumber/internal/logging"
10	"github.com/bnema/dumber/internal/ui/window"
11	"github.com/bnema/puregotk/v4/gtk"
12)
13
14func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*browserWindow, error) {
15	log := logging.FromContext(ctx)
16	log.Debug().
17		Str("url_host", logging.SafeURLHost(initialURL)).
18		Bool("using_factory", a.browserWindowFactory != nil).
19		Int("window_count_before", len(a.browserWindows)).
20		Msg("ui: create browser window started")
21
22	if a.browserWindowFactory != nil {
23		created, err := a.browserWindowFactory(ctx, initialURL)
24		if err != nil {
25			log.Warn().Err(err).
26				Str("url_host", logging.SafeURLHost(initialURL)).
27				Msg("ui: browser window factory failed")
28			return nil, err
29		}
30		created.ensureTabs()
31		a.wireBrowserWindowActivationTracking(created)
32		created.initChrome(ctx, a)
33		return created, nil
34	}
35
36	runtimeCfg := a.runtimeConfigSnapshot().UI
37	mainWindow, err := window.New(ctx, a.gtkApp, runtimeCfg.Workspace.TabBarPosition)
38	if err != nil {
39		log.Warn().Err(err).
40			Str("url_host", logging.SafeURLHost(initialURL)).
41			Msg("ui: GTK browser window shell creation failed")
42		return nil, err
43	}
44	browserWindow := &browserWindow{
45		id:         a.generateWindowID(),
46		initialURL: initialURL,
47		tabs:       entity.NewTabList(),
48		mainWindow: mainWindow,
49	}
50	a.wireBrowserWindowActivationTracking(browserWindow)
51	if a.mainWindow == nil {
52		a.mainWindow = mainWindow
53	}
54
55	closeRequestCb := func(_ gtk.Window) bool {
56		log.Info().Msg("browser window close requested")
57		a.removeBrowserWindow(browserWindow.id)
58		return false
59	}
60	mainWindow.Window().ConnectCloseRequest(&closeRequestCb)
61
62	a.initBrowserWindowOverlays(mainWindow, browserWindow, runtimeCfg)
63	a.wireBrowserWindowPermissionIndicator(browserWindow)
64	if a.kbDispatcher != nil {
65		a.initBrowserWindowInput(ctx, browserWindow)
66	}
67	if a.tabCoord != nil {
68		a.wireBrowserWindowTabBar(ctx, browserWindow)
69	}
70	browserWindow.initChrome(ctx, a)
71
72	// Apply GTK CSS styling from theme manager.
73	if a.deps == nil || a.deps.Theme == nil {
74		return browserWindow, nil
75	}
76	if display := mainWindow.Window().GetDisplay(); display != nil {
77		a.deps.Theme.ApplyToDisplay(ctx, display)
78	}
79	log.Debug().
80		Str("window_id", browserWindow.id).
81		Str("url_host", logging.SafeURLHost(initialURL)).
82		Msg("ui: create browser window completed")
83	return browserWindow, nil
84}
85
86func (a *App) openInitialBrowserWindowShell(ctx context.Context, initialURL string) error {
87	log := logging.FromContext(ctx)
88	created, err := a.createBrowserWindow(ctx, initialURL)
89	if err != nil {
90		log.Warn().Err(err).
91			Str("url_host", logging.SafeURLHost(initialURL)).
92			Int("window_count_after", len(a.browserWindows)).
93			Msg("ui: initial browser window shell creation failed")
94		return err
95	}
96	a.registerBrowserWindow(created)
97	log.Debug().
98		Str("window_id", created.id).
99		Str("url_host", logging.SafeURLHost(initialURL)).
100		Msg("ui: initial browser window shell registered")
101	a.activateBrowserWindow(created)
102	return nil
103}
104
105func (a *App) openFreshWindow(ctx context.Context, url string) error {
106	log := logging.FromContext(ctx)
107	log.Debug().
108		Str("url_host", logging.SafeURLHost(url)).
109		Int("window_count_before", len(a.browserWindows)).
110		Bool("has_tab_coord", a.tabCoord != nil).
111		Bool("has_tabs_uc", a.tabsUC != nil).
112		Msg("ui: open fresh window started")
113
114	created, err := a.createBrowserWindow(ctx, url)
115	if err != nil {
116		log.Warn().Err(err).
117			Str("url_host", logging.SafeURLHost(url)).
118			Int("window_count_after", len(a.browserWindows)).
119			Msg("ui: open fresh window could not create browser window")
120		return err
121	}
122	log.Debug().
123		Str("window_id", created.id).
124		Str("url_host", logging.SafeURLHost(url)).
125		Msg("ui: open fresh window created browser window shell")
126	a.registerBrowserWindow(created)
127	log.Debug().
128		Str("window_id", created.id).
129		Int("window_count_after_register", len(a.browserWindows)).
130		Msg("ui: open fresh window registered browser window")
131
132	var openErr error
133	path := "tabs_uc"
134	if a.tabCoord != nil {
135		path = "tab_coord"
136		log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tab coordinator")
137		openErr = a.openFreshWindowWithTabCoord(ctx, url, created)
138	} else if a.tabsUC == nil {
139		log.Debug().Str("window_id", created.id).Msg("ui: activating fresh browser window without tab use case")
140		a.activateBrowserWindow(created)
141		return nil
142	} else {
143		log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tabs use case")
144		openErr = a.openFreshWindowWithTabsUC(ctx, url, created)
145	}
146	if openErr != nil {
147		log.Warn().Err(openErr).
148			Str("window_id", created.id).
149			Str("path", path).
150			Int("window_count_after", len(a.browserWindows)).
151			Msg("ui: open fresh window initial tab creation failed")
152		return openErr
153	}
154	log.Debug().
155		Str("window_id", created.id).
156		Str("path", path).
157		Msg("ui: activating fresh browser window after tab creation")
158	a.activateBrowserWindow(created)
159	return nil
160}
161
162func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, created *browserWindow) error {
163	log := logging.FromContext(ctx)
164	previousTarget := a.tabCoord.CurrentTarget()
165	target := a.ensureTabTargetForBrowserWindow(created)
166	a.tabCoord.SetCurrentTarget(target)
167	defer func() {
168		a.tabCoord.SetCurrentTarget(previousTarget)
169	}()
170
171	log.Debug().
172		Str("window_id", created.id).
173		Str("url_host", logging.SafeURLHost(url)).
174		Msg("ui: tab coordinator create started for fresh window")
175	createdTab, createErr := a.tabCoord.Create(ctx, target, url)
176	if createErr != nil {
177		log.Warn().Err(createErr).
178			Str("window_id", created.id).
179			Msg("ui: tab coordinator create failed for fresh window; rolling back window")
180		a.removeBrowserWindow(created.id)
181		if created.mainWindow != nil {
182			created.mainWindow.Destroy()
183		}
184		return createErr
185	}
186	log.Debug().
187		Str("window_id", created.id).
188		Str("tab_id", string(createdTab.ID)).
189		Msg("ui: tab coordinator create completed for fresh window")
190	if a.workspaceViews[createdTab.ID] == nil {
191		log.Warn().
192			Str("window_id", created.id).
193			Str("tab_id", string(createdTab.ID)).
194			Msg("ui: fresh window tab has no workspace view; rolling back window")
195		if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
196			createdTabs.Remove(createdTab.ID)
197		}
198		if created.mainWindow != nil {
199			if tabBar := created.mainWindow.TabBar(); tabBar != nil {
200				tabBar.RemoveTab(createdTab.ID)
201				if activeTab := a.activeTabForBrowserWindow(created); activeTab != nil {
202					tabBar.SetActive(activeTab.ID)
203				}
204			}
205		}
206		a.removeBrowserWindow(created.id)
207		if created.mainWindow != nil {
208			created.mainWindow.Destroy()
209		}
210		return fmt.Errorf("workspace view not created for tab %s", createdTab.ID)
211	}
212	a.setBrowserWindowForTab(createdTab.ID, created)
213	// Tab was already created in the per-window TabList via tabCoord.Create;
214	// active state is managed by TabList.SetActive.
215	if created.mainWindow != nil {
216		created.mainWindow.Show()
217	}
218	return nil
219}
220
221func (a *App) openFreshWindowWithTabsUC(ctx context.Context, url string, created *browserWindow) error {
222	log := logging.FromContext(ctx)
223	log.Debug().
224		Str("window_id", created.id).
225		Str("url_host", logging.SafeURLHost(url)).
226		Msg("ui: tabs use case create started for fresh window")
227	output, err := a.tabsUC.Create(ctx, usecase.CreateTabInput{
228		TabList:    a.tabs,
229		InitialURL: url,
230	})
231	if err != nil {
232		log.Warn().Err(err).
233			Str("window_id", created.id).
234			Msg("ui: tabs use case create failed for fresh window; rolling back window")
235		a.removeBrowserWindow(created.id)
236		if created.mainWindow != nil {
237			created.mainWindow.Destroy()
238		}
239		return err
240	}
241	log.Debug().
242		Str("window_id", created.id).
243		Str("tab_id", string(output.Tab.ID)).
244		Msg("ui: tabs use case create completed for fresh window")
245	a.setBrowserWindowForTab(output.Tab.ID, created)
246	// Mirror the tabsUC-created tab into the new browser window's tab list.
247	// This path uses the tabs usecase directly instead of tabCoord.Create,
248	// so the per-window TabList must be populated explicitly.
249	a.ensureTabListForBrowserWindow(created).Add(output.Tab)
250	if a.widgetFactory != nil || a.workspaceViewCreateOverride != nil {
251		a.createWorkspaceView(ctx, output.Tab)
252		a.switchWorkspaceView(ctx, output.Tab.ID)
253	}
254	if a.workspaceViews[output.Tab.ID] == nil {
255		log.Warn().
256			Str("window_id", created.id).
257			Str("tab_id", string(output.Tab.ID)).
258			Msg("ui: fresh window tabs use case tab has no workspace view; rolling back window")
259		if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
260			createdTabs.Remove(output.Tab.ID)
261		}
262		if a.tabs != nil {
263			a.tabs.Remove(output.Tab.ID)
264		}
265		if created.mainWindow != nil {
266			if tabBar := created.mainWindow.TabBar(); tabBar != nil {
267				tabBar.RemoveTab(output.Tab.ID)
268				if activeTab := a.activeTabForBrowserWindow(created); activeTab != nil {
269					tabBar.SetActive(activeTab.ID)
270				}
271			}
272		}
273		a.removeBrowserWindow(created.id)
274		if created.mainWindow != nil {
275			created.mainWindow.Destroy()
276		}
277		return fmt.Errorf("workspace view not created for tab %s", output.Tab.ID)
278	}
279	if created.mainWindow != nil {
280		created.mainWindow.Show()
281	}
282	return nil
283}

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