diff options
| author | adamdottv <[email protected]> | 2025-04-28 08:46:09 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-04-30 07:46:34 -0500 |
| commit | 61b605e724eb4cc50ab831534fcdd18e031d68eb (patch) | |
| tree | b15b074a8fed1931e179a8d3df08d05b753c7aa3 /internal/tui/theme | |
| parent | 61d9dc95111d2645a49816f6d9d6cc1014be1a22 (diff) | |
| download | opencode-61b605e724eb4cc50ab831534fcdd18e031d68eb.tar.gz opencode-61b605e724eb4cc50ab831534fcdd18e031d68eb.zip | |
feat: themes
Diffstat (limited to 'internal/tui/theme')
| -rw-r--r-- | internal/tui/theme/catppuccin.go | 248 | ||||
| -rw-r--r-- | internal/tui/theme/gruvbox.go | 302 | ||||
| -rw-r--r-- | internal/tui/theme/manager.go | 113 | ||||
| -rw-r--r-- | internal/tui/theme/theme.go | 208 | ||||
| -rw-r--r-- | internal/tui/theme/theme_test.go | 89 |
5 files changed, 960 insertions, 0 deletions
diff --git a/internal/tui/theme/catppuccin.go b/internal/tui/theme/catppuccin.go new file mode 100644 index 000000000..a843100ab --- /dev/null +++ b/internal/tui/theme/catppuccin.go @@ -0,0 +1,248 @@ +package theme + +import ( + catppuccin "github.com/catppuccin/go" + "github.com/charmbracelet/lipgloss" +) + +// CatppuccinTheme implements the Theme interface with Catppuccin colors. +// It provides both dark (Mocha) and light (Latte) variants. +type CatppuccinTheme struct { + BaseTheme +} + +// NewCatppuccinTheme creates a new instance of the Catppuccin theme. +func NewCatppuccinTheme() *CatppuccinTheme { + // Get the Catppuccin palettes + mocha := catppuccin.Mocha + latte := catppuccin.Latte + + theme := &CatppuccinTheme{} + + // Base colors + theme.PrimaryColor = lipgloss.AdaptiveColor{ + Dark: mocha.Blue().Hex, + Light: latte.Blue().Hex, + } + theme.SecondaryColor = lipgloss.AdaptiveColor{ + Dark: mocha.Mauve().Hex, + Light: latte.Mauve().Hex, + } + theme.AccentColor = lipgloss.AdaptiveColor{ + Dark: mocha.Peach().Hex, + Light: latte.Peach().Hex, + } + + // Status colors + theme.ErrorColor = lipgloss.AdaptiveColor{ + Dark: mocha.Red().Hex, + Light: latte.Red().Hex, + } + theme.WarningColor = lipgloss.AdaptiveColor{ + Dark: mocha.Peach().Hex, + Light: latte.Peach().Hex, + } + theme.SuccessColor = lipgloss.AdaptiveColor{ + Dark: mocha.Green().Hex, + Light: latte.Green().Hex, + } + theme.InfoColor = lipgloss.AdaptiveColor{ + Dark: mocha.Blue().Hex, + Light: latte.Blue().Hex, + } + + // Text colors + theme.TextColor = lipgloss.AdaptiveColor{ + Dark: mocha.Text().Hex, + Light: latte.Text().Hex, + } + theme.TextMutedColor = lipgloss.AdaptiveColor{ + Dark: mocha.Subtext0().Hex, + Light: latte.Subtext0().Hex, + } + theme.TextEmphasizedColor = lipgloss.AdaptiveColor{ + Dark: mocha.Lavender().Hex, + Light: latte.Lavender().Hex, + } + + // Background colors + theme.BackgroundColor = lipgloss.AdaptiveColor{ + Dark: "#212121", // From existing styles + Light: "#EEEEEE", // Light equivalent + } + theme.BackgroundSecondaryColor = lipgloss.AdaptiveColor{ + Dark: "#2c2c2c", // From existing styles + Light: "#E0E0E0", // Light equivalent + } + theme.BackgroundDarkerColor = lipgloss.AdaptiveColor{ + Dark: "#181818", // From existing styles + Light: "#F5F5F5", // Light equivalent + } + + // Border colors + theme.BorderNormalColor = lipgloss.AdaptiveColor{ + Dark: "#4b4c5c", // From existing styles + Light: "#BDBDBD", // Light equivalent + } + theme.BorderFocusedColor = lipgloss.AdaptiveColor{ + Dark: mocha.Blue().Hex, + Light: latte.Blue().Hex, + } + theme.BorderDimColor = lipgloss.AdaptiveColor{ + Dark: mocha.Surface0().Hex, + Light: latte.Surface0().Hex, + } + + // Diff view colors + theme.DiffAddedColor = lipgloss.AdaptiveColor{ + Dark: "#478247", // From existing diff.go + Light: "#2E7D32", // Light equivalent + } + theme.DiffRemovedColor = lipgloss.AdaptiveColor{ + Dark: "#7C4444", // From existing diff.go + Light: "#C62828", // Light equivalent + } + theme.DiffContextColor = lipgloss.AdaptiveColor{ + Dark: "#a0a0a0", // From existing diff.go + Light: "#757575", // Light equivalent + } + theme.DiffHunkHeaderColor = lipgloss.AdaptiveColor{ + Dark: "#a0a0a0", // From existing diff.go + Light: "#757575", // Light equivalent + } + theme.DiffHighlightAddedColor = lipgloss.AdaptiveColor{ + Dark: "#DAFADA", // From existing diff.go + Light: "#A5D6A7", // Light equivalent + } + theme.DiffHighlightRemovedColor = lipgloss.AdaptiveColor{ + Dark: "#FADADD", // From existing diff.go + Light: "#EF9A9A", // Light equivalent + } + theme.DiffAddedBgColor = lipgloss.AdaptiveColor{ + Dark: "#303A30", // From existing diff.go + Light: "#E8F5E9", // Light equivalent + } + theme.DiffRemovedBgColor = lipgloss.AdaptiveColor{ + Dark: "#3A3030", // From existing diff.go + Light: "#FFEBEE", // Light equivalent + } + theme.DiffContextBgColor = lipgloss.AdaptiveColor{ + Dark: "#212121", // From existing diff.go + Light: "#F5F5F5", // Light equivalent + } + theme.DiffLineNumberColor = lipgloss.AdaptiveColor{ + Dark: "#888888", // From existing diff.go + Light: "#9E9E9E", // Light equivalent + } + theme.DiffAddedLineNumberBgColor = lipgloss.AdaptiveColor{ + Dark: "#293229", // From existing diff.go + Light: "#C8E6C9", // Light equivalent + } + theme.DiffRemovedLineNumberBgColor = lipgloss.AdaptiveColor{ + Dark: "#332929", // From existing diff.go + Light: "#FFCDD2", // Light equivalent + } + + // Markdown colors + theme.MarkdownTextColor = lipgloss.AdaptiveColor{ + Dark: mocha.Text().Hex, + Light: latte.Text().Hex, + } + theme.MarkdownHeadingColor = lipgloss.AdaptiveColor{ + Dark: mocha.Mauve().Hex, + Light: latte.Mauve().Hex, + } + theme.MarkdownLinkColor = lipgloss.AdaptiveColor{ + Dark: mocha.Sky().Hex, + Light: latte.Sky().Hex, + } + theme.MarkdownLinkTextColor = lipgloss.AdaptiveColor{ + Dark: mocha.Pink().Hex, + Light: latte.Pink().Hex, + } + theme.MarkdownCodeColor = lipgloss.AdaptiveColor{ + Dark: mocha.Green().Hex, + Light: latte.Green().Hex, + } + theme.MarkdownBlockQuoteColor = lipgloss.AdaptiveColor{ + Dark: mocha.Yellow().Hex, + Light: latte.Yellow().Hex, + } + theme.MarkdownEmphColor = lipgloss.AdaptiveColor{ + Dark: mocha.Yellow().Hex, + Light: latte.Yellow().Hex, + } + theme.MarkdownStrongColor = lipgloss.AdaptiveColor{ + Dark: mocha.Peach().Hex, + Light: latte.Peach().Hex, + } + theme.MarkdownHorizontalRuleColor = lipgloss.AdaptiveColor{ + Dark: mocha.Overlay0().Hex, + Light: latte.Overlay0().Hex, + } + theme.MarkdownListItemColor = lipgloss.AdaptiveColor{ + Dark: mocha.Blue().Hex, + Light: latte.Blue().Hex, + } + theme.MarkdownListEnumerationColor = lipgloss.AdaptiveColor{ + Dark: mocha.Sky().Hex, + Light: latte.Sky().Hex, + } + theme.MarkdownImageColor = lipgloss.AdaptiveColor{ + Dark: mocha.Sapphire().Hex, + Light: latte.Sapphire().Hex, + } + theme.MarkdownImageTextColor = lipgloss.AdaptiveColor{ + Dark: mocha.Pink().Hex, + Light: latte.Pink().Hex, + } + theme.MarkdownCodeBlockColor = lipgloss.AdaptiveColor{ + Dark: mocha.Text().Hex, + Light: latte.Text().Hex, + } + + // Syntax highlighting colors + theme.SyntaxCommentColor = lipgloss.AdaptiveColor{ + Dark: mocha.Overlay1().Hex, + Light: latte.Overlay1().Hex, + } + theme.SyntaxKeywordColor = lipgloss.AdaptiveColor{ + Dark: mocha.Pink().Hex, + Light: latte.Pink().Hex, + } + theme.SyntaxFunctionColor = lipgloss.AdaptiveColor{ + Dark: mocha.Green().Hex, + Light: latte.Green().Hex, + } + theme.SyntaxVariableColor = lipgloss.AdaptiveColor{ + Dark: mocha.Sky().Hex, + Light: latte.Sky().Hex, + } + theme.SyntaxStringColor = lipgloss.AdaptiveColor{ + Dark: mocha.Yellow().Hex, + Light: latte.Yellow().Hex, + } + theme.SyntaxNumberColor = lipgloss.AdaptiveColor{ + Dark: mocha.Teal().Hex, + Light: latte.Teal().Hex, + } + theme.SyntaxTypeColor = lipgloss.AdaptiveColor{ + Dark: mocha.Sky().Hex, + Light: latte.Sky().Hex, + } + theme.SyntaxOperatorColor = lipgloss.AdaptiveColor{ + Dark: mocha.Pink().Hex, + Light: latte.Pink().Hex, + } + theme.SyntaxPunctuationColor = lipgloss.AdaptiveColor{ + Dark: mocha.Text().Hex, + Light: latte.Text().Hex, + } + + return theme +} + +func init() { + // Register the Catppuccin theme with the theme manager + RegisterTheme("catppuccin", NewCatppuccinTheme()) +}
\ No newline at end of file diff --git a/internal/tui/theme/gruvbox.go b/internal/tui/theme/gruvbox.go new file mode 100644 index 000000000..ed544b84d --- /dev/null +++ b/internal/tui/theme/gruvbox.go @@ -0,0 +1,302 @@ +package theme + +import ( + "github.com/charmbracelet/lipgloss" +) + +// Gruvbox color palette constants +const ( + // Dark theme colors + gruvboxDarkBg0 = "#282828" + gruvboxDarkBg0Soft = "#32302f" + gruvboxDarkBg1 = "#3c3836" + gruvboxDarkBg2 = "#504945" + gruvboxDarkBg3 = "#665c54" + gruvboxDarkBg4 = "#7c6f64" + gruvboxDarkFg0 = "#fbf1c7" + gruvboxDarkFg1 = "#ebdbb2" + gruvboxDarkFg2 = "#d5c4a1" + gruvboxDarkFg3 = "#bdae93" + gruvboxDarkFg4 = "#a89984" + gruvboxDarkGray = "#928374" + gruvboxDarkRed = "#cc241d" + gruvboxDarkRedBright = "#fb4934" + gruvboxDarkGreen = "#98971a" + gruvboxDarkGreenBright = "#b8bb26" + gruvboxDarkYellow = "#d79921" + gruvboxDarkYellowBright = "#fabd2f" + gruvboxDarkBlue = "#458588" + gruvboxDarkBlueBright = "#83a598" + gruvboxDarkPurple = "#b16286" + gruvboxDarkPurpleBright = "#d3869b" + gruvboxDarkAqua = "#689d6a" + gruvboxDarkAquaBright = "#8ec07c" + gruvboxDarkOrange = "#d65d0e" + gruvboxDarkOrangeBright = "#fe8019" + + // Light theme colors + gruvboxLightBg0 = "#fbf1c7" + gruvboxLightBg0Soft = "#f2e5bc" + gruvboxLightBg1 = "#ebdbb2" + gruvboxLightBg2 = "#d5c4a1" + gruvboxLightBg3 = "#bdae93" + gruvboxLightBg4 = "#a89984" + gruvboxLightFg0 = "#282828" + gruvboxLightFg1 = "#3c3836" + gruvboxLightFg2 = "#504945" + gruvboxLightFg3 = "#665c54" + gruvboxLightFg4 = "#7c6f64" + gruvboxLightGray = "#928374" + gruvboxLightRed = "#9d0006" + gruvboxLightRedBright = "#cc241d" + gruvboxLightGreen = "#79740e" + gruvboxLightGreenBright = "#98971a" + gruvboxLightYellow = "#b57614" + gruvboxLightYellowBright = "#d79921" + gruvboxLightBlue = "#076678" + gruvboxLightBlueBright = "#458588" + gruvboxLightPurple = "#8f3f71" + gruvboxLightPurpleBright = "#b16286" + gruvboxLightAqua = "#427b58" + gruvboxLightAquaBright = "#689d6a" + gruvboxLightOrange = "#af3a03" + gruvboxLightOrangeBright = "#d65d0e" +) + +// GruvboxTheme implements the Theme interface with Gruvbox colors. +// It provides both dark and light variants. +type GruvboxTheme struct { + BaseTheme +} + +// NewGruvboxTheme creates a new instance of the Gruvbox theme. +func NewGruvboxTheme() *GruvboxTheme { + theme := &GruvboxTheme{} + + // Base colors + theme.PrimaryColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBlueBright, + Light: gruvboxLightBlueBright, + } + theme.SecondaryColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkPurpleBright, + Light: gruvboxLightPurpleBright, + } + theme.AccentColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkOrangeBright, + Light: gruvboxLightOrangeBright, + } + + // Status colors + theme.ErrorColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkRedBright, + Light: gruvboxLightRedBright, + } + theme.WarningColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkYellowBright, + Light: gruvboxLightYellowBright, + } + theme.SuccessColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkGreenBright, + Light: gruvboxLightGreenBright, + } + theme.InfoColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBlueBright, + Light: gruvboxLightBlueBright, + } + + // Text colors + theme.TextColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg1, + Light: gruvboxLightFg1, + } + theme.TextMutedColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg4, + Light: gruvboxLightFg4, + } + theme.TextEmphasizedColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkYellowBright, + Light: gruvboxLightYellowBright, + } + + // Background colors + theme.BackgroundColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBg0, + Light: gruvboxLightBg0, + } + theme.BackgroundSecondaryColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBg1, + Light: gruvboxLightBg1, + } + theme.BackgroundDarkerColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBg0Soft, + Light: gruvboxLightBg0Soft, + } + + // Border colors + theme.BorderNormalColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBg2, + Light: gruvboxLightBg2, + } + theme.BorderFocusedColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBlueBright, + Light: gruvboxLightBlueBright, + } + theme.BorderDimColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBg1, + Light: gruvboxLightBg1, + } + + // Diff view colors + theme.DiffAddedColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkGreenBright, + Light: gruvboxLightGreenBright, + } + theme.DiffRemovedColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkRedBright, + Light: gruvboxLightRedBright, + } + theme.DiffContextColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg4, + Light: gruvboxLightFg4, + } + theme.DiffHunkHeaderColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg3, + Light: gruvboxLightFg3, + } + theme.DiffHighlightAddedColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkGreenBright, + Light: gruvboxLightGreenBright, + } + theme.DiffHighlightRemovedColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkRedBright, + Light: gruvboxLightRedBright, + } + theme.DiffAddedBgColor = lipgloss.AdaptiveColor{ + Dark: "#3C4C3C", // Darker green background + Light: "#E8F5E9", // Light green background + } + theme.DiffRemovedBgColor = lipgloss.AdaptiveColor{ + Dark: "#4C3C3C", // Darker red background + Light: "#FFEBEE", // Light red background + } + theme.DiffContextBgColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBg0, + Light: gruvboxLightBg0, + } + theme.DiffLineNumberColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg4, + Light: gruvboxLightFg4, + } + theme.DiffAddedLineNumberBgColor = lipgloss.AdaptiveColor{ + Dark: "#32432F", // Slightly darker green + Light: "#C8E6C9", // Light green + } + theme.DiffRemovedLineNumberBgColor = lipgloss.AdaptiveColor{ + Dark: "#43322F", // Slightly darker red + Light: "#FFCDD2", // Light red + } + + // Markdown colors + theme.MarkdownTextColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg1, + Light: gruvboxLightFg1, + } + theme.MarkdownHeadingColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkYellowBright, + Light: gruvboxLightYellowBright, + } + theme.MarkdownLinkColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBlueBright, + Light: gruvboxLightBlueBright, + } + theme.MarkdownLinkTextColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkAquaBright, + Light: gruvboxLightAquaBright, + } + theme.MarkdownCodeColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkGreenBright, + Light: gruvboxLightGreenBright, + } + theme.MarkdownBlockQuoteColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkAquaBright, + Light: gruvboxLightAquaBright, + } + theme.MarkdownEmphColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkYellowBright, + Light: gruvboxLightYellowBright, + } + theme.MarkdownStrongColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkOrangeBright, + Light: gruvboxLightOrangeBright, + } + theme.MarkdownHorizontalRuleColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBg3, + Light: gruvboxLightBg3, + } + theme.MarkdownListItemColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBlueBright, + Light: gruvboxLightBlueBright, + } + theme.MarkdownListEnumerationColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBlueBright, + Light: gruvboxLightBlueBright, + } + theme.MarkdownImageColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkPurpleBright, + Light: gruvboxLightPurpleBright, + } + theme.MarkdownImageTextColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkAquaBright, + Light: gruvboxLightAquaBright, + } + theme.MarkdownCodeBlockColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg1, + Light: gruvboxLightFg1, + } + + // Syntax highlighting colors + theme.SyntaxCommentColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkGray, + Light: gruvboxLightGray, + } + theme.SyntaxKeywordColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkRedBright, + Light: gruvboxLightRedBright, + } + theme.SyntaxFunctionColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkGreenBright, + Light: gruvboxLightGreenBright, + } + theme.SyntaxVariableColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkBlueBright, + Light: gruvboxLightBlueBright, + } + theme.SyntaxStringColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkYellowBright, + Light: gruvboxLightYellowBright, + } + theme.SyntaxNumberColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkPurpleBright, + Light: gruvboxLightPurpleBright, + } + theme.SyntaxTypeColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkYellow, + Light: gruvboxLightYellow, + } + theme.SyntaxOperatorColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkAquaBright, + Light: gruvboxLightAquaBright, + } + theme.SyntaxPunctuationColor = lipgloss.AdaptiveColor{ + Dark: gruvboxDarkFg1, + Light: gruvboxLightFg1, + } + + return theme +} + +func init() { + // Register the Gruvbox theme with the theme manager + RegisterTheme("gruvbox", NewGruvboxTheme()) +}
\ No newline at end of file diff --git a/internal/tui/theme/manager.go b/internal/tui/theme/manager.go new file mode 100644 index 000000000..161563c26 --- /dev/null +++ b/internal/tui/theme/manager.go @@ -0,0 +1,113 @@ +package theme + +import ( + "fmt" + "slices" + "strings" + "sync" + + "github.com/alecthomas/chroma/v2/styles" + "github.com/opencode-ai/opencode/internal/config" + "github.com/opencode-ai/opencode/internal/logging" +) + +// 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: "", +} + +// 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") + 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 + logging.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 { + 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] +} + +// 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) +} diff --git a/internal/tui/theme/theme.go b/internal/tui/theme/theme.go new file mode 100644 index 000000000..4ee14a07f --- /dev/null +++ b/internal/tui/theme/theme.go @@ -0,0 +1,208 @@ +package theme + +import ( + "github.com/charmbracelet/lipgloss" +) + +// Theme defines the interface for all UI themes in the application. +// All colors must be defined as lipgloss.AdaptiveColor to support +// both light and dark terminal backgrounds. +type Theme interface { + // Base colors + Primary() lipgloss.AdaptiveColor + Secondary() lipgloss.AdaptiveColor + Accent() lipgloss.AdaptiveColor + + // Status colors + Error() lipgloss.AdaptiveColor + Warning() lipgloss.AdaptiveColor + Success() lipgloss.AdaptiveColor + Info() lipgloss.AdaptiveColor + + // Text colors + Text() lipgloss.AdaptiveColor + TextMuted() lipgloss.AdaptiveColor + TextEmphasized() lipgloss.AdaptiveColor + + // Background colors + Background() lipgloss.AdaptiveColor + BackgroundSecondary() lipgloss.AdaptiveColor + BackgroundDarker() lipgloss.AdaptiveColor + + // Border colors + BorderNormal() lipgloss.AdaptiveColor + BorderFocused() lipgloss.AdaptiveColor + BorderDim() lipgloss.AdaptiveColor + + // Diff view colors + DiffAdded() lipgloss.AdaptiveColor + DiffRemoved() lipgloss.AdaptiveColor + DiffContext() lipgloss.AdaptiveColor + DiffHunkHeader() lipgloss.AdaptiveColor + DiffHighlightAdded() lipgloss.AdaptiveColor + DiffHighlightRemoved() lipgloss.AdaptiveColor + DiffAddedBg() lipgloss.AdaptiveColor + DiffRemovedBg() lipgloss.AdaptiveColor + DiffContextBg() lipgloss.AdaptiveColor + DiffLineNumber() lipgloss.AdaptiveColor + DiffAddedLineNumberBg() lipgloss.AdaptiveColor + DiffRemovedLineNumberBg() lipgloss.AdaptiveColor + + // Markdown colors + MarkdownText() lipgloss.AdaptiveColor + MarkdownHeading() lipgloss.AdaptiveColor + MarkdownLink() lipgloss.AdaptiveColor + MarkdownLinkText() lipgloss.AdaptiveColor + MarkdownCode() lipgloss.AdaptiveColor + MarkdownBlockQuote() lipgloss.AdaptiveColor + MarkdownEmph() lipgloss.AdaptiveColor + MarkdownStrong() lipgloss.AdaptiveColor + MarkdownHorizontalRule() lipgloss.AdaptiveColor + MarkdownListItem() lipgloss.AdaptiveColor + MarkdownListEnumeration() lipgloss.AdaptiveColor + MarkdownImage() lipgloss.AdaptiveColor + MarkdownImageText() lipgloss.AdaptiveColor + MarkdownCodeBlock() lipgloss.AdaptiveColor + + // Syntax highlighting colors + SyntaxComment() lipgloss.AdaptiveColor + SyntaxKeyword() lipgloss.AdaptiveColor + SyntaxFunction() lipgloss.AdaptiveColor + SyntaxVariable() lipgloss.AdaptiveColor + SyntaxString() lipgloss.AdaptiveColor + SyntaxNumber() lipgloss.AdaptiveColor + SyntaxType() lipgloss.AdaptiveColor + SyntaxOperator() lipgloss.AdaptiveColor + SyntaxPunctuation() lipgloss.AdaptiveColor +} + +// BaseTheme provides a default implementation of the Theme interface +// that can be embedded in concrete theme implementations. +type BaseTheme struct { + // Base colors + PrimaryColor lipgloss.AdaptiveColor + SecondaryColor lipgloss.AdaptiveColor + AccentColor lipgloss.AdaptiveColor + + // Status colors + ErrorColor lipgloss.AdaptiveColor + WarningColor lipgloss.AdaptiveColor + SuccessColor lipgloss.AdaptiveColor + InfoColor lipgloss.AdaptiveColor + + // Text colors + TextColor lipgloss.AdaptiveColor + TextMutedColor lipgloss.AdaptiveColor + TextEmphasizedColor lipgloss.AdaptiveColor + + // Background colors + BackgroundColor lipgloss.AdaptiveColor + BackgroundSecondaryColor lipgloss.AdaptiveColor + BackgroundDarkerColor lipgloss.AdaptiveColor + + // Border colors + BorderNormalColor lipgloss.AdaptiveColor + BorderFocusedColor lipgloss.AdaptiveColor + BorderDimColor lipgloss.AdaptiveColor + + // Diff view colors + DiffAddedColor lipgloss.AdaptiveColor + DiffRemovedColor lipgloss.AdaptiveColor + DiffContextColor lipgloss.AdaptiveColor + DiffHunkHeaderColor lipgloss.AdaptiveColor + DiffHighlightAddedColor lipgloss.AdaptiveColor + DiffHighlightRemovedColor lipgloss.AdaptiveColor + DiffAddedBgColor lipgloss.AdaptiveColor + DiffRemovedBgColor lipgloss.AdaptiveColor + DiffContextBgColor lipgloss.AdaptiveColor + DiffLineNumberColor lipgloss.AdaptiveColor + DiffAddedLineNumberBgColor lipgloss.AdaptiveColor + DiffRemovedLineNumberBgColor lipgloss.AdaptiveColor + + // Markdown colors + MarkdownTextColor lipgloss.AdaptiveColor + MarkdownHeadingColor lipgloss.AdaptiveColor + MarkdownLinkColor lipgloss.AdaptiveColor + MarkdownLinkTextColor lipgloss.AdaptiveColor + MarkdownCodeColor lipgloss.AdaptiveColor + MarkdownBlockQuoteColor lipgloss.AdaptiveColor + MarkdownEmphColor lipgloss.AdaptiveColor + MarkdownStrongColor lipgloss.AdaptiveColor + MarkdownHorizontalRuleColor lipgloss.AdaptiveColor + MarkdownListItemColor lipgloss.AdaptiveColor + MarkdownListEnumerationColor lipgloss.AdaptiveColor + MarkdownImageColor lipgloss.AdaptiveColor + MarkdownImageTextColor lipgloss.AdaptiveColor + MarkdownCodeBlockColor lipgloss.AdaptiveColor + + // Syntax highlighting colors + SyntaxCommentColor lipgloss.AdaptiveColor + SyntaxKeywordColor lipgloss.AdaptiveColor + SyntaxFunctionColor lipgloss.AdaptiveColor + SyntaxVariableColor lipgloss.AdaptiveColor + SyntaxStringColor lipgloss.AdaptiveColor + SyntaxNumberColor lipgloss.AdaptiveColor + SyntaxTypeColor lipgloss.AdaptiveColor + SyntaxOperatorColor lipgloss.AdaptiveColor + SyntaxPunctuationColor lipgloss.AdaptiveColor +} + +// Implement the Theme interface for BaseTheme +func (t *BaseTheme) Primary() lipgloss.AdaptiveColor { return t.PrimaryColor } +func (t *BaseTheme) Secondary() lipgloss.AdaptiveColor { return t.SecondaryColor } +func (t *BaseTheme) Accent() lipgloss.AdaptiveColor { return t.AccentColor } + +func (t *BaseTheme) Error() lipgloss.AdaptiveColor { return t.ErrorColor } +func (t *BaseTheme) Warning() lipgloss.AdaptiveColor { return t.WarningColor } +func (t *BaseTheme) Success() lipgloss.AdaptiveColor { return t.SuccessColor } +func (t *BaseTheme) Info() lipgloss.AdaptiveColor { return t.InfoColor } + +func (t *BaseTheme) Text() lipgloss.AdaptiveColor { return t.TextColor } +func (t *BaseTheme) TextMuted() lipgloss.AdaptiveColor { return t.TextMutedColor } +func (t *BaseTheme) TextEmphasized() lipgloss.AdaptiveColor { return t.TextEmphasizedColor } + +func (t *BaseTheme) Background() lipgloss.AdaptiveColor { return t.BackgroundColor } +func (t *BaseTheme) BackgroundSecondary() lipgloss.AdaptiveColor { return t.BackgroundSecondaryColor } +func (t *BaseTheme) BackgroundDarker() lipgloss.AdaptiveColor { return t.BackgroundDarkerColor } + +func (t *BaseTheme) BorderNormal() lipgloss.AdaptiveColor { return t.BorderNormalColor } +func (t *BaseTheme) BorderFocused() lipgloss.AdaptiveColor { return t.BorderFocusedColor } +func (t *BaseTheme) BorderDim() lipgloss.AdaptiveColor { return t.BorderDimColor } + +func (t *BaseTheme) DiffAdded() lipgloss.AdaptiveColor { return t.DiffAddedColor } +func (t *BaseTheme) DiffRemoved() lipgloss.AdaptiveColor { return t.DiffRemovedColor } +func (t *BaseTheme) DiffContext() lipgloss.AdaptiveColor { return t.DiffContextColor } +func (t *BaseTheme) DiffHunkHeader() lipgloss.AdaptiveColor { return t.DiffHunkHeaderColor } +func (t *BaseTheme) DiffHighlightAdded() lipgloss.AdaptiveColor { return t.DiffHighlightAddedColor } +func (t *BaseTheme) DiffHighlightRemoved() lipgloss.AdaptiveColor { return t.DiffHighlightRemovedColor } +func (t *BaseTheme) DiffAddedBg() lipgloss.AdaptiveColor { return t.DiffAddedBgColor } +func (t *BaseTheme) DiffRemovedBg() lipgloss.AdaptiveColor { return t.DiffRemovedBgColor } +func (t *BaseTheme) DiffContextBg() lipgloss.AdaptiveColor { return t.DiffContextBgColor } +func (t *BaseTheme) DiffLineNumber() lipgloss.AdaptiveColor { return t.DiffLineNumberColor } +func (t *BaseTheme) DiffAddedLineNumberBg() lipgloss.AdaptiveColor { return t.DiffAddedLineNumberBgColor } +func (t *BaseTheme) DiffRemovedLineNumberBg() lipgloss.AdaptiveColor { return t.DiffRemovedLineNumberBgColor } + +func (t *BaseTheme) MarkdownText() lipgloss.AdaptiveColor { return t.MarkdownTextColor } +func (t *BaseTheme) MarkdownHeading() lipgloss.AdaptiveColor { return t.MarkdownHeadingColor } +func (t *BaseTheme) MarkdownLink() lipgloss.AdaptiveColor { return t.MarkdownLinkColor } +func (t *BaseTheme) MarkdownLinkText() lipgloss.AdaptiveColor { return t.MarkdownLinkTextColor } +func (t *BaseTheme) MarkdownCode() lipgloss.AdaptiveColor { return t.MarkdownCodeColor } +func (t *BaseTheme) MarkdownBlockQuote() lipgloss.AdaptiveColor { return t.MarkdownBlockQuoteColor } +func (t *BaseTheme) MarkdownEmph() lipgloss.AdaptiveColor { return t.MarkdownEmphColor } +func (t *BaseTheme) MarkdownStrong() lipgloss.AdaptiveColor { return t.MarkdownStrongColor } +func (t *BaseTheme) MarkdownHorizontalRule() lipgloss.AdaptiveColor { return t.MarkdownHorizontalRuleColor } +func (t *BaseTheme) MarkdownListItem() lipgloss.AdaptiveColor { return t.MarkdownListItemColor } +func (t *BaseTheme) MarkdownListEnumeration() lipgloss.AdaptiveColor { return t.MarkdownListEnumerationColor } +func (t *BaseTheme) MarkdownImage() lipgloss.AdaptiveColor { return t.MarkdownImageColor } +func (t *BaseTheme) MarkdownImageText() lipgloss.AdaptiveColor { return t.MarkdownImageTextColor } +func (t *BaseTheme) MarkdownCodeBlock() lipgloss.AdaptiveColor { return t.MarkdownCodeBlockColor } + +func (t *BaseTheme) SyntaxComment() lipgloss.AdaptiveColor { return t.SyntaxCommentColor } +func (t *BaseTheme) SyntaxKeyword() lipgloss.AdaptiveColor { return t.SyntaxKeywordColor } +func (t *BaseTheme) SyntaxFunction() lipgloss.AdaptiveColor { return t.SyntaxFunctionColor } +func (t *BaseTheme) SyntaxVariable() lipgloss.AdaptiveColor { return t.SyntaxVariableColor } +func (t *BaseTheme) SyntaxString() lipgloss.AdaptiveColor { return t.SyntaxStringColor } +func (t *BaseTheme) SyntaxNumber() lipgloss.AdaptiveColor { return t.SyntaxNumberColor } +func (t *BaseTheme) SyntaxType() lipgloss.AdaptiveColor { return t.SyntaxTypeColor } +func (t *BaseTheme) SyntaxOperator() lipgloss.AdaptiveColor { return t.SyntaxOperatorColor } +func (t *BaseTheme) SyntaxPunctuation() lipgloss.AdaptiveColor { return t.SyntaxPunctuationColor }
\ No newline at end of file diff --git a/internal/tui/theme/theme_test.go b/internal/tui/theme/theme_test.go new file mode 100644 index 000000000..5ec810e33 --- /dev/null +++ b/internal/tui/theme/theme_test.go @@ -0,0 +1,89 @@ +package theme + +import ( + "testing" +) + +func TestThemeRegistration(t *testing.T) { + // Get list of available themes + availableThemes := AvailableThemes() + + // Check if "catppuccin" theme is registered + catppuccinFound := false + for _, themeName := range availableThemes { + if themeName == "catppuccin" { + catppuccinFound = true + break + } + } + + if !catppuccinFound { + t.Errorf("Catppuccin theme is not registered") + } + + // Check if "gruvbox" theme is registered + gruvboxFound := false + for _, themeName := range availableThemes { + if themeName == "gruvbox" { + gruvboxFound = true + break + } + } + + if !gruvboxFound { + t.Errorf("Gruvbox theme is not registered") + } + + // Check if "monokai" theme is registered + monokaiFound := false + for _, themeName := range availableThemes { + if themeName == "monokai" { + monokaiFound = true + break + } + } + + if !monokaiFound { + t.Errorf("Monokai theme is not registered") + } + + // Try to get the themes and make sure they're not nil + catppuccin := GetTheme("catppuccin") + if catppuccin == nil { + t.Errorf("Catppuccin theme is nil") + } + + gruvbox := GetTheme("gruvbox") + if gruvbox == nil { + t.Errorf("Gruvbox theme is nil") + } + + monokai := GetTheme("monokai") + if monokai == nil { + t.Errorf("Monokai theme is nil") + } + + // Test switching theme + originalTheme := CurrentThemeName() + + err := SetTheme("gruvbox") + if err != nil { + t.Errorf("Failed to set theme to gruvbox: %v", err) + } + + if CurrentThemeName() != "gruvbox" { + t.Errorf("Theme not properly switched to gruvbox") + } + + err = SetTheme("monokai") + if err != nil { + t.Errorf("Failed to set theme to monokai: %v", err) + } + + if CurrentThemeName() != "monokai" { + t.Errorf("Theme not properly switched to monokai") + } + + // Switch back to original theme + _ = SetTheme(originalTheme) +}
\ No newline at end of file |
