summaryrefslogtreecommitdiffhomepage
path: root/pkg/tui/theme/theme.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/theme.go
parent020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff)
downloadopencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz
opencode-99af6146d5def31c59993636d60eb75a483a283b.zip
openapi
Diffstat (limited to 'pkg/tui/theme/theme.go')
-rw-r--r--pkg/tui/theme/theme.go290
1 files changed, 290 insertions, 0 deletions
diff --git a/pkg/tui/theme/theme.go b/pkg/tui/theme/theme.go
new file mode 100644
index 000000000..c97b95478
--- /dev/null
+++ b/pkg/tui/theme/theme.go
@@ -0,0 +1,290 @@
+package theme
+
+import (
+ "fmt"
+ "regexp"
+
+ "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 }
+
+// ParseAdaptiveColor parses a color value from the config file into a lipgloss.AdaptiveColor.
+// It accepts either a string (hex color) or a map with "dark" and "light" keys.
+func ParseAdaptiveColor(value any) (lipgloss.AdaptiveColor, error) {
+ // Regular expression to validate hex color format
+ hexColorRegex := regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
+
+ // Case 1: String value (same color for both dark and light modes)
+ if hexColor, ok := value.(string); ok {
+ if !hexColorRegex.MatchString(hexColor) {
+ return lipgloss.AdaptiveColor{}, fmt.Errorf("invalid hex color format: %s", hexColor)
+ }
+ return lipgloss.AdaptiveColor{
+ Dark: hexColor,
+ Light: hexColor,
+ }, nil
+ }
+
+ // Case 2: Int value between 0 and 255
+ if numericVal, ok := value.(float64); ok {
+ intVal := int(numericVal)
+ if intVal < 0 || intVal > 255 {
+ return lipgloss.AdaptiveColor{}, fmt.Errorf("invalid int color value (must be between 0 and 255): %d", intVal)
+ }
+ return lipgloss.AdaptiveColor{
+ Dark: fmt.Sprintf("%d", intVal),
+ Light: fmt.Sprintf("%d", intVal),
+ }, nil
+ }
+
+ // Case 3: Map with dark and light keys
+ if colorMap, ok := value.(map[string]any); ok {
+ darkVal, darkOk := colorMap["dark"]
+ lightVal, lightOk := colorMap["light"]
+
+ if !darkOk || !lightOk {
+ return lipgloss.AdaptiveColor{}, fmt.Errorf("color map must contain both 'dark' and 'light' keys")
+ }
+
+ darkHex, darkIsString := darkVal.(string)
+ lightHex, lightIsString := lightVal.(string)
+
+ if !darkIsString || !lightIsString {
+ darkVal, darkIsNumber := darkVal.(float64)
+ lightVal, lightIsNumber := lightVal.(float64)
+
+ if !darkIsNumber || !lightIsNumber {
+ return lipgloss.AdaptiveColor{}, fmt.Errorf("color map values must be strings or ints")
+ }
+
+ darkInt := int(darkVal)
+ lightInt := int(lightVal)
+
+ return lipgloss.AdaptiveColor{
+ Dark: fmt.Sprintf("%d", darkInt),
+ Light: fmt.Sprintf("%d", lightInt),
+ }, nil
+ }
+
+ if !hexColorRegex.MatchString(darkHex) || !hexColorRegex.MatchString(lightHex) {
+ return lipgloss.AdaptiveColor{}, fmt.Errorf("invalid hex color format")
+ }
+
+ return lipgloss.AdaptiveColor{
+ Dark: darkHex,
+ Light: lightHex,
+ }, nil
+ }
+
+ return lipgloss.AdaptiveColor{}, fmt.Errorf("color must be either a hex string or an object with dark/light keys")
+}