| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- package dialog
- import (
- "github.com/charmbracelet/bubbles/key"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- utilComponents "github.com/sst/opencode/internal/components/util"
- "github.com/sst/opencode/internal/layout"
- "github.com/sst/opencode/internal/styles"
- "github.com/sst/opencode/internal/theme"
- "github.com/sst/opencode/internal/util"
- )
- // Command represents a command that can be executed
- type Command struct {
- ID string
- Title string
- Description string
- Handler func(cmd Command) tea.Cmd
- }
- func (ci Command) Render(selected bool, width int) string {
- t := theme.CurrentTheme()
- baseStyle := styles.BaseStyle()
- descStyle := baseStyle.Width(width).Foreground(t.TextMuted())
- itemStyle := baseStyle.Width(width).
- Foreground(t.Text()).
- Background(t.Background())
- if selected {
- itemStyle = itemStyle.
- Background(t.Primary()).
- Foreground(t.Background()).
- Bold(true)
- descStyle = descStyle.
- Background(t.Primary()).
- Foreground(t.Background())
- }
- title := itemStyle.Padding(0, 1).Render(ci.Title)
- if ci.Description != "" {
- description := descStyle.Padding(0, 1).Render(ci.Description)
- return lipgloss.JoinVertical(lipgloss.Left, title, description)
- }
- return title
- }
- // CommandSelectedMsg is sent when a command is selected
- type CommandSelectedMsg struct {
- Command Command
- }
- // CloseCommandDialogMsg is sent when the command dialog is closed
- type CloseCommandDialogMsg struct{}
- // CommandDialog interface for the command selection dialog
- type CommandDialog interface {
- tea.Model
- layout.Bindings
- SetCommands(commands []Command)
- }
- type commandDialogCmp struct {
- listView utilComponents.SimpleList[Command]
- width int
- height int
- }
- type commandKeyMap struct {
- Enter key.Binding
- Escape key.Binding
- }
- var commandKeys = commandKeyMap{
- Enter: key.NewBinding(
- key.WithKeys("enter"),
- key.WithHelp("enter", "select command"),
- ),
- Escape: key.NewBinding(
- key.WithKeys("esc"),
- key.WithHelp("esc", "close"),
- ),
- }
- func (c *commandDialogCmp) Init() tea.Cmd {
- return c.listView.Init()
- }
- func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- var cmds []tea.Cmd
- switch msg := msg.(type) {
- case tea.KeyMsg:
- switch {
- case key.Matches(msg, commandKeys.Enter):
- selectedItem, idx := c.listView.GetSelectedItem()
- if idx != -1 {
- return c, util.CmdHandler(CommandSelectedMsg{
- Command: selectedItem,
- })
- }
- case key.Matches(msg, commandKeys.Escape):
- return c, util.CmdHandler(CloseCommandDialogMsg{})
- }
- case tea.WindowSizeMsg:
- c.width = msg.Width
- c.height = msg.Height
- }
- u, cmd := c.listView.Update(msg)
- c.listView = u.(utilComponents.SimpleList[Command])
- cmds = append(cmds, cmd)
- return c, tea.Batch(cmds...)
- }
- func (c *commandDialogCmp) View() string {
- t := theme.CurrentTheme()
- baseStyle := styles.BaseStyle()
- maxWidth := 40
- commands := c.listView.GetItems()
- for _, cmd := range commands {
- if len(cmd.Title) > maxWidth-4 {
- maxWidth = len(cmd.Title) + 4
- }
- if cmd.Description != "" {
- if len(cmd.Description) > maxWidth-4 {
- maxWidth = len(cmd.Description) + 4
- }
- }
- }
- c.listView.SetMaxWidth(maxWidth)
- title := baseStyle.
- Foreground(t.Primary()).
- Bold(true).
- Width(maxWidth).
- Padding(0, 1).
- Render("Commands")
- content := lipgloss.JoinVertical(
- lipgloss.Left,
- title,
- baseStyle.Width(maxWidth).Render(""),
- baseStyle.Width(maxWidth).Render(c.listView.View()),
- baseStyle.Width(maxWidth).Render(""),
- )
- return baseStyle.Padding(1, 2).
- Border(lipgloss.RoundedBorder()).
- BorderBackground(t.Background()).
- BorderForeground(t.TextMuted()).
- Width(lipgloss.Width(content) + 4).
- Render(content)
- }
- func (c *commandDialogCmp) BindingKeys() []key.Binding {
- return layout.KeyMapToSlice(commandKeys)
- }
- func (c *commandDialogCmp) SetCommands(commands []Command) {
- c.listView.SetItems(commands)
- }
- // NewCommandDialogCmp creates a new command selection dialog
- func NewCommandDialogCmp() CommandDialog {
- listView := utilComponents.NewSimpleList[Command](
- []Command{},
- 10,
- "No commands available",
- true,
- )
- return &commandDialogCmp{
- listView: listView,
- }
- }
|