v0.1 bf7c821

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

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