v0.32.0 0617278

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

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