2
0

theme.go 12 KB

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