manager.go 6.8 KB

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