theme.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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/status"
  8. "github.com/sst/opencode/internal/styles"
  9. "github.com/sst/opencode/internal/theme"
  10. "github.com/sst/opencode/internal/util"
  11. )
  12. // ThemeChangedMsg is sent when the theme is changed
  13. type ThemeChangedMsg struct {
  14. ThemeName string
  15. }
  16. // ThemeDialog interface for the theme switching dialog
  17. type ThemeDialog interface {
  18. layout.Modal
  19. }
  20. type themeItem struct {
  21. name string
  22. }
  23. func (t themeItem) Render(selected bool, width int) string {
  24. th := theme.CurrentTheme()
  25. baseStyle := styles.BaseStyle().
  26. Width(width - 2).
  27. Background(th.BackgroundElement())
  28. if selected {
  29. baseStyle = baseStyle.
  30. Background(th.Primary()).
  31. Foreground(th.BackgroundElement()).
  32. Bold(true)
  33. } else {
  34. baseStyle = baseStyle.
  35. Foreground(th.Text())
  36. }
  37. return baseStyle.Padding(0, 1).Render(t.name)
  38. }
  39. type themeDialog struct {
  40. width int
  41. height int
  42. modal *modal.Modal
  43. list list.List[themeItem]
  44. }
  45. func (t *themeDialog) Init() tea.Cmd {
  46. return nil
  47. }
  48. func (t *themeDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  49. switch msg := msg.(type) {
  50. case tea.WindowSizeMsg:
  51. t.width = msg.Width
  52. t.height = msg.Height
  53. case tea.KeyMsg:
  54. switch msg.String() {
  55. case "enter":
  56. if item, idx := t.list.GetSelectedItem(); idx >= 0 {
  57. previousTheme := theme.CurrentThemeName()
  58. selectedTheme := item.name
  59. if previousTheme == selectedTheme {
  60. return t, util.CmdHandler(modal.CloseModalMsg{})
  61. }
  62. if err := theme.SetTheme(selectedTheme); err != nil {
  63. status.Error(err.Error())
  64. return t, nil
  65. }
  66. return t, tea.Sequence(
  67. util.CmdHandler(modal.CloseModalMsg{}),
  68. util.CmdHandler(ThemeChangedMsg{ThemeName: selectedTheme}),
  69. )
  70. }
  71. }
  72. }
  73. var cmd tea.Cmd
  74. listModel, cmd := t.list.Update(msg)
  75. t.list = listModel.(list.List[themeItem])
  76. return t, cmd
  77. }
  78. func (t *themeDialog) Render(background string) string {
  79. return t.modal.Render(t.list.View(), background)
  80. }
  81. func (t *themeDialog) Close() tea.Cmd {
  82. return nil
  83. }
  84. // NewThemeDialog creates a new theme switching dialog
  85. func NewThemeDialog() ThemeDialog {
  86. themes := theme.AvailableThemes()
  87. currentTheme := theme.CurrentThemeName()
  88. var themeItems []themeItem
  89. var selectedIdx int
  90. for i, name := range themes {
  91. themeItems = append(themeItems, themeItem{name: name})
  92. if name == currentTheme {
  93. selectedIdx = i
  94. }
  95. }
  96. list := list.NewListComponent(
  97. themeItems,
  98. 10, // maxVisibleThemes
  99. "No themes available",
  100. true,
  101. )
  102. // Set the initial selection to the current theme
  103. list.SetSelectedIndex(selectedIdx)
  104. return &themeDialog{
  105. list: list,
  106. modal: modal.New(modal.WithTitle("Select Theme"), modal.WithMaxWidth(40)),
  107. }
  108. }