v0.23.2 bfd5eb4

setup.go RawHistoryFolder

1package cmd
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/spf13/cobra"
8
9	"github.com/bnema/dumber/assets"
10	"github.com/bnema/dumber/internal/application/usecase"
11	"github.com/bnema/dumber/internal/infrastructure/desktop"
12)
13
14var setupCmd = &cobra.Command{
15	Use:   "setup",
16	Short: "Setup desktop integration",
17	Long: `Setup dumber's integration with the desktop environment.
18
19Subcommands:
20  install  - Install desktop file to ~/.local/share/applications/
21  default  - Set dumber as the default web browser`,
22}
23
24var setupInstallCmd = &cobra.Command{
25	Use:   "install",
26	Short: "Install desktop file",
27	Long: `Install the dumber.desktop file to the user's applications directory.
28
29This registers dumber with the desktop environment, making it appear in
30application menus and available as a browser choice.
31
32Location: $XDG_DATA_HOME/applications/dumber.desktop
33         (typically ~/.local/share/applications/dumber.desktop)
34
35This command is idempotent - safe to run multiple times.`,
36	RunE: runSetupInstall,
37}
38
39var setupDefaultCmd = &cobra.Command{
40	Use:   "default",
41	Short: "Set dumber as default browser",
42	Long: `Set dumber as the default web browser using xdg-settings.
43
44This makes dumber handle http:// and https:// links from other applications.
45
46Requires the desktop file to be installed first (run 'dumber setup install').
47
48This command is idempotent - safe to run multiple times.`,
49	RunE: runSetupDefault,
50}
51
52func init() {
53	rootCmd.AddCommand(setupCmd)
54	setupCmd.AddCommand(setupInstallCmd)
55	setupCmd.AddCommand(setupDefaultCmd)
56}
57
58func runSetupInstall(_ *cobra.Command, _ []string) error {
59	app := GetApp()
60	if app == nil {
61		return fmt.Errorf("app not initialized")
62	}
63
64	theme := app.Theme
65	adapter := desktop.New()
66	uc := usecase.NewInstallDesktopUseCase(adapter)
67
68	input := usecase.InstallDesktopInput{
69		IconData: assets.LogoSVG,
70	}
71
72	result, err := uc.Execute(app.Ctx(), input)
73	if err != nil {
74		fmt.Printf("%s %s\n", theme.ErrorStyle.Render("\u2717"), err.Error())
75		return err
76	}
77
78	// Desktop file status
79	if result.WasDesktopExisting {
80		fmt.Printf("%s Desktop file updated at %s\n",
81			theme.SuccessStyle.Render("\u2713"),
82			theme.Highlight.Render(result.DesktopPath))
83	} else {
84		fmt.Printf("%s Desktop file installed to %s\n",
85			theme.SuccessStyle.Render("\u2713"),
86			theme.Highlight.Render(result.DesktopPath))
87	}
88
89	// Icon status
90	if result.IconPath != "" {
91		if result.WasIconExisting {
92			fmt.Printf("%s Icon updated at %s\n",
93				theme.SuccessStyle.Render("\u2713"),
94				theme.Highlight.Render(result.IconPath))
95		} else {
96			fmt.Printf("%s Icon installed to %s\n",
97				theme.SuccessStyle.Render("\u2713"),
98				theme.Highlight.Render(result.IconPath))
99		}
100	}
101
102	fmt.Println()
103	fmt.Println(theme.Subtle.Render("Run 'dumber setup default' to set as default browser"))
104
105	return nil
106}
107
108func runSetupDefault(_ *cobra.Command, _ []string) error {
109	app := GetApp()
110	if app == nil {
111		return fmt.Errorf("app not initialized")
112	}
113
114	theme := app.Theme
115	adapter := desktop.New()
116	uc := usecase.NewSetDefaultBrowserUseCase(adapter)
117
118	result, err := uc.Execute(app.Ctx())
119	if err != nil {
120		errMsg := err.Error()
121		fmt.Printf("%s %s\n", theme.ErrorStyle.Render("\u2717"), errMsg)
122		if strings.Contains(errMsg, "desktop file not installed") {
123			fmt.Println()
124			fmt.Println(theme.Subtle.Render("Hint: Run 'dumber setup install' first"))
125		}
126		return err
127	}
128
129	if result.WasAlreadyDefault {
130		fmt.Printf("%s Dumber is already the default browser\n",
131			theme.SuccessStyle.Render("\u2713"))
132	} else {
133		fmt.Printf("%s Dumber is now the default browser\n",
134			theme.SuccessStyle.Render("\u2713"))
135	}
136
137	return nil
138}

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