v0.2.0 759cc92

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