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}