defaults.go RawHistoryFolder

1package config
2
3import (
4	"os"
5	"path/filepath"
6
7	"github.com/bnema/dumber/internal/domain/entity"
8)
9
10// Default configuration constants
11const (
12	// History defaults
13	defaultMaxHistoryEntries = 10000 // entries
14	defaultRetentionDays     = 365   // 1 year
15
16	// Dmenu defaults
17	defaultMaxHistoryDays = 30 // days
18
19	// Logging defaults
20	defaultMaxLogAgeDays = 7   // days
21	defaultMaxLogFiles   = 100 // session log files
22
23	// Appearance defaults
24	defaultSidebarWidth                  = 320 // px, clamped to [280, 380]
25	defaultFontSize                      = 16  // points
26	defaultExternalThemeProvider         = "noctalia"
27	defaultExternalThemeFormat           = "colors-json"
28	defaultExternalThemeColorsFilename   = "colors.json"
29	defaultExternalThemeTemplateFormat   = "dumber-json"
30	defaultExternalThemeTemplateFilename = "noctalia-theme.json"
31
32	// Workspace defaults
33	defaultNewPaneURL = "about:blank"
34
35	// Omnibox defaults
36	defaultOmniboxStyle             = "command"
37	defaultOmniboxInitialBehavior   = OmniboxInitialBehaviorRecent
38	defaultOmniboxMaxHistoryDays    = 30
39	defaultOmniboxAutoOpenOnNewPane = false
40
41	// Workspace defaults
42	defaultPaneActivationShortcut          = "ctrl+p"
43	defaultPaneTimeoutMilliseconds         = 3000
44	defaultTabActivationShortcut           = "ctrl+t"
45	defaultTabTimeoutMilliseconds          = 3000
46	defaultResizeActivationShortcut        = "ctrl+n"
47	defaultResizeTimeoutMilliseconds       = 3000
48	defaultPageActivationShortcut          = "ctrl+y"
49	defaultPageTimeoutMilliseconds         = 0
50	defaultPageSequenceTimeoutMilliseconds = 500
51	defaultResizeStepPercent               = 5.0
52	defaultResizeMinPanePercent            = 10.0
53	defaultTabBarPosition                  = "bottom"
54	defaultPopupPlacement                  = "right"
55	defaultExternalLinkBehavior            = ExternalLinkBehaviorWindowed
56	defaultExternalLinkPlacement           = ExternalLinkPlacementRight
57	defaultFloatingPaneWidthPct            = 0.82
58	defaultFloatingPaneHeightPct           = 0.72
59
60	// Session defaults
61	defaultSessionActivationShortcut  = "ctrl+o"
62	defaultSessionTimeoutMilliseconds = 3000
63	defaultSnapshotIntervalMs         = 5000
64	defaultMaxExitedSessions          = 50
65	defaultMaxExitedSessionAgeDays    = 7
66
67	// Workspace styling defaults
68	// Active pane border (overlay)
69	defaultBorderWidth = 1
70	defaultBorderColor = "@theme_selected_bg_color"
71
72	// Mode border width (consolidated for all modes)
73	defaultModeBorderWidth = 4
74
75	// Mode colors (used for both borders and toaster)
76	defaultPaneModeColor    = "#4A90E2" // Blue for pane mode
77	defaultTabModeColor     = "#FFA500" // Orange for tab mode
78	defaultSessionModeColor = "#9B59B6" // Purple for session mode
79	defaultVimModeColor     = "#F7768E"
80	defaultResizeModeColor  = "#00D4AA" // Cyan/teal for resize mode
81
82	// Mode indicator toaster
83	defaultModeIndicatorToasterEnabled = true
84
85	// Other styling
86	defaultTransitionDuration = 120
87	defaultUIScale            = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)
88
89	// Performance defaults
90	defaultZoomCacheSize              = 256 // domains to cache (~20KB memory)
91	defaultWebViewPoolPrewarmCount    = 4   // WebViews to pre-create at startup
92	defaultCEFWindowlessFrameRate     = 60  // static OSR frame rate when adaptive CEF pacing is disabled
93	defaultCEFWindowlessFrameRateMax  = 240 // adaptive OSR frame-rate hard cap
94	defaultCEFScrollMultiplier        = 1.0
95	defaultCEFScrollPreciseMultiplier = 2.5
96	defaultCEFScrollMaxDelta          = 0
97	defaultCEFScrollTouchpadInertia   = true
98	defaultCEFScrollWheelSmoothing    = true
99	defaultCEFTouchpadNavigation      = true
100	// Require a deliberate swipe before history navigation so ordinary
101	// horizontal scrolling is unlikely to cross the commit threshold.
102	defaultCEFTouchpadNavigationDelta = 320.0
103	defaultCEFTouchpadNavigationRatio = 0.5
104
105	// Skia threading defaults (0 = unset, -1 = unset for GPU threads)
106	defaultSkiaCPUPaintingThreads = 0
107	defaultSkiaGPUPaintingThreads = -1 // -1 means unset; 0 would disable GPU tile painting
108)
109
110func defaultBrowsingContextConfig() BrowsingContextConfig {
111	return BrowsingContextConfig{
112		Behavior:             PopupBehaviorSplit,
113		Placement:            defaultPopupPlacement,
114		OpenInNewPane:        true,
115		FollowPaneContext:    true,
116		BlankTargetBehavior:  "stacked",
117		EnableSmartDetection: true,
118		OAuthAutoClose:       true,
119	}
120}
121
122// getDefaultLogDir returns the default log directory, falls back to empty string on error
123func getDefaultLogDir() string {
124	logDir, err := GetLogDir()
125	if err != nil {
126		return ""
127	}
128	return logDir
129}
130
131// getDefaultExternalThemePath returns Noctalia's native colors.json path.
132func getDefaultExternalThemePath() string {
133	return getDefaultExternalThemePathForFormat(defaultExternalThemeFormat)
134}
135
136func getDefaultExternalThemePathForFormat(format string) string {
137	switch format {
138	case defaultExternalThemeTemplateFormat:
139		return getDefaultExternalThemeTemplatePath()
140	default:
141		return getDefaultNoctaliaColorsPath()
142	}
143}
144
145func getDefaultNoctaliaColorsPath() string {
146	configHome, err := os.UserConfigDir()
147	if err != nil {
148		return ""
149	}
150	return filepath.Join(configHome, defaultExternalThemeProvider, defaultExternalThemeColorsFilename)
151}
152
153func getDefaultExternalThemeTemplatePath() string {
154	configDir, err := GetConfigDir()
155	if err != nil {
156		return ""
157	}
158	return filepath.Join(configDir, defaultExternalThemeTemplateFilename)
159}
160
161// DefaultConfig returns the default configuration values for dumber.
162func DefaultConfig() *Config {
163	browsingContextDefaults := defaultBrowsingContextConfig()
164
165	return &Config{
166		Database: DatabaseConfig{
167			// Path is set dynamically in config.Load()
168		},
169		History: HistoryConfig{
170			MaxEntries:          defaultMaxHistoryEntries,
171			RetentionPeriodDays: defaultRetentionDays, // 1 year
172			CleanupIntervalDays: 1,                    // daily cleanup
173		},
174		SearchShortcuts:     GetDefaultSearchShortcuts(),
175		DefaultSearchEngine: "https://duckduckgo.com/?q=%s",
176		Dmenu: DmenuConfig{
177			MaxHistoryDays:   defaultMaxHistoryDays,
178			ShowVisitCount:   true,
179			ShowLastVisited:  true,
180			HistoryPrefix:    "🕒",
181			ShortcutPrefix:   "🔍",
182			URLPrefix:        "🌐",
183			DateFormat:       "2006-01-02 15:04",
184			SortByVisitCount: true,
185		},
186		Logging: LoggingConfig{
187			Level:          "info",
188			Format:         "text", // text or json
189			MaxAge:         defaultMaxLogAgeDays,
190			MaxFiles:       defaultMaxLogFiles,
191			LogDir:         getDefaultLogDir(),
192			EnableFileLog:  true,
193			CaptureConsole: false, // Disabled by default
194			CaptureGTKLogs: false, // Disabled by default
195		},
196		Appearance: AppearanceConfig{
197			SansFont:        "Fira Sans",
198			SerifFont:       "Fira Sans",
199			MonospaceFont:   "Fira Code",
200			GtkFont:         "Adwaita Sans",
201			DefaultFontSize: defaultFontSize,
202			LightPalette: ColorPalette{
203				Background:     "#fafafa",
204				Surface:        "#f4f4f5",
205				SurfaceVariant: "#e4e4e7",
206				Text:           "#18181b",
207				Muted:          "#71717a",
208				Accent:         "#22c55e", // Green-500 - vibrant primary
209				Border:         "#d4d4d8",
210			},
211			DarkPalette: ColorPalette{
212				Background:     "#0a0a0b",
213				Surface:        "#18181b",
214				SurfaceVariant: "#27272a",
215				Text:           "#fafafa",
216				Muted:          "#a1a1aa",
217				Accent:         "#4ade80", // Green-400 - vibrant primary
218				Border:         "#3f3f46",
219			},
220			ColorScheme: "default", // default follows system theme
221			ExternalTheme: entity.ExternalThemeConfig{
222				Enabled:  false,
223				Provider: defaultExternalThemeProvider,
224				Format:   defaultExternalThemeFormat,
225				Path:     getDefaultExternalThemePath(),
226			},
227		},
228		Debug: DebugConfig{
229			EnableDevTools: true,
230		},
231		Engine: EngineConfig{
232			Type:             EngineTypeCEF,
233			Profile:          ProfileDefault,
234			PoolPrewarmCount: defaultWebViewPoolPrewarmCount,
235			ZoomCacheSize:    defaultZoomCacheSize,
236			// With ITP enabled, WebKit ignores ACCEPT_NO_THIRD_PARTY — ITP handles
237			// third-party cookie isolation more intelligently. Using Always + ITP
238			// matches Epiphany's model and avoids a misleading setting.
239			CookiePolicy: CookiePolicyAlways,
240			CEF: CEFEngineConfig{
241				RenderStack:                 CEFRenderStackVulkan,
242				AdaptiveWindowlessFrameRate: true,
243				WindowlessFrameRate:         0,
244				WindowlessFrameRateMax:      defaultCEFWindowlessFrameRateMax,
245				LogFile:                     "",
246				EnableAudioHandler:          true,
247				Input: CEFInputConfig{
248					ScrollWheelMultiplier:              defaultCEFScrollMultiplier,
249					ScrollPreciseMultiplier:            defaultCEFScrollPreciseMultiplier,
250					ScrollHorizontalMultiplier:         defaultCEFScrollMultiplier,
251					ScrollVerticalMultiplier:           defaultCEFScrollMultiplier,
252					ScrollMaxDelta:                     defaultCEFScrollMaxDelta,
253					ScrollTouchpadInertia:              defaultCEFScrollTouchpadInertia,
254					ScrollWheelSmoothing:               defaultCEFScrollWheelSmoothing,
255					TouchpadNavigationEnabled:          defaultCEFTouchpadNavigation,
256					TouchpadNavigationMinDelta:         defaultCEFTouchpadNavigationDelta,
257					TouchpadNavigationMaxVerticalRatio: defaultCEFTouchpadNavigationRatio,
258				},
259			},
260			WebKit: WebKitEngineConfig{
261				ITPEnabled:             true,
262				SkiaCPUPaintingThreads: defaultSkiaCPUPaintingThreads,
263				SkiaGPUPaintingThreads: defaultSkiaGPUPaintingThreads,
264				GSKRenderer:            GSKRendererAuto,
265				GLRenderingMode:        GLRenderingModeAuto,
266			},
267		},
268		DefaultWebpageZoom: 1.2,                 // 120% default zoom for better readability
269		DefaultUIScale:     defaultUIScale,      // 1.0 = 100%, 2.0 = 200%
270		SidebarWidth:       defaultSidebarWidth, // 320px, clamped to [280, 380]
271		Workspace: WorkspaceConfig{
272			NewPaneURL:        defaultNewPaneURL,
273			SwitchToTabOnMove: true,
274			PaneMode: PaneModeConfig{
275				ActivationShortcut:  defaultPaneActivationShortcut,
276				TimeoutMilliseconds: defaultPaneTimeoutMilliseconds,
277				Actions: map[string]ActionBinding{
278					"split-right":           {Keys: []string{"arrowright", "r"}, Desc: "Split pane to the right"},
279					"split-left":            {Keys: []string{"arrowleft", "l"}, Desc: "Split pane to the left"},
280					"split-up":              {Keys: []string{"arrowup", "u"}, Desc: "Split pane upward"},
281					"split-down":            {Keys: []string{"arrowdown", "d"}, Desc: "Split pane downward"},
282					"stack-pane":            {Keys: []string{"s"}, Desc: "Stack pane with sibling"},
283					"close-pane":            {Keys: []string{"x"}, Desc: "Close current pane"},
284					"move-pane-to-tab":      {Keys: []string{"m"}, Desc: "Move pane to different tab"},
285					"move-pane-to-next-tab": {Keys: []string{"M", "shift+m"}, Desc: "Move pane to next tab"},
286					"eject-pane-to-window":  {Keys: []string{"w"}, Desc: "Eject active pane to a new window"},
287
288					"consume-or-expel-left":  {Keys: []string{"["}, Desc: "Consume/expel pane left"},
289					"consume-or-expel-right": {Keys: []string{"]"}, Desc: "Consume/expel pane right"},
290					"consume-or-expel-up":    {Keys: []string{"{"}, Desc: "Consume/expel pane up"},
291					"consume-or-expel-down":  {Keys: []string{"}"}, Desc: "Consume/expel pane down"},
292
293					"focus-right": {Keys: []string{"shift+arrowright", "shift+l"}, Desc: "Focus pane to the right"},
294					"focus-left":  {Keys: []string{"shift+arrowleft", "shift+h"}, Desc: "Focus pane to the left"},
295					"focus-up":    {Keys: []string{"shift+arrowup", "shift+k"}, Desc: "Focus pane above"},
296					"focus-down":  {Keys: []string{"shift+arrowdown", "shift+j"}, Desc: "Focus pane below"},
297					"confirm":     {Keys: []string{"enter"}, Desc: "Confirm action"},
298					"cancel":      {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
299				},
300			},
301			TabMode: TabModeConfig{
302				ActivationShortcut:  defaultTabActivationShortcut,
303				TimeoutMilliseconds: defaultTabTimeoutMilliseconds,
304				Actions: map[string]ActionBinding{
305					"new-tab":      {Keys: []string{"n", "c"}, Desc: "Create new tab"},
306					"close-tab":    {Keys: []string{"x"}, Desc: "Close current tab"},
307					"next-tab":     {Keys: []string{"l", "tab"}, Desc: "Switch to next tab"},
308					"previous-tab": {Keys: []string{"h", "shift+tab"}, Desc: "Switch to previous tab"},
309					"rename-tab":   {Keys: []string{"r"}, Desc: "Rename current tab"},
310					"confirm":      {Keys: []string{"enter"}, Desc: "Confirm action"},
311					"cancel":       {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
312				},
313			},
314			VimMode: VimModeConfig{
315				ActivationShortcut:          defaultPageActivationShortcut,
316				TimeoutMilliseconds:         defaultPageTimeoutMilliseconds,
317				SequenceTimeoutMilliseconds: defaultPageSequenceTimeoutMilliseconds,
318				Actions: map[string]ActionBinding{
319					"vim-scroll-left":      {Keys: []string{"h"}, Desc: "Scroll page left"},
320					"vim-scroll-down":      {Keys: []string{"j"}, Desc: "Scroll page down"},
321					"vim-scroll-up":        {Keys: []string{"k"}, Desc: "Scroll page up"},
322					"vim-scroll-right":     {Keys: []string{"l"}, Desc: "Scroll page right"},
323					"vim-scroll-down-fast": {Keys: []string{"shift+j"}, Desc: "Scroll page down fast"},
324					"vim-scroll-up-fast":   {Keys: []string{"shift+k"}, Desc: "Scroll page up fast"},
325					"confirm":              {Keys: []string{"enter"}, Desc: "Open selected heading link and exit mode"},
326					"cancel":               {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
327					"heading-next":         {Keys: []string{"]]"}, Desc: "Jump to next heading"},
328					"heading-prev":         {Keys: []string{"[["}, Desc: "Jump to previous heading"},
329					"focus-input":          {Keys: []string{"gi"}, Desc: "Focus next page input"},
330					"code-next":            {Keys: []string{"]c"}, Desc: "Jump to next code block"},
331					"code-prev":            {Keys: []string{"[c"}, Desc: "Jump to previous code block"},
332					"table-next":           {Keys: []string{"]t"}, Desc: "Jump to next table"},
333					"image-next":           {Keys: []string{"]i"}, Desc: "Jump to next image"},
334					"list-next":            {Keys: []string{"]l"}, Desc: "Jump to next list"},
335					"outline":              {Keys: []string{"gO"}, Desc: "Show page outline"},
336					"yank-section":         {Keys: []string{"yah"}, Desc: "Yank current section"},
337					"half-page-down":       {Keys: []string{"<C-d>"}, Desc: "Scroll half page down"},
338					"half-page-up":         {Keys: []string{"<C-u>"}, Desc: "Scroll half page up"},
339				},
340			},
341			ResizeMode: ResizeModeConfig{
342				ActivationShortcut:  defaultResizeActivationShortcut,
343				TimeoutMilliseconds: defaultResizeTimeoutMilliseconds,
344				StepPercent:         defaultResizeStepPercent,
345				MinPanePercent:      defaultResizeMinPanePercent,
346				Actions: map[string]ActionBinding{
347					"resize-increase-left":  {Keys: []string{"h", "arrowleft"}, Desc: "Increase size leftward"},
348					"resize-increase-down":  {Keys: []string{"j", "arrowdown"}, Desc: "Increase size downward"},
349					"resize-increase-up":    {Keys: []string{"k", "arrowup"}, Desc: "Increase size upward"},
350					"resize-increase-right": {Keys: []string{"l", "arrowright"}, Desc: "Increase size rightward"},
351					"resize-decrease-left":  {Keys: []string{"H"}, Desc: "Decrease size leftward"},
352					"resize-decrease-down":  {Keys: []string{"J"}, Desc: "Decrease size downward"},
353					"resize-decrease-up":    {Keys: []string{"K"}, Desc: "Decrease size upward"},
354					"resize-decrease-right": {Keys: []string{"L"}, Desc: "Decrease size rightward"},
355					"resize-increase":       {Keys: []string{"+", "="}, Desc: "Increase pane size"},
356					"resize-decrease":       {Keys: []string{"-"}, Desc: "Decrease pane size"},
357					"confirm":               {Keys: []string{"enter"}, Desc: "Confirm action"},
358					"cancel":                {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
359				},
360			},
361			Shortcuts: GlobalShortcutsConfig{
362				Actions: map[string]ActionBinding{
363					"toggle-floating-pane":         {Keys: []string{"alt+f"}, Desc: "Toggle floating pane"},
364					"toggle-history-systemview":    {Keys: []string{"ctrl+h"}, Desc: "Toggle History in right split"},
365					"toggle-favorites-sidebar":     {Keys: []string{"ctrl+b"}, Desc: "Toggle Favorites sidebar"},
366					"toggle-current-page-favorite": {Keys: []string{"ctrl+d"}, Desc: "Add/remove current page favorite"},
367					"toggle-config-systemview":     {Keys: []string{}, Desc: "Toggle Config in right split"},
368					"close-pane":                   {Keys: []string{"ctrl+w"}, Desc: "Close active pane"},
369					"next-tab":                     {Keys: []string{"ctrl+tab"}, Desc: "Switch to next tab"},
370					"previous-tab":                 {Keys: []string{"ctrl+shift+tab"}, Desc: "Switch to previous tab"},
371					"consume-or-expel-left":        {Keys: []string{"alt+["}, Desc: "Consume/expel pane left"},
372					"consume-or-expel-right":       {Keys: []string{"alt+]"}, Desc: "Consume/expel pane right"},
373					"consume-or-expel-up":          {Keys: []string{"alt+{"}, Desc: "Consume/expel pane up"},
374					"consume-or-expel-down":        {Keys: []string{"alt+}"}, Desc: "Consume/expel pane down"},
375				},
376			},
377			FloatingPane: FloatingPaneConfig{
378				WidthPct:  defaultFloatingPaneWidthPct,
379				HeightPct: defaultFloatingPaneHeightPct,
380				Profiles:  map[string]FloatingPaneProfile{},
381			},
382			ExternalLinks: ExternalLinksConfig{
383				Behavior:  defaultExternalLinkBehavior,
384				Placement: defaultExternalLinkPlacement,
385			},
386			TabBarPosition:          defaultTabBarPosition,
387			HideTabBarWhenSingleTab: true,
388			BrowsingContexts:        browsingContextDefaults,
389			Popups:                  browsingContextDefaults,
390			Styling: WorkspaceStylingConfig{
391				BorderWidth:                 defaultBorderWidth,
392				BorderColor:                 defaultBorderColor,
393				ModeBorderWidth:             defaultModeBorderWidth,
394				PaneModeColor:               defaultPaneModeColor,
395				TabModeColor:                defaultTabModeColor,
396				SessionModeColor:            defaultSessionModeColor,
397				ResizeModeColor:             defaultResizeModeColor,
398				VimModeColor:                defaultVimModeColor,
399				ModeLegendLinger:            true,
400				ModeIndicatorToasterEnabled: defaultModeIndicatorToasterEnabled,
401				ModeLegend:                  "delay",
402				ModeLegendDelayMs:           500,
403				ModeLegendAnimations:        true,
404				TransitionDuration:          defaultTransitionDuration,
405			},
406		},
407		ContentFiltering: ContentFilteringConfig{
408			Enabled:    true, // Ad blocking enabled by default
409			AutoUpdate: true, // Auto-update filters from GitHub releases
410		},
411		Clipboard: ClipboardConfig{
412			AutoCopyOnSelection: true, // Enabled by default (zellij-style)
413		},
414		Omnibox: OmniboxConfig{
415			Style:             defaultOmniboxStyle,
416			InitialBehavior:   defaultOmniboxInitialBehavior,
417			MaxHistoryDays:    defaultOmniboxMaxHistoryDays,
418			AutoOpenOnNewPane: defaultOmniboxAutoOpenOnNewPane,
419		},
420		Session: SessionConfig{
421			AutoRestore:             false,
422			SnapshotIntervalMs:      defaultSnapshotIntervalMs,
423			MaxExitedSessions:       defaultMaxExitedSessions,
424			MaxExitedSessionAgeDays: defaultMaxExitedSessionAgeDays,
425			SessionMode: SessionModeConfig{
426				ActivationShortcut:  defaultSessionActivationShortcut,
427				TimeoutMilliseconds: defaultSessionTimeoutMilliseconds,
428				Actions: map[string]ActionBinding{
429					"session-manager": {Keys: []string{"s", "w"}, Desc: "Open session manager"},
430					"confirm":         {Keys: []string{"enter"}, Desc: "Confirm action"},
431					"cancel":          {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
432				},
433			},
434		},
435		Media: MediaConfig{
436			HardwareDecodingMode:     HardwareDecodingAuto, // auto allows sw fallback
437			PreferAV1:                false,                // Don't force codec preference, let site choose
438			ShowDiagnosticsOnStartup: false,                // Disabled - diagnostics can be noisy
439			// GStreamer fields (ForceVSync, GLRenderingMode, GStreamerDebugLevel)
440			// moved to [engine.webkit] — zero values here prevent them from being
441			// written back when marshaling the Config struct.
442		},
443		Update: UpdateConfig{
444			EnableOnStartup:     true,  // Check for updates on startup by default
445			AutoDownload:        false, // Conservative: don't auto-download by default
446			NotifyOnNewSettings: true,  // Show toast when new config settings available
447		},
448		Downloads: DownloadsConfig{
449			Path: "", // Empty = use XDG_DOWNLOAD_DIR or ~/Downloads
450		},
451	}
452}
453
454// GetDefaultSearchShortcuts returns the default search shortcuts.
455func GetDefaultSearchShortcuts() map[string]SearchShortcut {
456	return map[string]SearchShortcut{
457		"ddg": {
458			URL:         "https://duckduckgo.com/?q=%s",
459			Description: "DuckDuckGo search",
460		},
461		"g": {
462			URL:         "https://www.google.com/search?q=%s",
463			Description: "Google search",
464		},
465		"gi": {
466			URL:         "https://www.google.com/search?tbm=isch&q=%s",
467			Description: "Google Images search",
468		},
469		"gh": {
470			URL:         "https://github.com/search?q=%s",
471			Description: "GitHub search",
472		},
473		"go": {
474			URL:         "https://pkg.go.dev/search?q=%s",
475			Description: "Go package search",
476		},
477		"mdn": {
478			URL:         "https://developer.mozilla.org/en-US/search?q=%s",
479			Description: "MDN Web Docs search",
480		},
481		"npm": {
482			URL:         "https://www.npmjs.com/search?q=%s",
483			Description: "npm package search",
484		},
485		"r": {
486			URL:         "https://www.reddit.com/search?q=%s",
487			Description: "Reddit search",
488		},
489		"so": {
490			URL:         "https://stackoverflow.com/search?q=%s",
491			Description: "Stack Overflow search",
492		},
493		"w": {
494			URL:         "https://en.wikipedia.org/wiki/%s",
495			Description: "Wikipedia search",
496		},
497		"yt": {
498			URL:         "https://www.youtube.com/results?search_query=%s",
499			Description: "YouTube search",
500		},
501	}
502}

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