manager.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package theme
  2. import (
  3. "fmt"
  4. "slices"
  5. "strings"
  6. "sync"
  7. "github.com/alecthomas/chroma/v2/styles"
  8. "github.com/opencode-ai/opencode/internal/config"
  9. "github.com/opencode-ai/opencode/internal/logging"
  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. // RegisterTheme adds a new theme to the registry.
  24. // If this is the first theme registered, it becomes the default.
  25. func RegisterTheme(name string, theme Theme) {
  26. globalManager.mu.Lock()
  27. defer globalManager.mu.Unlock()
  28. globalManager.themes[name] = theme
  29. // If this is the first theme, make it the default
  30. if globalManager.currentName == "" {
  31. globalManager.currentName = name
  32. }
  33. }
  34. // SetTheme changes the active theme to the one with the specified name.
  35. // Returns an error if the theme doesn't exist.
  36. func SetTheme(name string) error {
  37. globalManager.mu.Lock()
  38. defer globalManager.mu.Unlock()
  39. delete(styles.Registry, "charm")
  40. if _, exists := globalManager.themes[name]; !exists {
  41. return fmt.Errorf("theme '%s' not found", name)
  42. }
  43. globalManager.currentName = name
  44. // Update the config file using viper
  45. if err := updateConfigTheme(name); err != nil {
  46. // Log the error but don't fail the theme change
  47. logging.Warn("Warning: Failed to update config file with new theme", "err", err)
  48. }
  49. return nil
  50. }
  51. // CurrentTheme returns the currently active theme.
  52. // If no theme is set, it returns nil.
  53. func CurrentTheme() Theme {
  54. globalManager.mu.RLock()
  55. defer globalManager.mu.RUnlock()
  56. if globalManager.currentName == "" {
  57. return nil
  58. }
  59. return globalManager.themes[globalManager.currentName]
  60. }
  61. // CurrentThemeName returns the name of the currently active theme.
  62. func CurrentThemeName() string {
  63. globalManager.mu.RLock()
  64. defer globalManager.mu.RUnlock()
  65. return globalManager.currentName
  66. }
  67. // AvailableThemes returns a list of all registered theme names.
  68. func AvailableThemes() []string {
  69. globalManager.mu.RLock()
  70. defer globalManager.mu.RUnlock()
  71. names := make([]string, 0, len(globalManager.themes))
  72. for name := range globalManager.themes {
  73. names = append(names, name)
  74. }
  75. slices.SortFunc(names, func(a, b string) int {
  76. if a == "opencode" {
  77. return -1
  78. } else if b == "opencode" {
  79. return 1
  80. }
  81. return strings.Compare(a, b)
  82. })
  83. return names
  84. }
  85. // GetTheme returns a specific theme by name.
  86. // Returns nil if the theme doesn't exist.
  87. func GetTheme(name string) Theme {
  88. globalManager.mu.RLock()
  89. defer globalManager.mu.RUnlock()
  90. return globalManager.themes[name]
  91. }
  92. // updateConfigTheme updates the theme setting in the configuration file
  93. func updateConfigTheme(themeName string) error {
  94. // Use the config package to update the theme
  95. return config.UpdateTheme(themeName)
  96. }