theme.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package dialog
  2. import (
  3. tea "github.com/charmbracelet/bubbletea/v2"
  4. list "github.com/sst/opencode/internal/components/list"
  5. "github.com/sst/opencode/internal/components/modal"
  6. "github.com/sst/opencode/internal/layout"
  7. "github.com/sst/opencode/internal/styles"
  8. "github.com/sst/opencode/internal/theme"
  9. "github.com/sst/opencode/internal/util"
  10. )
  11. // ThemeSelectedMsg is sent when the theme is changed
  12. type ThemeSelectedMsg struct {
  13. ThemeName string
  14. }
  15. // ThemeDialog interface for the theme switching dialog
  16. type ThemeDialog interface {
  17. layout.Modal
  18. }
  19. type themeDialog struct {
  20. width int
  21. height int
  22. modal *modal.Modal
  23. list list.List[list.Item]
  24. originalTheme string
  25. themeApplied bool
  26. }
  27. func (t *themeDialog) Init() tea.Cmd {
  28. return nil
  29. }
  30. func (t *themeDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  31. switch msg := msg.(type) {
  32. case tea.WindowSizeMsg:
  33. t.width = msg.Width
  34. t.height = msg.Height
  35. case tea.KeyMsg:
  36. switch msg.String() {
  37. case "enter":
  38. if item, idx := t.list.GetSelectedItem(); idx >= 0 {
  39. if stringItem, ok := item.(list.StringItem); ok {
  40. selectedTheme := string(stringItem)
  41. if err := theme.SetTheme(selectedTheme); err != nil {
  42. // status.Error(err.Error())
  43. return t, nil
  44. }
  45. t.themeApplied = true
  46. return t, tea.Sequence(
  47. util.CmdHandler(modal.CloseModalMsg{}),
  48. util.CmdHandler(ThemeSelectedMsg{ThemeName: selectedTheme}),
  49. )
  50. }
  51. }
  52. }
  53. }
  54. _, prevIdx := t.list.GetSelectedItem()
  55. var cmd tea.Cmd
  56. listModel, cmd := t.list.Update(msg)
  57. t.list = listModel.(list.List[list.Item])
  58. if item, newIdx := t.list.GetSelectedItem(); newIdx >= 0 && newIdx != prevIdx {
  59. if stringItem, ok := item.(list.StringItem); ok {
  60. theme.SetTheme(string(stringItem))
  61. return t, util.CmdHandler(ThemeSelectedMsg{ThemeName: string(stringItem)})
  62. }
  63. }
  64. return t, cmd
  65. }
  66. func (t *themeDialog) Render(background string) string {
  67. return t.modal.Render(t.list.View(), background)
  68. }
  69. func (t *themeDialog) Close() tea.Cmd {
  70. if !t.themeApplied {
  71. theme.SetTheme(t.originalTheme)
  72. return util.CmdHandler(ThemeSelectedMsg{ThemeName: t.originalTheme})
  73. }
  74. return nil
  75. }
  76. // NewThemeDialog creates a new theme switching dialog
  77. func NewThemeDialog() ThemeDialog {
  78. themes := theme.AvailableThemes()
  79. currentTheme := theme.CurrentThemeName()
  80. var selectedIdx int
  81. for i, name := range themes {
  82. if name == currentTheme {
  83. selectedIdx = i
  84. }
  85. }
  86. // Convert themes to list items
  87. items := make([]list.Item, len(themes))
  88. for i, theme := range themes {
  89. items[i] = list.StringItem(theme)
  90. }
  91. listComponent := list.NewListComponent(
  92. list.WithItems(items),
  93. list.WithMaxVisibleHeight[list.Item](10),
  94. list.WithFallbackMessage[list.Item]("No themes available"),
  95. list.WithAlphaNumericKeys[list.Item](true),
  96. list.WithRenderFunc(func(item list.Item, selected bool, width int, baseStyle styles.Style) string {
  97. return item.Render(selected, width, baseStyle)
  98. }),
  99. list.WithSelectableFunc(func(item list.Item) bool {
  100. return item.Selectable()
  101. }),
  102. )
  103. // Set the initial selection to the current theme
  104. listComponent.SetSelectedIndex(selectedIdx)
  105. // Set the max width for the list to match the modal width
  106. listComponent.SetMaxWidth(36) // 40 (modal max width) - 4 (modal padding)
  107. return &themeDialog{
  108. list: listComponent,
  109. modal: modal.New(modal.WithTitle("Select Theme"), modal.WithMaxWidth(40)),
  110. originalTheme: currentTheme,
  111. themeApplied: false,
  112. }
  113. }