v0.3.0 d226234

config_service.go RawHistoryFolder

1package services
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"path/filepath"
8
9	"github.com/bnema/dumber/internal/config"
10	"strings"
11)
12
13// ConfigService handles configuration operations for the application.
14type ConfigService struct {
15	config     *config.Config
16	configPath string
17}
18
19const defaultDirPerm = 0o750
20
21// ConfigInfo represents configuration information for the frontend.
22type ConfigInfo struct {
23	ConfigPath      string                           `json:"config_path"`
24	DatabasePath    string                           `json:"database_path"`
25	SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"`
26	DmenuSettings   *config.DmenuConfig              `json:"dmenu_settings"`
27	RenderingMode   string                           `json:"rendering_mode"`
28}
29
30// NewConfigService creates a new ConfigService instance.
31func NewConfigService(cfg *config.Config, configPath string) *ConfigService {
32	return &ConfigService{
33		config:     cfg,
34		configPath: configPath,
35	}
36}
37
38// GetConfigInfo returns comprehensive configuration information.
39func (s *ConfigService) GetConfigInfo(ctx context.Context) (*ConfigInfo, error) {
40	_ = ctx
41	return &ConfigInfo{
42		ConfigPath:      s.configPath,
43		DatabasePath:    s.config.Database.Path,
44		SearchShortcuts: s.config.SearchShortcuts,
45		DmenuSettings:   &s.config.Dmenu,
46		RenderingMode:   string(s.config.RenderingMode),
47	}, nil
48}
49
50// GetSearchShortcuts returns all configured search shortcuts.
51func (s *ConfigService) GetSearchShortcuts(ctx context.Context) (map[string]config.SearchShortcut, error) {
52	_ = ctx
53	return s.config.SearchShortcuts, nil
54}
55
56// AddSearchShortcut adds a new search shortcut.
57func (s *ConfigService) AddSearchShortcut(ctx context.Context, key string, shortcut config.SearchShortcut) error {
58	_ = ctx
59	if key == "" {
60		return fmt.Errorf("shortcut key cannot be empty")
61	}
62
63	if shortcut.URL == "" {
64		return fmt.Errorf("shortcut URL cannot be empty")
65	}
66
67	if s.config.SearchShortcuts == nil {
68		s.config.SearchShortcuts = make(map[string]config.SearchShortcut)
69	}
70
71	s.config.SearchShortcuts[key] = shortcut
72	return s.saveConfig()
73}
74
75// UpdateSearchShortcut updates an existing search shortcut.
76func (s *ConfigService) UpdateSearchShortcut(ctx context.Context, key string, shortcut config.SearchShortcut) error {
77	_ = ctx
78	if key == "" {
79		return fmt.Errorf("shortcut key cannot be empty")
80	}
81
82	if _, exists := s.config.SearchShortcuts[key]; !exists {
83		return fmt.Errorf("shortcut '%s' does not exist", key)
84	}
85
86	if shortcut.URL == "" {
87		return fmt.Errorf("shortcut URL cannot be empty")
88	}
89
90	s.config.SearchShortcuts[key] = shortcut
91	return s.saveConfig()
92}
93
94// DeleteSearchShortcut removes a search shortcut.
95func (s *ConfigService) DeleteSearchShortcut(ctx context.Context, key string) error {
96	_ = ctx
97	if key == "" {
98		return fmt.Errorf("shortcut key cannot be empty")
99	}
100
101	if _, exists := s.config.SearchShortcuts[key]; !exists {
102		return fmt.Errorf("shortcut '%s' does not exist", key)
103	}
104
105	delete(s.config.SearchShortcuts, key)
106	return s.saveConfig()
107}
108
109// GetDmenuSettings returns current dmenu settings.
110func (s *ConfigService) GetDmenuSettings(ctx context.Context) (*config.DmenuConfig, error) {
111	_ = ctx
112	return &s.config.Dmenu, nil
113}
114
115// UpdateDmenuSettings updates dmenu configuration.
116func (s *ConfigService) UpdateDmenuSettings(ctx context.Context, settings *config.DmenuConfig) error {
117	_ = ctx
118	if settings == nil {
119		return fmt.Errorf("dmenu settings cannot be nil")
120	}
121
122	s.config.Dmenu = *settings
123	return s.saveConfig()
124}
125
126// GetDatabasePath returns the current database path.
127func (s *ConfigService) GetDatabasePath(ctx context.Context) (string, error) {
128	_ = ctx
129	return s.config.Database.Path, nil
130}
131
132// SetDatabasePath updates the database path.
133func (s *ConfigService) SetDatabasePath(ctx context.Context, path string) error {
134	_ = ctx
135	if path == "" {
136		return fmt.Errorf("database path cannot be empty")
137	}
138
139	// Validate that the directory exists or can be created
140	dir := filepath.Dir(path)
141	if err := os.MkdirAll(dir, defaultDirPerm); err != nil {
142		return fmt.Errorf("failed to create database directory: %w", err)
143	}
144
145	s.config.Database.Path = path
146	return s.saveConfig()
147}
148
149// GetDefaultBrowser returns the default browser setting.
150func (s *ConfigService) GetDefaultBrowser(ctx context.Context) (string, error) {
151	_ = ctx
152	// Default browser is now built into the app
153	return "dumber (built-in)", nil
154}
155
156// SetDefaultBrowser updates the default browser setting.
157func (s *ConfigService) SetDefaultBrowser(ctx context.Context, browser string) error {
158	_ = ctx
159	_ = browser
160	// Default browser setting is not needed for built-in browser
161	return fmt.Errorf("default browser is built-in and cannot be changed")
162}
163
164// ReloadConfig reloads configuration from file.
165func (s *ConfigService) ReloadConfig(ctx context.Context) error {
166	_ = ctx
167	// Create new manager and load config
168	manager, err := config.NewManager()
169	if err != nil {
170		return fmt.Errorf("failed to create config manager: %w", err)
171	}
172
173	if err := manager.Load(); err != nil {
174		return fmt.Errorf("failed to load config: %w", err)
175	}
176
177	s.config = manager.Get()
178	return nil
179}
180
181// ValidateConfig validates the current configuration.
182func (s *ConfigService) ValidateConfig(ctx context.Context) ([]string, error) {
183	_ = ctx
184	var errors []string
185
186	// Validate database path
187	if s.config.Database.Path == "" {
188		errors = append(errors, "database path is not set")
189	} else {
190		dir := filepath.Dir(s.config.Database.Path)
191		if _, err := os.Stat(dir); os.IsNotExist(err) {
192			errors = append(errors, fmt.Sprintf("database directory does not exist: %s", dir))
193		}
194	}
195
196	// Validate search shortcuts
197	if len(s.config.SearchShortcuts) == 0 {
198		errors = append(errors, "no search shortcuts configured")
199	} else {
200		for key, shortcut := range s.config.SearchShortcuts {
201			if shortcut.URL == "" {
202				errors = append(errors, fmt.Sprintf("search shortcut '%s' has empty URL", key))
203			}
204		}
205	}
206
207	// Check for default search shortcut
208	if _, hasDefault := s.config.SearchShortcuts["g"]; !hasDefault {
209		errors = append(errors, "no default search shortcut 'g' configured")
210	}
211
212	// Validate dmenu settings
213	if s.config.Dmenu.MaxHistoryItems < 1 {
214		errors = append(errors, "dmenu max_history_items must be at least 1")
215	}
216
217	return errors, nil
218}
219
220// GetConfigPath returns the path to the configuration file.
221func (s *ConfigService) GetConfigPath(ctx context.Context) (string, error) {
222	_ = ctx
223	return s.configPath, nil
224}
225
226// ExportConfig returns the current configuration as JSON.
227func (s *ConfigService) ExportConfig(ctx context.Context) (*config.Config, error) {
228	_ = ctx
229	// Return a copy of the config to prevent modifications
230	configCopy := *s.config
231	return &configCopy, nil
232}
233
234// ImportConfig imports configuration from a Config object.
235func (s *ConfigService) ImportConfig(ctx context.Context, newConfig *config.Config) error {
236	if newConfig == nil {
237		return fmt.Errorf("config cannot be nil")
238	}
239
240	// Validate the new config
241	tempService := &ConfigService{config: newConfig}
242	validationErrors, err := tempService.ValidateConfig(ctx)
243	if err != nil {
244		return fmt.Errorf("failed to validate config: %w", err)
245	}
246
247	if len(validationErrors) > 0 {
248		return fmt.Errorf("config validation failed: %v", validationErrors)
249	}
250
251	// Apply the new config
252	s.config = newConfig
253	return s.saveConfig()
254}
255
256// GetRenderingMode returns the current rendering mode (auto|gpu|cpu).
257func (s *ConfigService) GetRenderingMode(ctx context.Context) (string, error) {
258	_ = ctx
259	return string(s.config.RenderingMode), nil
260}
261
262// SetRenderingMode updates the rendering mode (auto|gpu|cpu).
263func (s *ConfigService) SetRenderingMode(ctx context.Context, mode string) error {
264	_ = ctx
265	m := strings.ToLower(strings.TrimSpace(mode))
266	switch m {
267	case "auto", "gpu", "cpu":
268		s.config.RenderingMode = config.RenderingMode(m)
269		return s.saveConfig()
270	default:
271		return fmt.Errorf("invalid rendering mode: %s", mode)
272	}
273}
274
275// saveConfig saves the current configuration to file.
276func (s *ConfigService) saveConfig() error {
277	// For now, return success - implementing config saving would require
278	// extending the config package with save functionality
279	return nil
280}

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