manager.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package theme
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "slices"
  6. "strings"
  7. "sync"
  8. "github.com/alecthomas/chroma/v2/styles"
  9. )
  10. // Manager handles theme registration, selection, and retrieval.
  11. // It maintains a registry of available themes and tracks the currently active theme.
  12. type Manager struct {
  13. themes map[string]Theme
  14. currentName string
  15. mu sync.RWMutex
  16. }
  17. // Global instance of the theme manager
  18. var globalManager = &Manager{
  19. themes: make(map[string]Theme),
  20. currentName: "",
  21. }
  22. // Default theme instance for custom theme defaulting
  23. var defaultThemeColors = NewOpenCodeTheme()
  24. // RegisterTheme adds a new theme to the registry.
  25. // If this is the first theme registered, it becomes the default.
  26. func RegisterTheme(name string, theme Theme) {
  27. globalManager.mu.Lock()
  28. defer globalManager.mu.Unlock()
  29. globalManager.themes[name] = theme
  30. // If this is the first theme, make it the default
  31. if globalManager.currentName == "" {
  32. globalManager.currentName = name
  33. }
  34. }
  35. // SetTheme changes the active theme to the one with the specified name.
  36. // Returns an error if the theme doesn't exist.
  37. func SetTheme(name string) error {
  38. globalManager.mu.Lock()
  39. defer globalManager.mu.Unlock()
  40. delete(styles.Registry, "charm")
  41. // Handle custom theme
  42. // if name == "custom" {
  43. // cfg := config.Get()
  44. // if cfg == nil || cfg.TUI.CustomTheme == nil || len(cfg.TUI.CustomTheme) == 0 {
  45. // return fmt.Errorf("custom theme selected but no custom theme colors defined in config")
  46. // }
  47. //
  48. // customTheme, err := LoadCustomTheme(cfg.TUI.CustomTheme)
  49. // if err != nil {
  50. // return fmt.Errorf("failed to load custom theme: %w", err)
  51. // }
  52. //
  53. // // Register the custom theme
  54. // globalManager.themes["custom"] = customTheme
  55. if _, exists := globalManager.themes[name]; !exists {
  56. return fmt.Errorf("theme '%s' not found", name)
  57. }
  58. globalManager.currentName = name
  59. return nil
  60. }
  61. // CurrentTheme returns the currently active theme.
  62. // If no theme is set, it returns nil.
  63. func CurrentTheme() Theme {
  64. globalManager.mu.RLock()
  65. defer globalManager.mu.RUnlock()
  66. if globalManager.currentName == "" {
  67. return nil
  68. }
  69. return globalManager.themes[globalManager.currentName]
  70. }
  71. // CurrentThemeName returns the name of the currently active theme.
  72. func CurrentThemeName() string {
  73. globalManager.mu.RLock()
  74. defer globalManager.mu.RUnlock()
  75. return globalManager.currentName
  76. }
  77. // AvailableThemes returns a list of all registered theme names.
  78. func AvailableThemes() []string {
  79. globalManager.mu.RLock()
  80. defer globalManager.mu.RUnlock()
  81. names := make([]string, 0, len(globalManager.themes))
  82. for name := range globalManager.themes {
  83. names = append(names, name)
  84. }
  85. slices.SortFunc(names, func(a, b string) int {
  86. if a == "opencode" {
  87. return -1
  88. } else if b == "opencode" {
  89. return 1
  90. }
  91. return strings.Compare(a, b)
  92. })
  93. return names
  94. }
  95. // GetTheme returns a specific theme by name.
  96. // Returns nil if the theme doesn't exist.
  97. func GetTheme(name string) Theme {
  98. globalManager.mu.RLock()
  99. defer globalManager.mu.RUnlock()
  100. return globalManager.themes[name]
  101. }
  102. // LoadCustomTheme creates a new theme instance based on the custom theme colors
  103. // defined in the configuration. It uses the default OpenCode theme as a base
  104. // and overrides colors that are specified in the customTheme map.
  105. func LoadCustomTheme(customTheme map[string]any) (Theme, error) {
  106. // Create a new theme based on the default OpenCode theme
  107. theme := NewOpenCodeTheme()
  108. // Process each color in the custom theme map
  109. for key, value := range customTheme {
  110. adaptiveColor, err := ParseAdaptiveColor(value)
  111. if err != nil {
  112. slog.Warn("Invalid color definition in custom theme", "key", key, "error", err)
  113. continue // Skip this color but continue processing others
  114. }
  115. // Set the color in the theme based on the key
  116. switch strings.ToLower(key) {
  117. case "primary":
  118. theme.PrimaryColor = adaptiveColor
  119. case "secondary":
  120. theme.SecondaryColor = adaptiveColor
  121. case "accent":
  122. theme.AccentColor = adaptiveColor
  123. case "error":
  124. theme.ErrorColor = adaptiveColor
  125. case "warning":
  126. theme.WarningColor = adaptiveColor
  127. case "success":
  128. theme.SuccessColor = adaptiveColor
  129. case "info":
  130. theme.InfoColor = adaptiveColor
  131. case "text":
  132. theme.TextColor = adaptiveColor
  133. case "textmuted":
  134. theme.TextMutedColor = adaptiveColor
  135. case "textemphasized":
  136. theme.TextEmphasizedColor = adaptiveColor
  137. case "background":
  138. theme.BackgroundColor = adaptiveColor
  139. case "backgroundsecondary":
  140. theme.BackgroundSecondaryColor = adaptiveColor
  141. case "backgrounddarker":
  142. theme.BackgroundDarkerColor = adaptiveColor
  143. case "bordernormal":
  144. theme.BorderNormalColor = adaptiveColor
  145. case "borderfocused":
  146. theme.BorderFocusedColor = adaptiveColor
  147. case "borderdim":
  148. theme.BorderDimColor = adaptiveColor
  149. case "diffadded":
  150. theme.DiffAddedColor = adaptiveColor
  151. case "diffremoved":
  152. theme.DiffRemovedColor = adaptiveColor
  153. case "diffcontext":
  154. theme.DiffContextColor = adaptiveColor
  155. case "diffhunkheader":
  156. theme.DiffHunkHeaderColor = adaptiveColor
  157. case "diffhighlightadded":
  158. theme.DiffHighlightAddedColor = adaptiveColor
  159. case "diffhighlightremoved":
  160. theme.DiffHighlightRemovedColor = adaptiveColor
  161. case "diffaddedbg":
  162. theme.DiffAddedBgColor = adaptiveColor
  163. case "diffremovedbg":
  164. theme.DiffRemovedBgColor = adaptiveColor
  165. case "diffcontextbg":
  166. theme.DiffContextBgColor = adaptiveColor
  167. case "difflinenumber":
  168. theme.DiffLineNumberColor = adaptiveColor
  169. case "diffaddedlinenumberbg":
  170. theme.DiffAddedLineNumberBgColor = adaptiveColor
  171. case "diffremovedlinenumberbg":
  172. theme.DiffRemovedLineNumberBgColor = adaptiveColor
  173. case "syntaxcomment":
  174. theme.SyntaxCommentColor = adaptiveColor
  175. case "syntaxkeyword":
  176. theme.SyntaxKeywordColor = adaptiveColor
  177. case "syntaxfunction":
  178. theme.SyntaxFunctionColor = adaptiveColor
  179. case "syntaxvariable":
  180. theme.SyntaxVariableColor = adaptiveColor
  181. case "syntaxstring":
  182. theme.SyntaxStringColor = adaptiveColor
  183. case "syntaxnumber":
  184. theme.SyntaxNumberColor = adaptiveColor
  185. case "syntaxtype":
  186. theme.SyntaxTypeColor = adaptiveColor
  187. case "syntaxoperator":
  188. theme.SyntaxOperatorColor = adaptiveColor
  189. case "syntaxpunctuation":
  190. theme.SyntaxPunctuationColor = adaptiveColor
  191. case "markdowntext":
  192. theme.MarkdownTextColor = adaptiveColor
  193. case "markdownheading":
  194. theme.MarkdownHeadingColor = adaptiveColor
  195. case "markdownlink":
  196. theme.MarkdownLinkColor = adaptiveColor
  197. case "markdownlinktext":
  198. theme.MarkdownLinkTextColor = adaptiveColor
  199. case "markdowncode":
  200. theme.MarkdownCodeColor = adaptiveColor
  201. case "markdownblockquote":
  202. theme.MarkdownBlockQuoteColor = adaptiveColor
  203. case "markdownemph":
  204. theme.MarkdownEmphColor = adaptiveColor
  205. case "markdownstrong":
  206. theme.MarkdownStrongColor = adaptiveColor
  207. case "markdownhorizontalrule":
  208. theme.MarkdownHorizontalRuleColor = adaptiveColor
  209. case "markdownlistitem":
  210. theme.MarkdownListItemColor = adaptiveColor
  211. case "markdownlistitemenum":
  212. theme.MarkdownListEnumerationColor = adaptiveColor
  213. case "markdownimage":
  214. theme.MarkdownImageColor = adaptiveColor
  215. case "markdownimagetext":
  216. theme.MarkdownImageTextColor = adaptiveColor
  217. case "markdowncodeblock":
  218. theme.MarkdownCodeBlockColor = adaptiveColor
  219. case "markdownlistenumeration":
  220. theme.MarkdownListEnumerationColor = adaptiveColor
  221. default:
  222. slog.Warn("Unknown color key in custom theme", "key", key)
  223. }
  224. }
  225. return theme, nil
  226. }