diff options
| author | Dax Raad <[email protected]> | 2025-11-02 18:43:17 -0500 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-11-02 18:43:33 -0500 |
| commit | f68374ad2223ddc213bdea9519ca6a699819ee0e (patch) | |
| tree | 04f0fe21b8e12cd62d7274961bb0cff64f966f40 /packages/tui/internal/theme/loader.go | |
| parent | 5e86c9b7916f75c7ad227b80eab18c7c54fc8ffe (diff) | |
| download | opencode-f68374ad2223ddc213bdea9519ca6a699819ee0e.tar.gz opencode-f68374ad2223ddc213bdea9519ca6a699819ee0e.zip | |
DELETE GO BUBBLETEA CRAP HOORAY
Diffstat (limited to 'packages/tui/internal/theme/loader.go')
| -rw-r--r-- | packages/tui/internal/theme/loader.go | 408 |
1 files changed, 0 insertions, 408 deletions
diff --git a/packages/tui/internal/theme/loader.go b/packages/tui/internal/theme/loader.go deleted file mode 100644 index b3d2f0982..000000000 --- a/packages/tui/internal/theme/loader.go +++ /dev/null @@ -1,408 +0,0 @@ -package theme - -import ( - "embed" - "encoding/json" - "fmt" - "image/color" - "os" - "path" - "path/filepath" - "strings" - - "github.com/charmbracelet/lipgloss/v2" - "github.com/charmbracelet/lipgloss/v2/compat" -) - -//go:embed themes/*.json -var themesFS embed.FS - -type JSONTheme struct { - Defs map[string]any `json:"defs,omitempty"` - Theme map[string]any `json:"theme"` -} - -type LoadedTheme struct { - BaseTheme - name string -} - -func (t *LoadedTheme) Name() string { - return t.name -} - -type colorRef struct { - value any - resolved bool -} - -func LoadThemesFromJSON() error { - entries, err := themesFS.ReadDir("themes") - if err != nil { - return fmt.Errorf("failed to read themes directory: %w", err) - } - - for _, entry := range entries { - if !strings.HasSuffix(entry.Name(), ".json") { - continue - } - themeName := strings.TrimSuffix(entry.Name(), ".json") - data, err := themesFS.ReadFile(path.Join("themes", entry.Name())) - if err != nil { - return fmt.Errorf("failed to read theme file %s: %w", entry.Name(), err) - } - theme, err := parseJSONTheme(themeName, data) - if err != nil { - return fmt.Errorf("failed to parse theme %s: %w", themeName, err) - } - RegisterTheme(themeName, theme) - } - - return nil -} - -// LoadThemesFromDirectories loads themes from user directories in the correct override order. -// The hierarchy is (from lowest to highest priority): -// 1. Built-in themes (embedded) -// 2. USER_CONFIG/opencode/themes/*.json -// 3. PROJECT_ROOT/.opencode/themes/*.json -// 4. CWD/.opencode/themes/*.json -func LoadThemesFromDirectories(userConfig, projectRoot, cwd string) error { - if err := LoadThemesFromJSON(); err != nil { - return fmt.Errorf("failed to load built-in themes: %w", err) - } - - dirs := []string{ - filepath.Join(userConfig, "themes"), - filepath.Join(projectRoot, ".opencode", "themes"), - } - if cwd != projectRoot { - dirs = append(dirs, filepath.Join(cwd, ".opencode", "themes")) - } - - for _, dir := range dirs { - if err := loadThemesFromDirectory(dir); err != nil { - fmt.Printf("Warning: Failed to load themes from %s: %v\n", dir, err) - } - } - - return nil -} - -func loadThemesFromDirectory(dir string) error { - if _, err := os.Stat(dir); os.IsNotExist(err) { - return nil // Directory doesn't exist, which is fine - } - - entries, err := os.ReadDir(dir) - if err != nil { - return fmt.Errorf("failed to read directory: %w", err) - } - - for _, entry := range entries { - if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") { - continue - } - - themeName := strings.TrimSuffix(entry.Name(), ".json") - filePath := filepath.Join(dir, entry.Name()) - - data, err := os.ReadFile(filePath) - if err != nil { - fmt.Printf("Warning: Failed to read theme file %s: %v\n", filePath, err) - continue - } - - theme, err := parseJSONTheme(themeName, data) - if err != nil { - fmt.Printf("Warning: Failed to parse theme %s: %v\n", filePath, err) - continue - } - - RegisterTheme(themeName, theme) - } - - return nil -} - -func parseJSONTheme(name string, data []byte) (Theme, error) { - var jsonTheme JSONTheme - if err := json.Unmarshal(data, &jsonTheme); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) - } - theme := &LoadedTheme{ - name: name, - } - colorMap := make(map[string]*colorRef) - for key, value := range jsonTheme.Defs { - colorMap[key] = &colorRef{value: value, resolved: false} - } - for key, value := range jsonTheme.Theme { - colorMap[key] = &colorRef{value: value, resolved: false} - } - resolver := &colorResolver{ - colors: colorMap, - visited: make(map[string]bool), - } - for key, value := range jsonTheme.Theme { - resolved, err := resolver.resolveColor(key, value) - if err != nil { - return nil, fmt.Errorf("failed to resolve color %s: %w", key, err) - } - adaptiveColor, err := parseResolvedColor(resolved) - if err != nil { - return nil, fmt.Errorf("failed to parse color %s: %w", key, err) - } - if err := setThemeColor(theme, key, adaptiveColor); err != nil { - return nil, fmt.Errorf("failed to set color %s: %w", key, err) - } - } - - return theme, nil -} - -type colorResolver struct { - colors map[string]*colorRef - visited map[string]bool -} - -func (r *colorResolver) resolveColor(key string, value any) (any, error) { - if r.visited[key] { - return nil, fmt.Errorf("circular reference detected for color %s", key) - } - r.visited[key] = true - defer func() { r.visited[key] = false }() - - switch v := value.(type) { - case string: - if strings.HasPrefix(v, "#") || v == "none" { - return v, nil - } - return r.resolveReference(v) - case float64: - return v, nil - case map[string]any: - resolved := make(map[string]any) - - if dark, ok := v["dark"]; ok { - resolvedDark, err := r.resolveColorValue(dark) - if err != nil { - return nil, fmt.Errorf("failed to resolve dark variant: %w", err) - } - resolved["dark"] = resolvedDark - } - - if light, ok := v["light"]; ok { - resolvedLight, err := r.resolveColorValue(light) - if err != nil { - return nil, fmt.Errorf("failed to resolve light variant: %w", err) - } - resolved["light"] = resolvedLight - } - - return resolved, nil - default: - return nil, fmt.Errorf("invalid color value type: %T", value) - } -} - -func (r *colorResolver) resolveColorValue(value any) (any, error) { - switch v := value.(type) { - case string: - if strings.HasPrefix(v, "#") || v == "none" { - return v, nil - } - return r.resolveReference(v) - case float64: - return v, nil - default: - return nil, fmt.Errorf("invalid color value type: %T", value) - } -} - -func (r *colorResolver) resolveReference(ref string) (any, error) { - colorRef, exists := r.colors[ref] - if !exists { - return nil, fmt.Errorf("color reference '%s' not found", ref) - } - - if colorRef.resolved { - return colorRef.value, nil - } - - resolved, err := r.resolveColor(ref, colorRef.value) - if err != nil { - return nil, err - } - - colorRef.value = resolved - colorRef.resolved = true - - return resolved, nil -} - -func parseResolvedColor(value any) (compat.AdaptiveColor, error) { - switch v := value.(type) { - case string: - if v == "none" { - return compat.AdaptiveColor{ - Dark: lipgloss.NoColor{}, - Light: lipgloss.NoColor{}, - }, nil - } - return compat.AdaptiveColor{ - Dark: lipgloss.Color(v), - Light: lipgloss.Color(v), - }, nil - case float64: - colorStr := fmt.Sprintf("%d", int(v)) - return compat.AdaptiveColor{ - Dark: lipgloss.Color(colorStr), - Light: lipgloss.Color(colorStr), - }, nil - case map[string]any: - dark, darkOk := v["dark"] - light, lightOk := v["light"] - - if !darkOk || !lightOk { - return compat.AdaptiveColor{}, fmt.Errorf("color object must have both 'dark' and 'light' keys") - } - darkColor, err := parseColorValue(dark) - if err != nil { - return compat.AdaptiveColor{}, fmt.Errorf("failed to parse dark color: %w", err) - } - lightColor, err := parseColorValue(light) - if err != nil { - return compat.AdaptiveColor{}, fmt.Errorf("failed to parse light color: %w", err) - } - return compat.AdaptiveColor{ - Dark: darkColor, - Light: lightColor, - }, nil - default: - return compat.AdaptiveColor{}, fmt.Errorf("invalid resolved color type: %T", value) - } -} - -func parseColorValue(value any) (color.Color, error) { - switch v := value.(type) { - case string: - if v == "none" { - return lipgloss.NoColor{}, nil - } - return lipgloss.Color(v), nil - case float64: - return lipgloss.Color(fmt.Sprintf("%d", int(v))), nil - default: - return nil, fmt.Errorf("invalid color value type: %T", value) - } -} - -func setThemeColor(theme *LoadedTheme, key string, color compat.AdaptiveColor) error { - switch key { - case "primary": - theme.PrimaryColor = color - case "secondary": - theme.SecondaryColor = color - case "accent": - theme.AccentColor = color - case "error": - theme.ErrorColor = color - case "warning": - theme.WarningColor = color - case "success": - theme.SuccessColor = color - case "info": - theme.InfoColor = color - case "text": - theme.TextColor = color - case "textMuted": - theme.TextMutedColor = color - case "background": - theme.BackgroundColor = color - case "backgroundPanel": - theme.BackgroundPanelColor = color - case "backgroundElement": - theme.BackgroundElementColor = color - case "border": - theme.BorderColor = color - case "borderActive": - theme.BorderActiveColor = color - case "borderSubtle": - theme.BorderSubtleColor = color - case "diffAdded": - theme.DiffAddedColor = color - case "diffRemoved": - theme.DiffRemovedColor = color - case "diffContext": - theme.DiffContextColor = color - case "diffHunkHeader": - theme.DiffHunkHeaderColor = color - case "diffHighlightAdded": - theme.DiffHighlightAddedColor = color - case "diffHighlightRemoved": - theme.DiffHighlightRemovedColor = color - case "diffAddedBg": - theme.DiffAddedBgColor = color - case "diffRemovedBg": - theme.DiffRemovedBgColor = color - case "diffContextBg": - theme.DiffContextBgColor = color - case "diffLineNumber": - theme.DiffLineNumberColor = color - case "diffAddedLineNumberBg": - theme.DiffAddedLineNumberBgColor = color - case "diffRemovedLineNumberBg": - theme.DiffRemovedLineNumberBgColor = color - case "markdownText": - theme.MarkdownTextColor = color - case "markdownHeading": - theme.MarkdownHeadingColor = color - case "markdownLink": - theme.MarkdownLinkColor = color - case "markdownLinkText": - theme.MarkdownLinkTextColor = color - case "markdownCode": - theme.MarkdownCodeColor = color - case "markdownBlockQuote": - theme.MarkdownBlockQuoteColor = color - case "markdownEmph": - theme.MarkdownEmphColor = color - case "markdownStrong": - theme.MarkdownStrongColor = color - case "markdownHorizontalRule": - theme.MarkdownHorizontalRuleColor = color - case "markdownListItem": - theme.MarkdownListItemColor = color - case "markdownListEnumeration": - theme.MarkdownListEnumerationColor = color - case "markdownImage": - theme.MarkdownImageColor = color - case "markdownImageText": - theme.MarkdownImageTextColor = color - case "markdownCodeBlock": - theme.MarkdownCodeBlockColor = color - case "syntaxComment": - theme.SyntaxCommentColor = color - case "syntaxKeyword": - theme.SyntaxKeywordColor = color - case "syntaxFunction": - theme.SyntaxFunctionColor = color - case "syntaxVariable": - theme.SyntaxVariableColor = color - case "syntaxString": - theme.SyntaxStringColor = color - case "syntaxNumber": - theme.SyntaxNumberColor = color - case "syntaxType": - theme.SyntaxTypeColor = color - case "syntaxOperator": - theme.SyntaxOperatorColor = color - case "syntaxPunctuation": - theme.SyntaxPunctuationColor = color - default: - // Ignore unknown keys for forward compatibility - return nil - } - return nil -} |
