diff options
| author | Dax Raad <[email protected]> | 2025-05-18 22:30:41 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-05-26 12:40:17 -0400 |
| commit | 99af6146d5def31c59993636d60eb75a483a283b (patch) | |
| tree | eb57ed227c15cf9be54bc9f231c83895d812f881 /pkg/tui/theme/manager.go | |
| parent | 020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff) | |
| download | opencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz opencode-99af6146d5def31c59993636d60eb75a483a283b.zip | |
openapi
Diffstat (limited to 'pkg/tui/theme/manager.go')
| -rw-r--r-- | pkg/tui/theme/manager.go | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/pkg/tui/theme/manager.go b/pkg/tui/theme/manager.go new file mode 100644 index 000000000..5a5c791fb --- /dev/null +++ b/pkg/tui/theme/manager.go @@ -0,0 +1,265 @@ +package theme + +import ( + "fmt" + "log/slog" + "slices" + "strings" + "sync" + + "github.com/alecthomas/chroma/v2/styles" + "github.com/sst/opencode/internal/config" +) + +// Manager handles theme registration, selection, and retrieval. +// It maintains a registry of available themes and tracks the currently active theme. +type Manager struct { + themes map[string]Theme + currentName string + mu sync.RWMutex +} + +// Global instance of the theme manager +var globalManager = &Manager{ + themes: make(map[string]Theme), + currentName: "", +} + +// Default theme instance for custom theme defaulting +var defaultThemeColors = NewOpenCodeTheme() + +// RegisterTheme adds a new theme to the registry. +// If this is the first theme registered, it becomes the default. +func RegisterTheme(name string, theme Theme) { + globalManager.mu.Lock() + defer globalManager.mu.Unlock() + + globalManager.themes[name] = theme + + // If this is the first theme, make it the default + if globalManager.currentName == "" { + globalManager.currentName = name + } +} + +// SetTheme changes the active theme to the one with the specified name. +// Returns an error if the theme doesn't exist. +func SetTheme(name string) error { + globalManager.mu.Lock() + defer globalManager.mu.Unlock() + + delete(styles.Registry, "charm") + + // Handle custom theme + if name == "custom" { + cfg := config.Get() + if cfg == nil || cfg.TUI.CustomTheme == nil || len(cfg.TUI.CustomTheme) == 0 { + return fmt.Errorf("custom theme selected but no custom theme colors defined in config") + } + + customTheme, err := LoadCustomTheme(cfg.TUI.CustomTheme) + if err != nil { + return fmt.Errorf("failed to load custom theme: %w", err) + } + + // Register the custom theme + globalManager.themes["custom"] = customTheme + } else if _, exists := globalManager.themes[name]; !exists { + return fmt.Errorf("theme '%s' not found", name) + } + + globalManager.currentName = name + + // Update the config file using viper + if err := updateConfigTheme(name); err != nil { + // Log the error but don't fail the theme change + slog.Warn("Warning: Failed to update config file with new theme", "err", err) + } + + return nil +} + +// CurrentTheme returns the currently active theme. +// If no theme is set, it returns nil. +func CurrentTheme() Theme { + globalManager.mu.RLock() + defer globalManager.mu.RUnlock() + + if globalManager.currentName == "" { + return nil + } + + return globalManager.themes[globalManager.currentName] +} + +// CurrentThemeName returns the name of the currently active theme. +func CurrentThemeName() string { + globalManager.mu.RLock() + defer globalManager.mu.RUnlock() + + return globalManager.currentName +} + +// AvailableThemes returns a list of all registered theme names. +func AvailableThemes() []string { + globalManager.mu.RLock() + defer globalManager.mu.RUnlock() + + names := make([]string, 0, len(globalManager.themes)) + for name := range globalManager.themes { + names = append(names, name) + } + slices.SortFunc(names, func(a, b string) int { + if a == "opencode" { + return -1 + } else if b == "opencode" { + return 1 + } + return strings.Compare(a, b) + }) + return names +} + +// GetTheme returns a specific theme by name. +// Returns nil if the theme doesn't exist. +func GetTheme(name string) Theme { + globalManager.mu.RLock() + defer globalManager.mu.RUnlock() + + return globalManager.themes[name] +} + +// LoadCustomTheme creates a new theme instance based on the custom theme colors +// defined in the configuration. It uses the default OpenCode theme as a base +// and overrides colors that are specified in the customTheme map. +func LoadCustomTheme(customTheme map[string]any) (Theme, error) { + // Create a new theme based on the default OpenCode theme + theme := NewOpenCodeTheme() + + // Process each color in the custom theme map + for key, value := range customTheme { + adaptiveColor, err := ParseAdaptiveColor(value) + if err != nil { + slog.Warn("Invalid color definition in custom theme", "key", key, "error", err) + continue // Skip this color but continue processing others + } + + // Set the color in the theme based on the key + switch strings.ToLower(key) { + case "primary": + theme.PrimaryColor = adaptiveColor + case "secondary": + theme.SecondaryColor = adaptiveColor + case "accent": + theme.AccentColor = adaptiveColor + case "error": + theme.ErrorColor = adaptiveColor + case "warning": + theme.WarningColor = adaptiveColor + case "success": + theme.SuccessColor = adaptiveColor + case "info": + theme.InfoColor = adaptiveColor + case "text": + theme.TextColor = adaptiveColor + case "textmuted": + theme.TextMutedColor = adaptiveColor + case "textemphasized": + theme.TextEmphasizedColor = adaptiveColor + case "background": + theme.BackgroundColor = adaptiveColor + case "backgroundsecondary": + theme.BackgroundSecondaryColor = adaptiveColor + case "backgrounddarker": + theme.BackgroundDarkerColor = adaptiveColor + case "bordernormal": + theme.BorderNormalColor = adaptiveColor + case "borderfocused": + theme.BorderFocusedColor = adaptiveColor + case "borderdim": + theme.BorderDimColor = adaptiveColor + case "diffadded": + theme.DiffAddedColor = adaptiveColor + case "diffremoved": + theme.DiffRemovedColor = adaptiveColor + case "diffcontext": + theme.DiffContextColor = adaptiveColor + case "diffhunkheader": + theme.DiffHunkHeaderColor = adaptiveColor + case "diffhighlightadded": + theme.DiffHighlightAddedColor = adaptiveColor + case "diffhighlightremoved": + theme.DiffHighlightRemovedColor = adaptiveColor + case "diffaddedbg": + theme.DiffAddedBgColor = adaptiveColor + case "diffremovedbg": + theme.DiffRemovedBgColor = adaptiveColor + case "diffcontextbg": + theme.DiffContextBgColor = adaptiveColor + case "difflinenumber": + theme.DiffLineNumberColor = adaptiveColor + case "diffaddedlinenumberbg": + theme.DiffAddedLineNumberBgColor = adaptiveColor + case "diffremovedlinenumberbg": + theme.DiffRemovedLineNumberBgColor = adaptiveColor + case "syntaxcomment": + theme.SyntaxCommentColor = adaptiveColor + case "syntaxkeyword": + theme.SyntaxKeywordColor = adaptiveColor + case "syntaxfunction": + theme.SyntaxFunctionColor = adaptiveColor + case "syntaxvariable": + theme.SyntaxVariableColor = adaptiveColor + case "syntaxstring": + theme.SyntaxStringColor = adaptiveColor + case "syntaxnumber": + theme.SyntaxNumberColor = adaptiveColor + case "syntaxtype": + theme.SyntaxTypeColor = adaptiveColor + case "syntaxoperator": + theme.SyntaxOperatorColor = adaptiveColor + case "syntaxpunctuation": + theme.SyntaxPunctuationColor = adaptiveColor + case "markdowntext": + theme.MarkdownTextColor = adaptiveColor + case "markdownheading": + theme.MarkdownHeadingColor = adaptiveColor + case "markdownlink": + theme.MarkdownLinkColor = adaptiveColor + case "markdownlinktext": + theme.MarkdownLinkTextColor = adaptiveColor + case "markdowncode": + theme.MarkdownCodeColor = adaptiveColor + case "markdownblockquote": + theme.MarkdownBlockQuoteColor = adaptiveColor + case "markdownemph": + theme.MarkdownEmphColor = adaptiveColor + case "markdownstrong": + theme.MarkdownStrongColor = adaptiveColor + case "markdownhorizontalrule": + theme.MarkdownHorizontalRuleColor = adaptiveColor + case "markdownlistitem": + theme.MarkdownListItemColor = adaptiveColor + case "markdownlistitemenum": + theme.MarkdownListEnumerationColor = adaptiveColor + case "markdownimage": + theme.MarkdownImageColor = adaptiveColor + case "markdownimagetext": + theme.MarkdownImageTextColor = adaptiveColor + case "markdowncodeblock": + theme.MarkdownCodeBlockColor = adaptiveColor + case "markdownlistenumeration": + theme.MarkdownListEnumerationColor = adaptiveColor + default: + slog.Warn("Unknown color key in custom theme", "key", key) + } + } + + return theme, nil +} + +// updateConfigTheme updates the theme setting in the configuration file +func updateConfigTheme(themeName string) error { + // Use the config package to update the theme + return config.UpdateTheme(themeName) +} |
