diff options
| author | Dax Raad <[email protected]> | 2025-05-18 22:30:41 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-05-26 12:40:17 -0400 |
| commit | 99af6146d5def31c59993636d60eb75a483a283b (patch) | |
| tree | eb57ed227c15cf9be54bc9f231c83895d812f881 /pkg/tui/components/dialog/theme.go | |
| parent | 020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff) | |
| download | opencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz opencode-99af6146d5def31c59993636d60eb75a483a283b.zip | |
openapi
Diffstat (limited to 'pkg/tui/components/dialog/theme.go')
| -rw-r--r-- | pkg/tui/components/dialog/theme.go | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/pkg/tui/components/dialog/theme.go b/pkg/tui/components/dialog/theme.go new file mode 100644 index 000000000..54856e8a9 --- /dev/null +++ b/pkg/tui/components/dialog/theme.go @@ -0,0 +1,199 @@ +package dialog + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/sst/opencode/internal/status" + "github.com/sst/opencode/internal/tui/layout" + "github.com/sst/opencode/internal/tui/styles" + "github.com/sst/opencode/internal/tui/theme" + "github.com/sst/opencode/internal/tui/util" +) + +// ThemeChangedMsg is sent when the theme is changed +type ThemeChangedMsg struct { + ThemeName string +} + +// CloseThemeDialogMsg is sent when the theme dialog is closed +type CloseThemeDialogMsg struct{} + +// ThemeDialog interface for the theme switching dialog +type ThemeDialog interface { + tea.Model + layout.Bindings +} + +type themeDialogCmp struct { + themes []string + selectedIdx int + width int + height int + currentTheme string +} + +type themeKeyMap struct { + Up key.Binding + Down key.Binding + Enter key.Binding + Escape key.Binding + J key.Binding + K key.Binding +} + +var themeKeys = themeKeyMap{ + Up: key.NewBinding( + key.WithKeys("up"), + key.WithHelp("↑", "previous theme"), + ), + Down: key.NewBinding( + key.WithKeys("down"), + key.WithHelp("↓", "next theme"), + ), + Enter: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "select theme"), + ), + Escape: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "close"), + ), + J: key.NewBinding( + key.WithKeys("j"), + key.WithHelp("j", "next theme"), + ), + K: key.NewBinding( + key.WithKeys("k"), + key.WithHelp("k", "previous theme"), + ), +} + +func (t *themeDialogCmp) Init() tea.Cmd { + // Load available themes and update selectedIdx based on current theme + t.themes = theme.AvailableThemes() + t.currentTheme = theme.CurrentThemeName() + + // Find the current theme in the list + for i, name := range t.themes { + if name == t.currentTheme { + t.selectedIdx = i + break + } + } + + return nil +} + +func (t *themeDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, themeKeys.Up) || key.Matches(msg, themeKeys.K): + if t.selectedIdx > 0 { + t.selectedIdx-- + } + return t, nil + case key.Matches(msg, themeKeys.Down) || key.Matches(msg, themeKeys.J): + if t.selectedIdx < len(t.themes)-1 { + t.selectedIdx++ + } + return t, nil + case key.Matches(msg, themeKeys.Enter): + if len(t.themes) > 0 { + previousTheme := theme.CurrentThemeName() + selectedTheme := t.themes[t.selectedIdx] + if previousTheme == selectedTheme { + return t, util.CmdHandler(CloseThemeDialogMsg{}) + } + if err := theme.SetTheme(selectedTheme); err != nil { + status.Error(err.Error()) + return t, nil + } + return t, util.CmdHandler(ThemeChangedMsg{ + ThemeName: selectedTheme, + }) + } + case key.Matches(msg, themeKeys.Escape): + return t, util.CmdHandler(CloseThemeDialogMsg{}) + } + case tea.WindowSizeMsg: + t.width = msg.Width + t.height = msg.Height + } + return t, nil +} + +func (t *themeDialogCmp) View() string { + currentTheme := theme.CurrentTheme() + baseStyle := styles.BaseStyle() + + if len(t.themes) == 0 { + return baseStyle.Padding(1, 2). + Border(lipgloss.RoundedBorder()). + BorderBackground(currentTheme.Background()). + BorderForeground(currentTheme.TextMuted()). + Width(40). + Render("No themes available") + } + + // Calculate max width needed for theme names + maxWidth := 40 // Minimum width + for _, themeName := range t.themes { + if len(themeName) > maxWidth-4 { // Account for padding + maxWidth = len(themeName) + 4 + } + } + + maxWidth = max(30, min(maxWidth, t.width-15)) // Limit width to avoid overflow + + // Build the theme list + themeItems := make([]string, 0, len(t.themes)) + for i, themeName := range t.themes { + itemStyle := baseStyle.Width(maxWidth) + + if i == t.selectedIdx { + itemStyle = itemStyle. + Background(currentTheme.Primary()). + Foreground(currentTheme.Background()). + Bold(true) + } + + themeItems = append(themeItems, itemStyle.Padding(0, 1).Render(themeName)) + } + + title := baseStyle. + Foreground(currentTheme.Primary()). + Bold(true). + Width(maxWidth). + Padding(0, 1). + Render("Select Theme") + + content := lipgloss.JoinVertical( + lipgloss.Left, + title, + baseStyle.Width(maxWidth).Render(""), + baseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, themeItems...)), + baseStyle.Width(maxWidth).Render(""), + ) + + return baseStyle.Padding(1, 2). + Border(lipgloss.RoundedBorder()). + BorderBackground(currentTheme.Background()). + BorderForeground(currentTheme.TextMuted()). + Width(lipgloss.Width(content) + 4). + Render(content) +} + +func (t *themeDialogCmp) BindingKeys() []key.Binding { + return layout.KeyMapToSlice(themeKeys) +} + +// NewThemeDialogCmp creates a new theme switching dialog +func NewThemeDialogCmp() ThemeDialog { + return &themeDialogCmp{ + themes: []string{}, + selectedIdx: 0, + currentTheme: "", + } +} |
