| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- package dialog
- import (
- "github.com/charmbracelet/bubbles/key"
- "github.com/charmbracelet/bubbles/textarea"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- "github.com/sst/opencode/internal/status"
- 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"
- )
- type CompletionItem struct {
- title string
- Title string
- Value string
- }
- type CompletionItemI interface {
- utilComponents.SimpleListItem
- GetValue() string
- DisplayValue() string
- }
- func (ci *CompletionItem) Render(selected bool, width int) string {
- t := theme.CurrentTheme()
- baseStyle := styles.BaseStyle()
- itemStyle := baseStyle.
- Width(width).
- Padding(0, 1)
- if selected {
- itemStyle = itemStyle.
- Background(t.Background()).
- Foreground(t.Primary()).
- Bold(true)
- }
- title := itemStyle.Render(
- ci.GetValue(),
- )
- return title
- }
- func (ci *CompletionItem) DisplayValue() string {
- return ci.Title
- }
- func (ci *CompletionItem) GetValue() string {
- return ci.Value
- }
- func NewCompletionItem(completionItem CompletionItem) CompletionItemI {
- return &completionItem
- }
- type CompletionProvider interface {
- GetId() string
- GetEntry() CompletionItemI
- GetChildEntries(query string) ([]CompletionItemI, error)
- }
- type CompletionSelectedMsg struct {
- SearchString string
- CompletionValue string
- }
- type CompletionDialogCompleteItemMsg struct {
- Value string
- }
- type CompletionDialogCloseMsg struct{}
- type CompletionDialog interface {
- tea.Model
- layout.Bindings
- SetWidth(width int)
- }
- type completionDialogCmp struct {
- query string
- completionProvider CompletionProvider
- width int
- height int
- pseudoSearchTextArea textarea.Model
- listView utilComponents.SimpleList[CompletionItemI]
- }
- type completionDialogKeyMap struct {
- Complete key.Binding
- Cancel key.Binding
- }
- var completionDialogKeys = completionDialogKeyMap{
- Complete: key.NewBinding(
- key.WithKeys("tab", "enter"),
- ),
- Cancel: key.NewBinding(
- key.WithKeys(" ", "esc", "backspace"),
- ),
- }
- func (c *completionDialogCmp) Init() tea.Cmd {
- return nil
- }
- func (c *completionDialogCmp) complete(item CompletionItemI) tea.Cmd {
- value := c.pseudoSearchTextArea.Value()
- if value == "" {
- return nil
- }
- return tea.Batch(
- util.CmdHandler(CompletionSelectedMsg{
- SearchString: value,
- CompletionValue: item.GetValue(),
- }),
- c.close(),
- )
- }
- func (c *completionDialogCmp) close() tea.Cmd {
- c.listView.SetItems([]CompletionItemI{})
- c.pseudoSearchTextArea.Reset()
- c.pseudoSearchTextArea.Blur()
- return util.CmdHandler(CompletionDialogCloseMsg{})
- }
- func (c *completionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- var cmds []tea.Cmd
- switch msg := msg.(type) {
- case tea.KeyMsg:
- if c.pseudoSearchTextArea.Focused() {
- if !key.Matches(msg, completionDialogKeys.Complete) {
- var cmd tea.Cmd
- c.pseudoSearchTextArea, cmd = c.pseudoSearchTextArea.Update(msg)
- cmds = append(cmds, cmd)
- var query string
- query = c.pseudoSearchTextArea.Value()
- if query != "" {
- query = query[1:]
- }
- if query != c.query {
- items, err := c.completionProvider.GetChildEntries(query)
- if err != nil {
- status.Error(err.Error())
- }
- c.listView.SetItems(items)
- c.query = query
- }
- u, cmd := c.listView.Update(msg)
- c.listView = u.(utilComponents.SimpleList[CompletionItemI])
- cmds = append(cmds, cmd)
- }
- switch {
- case key.Matches(msg, completionDialogKeys.Complete):
- item, i := c.listView.GetSelectedItem()
- if i == -1 {
- return c, nil
- }
- cmd := c.complete(item)
- return c, cmd
- case key.Matches(msg, completionDialogKeys.Cancel):
- // Only close on backspace when there are no characters left
- if msg.String() != "backspace" || len(c.pseudoSearchTextArea.Value()) <= 0 {
- return c, c.close()
- }
- }
- return c, tea.Batch(cmds...)
- } else {
- items, err := c.completionProvider.GetChildEntries("")
- if err != nil {
- status.Error(err.Error())
- }
- c.listView.SetItems(items)
- c.pseudoSearchTextArea.SetValue(msg.String())
- return c, c.pseudoSearchTextArea.Focus()
- }
- case tea.WindowSizeMsg:
- c.width = msg.Width
- c.height = msg.Height
- }
- return c, tea.Batch(cmds...)
- }
- func (c *completionDialogCmp) View() string {
- t := theme.CurrentTheme()
- baseStyle := styles.BaseStyle()
- maxWidth := 40
- completions := c.listView.GetItems()
- for _, cmd := range completions {
- title := cmd.DisplayValue()
- if len(title) > maxWidth-4 {
- maxWidth = len(title) + 4
- }
- }
- c.listView.SetMaxWidth(maxWidth)
- return baseStyle.Padding(0, 0).
- Border(lipgloss.NormalBorder()).
- BorderBottom(false).
- BorderRight(false).
- BorderLeft(false).
- BorderBackground(t.Background()).
- BorderForeground(t.TextMuted()).
- Width(c.width).
- Render(c.listView.View())
- }
- func (c *completionDialogCmp) SetWidth(width int) {
- c.width = width
- }
- func (c *completionDialogCmp) BindingKeys() []key.Binding {
- return layout.KeyMapToSlice(completionDialogKeys)
- }
- func NewCompletionDialogCmp(completionProvider CompletionProvider) CompletionDialog {
- ti := textarea.New()
- items, err := completionProvider.GetChildEntries("")
- if err != nil {
- status.Error(err.Error())
- }
- li := utilComponents.NewSimpleList(
- items,
- 7,
- "No file matches found",
- false,
- )
- return &completionDialogCmp{
- query: "",
- completionProvider: completionProvider,
- pseudoSearchTextArea: ti,
- listView: li,
- }
- }
|