main 5041353

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				Style:             cfg.Omnibox.Style,
100				InitialBehavior:   cfg.Omnibox.InitialBehavior,
101				MaxHistoryDays:    cfg.Omnibox.MaxHistoryDays,
102				AutoOpenOnNewPane: cfg.Omnibox.AutoOpenOnNewPane,
103			},
104			Update: entity.RuntimeUpdateConfig{
105				EnableOnStartup:     cfg.Update.EnableOnStartup,
106				AutoDownload:        cfg.Update.AutoDownload,
107				NotifyOnNewSettings: cfg.Update.NotifyOnNewSettings,
108			},
109			Downloads: entity.RuntimeDownloadsConfig{Path: cfg.Downloads.Path},
110		},
111	}
112}
113
114func runtimeSearchShortcutsFromConfig(in map[string]config.SearchShortcut) map[string]entity.RuntimeSearchShortcut {
115	out := make(map[string]entity.RuntimeSearchShortcut, len(in))
116	for key, shortcut := range in {
117		out[key] = entity.RuntimeSearchShortcut{
118			URL:         shortcut.URL,
119			Description: shortcut.Description,
120		}
121	}
122	return out
123}
124
125func cloneRuntimeConfigSnapshot(snapshot entity.RuntimeConfigSnapshot) entity.RuntimeConfigSnapshot {
126	snapshot.UI.SearchShortcuts = cloneRuntimeSearchShortcuts(snapshot.UI.SearchShortcuts)
127	snapshot.UI.Workspace = cloneWorkspaceConfig(snapshot.UI.Workspace)
128	snapshot.UI.Session = cloneSessionConfig(snapshot.UI.Session)
129	return snapshot
130}
131
132func cloneRuntimeSearchShortcuts(in map[string]entity.RuntimeSearchShortcut) map[string]entity.RuntimeSearchShortcut {
133	if in == nil {
134		return nil
135	}
136	out := make(map[string]entity.RuntimeSearchShortcut, len(in))
137	maps.Copy(out, in)
138	return out
139}
140
141func cloneWorkspaceConfig(in entity.WorkspaceConfig) entity.WorkspaceConfig {
142	in.PrefixMode.Actions = cloneActionBindings(in.PrefixMode.Actions)
143	in.PaneMode.Actions = cloneActionBindings(in.PaneMode.Actions)
144	in.TabMode.Actions = cloneActionBindings(in.TabMode.Actions)
145	in.VimMode.Actions = cloneActionBindings(in.VimMode.Actions)
146	in.ResizeMode.Actions = cloneActionBindings(in.ResizeMode.Actions)
147	in.Shortcuts.Actions = cloneActionBindings(in.Shortcuts.Actions)
148	in.FloatingPane.Profiles = cloneFloatingPaneProfiles(in.FloatingPane.Profiles)
149	return in
150}
151
152func cloneSessionConfig(in entity.SessionConfig) entity.SessionConfig {
153	in.SessionMode.Actions = cloneActionBindings(in.SessionMode.Actions)
154	return in
155}
156
157func cloneActionBindings(in map[string]entity.ActionBinding) map[string]entity.ActionBinding {
158	if in == nil {
159		return nil
160	}
161	out := make(map[string]entity.ActionBinding, len(in))
162	for action, binding := range in {
163		binding.Keys = cloneStringSlice(binding.Keys)
164		out[action] = binding
165	}
166	return out
167}
168
169func cloneFloatingPaneProfiles(in map[string]entity.FloatingPaneProfile) map[string]entity.FloatingPaneProfile {
170	if in == nil {
171		return nil
172	}
173	out := make(map[string]entity.FloatingPaneProfile, len(in))
174	for name, profile := range in {
175		profile.Keys = cloneStringSlice(profile.Keys)
176		out[name] = profile
177	}
178	return out
179}
180
181func cloneStringSlice(in []string) []string {
182	if in == nil {
183		return nil
184	}
185	out := make([]string, len(in))
186	copy(out, in)
187	return out
188}
189
190func engineHardwareDecodingModeFromConfig(mode config.HardwareDecodingMode) entity.EngineHardwareDecodingMode {
191	switch mode {
192	case config.HardwareDecodingForce:
193		return entity.EngineHardwareDecodingForce
194	case config.HardwareDecodingDisable:
195		return entity.EngineHardwareDecodingDisable
196	default:
197		return entity.EngineHardwareDecodingAuto
198	}
199}

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