v0.31.0 bf0756b

runtime_config.go RawHistoryFolder

1package bootstrap
2
3import (
4	"maps"
5
6	"github.com/bnema/dumber/internal/application/port"
7	"github.com/bnema/dumber/internal/domain/entity"
8	"github.com/bnema/dumber/internal/infrastructure/config"
9)
10
11type configChangeManager interface {
12	Get() *config.Config
13	Watch() error
14	OnConfigChange(func(*config.Config))
15}
16
17type runtimeConfigProvider struct {
18	fallback entity.RuntimeConfigSnapshot
19	manager  configChangeManager
20}
21
22func NewRuntimeConfigProvider(cfg *config.Config, manager configChangeManager) port.RuntimeConfigProvider {
23	return &runtimeConfigProvider{
24		fallback: RuntimeConfigSnapshotFromConfig(cfg),
25		manager:  manager,
26	}
27}
28
29func (p *runtimeConfigProvider) Current() entity.RuntimeConfigSnapshot {
30	if p == nil {
31		return entity.RuntimeConfigSnapshot{}
32	}
33	if p.manager == nil {
34		return cloneRuntimeConfigSnapshot(p.fallback)
35	}
36	cfg := p.manager.Get()
37	if cfg == nil {
38		return cloneRuntimeConfigSnapshot(p.fallback)
39	}
40	return RuntimeConfigSnapshotFromConfig(cfg)
41}
42
43func (p *runtimeConfigProvider) Watch() error {
44	if p == nil || p.manager == nil {
45		return nil
46	}
47	return p.manager.Watch()
48}
49
50func (p *runtimeConfigProvider) OnChange(callback func(entity.RuntimeConfigSnapshot)) {
51	if p == nil || p.manager == nil || callback == nil {
52		return
53	}
54	p.manager.OnConfigChange(func(cfg *config.Config) {
55		if cfg == nil {
56			callback(cloneRuntimeConfigSnapshot(p.fallback))
57			return
58		}
59		callback(RuntimeConfigSnapshotFromConfig(cfg))
60	})
61}
62
63func EngineSettingsPayloadFromConfig(cfg *config.Config) entity.EngineSettingsPayload {
64	if cfg == nil {
65		return entity.EngineSettingsPayload{}
66	}
67	return entity.EngineSettingsPayload{
68		DefaultUIScale: cfg.DefaultUIScale,
69		WebContent: entity.EngineWebContentSettingsPayload{
70			SansFont:                  cfg.Appearance.SansFont,
71			SerifFont:                 cfg.Appearance.SerifFont,
72			MonospaceFont:             cfg.Appearance.MonospaceFont,
73			DefaultFontSize:           cfg.Appearance.DefaultFontSize,
74			EnableDevTools:            cfg.Debug.EnableDevTools,
75			CaptureConsole:            cfg.Logging.CaptureConsole,
76			DrawCompositingIndicators: cfg.Engine.WebKit.DrawCompositingIndicators,
77			HardwareDecoding:          engineHardwareDecodingModeFromConfig(cfg.Media.HardwareDecodingMode),
78			AutoCopyOnSelection:       cfg.Clipboard.AutoCopyOnSelection,
79		},
80	}
81}
82
83func RuntimeConfigSnapshotFromConfig(cfg *config.Config) entity.RuntimeConfigSnapshot {
84	if cfg == nil {
85		return entity.RuntimeConfigSnapshot{}
86	}
87	return entity.RuntimeConfigSnapshot{
88		EngineSettings: EngineSettingsPayloadFromConfig(cfg),
89		UI: entity.RuntimeUIConfig{
90			DefaultUIScale:      cfg.DefaultUIScale,
91			SidebarWidth:        cfg.SidebarWidth,
92			Appearance:          cfg.Appearance,
93			Workspace:           cloneWorkspaceConfig(cfg.Workspace),
94			Session:             cloneSessionConfig(cfg.Session),
95			Clipboard:           entity.RuntimeClipboardConfig{AutoCopyOnSelection: cfg.Clipboard.AutoCopyOnSelection},
96			SearchShortcuts:     runtimeSearchShortcutsFromConfig(cfg.SearchShortcuts),
97			DefaultSearchEngine: cfg.DefaultSearchEngine,
98			Omnibox: entity.RuntimeOmniboxConfig{
99				InitialBehavior:   cfg.Omnibox.InitialBehavior,
100				MostVisitedDays:   cfg.Omnibox.MostVisitedDays,
101				AutoOpenOnNewPane: cfg.Omnibox.AutoOpenOnNewPane,
102			},
103			Update: entity.RuntimeUpdateConfig{
104				EnableOnStartup:     cfg.Update.EnableOnStartup,
105				AutoDownload:        cfg.Update.AutoDownload,
106				NotifyOnNewSettings: cfg.Update.NotifyOnNewSettings,
107			},
108			Downloads: entity.RuntimeDownloadsConfig{Path: cfg.Downloads.Path},
109		},
110	}
111}
112
113func runtimeSearchShortcutsFromConfig(in map[string]config.SearchShortcut) map[string]entity.RuntimeSearchShortcut {
114	out := make(map[string]entity.RuntimeSearchShortcut, len(in))
115	for key, shortcut := range in {
116		out[key] = entity.RuntimeSearchShortcut{
117			URL:         shortcut.URL,
118			Description: shortcut.Description,
119		}
120	}
121	return out
122}
123
124func cloneRuntimeConfigSnapshot(snapshot entity.RuntimeConfigSnapshot) entity.RuntimeConfigSnapshot {
125	snapshot.UI.SearchShortcuts = cloneRuntimeSearchShortcuts(snapshot.UI.SearchShortcuts)
126	snapshot.UI.Workspace = cloneWorkspaceConfig(snapshot.UI.Workspace)
127	snapshot.UI.Session = cloneSessionConfig(snapshot.UI.Session)
128	return snapshot
129}
130
131func cloneRuntimeSearchShortcuts(in map[string]entity.RuntimeSearchShortcut) map[string]entity.RuntimeSearchShortcut {
132	if in == nil {
133		return nil
134	}
135	out := make(map[string]entity.RuntimeSearchShortcut, len(in))
136	maps.Copy(out, in)
137	return out
138}
139
140func cloneWorkspaceConfig(in entity.WorkspaceConfig) entity.WorkspaceConfig {
141	in.PaneMode.Actions = cloneActionBindings(in.PaneMode.Actions)
142	in.TabMode.Actions = cloneActionBindings(in.TabMode.Actions)
143	in.VimMode.Actions = cloneActionBindings(in.VimMode.Actions)
144	in.ResizeMode.Actions = cloneActionBindings(in.ResizeMode.Actions)
145	in.Shortcuts.Actions = cloneActionBindings(in.Shortcuts.Actions)
146	in.FloatingPane.Profiles = cloneFloatingPaneProfiles(in.FloatingPane.Profiles)
147	return in
148}
149
150func cloneSessionConfig(in entity.SessionConfig) entity.SessionConfig {
151	in.SessionMode.Actions = cloneActionBindings(in.SessionMode.Actions)
152	return in
153}
154
155func cloneActionBindings(in map[string]entity.ActionBinding) map[string]entity.ActionBinding {
156	if in == nil {
157		return nil
158	}
159	out := make(map[string]entity.ActionBinding, len(in))
160	for action, binding := range in {
161		binding.Keys = cloneStringSlice(binding.Keys)
162		out[action] = binding
163	}
164	return out
165}
166
167func cloneFloatingPaneProfiles(in map[string]entity.FloatingPaneProfile) map[string]entity.FloatingPaneProfile {
168	if in == nil {
169		return nil
170	}
171	out := make(map[string]entity.FloatingPaneProfile, len(in))
172	for name, profile := range in {
173		profile.Keys = cloneStringSlice(profile.Keys)
174		out[name] = profile
175	}
176	return out
177}
178
179func cloneStringSlice(in []string) []string {
180	if in == nil {
181		return nil
182	}
183	out := make([]string, len(in))
184	copy(out, in)
185	return out
186}
187
188func engineHardwareDecodingModeFromConfig(mode config.HardwareDecodingMode) entity.EngineHardwareDecodingMode {
189	switch mode {
190	case config.HardwareDecodingForce:
191		return entity.EngineHardwareDecodingForce
192	case config.HardwareDecodingDisable:
193		return entity.EngineHardwareDecodingDisable
194	default:
195		return entity.EngineHardwareDecodingAuto
196	}
197}

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