summaryrefslogtreecommitdiffhomepage
path: root/pkg/tui/theme/manager.go
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-18 22:30:41 -0400
committerDax Raad <[email protected]>2025-05-26 12:40:17 -0400
commit99af6146d5def31c59993636d60eb75a483a283b (patch)
treeeb57ed227c15cf9be54bc9f231c83895d812f881 /pkg/tui/theme/manager.go
parent020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff)
downloadopencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz
opencode-99af6146d5def31c59993636d60eb75a483a283b.zip
openapi
Diffstat (limited to 'pkg/tui/theme/manager.go')
-rw-r--r--pkg/tui/theme/manager.go265
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)
+}