summaryrefslogtreecommitdiffhomepage
path: root/internal/tui/theme
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-04-28 08:46:09 -0500
committeradamdottv <[email protected]>2025-04-30 07:46:34 -0500
commit61b605e724eb4cc50ab831534fcdd18e031d68eb (patch)
treeb15b074a8fed1931e179a8d3df08d05b753c7aa3 /internal/tui/theme
parent61d9dc95111d2645a49816f6d9d6cc1014be1a22 (diff)
downloadopencode-61b605e724eb4cc50ab831534fcdd18e031d68eb.tar.gz
opencode-61b605e724eb4cc50ab831534fcdd18e031d68eb.zip
feat: themes
Diffstat (limited to 'internal/tui/theme')
-rw-r--r--internal/tui/theme/catppuccin.go248
-rw-r--r--internal/tui/theme/gruvbox.go302
-rw-r--r--internal/tui/theme/manager.go113
-rw-r--r--internal/tui/theme/theme.go208
-rw-r--r--internal/tui/theme/theme_test.go89
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