theme.go 2.8 KB

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