bubble.go

  1package selection
  2
  3import (
  4	"strings"
  5
  6	tea "github.com/charmbracelet/bubbletea"
  7	"github.com/charmbracelet/lipgloss"
  8	"github.com/charmbracelet/soft-serve/internal/tui/style"
  9	"github.com/charmbracelet/soft-serve/pkg/tui/common"
 10	"github.com/muesli/reflow/truncate"
 11)
 12
 13type SelectedMsg struct {
 14	Name  string
 15	Index int
 16}
 17
 18type ActiveMsg struct {
 19	Name  string
 20	Index int
 21}
 22
 23type Bubble struct {
 24	Items        []string
 25	SelectedItem int
 26	styles       *style.Styles
 27}
 28
 29func NewBubble(items []string, styles *style.Styles) *Bubble {
 30	return &Bubble{
 31		Items:  items,
 32		styles: styles,
 33	}
 34}
 35
 36func (b *Bubble) Init() tea.Cmd {
 37	return nil
 38}
 39
 40func (b Bubble) View() string {
 41	s := strings.Builder{}
 42	repoNameMaxWidth := b.styles.Menu.GetWidth() - // menu width
 43		b.styles.Menu.GetHorizontalPadding() - // menu padding
 44		lipgloss.Width(b.styles.MenuCursor.String()) - // cursor
 45		b.styles.MenuItem.GetHorizontalFrameSize() // menu item gaps
 46	for i, item := range b.Items {
 47		item := truncate.StringWithTail(item, uint(repoNameMaxWidth), "…")
 48		if i == b.SelectedItem {
 49			s.WriteString(b.styles.MenuCursor.String())
 50			s.WriteString(b.styles.SelectedMenuItem.Render(item))
 51		} else {
 52			s.WriteString(b.styles.MenuItem.Render(item))
 53		}
 54		if i < len(b.Items)-1 {
 55			s.WriteRune('\n')
 56		}
 57	}
 58	return s.String()
 59}
 60
 61func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 62	cmds := make([]tea.Cmd, 0)
 63	switch msg := msg.(type) {
 64	case tea.KeyMsg:
 65		switch msg.String() {
 66		case "k", "up":
 67			if b.SelectedItem > 0 {
 68				b.SelectedItem--
 69				cmds = append(cmds, b.sendActiveMessage)
 70			}
 71		case "j", "down":
 72			if b.SelectedItem < len(b.Items)-1 {
 73				b.SelectedItem++
 74				cmds = append(cmds, b.sendActiveMessage)
 75			}
 76		case "enter":
 77			cmds = append(cmds, b.sendSelectedMessage)
 78		}
 79	}
 80	return b, tea.Batch(cmds...)
 81}
 82
 83func (b *Bubble) Help() []common.HelpEntry {
 84	return []common.HelpEntry{
 85		{Key: "↑/↓", Value: "navigate"},
 86	}
 87}
 88
 89func (b *Bubble) sendActiveMessage() tea.Msg {
 90	if b.SelectedItem >= 0 && b.SelectedItem < len(b.Items) {
 91		return ActiveMsg{
 92			Name:  b.Items[b.SelectedItem],
 93			Index: b.SelectedItem,
 94		}
 95	}
 96	return nil
 97}
 98
 99func (b *Bubble) sendSelectedMessage() tea.Msg {
100	if b.SelectedItem >= 0 && b.SelectedItem < len(b.Items) {
101		return SelectedMsg{
102			Name:  b.Items[b.SelectedItem],
103			Index: b.SelectedItem,
104		}
105	}
106	return nil
107}