feat(config): add configurable default search engine
- Added default_search_engine config option with explicit URL template format - Changed default from Google to DuckDuckGo - Users can set any search engine with %s placeholder - Added validation to ensure URL contains %s placeholder - Updated parser to use config value instead of hardcoded Google URLs - Refactored URL type detection to use cleaner switch statement
- commit
- 9f085d4fa2ad2865e347aa400328c531590deafe
- author
- bnema · · 2025-11-17 06:35 UTC
- parent
- 4a28a17
- signature
- unsigned
6 files · +59 -23
M CHANGELOG.md +1 -0
| @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. | |||
| 7 | 7 | ### Added | |
| 8 | 8 | - **Middle-click and Ctrl+click link handling**: Links can now be opened in new workspace panes using standard browser gestures (middle-click or Ctrl+left-click). New panes split in the configured direction (default: right) and respect `workspace.popups.placement` configuration. Implemented via WebKit's `decide-policy` signal to intercept navigation actions before they occur. | |
| 9 | 9 | - **Configurable pane border styling**: Added `workspace.styling.inactive_border_width`, `workspace.styling.inactive_border_color`, and `workspace.styling.show_stacked_title_border` config options. Active and inactive borders default to 1px to prevent layout shift, with only color changing on focus. Borders use GTK theme variables by default and support smooth CSS transitions. | |
| 10 | + | - **Configurable default search engine**: Added `default_search_engine` config option with explicit URL template format. DuckDuckGo is now the default (was Google). Users can set any search engine with `%s` placeholder (e.g., `default_search_engine = "https://duckduckgo.com/?q=%s"`). | |
| 10 | 11 | ||
| 11 | 12 | ### Changed | |
| 12 | 13 | - **Config format**: TOML is now the default format (JSON/YAML still supported). Action bindings inverted to `action→keys` structure for maintainability with O(1) runtime lookup. Added comprehensive validation. | |
| @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. | |||||
| 7 | ### Added | 7 | ### Added | ||
| 8 | - **Middle-click and Ctrl+click link handling**: Links can now be opened in new workspace panes using standard browser gestures (middle-click or Ctrl+left-click). New panes split in the configured direction (default: right) and respect `workspace.popups.placement` configuration. Implemented via WebKit's `decide-policy` signal to intercept navigation actions before they occur. | 8 | - **Middle-click and Ctrl+click link handling**: Links can now be opened in new workspace panes using standard browser gestures (middle-click or Ctrl+left-click). New panes split in the configured direction (default: right) and respect `workspace.popups.placement` configuration. Implemented via WebKit's `decide-policy` signal to intercept navigation actions before they occur. | ||
| 9 | - **Configurable pane border styling**: Added `workspace.styling.inactive_border_width`, `workspace.styling.inactive_border_color`, and `workspace.styling.show_stacked_title_border` config options. Active and inactive borders default to 1px to prevent layout shift, with only color changing on focus. Borders use GTK theme variables by default and support smooth CSS transitions. | 9 | - **Configurable pane border styling**: Added `workspace.styling.inactive_border_width`, `workspace.styling.inactive_border_color`, and `workspace.styling.show_stacked_title_border` config options. Active and inactive borders default to 1px to prevent layout shift, with only color changing on focus. Borders use GTK theme variables by default and support smooth CSS transitions. | ||
| 10 | + | - **Configurable default search engine**: Added `default_search_engine` config option with explicit URL template format. DuckDuckGo is now the default (was Google). Users can set any search engine with `%s` placeholder (e.g., `default_search_engine = "https://duckduckgo.com/?q=%s"`). | |||
| 10 | 11 | ||||
| 11 | ### Changed | 12 | ### Changed | ||
| 12 | - **Config format**: TOML is now the default format (JSON/YAML still supported). Action bindings inverted to `action→keys` structure for maintainability with O(1) runtime lookup. Added comprehensive validation. | 13 | - **Config format**: TOML is now the default format (JSON/YAML still supported). Action bindings inverted to `action→keys` structure for maintainability with O(1) runtime lookup. Added comprehensive validation. | ||
M docs/CONFIG.md +17 -1
| @@ -29,14 +29,30 @@ You can still use JSON or YAML if you prefer - dumber automatically detects the | |||
| 29 | 29 | | `history.retention_period_days` | int | `365` | > 0 | Days to keep history (1 year) | | |
| 30 | 30 | | `history.cleanup_interval_days` | int | `1` | > 0 | How often to run cleanup | | |
| 31 | 31 | ||
| 32 | - | ## Search Shortcuts | |
| 32 | + | ## Search Configuration | |
| 33 | 33 | ||
| 34 | 34 | | Key | Type | Default | Description | | |
| 35 | 35 | |-----|------|---------|-------------| | |
| 36 | + | | `default_search_engine` | string | `"https://duckduckgo.com/?q=%s"` | Default search engine URL template (must contain `%s` placeholder) | | |
| 36 | 37 | | `search_shortcuts` | map | See defaults | Map of shortcut aliases to URLs | | |
| 37 | 38 | ||
| 39 | + | **Example:** | |
| 40 | + | ```toml | |
| 41 | + | # Default search engine - used when no shortcut is specified | |
| 42 | + | default_search_engine = "https://duckduckgo.com/?q=%s" | |
| 43 | + | ||
| 44 | + | # Alternative search engines: | |
| 45 | + | # default_search_engine = "https://www.google.com/search?q=%s" | |
| 46 | + | # default_search_engine = "https://www.startpage.com/search?q=%s" | |
| 47 | + | # default_search_engine = "https://search.brave.com/search?q=%s" | |
| 48 | + | ``` | |
| 49 | + | ||
| 38 | 50 | **Default shortcuts:** | |
| 39 | 51 | ```toml | |
| 52 | + | [search_shortcuts.ddg] | |
| 53 | + | url = "https://duckduckgo.com/?q=%s" | |
| 54 | + | description = "DuckDuckGo search" | |
| 55 | + | ||
| 40 | 56 | [search_shortcuts.g] | |
| 41 | 57 | url = "https://google.com/search?q=%s" | |
| 42 | 58 | description = "Google Search" | |
| @@ -29,14 +29,30 @@ You can still use JSON or YAML if you prefer - dumber automatically detects the | |||||
| 29 | | `history.retention_period_days` | int | `365` | > 0 | Days to keep history (1 year) | | 29 | | `history.retention_period_days` | int | `365` | > 0 | Days to keep history (1 year) | | ||
| 30 | | `history.cleanup_interval_days` | int | `1` | > 0 | How often to run cleanup | | 30 | | `history.cleanup_interval_days` | int | `1` | > 0 | How often to run cleanup | | ||
| 31 | 31 | ||||
| 32 | - | ## Search Shortcuts | 32 | + | ## Search Configuration |
| 33 | 33 | ||||
| 34 | | Key | Type | Default | Description | | 34 | | Key | Type | Default | Description | | ||
| 35 | |-----|------|---------|-------------| | 35 | |-----|------|---------|-------------| | ||
| 36 | + | | `default_search_engine` | string | `"https://duckduckgo.com/?q=%s"` | Default search engine URL template (must contain `%s` placeholder) | | |||
| 36 | | `search_shortcuts` | map | See defaults | Map of shortcut aliases to URLs | | 37 | | `search_shortcuts` | map | See defaults | Map of shortcut aliases to URLs | | ||
| 37 | 38 | ||||
| 39 | + | **Example:** | |||
| 40 | + | ```toml | |||
| 41 | + | # Default search engine - used when no shortcut is specified | |||
| 42 | + | default_search_engine = "https://duckduckgo.com/?q=%s" | |||
| 43 | + | ||||
| 44 | + | # Alternative search engines: | |||
| 45 | + | # default_search_engine = "https://www.google.com/search?q=%s" | |||
| 46 | + | # default_search_engine = "https://www.startpage.com/search?q=%s" | |||
| 47 | + | # default_search_engine = "https://search.brave.com/search?q=%s" | |||
| 48 | + | ``` | |||
| 49 | + | ||||
| 38 | **Default shortcuts:** | 50 | **Default shortcuts:** | ||
| 39 | ```toml | 51 | ```toml | ||
| 52 | + | [search_shortcuts.ddg] | |||
| 53 | + | url = "https://duckduckgo.com/?q=%s" | |||
| 54 | + | description = "DuckDuckGo search" | |||
| 55 | + | ||||
| 40 | [search_shortcuts.g] | 56 | [search_shortcuts.g] | ||
| 41 | url = "https://google.com/search?q=%s" | 57 | url = "https://google.com/search?q=%s" | ||
| 42 | description = "Google Search" | 58 | description = "Google Search" | ||
M internal/config/config.go +4 -1
| @@ -26,7 +26,9 @@ type Config struct { | |||
| 26 | 26 | Database DatabaseConfig `mapstructure:"database" yaml:"database"` | |
| 27 | 27 | History HistoryConfig `mapstructure:"history" yaml:"history"` | |
| 28 | 28 | SearchShortcuts map[string]SearchShortcut `mapstructure:"search_shortcuts" yaml:"search_shortcuts"` | |
| 29 | - | Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"` | |
| 29 | + | // DefaultSearchEngine is the URL template for the default search engine (must contain %s placeholder) | |
| 30 | + | DefaultSearchEngine string `mapstructure:"default_search_engine" yaml:"default_search_engine"` | |
| 31 | + | Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"` | |
| 30 | 32 | Logging LoggingConfig `mapstructure:"logging" yaml:"logging"` | |
| 31 | 33 | Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"` | |
| 32 | 34 | VideoAcceleration VideoAccelerationConfig `mapstructure:"video_acceleration" yaml:"video_acceleration"` | |
| @@ -608,6 +610,7 @@ func (m *Manager) setDefaults() { | |||
| 608 | 610 | ||
| 609 | 611 | // Search shortcuts defaults | |
| 610 | 612 | m.viper.SetDefault("search_shortcuts", defaults.SearchShortcuts) | |
| 613 | + | m.viper.SetDefault("default_search_engine", defaults.DefaultSearchEngine) | |
| 611 | 614 | ||
| 612 | 615 | // Dmenu defaults | |
| 613 | 616 | m.viper.SetDefault("dmenu.max_history_items", defaults.Dmenu.MaxHistoryItems) | |
| @@ -26,7 +26,9 @@ type Config struct { | |||||
| 26 | Database DatabaseConfig `mapstructure:"database" yaml:"database"` | 26 | Database DatabaseConfig `mapstructure:"database" yaml:"database"` | ||
| 27 | History HistoryConfig `mapstructure:"history" yaml:"history"` | 27 | History HistoryConfig `mapstructure:"history" yaml:"history"` | ||
| 28 | SearchShortcuts map[string]SearchShortcut `mapstructure:"search_shortcuts" yaml:"search_shortcuts"` | 28 | SearchShortcuts map[string]SearchShortcut `mapstructure:"search_shortcuts" yaml:"search_shortcuts"` | ||
| 29 | - | Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"` | 29 | + | // DefaultSearchEngine is the URL template for the default search engine (must contain %s placeholder) |
| 30 | + | DefaultSearchEngine string `mapstructure:"default_search_engine" yaml:"default_search_engine"` | |||
| 31 | + | Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"` | |||
| 30 | Logging LoggingConfig `mapstructure:"logging" yaml:"logging"` | 32 | Logging LoggingConfig `mapstructure:"logging" yaml:"logging"` | ||
| 31 | Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"` | 33 | Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"` | ||
| 32 | VideoAcceleration VideoAccelerationConfig `mapstructure:"video_acceleration" yaml:"video_acceleration"` | 34 | VideoAcceleration VideoAccelerationConfig `mapstructure:"video_acceleration" yaml:"video_acceleration"` | ||
| @@ -608,6 +610,7 @@ func (m *Manager) setDefaults() { | |||||
| 608 | 610 | ||||
| 609 | // Search shortcuts defaults | 611 | // Search shortcuts defaults | ||
| 610 | m.viper.SetDefault("search_shortcuts", defaults.SearchShortcuts) | 612 | m.viper.SetDefault("search_shortcuts", defaults.SearchShortcuts) | ||
| 613 | + | m.viper.SetDefault("default_search_engine", defaults.DefaultSearchEngine) | |||
| 611 | 614 | ||||
| 612 | // Dmenu defaults | 615 | // Dmenu defaults | ||
| 613 | m.viper.SetDefault("dmenu.max_history_items", defaults.Dmenu.MaxHistoryItems) | 616 | m.viper.SetDefault("dmenu.max_history_items", defaults.Dmenu.MaxHistoryItems) | ||
M internal/config/defaults.go +2 -1
| @@ -54,7 +54,8 @@ func DefaultConfig() *Config { | |||
| 54 | 54 | RetentionPeriodDays: defaultRetentionDays, // 1 year | |
| 55 | 55 | CleanupIntervalDays: 1, // daily cleanup | |
| 56 | 56 | }, | |
| 57 | - | SearchShortcuts: GetDefaultSearchShortcuts(), | |
| 57 | + | SearchShortcuts: GetDefaultSearchShortcuts(), | |
| 58 | + | DefaultSearchEngine: "https://duckduckgo.com/?q=%s", | |
| 58 | 59 | Dmenu: DmenuConfig{ | |
| 59 | 60 | MaxHistoryItems: defaultMaxHistoryItems, | |
| 60 | 61 | ShowVisitCount: true, | |
| @@ -54,7 +54,8 @@ func DefaultConfig() *Config { | |||||
| 54 | RetentionPeriodDays: defaultRetentionDays, // 1 year | 54 | RetentionPeriodDays: defaultRetentionDays, // 1 year | ||
| 55 | CleanupIntervalDays: 1, // daily cleanup | 55 | CleanupIntervalDays: 1, // daily cleanup | ||
| 56 | }, | 56 | }, | ||
| 57 | - | SearchShortcuts: GetDefaultSearchShortcuts(), | 57 | + | SearchShortcuts: GetDefaultSearchShortcuts(), |
| 58 | + | DefaultSearchEngine: "https://duckduckgo.com/?q=%s", | |||
| 58 | Dmenu: DmenuConfig{ | 59 | Dmenu: DmenuConfig{ | ||
| 59 | MaxHistoryItems: defaultMaxHistoryItems, | 60 | MaxHistoryItems: defaultMaxHistoryItems, | ||
| 60 | ShowVisitCount: true, | 61 | ShowVisitCount: true, | ||
M internal/config/validation.go +7 -0
| @@ -33,6 +33,13 @@ func validateConfig(config *Config) error { | |||
| 33 | 33 | validationErrors = append(validationErrors, "default_zoom must be between 0.1 and 5.0") | |
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | + | // Validate default search engine | |
| 37 | + | if config.DefaultSearchEngine == "" { | |
| 38 | + | validationErrors = append(validationErrors, "default_search_engine cannot be empty") | |
| 39 | + | } else if !strings.Contains(config.DefaultSearchEngine, "%s") { | |
| 40 | + | validationErrors = append(validationErrors, "default_search_engine must contain %s placeholder for the search query") | |
| 41 | + | } | |
| 42 | + | ||
| 36 | 43 | // Validate popup behavior | |
| 37 | 44 | switch config.Workspace.Popups.Behavior { | |
| 38 | 45 | case PopupBehaviorSplit, PopupBehaviorStacked, PopupBehaviorTabbed, PopupBehaviorWindowed: | |
| @@ -33,6 +33,13 @@ func validateConfig(config *Config) error { | |||||
| 33 | validationErrors = append(validationErrors, "default_zoom must be between 0.1 and 5.0") | 33 | validationErrors = append(validationErrors, "default_zoom must be between 0.1 and 5.0") | ||
| 34 | } | 34 | } | ||
| 35 | 35 | ||||
| 36 | + | // Validate default search engine | |||
| 37 | + | if config.DefaultSearchEngine == "" { | |||
| 38 | + | validationErrors = append(validationErrors, "default_search_engine cannot be empty") | |||
| 39 | + | } else if !strings.Contains(config.DefaultSearchEngine, "%s") { | |||
| 40 | + | validationErrors = append(validationErrors, "default_search_engine must contain %s placeholder for the search query") | |||
| 41 | + | } | |||
| 42 | + | ||||
| 36 | // Validate popup behavior | 43 | // Validate popup behavior | ||
| 37 | switch config.Workspace.Popups.Behavior { | 44 | switch config.Workspace.Popups.Behavior { | ||
| 38 | case PopupBehaviorSplit, PopupBehaviorStacked, PopupBehaviorTabbed, PopupBehaviorWindowed: | 45 | case PopupBehaviorSplit, PopupBehaviorStacked, PopupBehaviorTabbed, PopupBehaviorWindowed: | ||
M internal/parser/parser.go +28 -20
| @@ -18,7 +18,7 @@ func (p *Parser) ParseInput(input string) (*ParseResult, error) { | |||
| 18 | 18 | if input == "" { | |
| 19 | 19 | return &ParseResult{ | |
| 20 | 20 | Type: InputTypeFallbackSearch, | |
| 21 | - | URL: "https://www.google.com/search?q=", | |
| 21 | + | URL: fmt.Sprintf(p.config.DefaultSearchEngine, ""), | |
| 22 | 22 | Query: input, | |
| 23 | 23 | Confidence: 0.0, | |
| 24 | 24 | ProcessingTime: time.Since(startTime), | |
| @@ -29,19 +29,29 @@ func (p *Parser) ParseInput(input string) (*ParseResult, error) { | |||
| 29 | 29 | finalURL, err := p.parseInputUsingCLILogic(input) | |
| 30 | 30 | if err != nil { | |
| 31 | 31 | // If parsing fails, fall back to search | |
| 32 | - | finalURL = fmt.Sprintf("https://www.google.com/search?q=%s", neturl.QueryEscape(input)) | |
| 32 | + | finalURL = fmt.Sprintf(p.config.DefaultSearchEngine, neturl.QueryEscape(input)) | |
| 33 | 33 | } | |
| 34 | 34 | ||
| 35 | 35 | // Determine result type based on the URL | |
| 36 | - | var resultType InputType = InputTypeFallbackSearch | |
| 37 | - | confidence := 0.1 | |
| 36 | + | var resultType InputType | |
| 37 | + | var confidence float64 | |
| 38 | 38 | ||
| 39 | - | if strings.Contains(finalURL, "google.com/search") { | |
| 39 | + | // Check if this is a search query result | |
| 40 | + | isSearchResult := strings.Contains(finalURL, neturl.QueryEscape(input)) | |
| 41 | + | ||
| 42 | + | switch { | |
| 43 | + | case isSearchResult: | |
| 40 | 44 | resultType = InputTypeFallbackSearch | |
| 41 | 45 | confidence = 0.1 | |
| 42 | - | } else if strings.HasPrefix(finalURL, "http://") || strings.HasPrefix(finalURL, "https://") || strings.HasPrefix(finalURL, "dumb://") || strings.HasPrefix(finalURL, "file://") { | |
| 46 | + | case strings.HasPrefix(finalURL, "http://"), | |
| 47 | + | strings.HasPrefix(finalURL, "https://"), | |
| 48 | + | strings.HasPrefix(finalURL, "dumb://"), | |
| 49 | + | strings.HasPrefix(finalURL, "file://"): | |
| 43 | 50 | resultType = InputTypeDirectURL | |
| 44 | 51 | confidence = 1.0 | |
| 52 | + | default: | |
| 53 | + | resultType = InputTypeFallbackSearch | |
| 54 | + | confidence = 0.1 | |
| 45 | 55 | } | |
| 46 | 56 | ||
| 47 | 57 | // Check if it was a shortcut | |
| @@ -140,7 +150,7 @@ func (p *Parser) parseInputUsingCLILogic(input string) (string, error) { | |||
| 140 | 150 | // 8. Everything else is a search query | |
| 141 | 151 | // This includes: single words, multi-word phrases, questions, special characters, etc. | |
| 142 | 152 | // Single words without TLDs should be treated as search queries, not hostnames | |
| 143 | - | return fmt.Sprintf("https://www.google.com/search?q=%s", neturl.QueryEscape(input)), nil | |
| 153 | + | return fmt.Sprintf(p.config.DefaultSearchEngine, neturl.QueryEscape(input)), nil | |
| 144 | 154 | } | |
| 145 | 155 | ||
| 146 | 156 | // parseSearchShortcut handles search shortcut inputs. | |
| @@ -256,22 +266,16 @@ func (p *Parser) processShortcut(shortcutKey, query string, shortcut config.Sear | |||
| 256 | 266 | // buildSearchURL builds a search URL using the default search engine. | |
| 257 | 267 | func (p *Parser) buildSearchURL(searchEngine, query string) string { | |
| 258 | 268 | q := neturl.QueryEscape(query) | |
| 259 | - | // If no specific search engine, use Google as default | |
| 269 | + | // If no specific search engine, use the configured default | |
| 260 | 270 | if searchEngine == "" { | |
| 261 | - | defaultShortcut, exists := p.config.SearchShortcuts["g"] | |
| 262 | - | if exists { | |
| 263 | - | url := strings.ReplaceAll(defaultShortcut.URL, "{query}", q) | |
| 264 | - | url = strings.ReplaceAll(url, "%s", q) | |
| 265 | - | return url | |
| 266 | - | } | |
| 267 | - | // Fallback to Google if no "g" shortcut configured | |
| 268 | - | return fmt.Sprintf("https://www.google.com/search?q=%s", q) | |
| 271 | + | return fmt.Sprintf(p.config.DefaultSearchEngine, q) | |
| 269 | 272 | } | |
| 270 | 273 | ||
| 271 | 274 | // Use specified search engine | |
| 272 | 275 | shortcut, exists := p.config.SearchShortcuts[searchEngine] | |
| 273 | 276 | if !exists { | |
| 274 | - | return fmt.Sprintf("https://www.google.com/search?q=%s", q) | |
| 277 | + | // Fallback to default search engine if shortcut not found | |
| 278 | + | return fmt.Sprintf(p.config.DefaultSearchEngine, q) | |
| 275 | 279 | } | |
| 276 | 280 | ||
| 277 | 281 | u := strings.ReplaceAll(shortcut.URL, "{query}", q) | |
| @@ -433,9 +437,13 @@ func (p *Parser) ValidateConfig() error { | |||
| 433 | 437 | return fmt.Errorf("no search shortcuts configured") | |
| 434 | 438 | } | |
| 435 | 439 | ||
| 436 | - | // Check for default search shortcut | |
| 437 | - | if _, hasGoogle := p.config.SearchShortcuts["g"]; !hasGoogle { | |
| 438 | - | // Warning: no default search configured | |
| 440 | + | // Check for default search engine | |
| 441 | + | if p.config.DefaultSearchEngine == "" { | |
| 442 | + | return fmt.Errorf("default search engine is not configured") | |
| 443 | + | } | |
| 444 | + | ||
| 445 | + | if !strings.Contains(p.config.DefaultSearchEngine, "%s") { | |
| 446 | + | return fmt.Errorf("default search engine must contain %%s placeholder") | |
| 439 | 447 | } | |
| 440 | 448 | ||
| 441 | 449 | return nil | |
| @@ -18,7 +18,7 @@ func (p *Parser) ParseInput(input string) (*ParseResult, error) { | |||||
| 18 | if input == "" { | 18 | if input == "" { | ||
| 19 | return &ParseResult{ | 19 | return &ParseResult{ | ||
| 20 | Type: InputTypeFallbackSearch, | 20 | Type: InputTypeFallbackSearch, | ||
| 21 | - | URL: "https://www.google.com/search?q=", | 21 | + | URL: fmt.Sprintf(p.config.DefaultSearchEngine, ""), |
| 22 | Query: input, | 22 | Query: input, | ||
| 23 | Confidence: 0.0, | 23 | Confidence: 0.0, | ||
| 24 | ProcessingTime: time.Since(startTime), | 24 | ProcessingTime: time.Since(startTime), | ||
| @@ -29,19 +29,29 @@ func (p *Parser) ParseInput(input string) (*ParseResult, error) { | |||||
| 29 | finalURL, err := p.parseInputUsingCLILogic(input) | 29 | finalURL, err := p.parseInputUsingCLILogic(input) | ||
| 30 | if err != nil { | 30 | if err != nil { | ||
| 31 | // If parsing fails, fall back to search | 31 | // If parsing fails, fall back to search | ||
| 32 | - | finalURL = fmt.Sprintf("https://www.google.com/search?q=%s", neturl.QueryEscape(input)) | 32 | + | finalURL = fmt.Sprintf(p.config.DefaultSearchEngine, neturl.QueryEscape(input)) |
| 33 | } | 33 | } | ||
| 34 | 34 | ||||
| 35 | // Determine result type based on the URL | 35 | // Determine result type based on the URL | ||
| 36 | - | var resultType InputType = InputTypeFallbackSearch | 36 | + | var resultType InputType |
| 37 | - | confidence := 0.1 | 37 | + | var confidence float64 |
| 38 | 38 | ||||
| 39 | - | if strings.Contains(finalURL, "google.com/search") { | 39 | + | // Check if this is a search query result |
| 40 | + | isSearchResult := strings.Contains(finalURL, neturl.QueryEscape(input)) | |||
| 41 | + | ||||
| 42 | + | switch { | |||
| 43 | + | case isSearchResult: | |||
| 40 | resultType = InputTypeFallbackSearch | 44 | resultType = InputTypeFallbackSearch | ||
| 41 | confidence = 0.1 | 45 | confidence = 0.1 | ||
| 42 | - | } else if strings.HasPrefix(finalURL, "http://") || strings.HasPrefix(finalURL, "https://") || strings.HasPrefix(finalURL, "dumb://") || strings.HasPrefix(finalURL, "file://") { | 46 | + | case strings.HasPrefix(finalURL, "http://"), |
| 47 | + | strings.HasPrefix(finalURL, "https://"), | |||
| 48 | + | strings.HasPrefix(finalURL, "dumb://"), | |||
| 49 | + | strings.HasPrefix(finalURL, "file://"): | |||
| 43 | resultType = InputTypeDirectURL | 50 | resultType = InputTypeDirectURL | ||
| 44 | confidence = 1.0 | 51 | confidence = 1.0 | ||
| 52 | + | default: | |||
| 53 | + | resultType = InputTypeFallbackSearch | |||
| 54 | + | confidence = 0.1 | |||
| 45 | } | 55 | } | ||
| 46 | 56 | ||||
| 47 | // Check if it was a shortcut | 57 | // Check if it was a shortcut | ||
| @@ -140,7 +150,7 @@ func (p *Parser) parseInputUsingCLILogic(input string) (string, error) { | |||||
| 140 | // 8. Everything else is a search query | 150 | // 8. Everything else is a search query | ||
| 141 | // This includes: single words, multi-word phrases, questions, special characters, etc. | 151 | // This includes: single words, multi-word phrases, questions, special characters, etc. | ||
| 142 | // Single words without TLDs should be treated as search queries, not hostnames | 152 | // Single words without TLDs should be treated as search queries, not hostnames | ||
| 143 | - | return fmt.Sprintf("https://www.google.com/search?q=%s", neturl.QueryEscape(input)), nil | 153 | + | return fmt.Sprintf(p.config.DefaultSearchEngine, neturl.QueryEscape(input)), nil |
| 144 | } | 154 | } | ||
| 145 | 155 | ||||
| 146 | // parseSearchShortcut handles search shortcut inputs. | 156 | // parseSearchShortcut handles search shortcut inputs. | ||
| @@ -256,22 +266,16 @@ func (p *Parser) processShortcut(shortcutKey, query string, shortcut config.Sear | |||||
| 256 | // buildSearchURL builds a search URL using the default search engine. | 266 | // buildSearchURL builds a search URL using the default search engine. | ||
| 257 | func (p *Parser) buildSearchURL(searchEngine, query string) string { | 267 | func (p *Parser) buildSearchURL(searchEngine, query string) string { | ||
| 258 | q := neturl.QueryEscape(query) | 268 | q := neturl.QueryEscape(query) | ||
| 259 | - | // If no specific search engine, use Google as default | 269 | + | // If no specific search engine, use the configured default |
| 260 | if searchEngine == "" { | 270 | if searchEngine == "" { | ||
| 261 | - | defaultShortcut, exists := p.config.SearchShortcuts["g"] | 271 | + | return fmt.Sprintf(p.config.DefaultSearchEngine, q) |
| 262 | - | if exists { | |||
| 263 | - | url := strings.ReplaceAll(defaultShortcut.URL, "{query}", q) | |||
| 264 | - | url = strings.ReplaceAll(url, "%s", q) | |||
| 265 | - | return url | |||
| 266 | - | } | |||
| 267 | - | // Fallback to Google if no "g" shortcut configured | |||
| 268 | - | return fmt.Sprintf("https://www.google.com/search?q=%s", q) | |||
| 269 | } | 272 | } | ||
| 270 | 273 | ||||
| 271 | // Use specified search engine | 274 | // Use specified search engine | ||
| 272 | shortcut, exists := p.config.SearchShortcuts[searchEngine] | 275 | shortcut, exists := p.config.SearchShortcuts[searchEngine] | ||
| 273 | if !exists { | 276 | if !exists { | ||
| 274 | - | return fmt.Sprintf("https://www.google.com/search?q=%s", q) | 277 | + | // Fallback to default search engine if shortcut not found |
| 278 | + | return fmt.Sprintf(p.config.DefaultSearchEngine, q) | |||
| 275 | } | 279 | } | ||
| 276 | 280 | ||||
| 277 | u := strings.ReplaceAll(shortcut.URL, "{query}", q) | 281 | u := strings.ReplaceAll(shortcut.URL, "{query}", q) | ||
| @@ -433,9 +437,13 @@ func (p *Parser) ValidateConfig() error { | |||||
| 433 | return fmt.Errorf("no search shortcuts configured") | 437 | return fmt.Errorf("no search shortcuts configured") | ||
| 434 | } | 438 | } | ||
| 435 | 439 | ||||
| 436 | - | // Check for default search shortcut | 440 | + | // Check for default search engine |
| 437 | - | if _, hasGoogle := p.config.SearchShortcuts["g"]; !hasGoogle { | 441 | + | if p.config.DefaultSearchEngine == "" { |
| 438 | - | // Warning: no default search configured | 442 | + | return fmt.Errorf("default search engine is not configured") |
| 443 | + | } | |||
| 444 | + | ||||
| 445 | + | if !strings.Contains(p.config.DefaultSearchEngine, "%s") { | |||
| 446 | + | return fmt.Errorf("default search engine must contain %%s placeholder") | |||
| 439 | } | 447 | } | ||
| 440 | 448 | ||||
| 441 | return nil | 449 | return nil | ||