| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- package theme
- import (
- "fmt"
- "regexp"
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/lipgloss/v2/compat"
- )
- // Theme defines the interface for all UI themes in the application.
- // All colors must be defined as compat.AdaptiveColor to support
- // both light and dark terminal backgrounds.
- type Theme interface {
- // Background colors
- Background() compat.AdaptiveColor // Radix 1
- BackgroundSubtle() compat.AdaptiveColor // Radix 2
- BackgroundElement() compat.AdaptiveColor // Radix 3
- // Border colors
- BorderSubtle() compat.AdaptiveColor // Radix 6
- Border() compat.AdaptiveColor // Radix 7
- BorderActive() compat.AdaptiveColor // Radix 8
- // Brand colors
- Primary() compat.AdaptiveColor // Radix 9
- Secondary() compat.AdaptiveColor
- Accent() compat.AdaptiveColor
- // Text colors
- TextMuted() compat.AdaptiveColor // Radix 11
- Text() compat.AdaptiveColor // Radix 12
- // Status colors
- Error() compat.AdaptiveColor
- Warning() compat.AdaptiveColor
- Success() compat.AdaptiveColor
- Info() compat.AdaptiveColor
- // Diff view colors
- DiffAdded() compat.AdaptiveColor
- DiffRemoved() compat.AdaptiveColor
- DiffContext() compat.AdaptiveColor
- DiffHunkHeader() compat.AdaptiveColor
- DiffHighlightAdded() compat.AdaptiveColor
- DiffHighlightRemoved() compat.AdaptiveColor
- DiffAddedBg() compat.AdaptiveColor
- DiffRemovedBg() compat.AdaptiveColor
- DiffContextBg() compat.AdaptiveColor
- DiffLineNumber() compat.AdaptiveColor
- DiffAddedLineNumberBg() compat.AdaptiveColor
- DiffRemovedLineNumberBg() compat.AdaptiveColor
- // Markdown colors
- MarkdownText() compat.AdaptiveColor
- MarkdownHeading() compat.AdaptiveColor
- MarkdownLink() compat.AdaptiveColor
- MarkdownLinkText() compat.AdaptiveColor
- MarkdownCode() compat.AdaptiveColor
- MarkdownBlockQuote() compat.AdaptiveColor
- MarkdownEmph() compat.AdaptiveColor
- MarkdownStrong() compat.AdaptiveColor
- MarkdownHorizontalRule() compat.AdaptiveColor
- MarkdownListItem() compat.AdaptiveColor
- MarkdownListEnumeration() compat.AdaptiveColor
- MarkdownImage() compat.AdaptiveColor
- MarkdownImageText() compat.AdaptiveColor
- MarkdownCodeBlock() compat.AdaptiveColor
- // Syntax highlighting colors
- SyntaxComment() compat.AdaptiveColor
- SyntaxKeyword() compat.AdaptiveColor
- SyntaxFunction() compat.AdaptiveColor
- SyntaxVariable() compat.AdaptiveColor
- SyntaxString() compat.AdaptiveColor
- SyntaxNumber() compat.AdaptiveColor
- SyntaxType() compat.AdaptiveColor
- SyntaxOperator() compat.AdaptiveColor
- SyntaxPunctuation() compat.AdaptiveColor
- }
- // BaseTheme provides a default implementation of the Theme interface
- // that can be embedded in concrete theme implementations.
- type BaseTheme struct {
- // Background colors
- BackgroundColor compat.AdaptiveColor
- BackgroundSubtleColor compat.AdaptiveColor
- BackgroundElementColor compat.AdaptiveColor
- // Border colors
- BorderSubtleColor compat.AdaptiveColor
- BorderColor compat.AdaptiveColor
- BorderActiveColor compat.AdaptiveColor
- // Brand colors
- PrimaryColor compat.AdaptiveColor
- SecondaryColor compat.AdaptiveColor
- AccentColor compat.AdaptiveColor
- // Text colors
- TextMutedColor compat.AdaptiveColor
- TextColor compat.AdaptiveColor
- // Status colors
- ErrorColor compat.AdaptiveColor
- WarningColor compat.AdaptiveColor
- SuccessColor compat.AdaptiveColor
- InfoColor compat.AdaptiveColor
- // Diff view colors
- DiffAddedColor compat.AdaptiveColor
- DiffRemovedColor compat.AdaptiveColor
- DiffContextColor compat.AdaptiveColor
- DiffHunkHeaderColor compat.AdaptiveColor
- DiffHighlightAddedColor compat.AdaptiveColor
- DiffHighlightRemovedColor compat.AdaptiveColor
- DiffAddedBgColor compat.AdaptiveColor
- DiffRemovedBgColor compat.AdaptiveColor
- DiffContextBgColor compat.AdaptiveColor
- DiffLineNumberColor compat.AdaptiveColor
- DiffAddedLineNumberBgColor compat.AdaptiveColor
- DiffRemovedLineNumberBgColor compat.AdaptiveColor
- // Markdown colors
- MarkdownTextColor compat.AdaptiveColor
- MarkdownHeadingColor compat.AdaptiveColor
- MarkdownLinkColor compat.AdaptiveColor
- MarkdownLinkTextColor compat.AdaptiveColor
- MarkdownCodeColor compat.AdaptiveColor
- MarkdownBlockQuoteColor compat.AdaptiveColor
- MarkdownEmphColor compat.AdaptiveColor
- MarkdownStrongColor compat.AdaptiveColor
- MarkdownHorizontalRuleColor compat.AdaptiveColor
- MarkdownListItemColor compat.AdaptiveColor
- MarkdownListEnumerationColor compat.AdaptiveColor
- MarkdownImageColor compat.AdaptiveColor
- MarkdownImageTextColor compat.AdaptiveColor
- MarkdownCodeBlockColor compat.AdaptiveColor
- // Syntax highlighting colors
- SyntaxCommentColor compat.AdaptiveColor
- SyntaxKeywordColor compat.AdaptiveColor
- SyntaxFunctionColor compat.AdaptiveColor
- SyntaxVariableColor compat.AdaptiveColor
- SyntaxStringColor compat.AdaptiveColor
- SyntaxNumberColor compat.AdaptiveColor
- SyntaxTypeColor compat.AdaptiveColor
- SyntaxOperatorColor compat.AdaptiveColor
- SyntaxPunctuationColor compat.AdaptiveColor
- }
- // Implement the Theme interface for BaseTheme
- func (t *BaseTheme) Primary() compat.AdaptiveColor { return t.PrimaryColor }
- func (t *BaseTheme) Secondary() compat.AdaptiveColor { return t.SecondaryColor }
- func (t *BaseTheme) Accent() compat.AdaptiveColor { return t.AccentColor }
- func (t *BaseTheme) Error() compat.AdaptiveColor { return t.ErrorColor }
- func (t *BaseTheme) Warning() compat.AdaptiveColor { return t.WarningColor }
- func (t *BaseTheme) Success() compat.AdaptiveColor { return t.SuccessColor }
- func (t *BaseTheme) Info() compat.AdaptiveColor { return t.InfoColor }
- func (t *BaseTheme) Text() compat.AdaptiveColor { return t.TextColor }
- func (t *BaseTheme) TextMuted() compat.AdaptiveColor { return t.TextMutedColor }
- func (t *BaseTheme) Background() compat.AdaptiveColor { return t.BackgroundColor }
- func (t *BaseTheme) BackgroundSubtle() compat.AdaptiveColor { return t.BackgroundSubtleColor }
- func (t *BaseTheme) BackgroundElement() compat.AdaptiveColor { return t.BackgroundElementColor }
- func (t *BaseTheme) Border() compat.AdaptiveColor { return t.BorderColor }
- func (t *BaseTheme) BorderActive() compat.AdaptiveColor { return t.BorderActiveColor }
- func (t *BaseTheme) BorderSubtle() compat.AdaptiveColor { return t.BorderSubtleColor }
- func (t *BaseTheme) DiffAdded() compat.AdaptiveColor { return t.DiffAddedColor }
- func (t *BaseTheme) DiffRemoved() compat.AdaptiveColor { return t.DiffRemovedColor }
- func (t *BaseTheme) DiffContext() compat.AdaptiveColor { return t.DiffContextColor }
- func (t *BaseTheme) DiffHunkHeader() compat.AdaptiveColor { return t.DiffHunkHeaderColor }
- func (t *BaseTheme) DiffHighlightAdded() compat.AdaptiveColor { return t.DiffHighlightAddedColor }
- func (t *BaseTheme) DiffHighlightRemoved() compat.AdaptiveColor { return t.DiffHighlightRemovedColor }
- func (t *BaseTheme) DiffAddedBg() compat.AdaptiveColor { return t.DiffAddedBgColor }
- func (t *BaseTheme) DiffRemovedBg() compat.AdaptiveColor { return t.DiffRemovedBgColor }
- func (t *BaseTheme) DiffContextBg() compat.AdaptiveColor { return t.DiffContextBgColor }
- func (t *BaseTheme) DiffLineNumber() compat.AdaptiveColor { return t.DiffLineNumberColor }
- func (t *BaseTheme) DiffAddedLineNumberBg() compat.AdaptiveColor {
- return t.DiffAddedLineNumberBgColor
- }
- func (t *BaseTheme) DiffRemovedLineNumberBg() compat.AdaptiveColor {
- return t.DiffRemovedLineNumberBgColor
- }
- func (t *BaseTheme) MarkdownText() compat.AdaptiveColor { return t.MarkdownTextColor }
- func (t *BaseTheme) MarkdownHeading() compat.AdaptiveColor { return t.MarkdownHeadingColor }
- func (t *BaseTheme) MarkdownLink() compat.AdaptiveColor { return t.MarkdownLinkColor }
- func (t *BaseTheme) MarkdownLinkText() compat.AdaptiveColor { return t.MarkdownLinkTextColor }
- func (t *BaseTheme) MarkdownCode() compat.AdaptiveColor { return t.MarkdownCodeColor }
- func (t *BaseTheme) MarkdownBlockQuote() compat.AdaptiveColor { return t.MarkdownBlockQuoteColor }
- func (t *BaseTheme) MarkdownEmph() compat.AdaptiveColor { return t.MarkdownEmphColor }
- func (t *BaseTheme) MarkdownStrong() compat.AdaptiveColor { return t.MarkdownStrongColor }
- func (t *BaseTheme) MarkdownHorizontalRule() compat.AdaptiveColor {
- return t.MarkdownHorizontalRuleColor
- }
- func (t *BaseTheme) MarkdownListItem() compat.AdaptiveColor { return t.MarkdownListItemColor }
- func (t *BaseTheme) MarkdownListEnumeration() compat.AdaptiveColor {
- return t.MarkdownListEnumerationColor
- }
- func (t *BaseTheme) MarkdownImage() compat.AdaptiveColor { return t.MarkdownImageColor }
- func (t *BaseTheme) MarkdownImageText() compat.AdaptiveColor { return t.MarkdownImageTextColor }
- func (t *BaseTheme) MarkdownCodeBlock() compat.AdaptiveColor { return t.MarkdownCodeBlockColor }
- func (t *BaseTheme) SyntaxComment() compat.AdaptiveColor { return t.SyntaxCommentColor }
- func (t *BaseTheme) SyntaxKeyword() compat.AdaptiveColor { return t.SyntaxKeywordColor }
- func (t *BaseTheme) SyntaxFunction() compat.AdaptiveColor { return t.SyntaxFunctionColor }
- func (t *BaseTheme) SyntaxVariable() compat.AdaptiveColor { return t.SyntaxVariableColor }
- func (t *BaseTheme) SyntaxString() compat.AdaptiveColor { return t.SyntaxStringColor }
- func (t *BaseTheme) SyntaxNumber() compat.AdaptiveColor { return t.SyntaxNumberColor }
- func (t *BaseTheme) SyntaxType() compat.AdaptiveColor { return t.SyntaxTypeColor }
- func (t *BaseTheme) SyntaxOperator() compat.AdaptiveColor { return t.SyntaxOperatorColor }
- func (t *BaseTheme) SyntaxPunctuation() compat.AdaptiveColor { return t.SyntaxPunctuationColor }
- // ParseAdaptiveColor parses a color value from the config file into a compat.AdaptiveColor.
- // It accepts either a string (hex color) or a map with "dark" and "light" keys.
- func ParseAdaptiveColor(value any) (compat.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 compat.AdaptiveColor{}, fmt.Errorf("invalid hex color format: %s", hexColor)
- }
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(hexColor),
- Light: lipgloss.Color(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 compat.AdaptiveColor{}, fmt.Errorf("invalid int color value (must be between 0 and 255): %d", intVal)
- }
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(fmt.Sprintf("%d", intVal)),
- Light: lipgloss.Color(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 compat.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 compat.AdaptiveColor{}, fmt.Errorf("color map values must be strings or ints")
- }
- darkInt := int(darkVal)
- lightInt := int(lightVal)
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(fmt.Sprintf("%d", darkInt)),
- Light: lipgloss.Color(fmt.Sprintf("%d", lightInt)),
- }, nil
- }
- if !hexColorRegex.MatchString(darkHex) || !hexColorRegex.MatchString(lightHex) {
- return compat.AdaptiveColor{}, fmt.Errorf("invalid hex color format")
- }
- return compat.AdaptiveColor{
- Dark: lipgloss.Color(darkHex),
- Light: lipgloss.Color(lightHex),
- }, nil
- }
- return compat.AdaptiveColor{}, fmt.Errorf("color must be either a hex string or an object with dark/light keys")
- }
|