theme.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package theme
  2. import (
  3. "fmt"
  4. "regexp"
  5. "github.com/charmbracelet/lipgloss"
  6. )
  7. // Theme defines the interface for all UI themes in the application.
  8. // All colors must be defined as lipgloss.AdaptiveColor to support
  9. // both light and dark terminal backgrounds.
  10. type Theme interface {
  11. // Background colors
  12. Background() lipgloss.AdaptiveColor // Radix 1
  13. BackgroundSubtle() lipgloss.AdaptiveColor // Radix 2
  14. BackgroundElement() lipgloss.AdaptiveColor // Radix 3
  15. // Border colors
  16. BorderSubtle() lipgloss.AdaptiveColor // Radix 6
  17. Border() lipgloss.AdaptiveColor // Radix 7
  18. BorderActive() lipgloss.AdaptiveColor // Radix 8
  19. // Brand colors
  20. Primary() lipgloss.AdaptiveColor // Radix 9
  21. Secondary() lipgloss.AdaptiveColor
  22. Accent() lipgloss.AdaptiveColor
  23. // Text colors
  24. TextMuted() lipgloss.AdaptiveColor // Radix 11
  25. Text() lipgloss.AdaptiveColor // Radix 12
  26. // Status colors
  27. Error() lipgloss.AdaptiveColor
  28. Warning() lipgloss.AdaptiveColor
  29. Success() lipgloss.AdaptiveColor
  30. Info() lipgloss.AdaptiveColor
  31. // Diff view colors
  32. DiffAdded() lipgloss.AdaptiveColor
  33. DiffRemoved() lipgloss.AdaptiveColor
  34. DiffContext() lipgloss.AdaptiveColor
  35. DiffHunkHeader() lipgloss.AdaptiveColor
  36. DiffHighlightAdded() lipgloss.AdaptiveColor
  37. DiffHighlightRemoved() lipgloss.AdaptiveColor
  38. DiffAddedBg() lipgloss.AdaptiveColor
  39. DiffRemovedBg() lipgloss.AdaptiveColor
  40. DiffContextBg() lipgloss.AdaptiveColor
  41. DiffLineNumber() lipgloss.AdaptiveColor
  42. DiffAddedLineNumberBg() lipgloss.AdaptiveColor
  43. DiffRemovedLineNumberBg() lipgloss.AdaptiveColor
  44. // Markdown colors
  45. MarkdownText() lipgloss.AdaptiveColor
  46. MarkdownHeading() lipgloss.AdaptiveColor
  47. MarkdownLink() lipgloss.AdaptiveColor
  48. MarkdownLinkText() lipgloss.AdaptiveColor
  49. MarkdownCode() lipgloss.AdaptiveColor
  50. MarkdownBlockQuote() lipgloss.AdaptiveColor
  51. MarkdownEmph() lipgloss.AdaptiveColor
  52. MarkdownStrong() lipgloss.AdaptiveColor
  53. MarkdownHorizontalRule() lipgloss.AdaptiveColor
  54. MarkdownListItem() lipgloss.AdaptiveColor
  55. MarkdownListEnumeration() lipgloss.AdaptiveColor
  56. MarkdownImage() lipgloss.AdaptiveColor
  57. MarkdownImageText() lipgloss.AdaptiveColor
  58. MarkdownCodeBlock() lipgloss.AdaptiveColor
  59. // Syntax highlighting colors
  60. SyntaxComment() lipgloss.AdaptiveColor
  61. SyntaxKeyword() lipgloss.AdaptiveColor
  62. SyntaxFunction() lipgloss.AdaptiveColor
  63. SyntaxVariable() lipgloss.AdaptiveColor
  64. SyntaxString() lipgloss.AdaptiveColor
  65. SyntaxNumber() lipgloss.AdaptiveColor
  66. SyntaxType() lipgloss.AdaptiveColor
  67. SyntaxOperator() lipgloss.AdaptiveColor
  68. SyntaxPunctuation() lipgloss.AdaptiveColor
  69. }
  70. // BaseTheme provides a default implementation of the Theme interface
  71. // that can be embedded in concrete theme implementations.
  72. type BaseTheme struct {
  73. // Background colors
  74. BackgroundColor lipgloss.AdaptiveColor
  75. BackgroundSubtleColor lipgloss.AdaptiveColor
  76. BackgroundElementColor lipgloss.AdaptiveColor
  77. // Border colors
  78. BorderSubtleColor lipgloss.AdaptiveColor
  79. BorderColor lipgloss.AdaptiveColor
  80. BorderActiveColor lipgloss.AdaptiveColor
  81. // Brand colors
  82. PrimaryColor lipgloss.AdaptiveColor
  83. SecondaryColor lipgloss.AdaptiveColor
  84. AccentColor lipgloss.AdaptiveColor
  85. // Text colors
  86. TextMutedColor lipgloss.AdaptiveColor
  87. TextColor lipgloss.AdaptiveColor
  88. // Status colors
  89. ErrorColor lipgloss.AdaptiveColor
  90. WarningColor lipgloss.AdaptiveColor
  91. SuccessColor lipgloss.AdaptiveColor
  92. InfoColor lipgloss.AdaptiveColor
  93. // Diff view colors
  94. DiffAddedColor lipgloss.AdaptiveColor
  95. DiffRemovedColor lipgloss.AdaptiveColor
  96. DiffContextColor lipgloss.AdaptiveColor
  97. DiffHunkHeaderColor lipgloss.AdaptiveColor
  98. DiffHighlightAddedColor lipgloss.AdaptiveColor
  99. DiffHighlightRemovedColor lipgloss.AdaptiveColor
  100. DiffAddedBgColor lipgloss.AdaptiveColor
  101. DiffRemovedBgColor lipgloss.AdaptiveColor
  102. DiffContextBgColor lipgloss.AdaptiveColor
  103. DiffLineNumberColor lipgloss.AdaptiveColor
  104. DiffAddedLineNumberBgColor lipgloss.AdaptiveColor
  105. DiffRemovedLineNumberBgColor lipgloss.AdaptiveColor
  106. // Markdown colors
  107. MarkdownTextColor lipgloss.AdaptiveColor
  108. MarkdownHeadingColor lipgloss.AdaptiveColor
  109. MarkdownLinkColor lipgloss.AdaptiveColor
  110. MarkdownLinkTextColor lipgloss.AdaptiveColor
  111. MarkdownCodeColor lipgloss.AdaptiveColor
  112. MarkdownBlockQuoteColor lipgloss.AdaptiveColor
  113. MarkdownEmphColor lipgloss.AdaptiveColor
  114. MarkdownStrongColor lipgloss.AdaptiveColor
  115. MarkdownHorizontalRuleColor lipgloss.AdaptiveColor
  116. MarkdownListItemColor lipgloss.AdaptiveColor
  117. MarkdownListEnumerationColor lipgloss.AdaptiveColor
  118. MarkdownImageColor lipgloss.AdaptiveColor
  119. MarkdownImageTextColor lipgloss.AdaptiveColor
  120. MarkdownCodeBlockColor lipgloss.AdaptiveColor
  121. // Syntax highlighting colors
  122. SyntaxCommentColor lipgloss.AdaptiveColor
  123. SyntaxKeywordColor lipgloss.AdaptiveColor
  124. SyntaxFunctionColor lipgloss.AdaptiveColor
  125. SyntaxVariableColor lipgloss.AdaptiveColor
  126. SyntaxStringColor lipgloss.AdaptiveColor
  127. SyntaxNumberColor lipgloss.AdaptiveColor
  128. SyntaxTypeColor lipgloss.AdaptiveColor
  129. SyntaxOperatorColor lipgloss.AdaptiveColor
  130. SyntaxPunctuationColor lipgloss.AdaptiveColor
  131. }
  132. // Implement the Theme interface for BaseTheme
  133. func (t *BaseTheme) Primary() lipgloss.AdaptiveColor { return t.PrimaryColor }
  134. func (t *BaseTheme) Secondary() lipgloss.AdaptiveColor { return t.SecondaryColor }
  135. func (t *BaseTheme) Accent() lipgloss.AdaptiveColor { return t.AccentColor }
  136. func (t *BaseTheme) Error() lipgloss.AdaptiveColor { return t.ErrorColor }
  137. func (t *BaseTheme) Warning() lipgloss.AdaptiveColor { return t.WarningColor }
  138. func (t *BaseTheme) Success() lipgloss.AdaptiveColor { return t.SuccessColor }
  139. func (t *BaseTheme) Info() lipgloss.AdaptiveColor { return t.InfoColor }
  140. func (t *BaseTheme) Text() lipgloss.AdaptiveColor { return t.TextColor }
  141. func (t *BaseTheme) TextMuted() lipgloss.AdaptiveColor { return t.TextMutedColor }
  142. func (t *BaseTheme) Background() lipgloss.AdaptiveColor { return t.BackgroundColor }
  143. func (t *BaseTheme) BackgroundSubtle() lipgloss.AdaptiveColor { return t.BackgroundSubtleColor }
  144. func (t *BaseTheme) BackgroundElement() lipgloss.AdaptiveColor { return t.BackgroundElementColor }
  145. func (t *BaseTheme) Border() lipgloss.AdaptiveColor { return t.BorderColor }
  146. func (t *BaseTheme) BorderActive() lipgloss.AdaptiveColor { return t.BorderActiveColor }
  147. func (t *BaseTheme) BorderSubtle() lipgloss.AdaptiveColor { return t.BorderSubtleColor }
  148. func (t *BaseTheme) DiffAdded() lipgloss.AdaptiveColor { return t.DiffAddedColor }
  149. func (t *BaseTheme) DiffRemoved() lipgloss.AdaptiveColor { return t.DiffRemovedColor }
  150. func (t *BaseTheme) DiffContext() lipgloss.AdaptiveColor { return t.DiffContextColor }
  151. func (t *BaseTheme) DiffHunkHeader() lipgloss.AdaptiveColor { return t.DiffHunkHeaderColor }
  152. func (t *BaseTheme) DiffHighlightAdded() lipgloss.AdaptiveColor { return t.DiffHighlightAddedColor }
  153. func (t *BaseTheme) DiffHighlightRemoved() lipgloss.AdaptiveColor { return t.DiffHighlightRemovedColor }
  154. func (t *BaseTheme) DiffAddedBg() lipgloss.AdaptiveColor { return t.DiffAddedBgColor }
  155. func (t *BaseTheme) DiffRemovedBg() lipgloss.AdaptiveColor { return t.DiffRemovedBgColor }
  156. func (t *BaseTheme) DiffContextBg() lipgloss.AdaptiveColor { return t.DiffContextBgColor }
  157. func (t *BaseTheme) DiffLineNumber() lipgloss.AdaptiveColor { return t.DiffLineNumberColor }
  158. func (t *BaseTheme) DiffAddedLineNumberBg() lipgloss.AdaptiveColor {
  159. return t.DiffAddedLineNumberBgColor
  160. }
  161. func (t *BaseTheme) DiffRemovedLineNumberBg() lipgloss.AdaptiveColor {
  162. return t.DiffRemovedLineNumberBgColor
  163. }
  164. func (t *BaseTheme) MarkdownText() lipgloss.AdaptiveColor { return t.MarkdownTextColor }
  165. func (t *BaseTheme) MarkdownHeading() lipgloss.AdaptiveColor { return t.MarkdownHeadingColor }
  166. func (t *BaseTheme) MarkdownLink() lipgloss.AdaptiveColor { return t.MarkdownLinkColor }
  167. func (t *BaseTheme) MarkdownLinkText() lipgloss.AdaptiveColor { return t.MarkdownLinkTextColor }
  168. func (t *BaseTheme) MarkdownCode() lipgloss.AdaptiveColor { return t.MarkdownCodeColor }
  169. func (t *BaseTheme) MarkdownBlockQuote() lipgloss.AdaptiveColor { return t.MarkdownBlockQuoteColor }
  170. func (t *BaseTheme) MarkdownEmph() lipgloss.AdaptiveColor { return t.MarkdownEmphColor }
  171. func (t *BaseTheme) MarkdownStrong() lipgloss.AdaptiveColor { return t.MarkdownStrongColor }
  172. func (t *BaseTheme) MarkdownHorizontalRule() lipgloss.AdaptiveColor {
  173. return t.MarkdownHorizontalRuleColor
  174. }
  175. func (t *BaseTheme) MarkdownListItem() lipgloss.AdaptiveColor { return t.MarkdownListItemColor }
  176. func (t *BaseTheme) MarkdownListEnumeration() lipgloss.AdaptiveColor {
  177. return t.MarkdownListEnumerationColor
  178. }
  179. func (t *BaseTheme) MarkdownImage() lipgloss.AdaptiveColor { return t.MarkdownImageColor }
  180. func (t *BaseTheme) MarkdownImageText() lipgloss.AdaptiveColor { return t.MarkdownImageTextColor }
  181. func (t *BaseTheme) MarkdownCodeBlock() lipgloss.AdaptiveColor { return t.MarkdownCodeBlockColor }
  182. func (t *BaseTheme) SyntaxComment() lipgloss.AdaptiveColor { return t.SyntaxCommentColor }
  183. func (t *BaseTheme) SyntaxKeyword() lipgloss.AdaptiveColor { return t.SyntaxKeywordColor }
  184. func (t *BaseTheme) SyntaxFunction() lipgloss.AdaptiveColor { return t.SyntaxFunctionColor }
  185. func (t *BaseTheme) SyntaxVariable() lipgloss.AdaptiveColor { return t.SyntaxVariableColor }
  186. func (t *BaseTheme) SyntaxString() lipgloss.AdaptiveColor { return t.SyntaxStringColor }
  187. func (t *BaseTheme) SyntaxNumber() lipgloss.AdaptiveColor { return t.SyntaxNumberColor }
  188. func (t *BaseTheme) SyntaxType() lipgloss.AdaptiveColor { return t.SyntaxTypeColor }
  189. func (t *BaseTheme) SyntaxOperator() lipgloss.AdaptiveColor { return t.SyntaxOperatorColor }
  190. func (t *BaseTheme) SyntaxPunctuation() lipgloss.AdaptiveColor { return t.SyntaxPunctuationColor }
  191. // ParseAdaptiveColor parses a color value from the config file into a lipgloss.AdaptiveColor.
  192. // It accepts either a string (hex color) or a map with "dark" and "light" keys.
  193. func ParseAdaptiveColor(value any) (lipgloss.AdaptiveColor, error) {
  194. // Regular expression to validate hex color format
  195. hexColorRegex := regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
  196. // Case 1: String value (same color for both dark and light modes)
  197. if hexColor, ok := value.(string); ok {
  198. if !hexColorRegex.MatchString(hexColor) {
  199. return lipgloss.AdaptiveColor{}, fmt.Errorf("invalid hex color format: %s", hexColor)
  200. }
  201. return lipgloss.AdaptiveColor{
  202. Dark: hexColor,
  203. Light: hexColor,
  204. }, nil
  205. }
  206. // Case 2: Int value between 0 and 255
  207. if numericVal, ok := value.(float64); ok {
  208. intVal := int(numericVal)
  209. if intVal < 0 || intVal > 255 {
  210. return lipgloss.AdaptiveColor{}, fmt.Errorf("invalid int color value (must be between 0 and 255): %d", intVal)
  211. }
  212. return lipgloss.AdaptiveColor{
  213. Dark: fmt.Sprintf("%d", intVal),
  214. Light: fmt.Sprintf("%d", intVal),
  215. }, nil
  216. }
  217. // Case 3: Map with dark and light keys
  218. if colorMap, ok := value.(map[string]any); ok {
  219. darkVal, darkOk := colorMap["dark"]
  220. lightVal, lightOk := colorMap["light"]
  221. if !darkOk || !lightOk {
  222. return lipgloss.AdaptiveColor{}, fmt.Errorf("color map must contain both 'dark' and 'light' keys")
  223. }
  224. darkHex, darkIsString := darkVal.(string)
  225. lightHex, lightIsString := lightVal.(string)
  226. if !darkIsString || !lightIsString {
  227. darkVal, darkIsNumber := darkVal.(float64)
  228. lightVal, lightIsNumber := lightVal.(float64)
  229. if !darkIsNumber || !lightIsNumber {
  230. return lipgloss.AdaptiveColor{}, fmt.Errorf("color map values must be strings or ints")
  231. }
  232. darkInt := int(darkVal)
  233. lightInt := int(lightVal)
  234. return lipgloss.AdaptiveColor{
  235. Dark: fmt.Sprintf("%d", darkInt),
  236. Light: fmt.Sprintf("%d", lightInt),
  237. }, nil
  238. }
  239. if !hexColorRegex.MatchString(darkHex) || !hexColorRegex.MatchString(lightHex) {
  240. return lipgloss.AdaptiveColor{}, fmt.Errorf("invalid hex color format")
  241. }
  242. return lipgloss.AdaptiveColor{
  243. Dark: darkHex,
  244. Light: lightHex,
  245. }, nil
  246. }
  247. return lipgloss.AdaptiveColor{}, fmt.Errorf("color must be either a hex string or an object with dark/light keys")
  248. }