v0.31.0 bf0756b

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

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