1package refs
2
3import (
4 "fmt"
5 "io"
6 "sort"
7
8 "github.com/charmbracelet/bubbles/list"
9 tea "github.com/charmbracelet/bubbletea"
10 "github.com/charmbracelet/soft-serve/internal/tui/style"
11 "github.com/charmbracelet/soft-serve/pkg/git"
12 "github.com/charmbracelet/soft-serve/pkg/tui/common"
13)
14
15type RefMsg = *git.Reference
16
17type item struct {
18 *git.Reference
19}
20
21func (i item) Short() string {
22 return i.Reference.Name().Short()
23}
24
25func (i item) FilterValue() string { return i.Short() }
26
27type items []item
28
29func (cl items) Len() int { return len(cl) }
30func (cl items) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
31func (cl items) Less(i, j int) bool {
32 return cl[i].Short() < cl[j].Short()
33}
34
35type itemDelegate struct {
36 style *style.Styles
37}
38
39func (d itemDelegate) Height() int { return 1 }
40func (d itemDelegate) Spacing() int { return 0 }
41func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
42func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
43 s := d.style
44 i, ok := listItem.(item)
45 if !ok {
46 return
47 }
48
49 ref := i.Short()
50 if i.Reference.IsTag() {
51 ref = s.RefItemTag.Render(ref)
52 }
53 ref = s.RefItemBranch.Render(ref)
54 refMaxWidth := m.Width() -
55 s.RefItemSelector.GetMarginLeft() -
56 s.RefItemSelector.GetWidth() -
57 s.RefItemInactive.GetMarginLeft()
58 ref = common.TruncateString(ref, refMaxWidth, "…")
59 if index == m.Index() {
60 fmt.Fprint(w, s.RefItemSelector.Render(">")+
61 s.RefItemActive.Render(ref))
62 } else {
63 fmt.Fprint(w, s.LogItemSelector.Render(" ")+
64 s.RefItemInactive.Render(ref))
65 }
66}
67
68type Bubble struct {
69 repo common.GitRepo
70 list list.Model
71 style *style.Styles
72 width int
73 widthMargin int
74 height int
75 heightMargin int
76 ref *git.Reference
77}
78
79func NewBubble(repo common.GitRepo, styles *style.Styles, width, widthMargin, height, heightMargin int) *Bubble {
80 head, err := repo.HEAD()
81 if err != nil {
82 return nil
83 }
84 l := list.NewModel([]list.Item{}, itemDelegate{styles}, width-widthMargin, height-heightMargin)
85 l.SetShowFilter(false)
86 l.SetShowHelp(false)
87 l.SetShowPagination(true)
88 l.SetShowStatusBar(false)
89 l.SetShowTitle(false)
90 l.SetFilteringEnabled(false)
91 l.DisableQuitKeybindings()
92 b := &Bubble{
93 repo: repo,
94 style: styles,
95 width: width,
96 height: height,
97 widthMargin: widthMargin,
98 heightMargin: heightMargin,
99 list: l,
100 ref: head,
101 }
102 b.SetSize(width, height)
103 return b
104}
105
106func (b *Bubble) SetBranch(ref *git.Reference) (tea.Model, tea.Cmd) {
107 b.ref = ref
108 return b, func() tea.Msg {
109 return RefMsg(ref)
110 }
111}
112
113func (b *Bubble) reset() tea.Cmd {
114 cmd := b.updateItems()
115 b.SetSize(b.width, b.height)
116 return cmd
117}
118
119func (b *Bubble) Init() tea.Cmd {
120 return nil
121}
122
123func (b *Bubble) SetSize(width, height int) {
124 b.width = width
125 b.height = height
126 b.list.SetSize(width-b.widthMargin, height-b.heightMargin)
127 b.list.Styles.PaginationStyle = b.style.RefPaginator.Copy().Width(width - b.widthMargin)
128}
129
130func (b *Bubble) Help() []common.HelpEntry {
131 return nil
132}
133
134func (b *Bubble) updateItems() tea.Cmd {
135 its := make(items, 0)
136 tags := make(items, 0)
137 refs, err := b.repo.References()
138 if err != nil {
139 return func() tea.Msg { return common.ErrMsg{Err: err} }
140 }
141 for _, r := range refs {
142 if r.IsTag() {
143 tags = append(tags, item{r})
144 } else if r.IsBranch() {
145 its = append(its, item{r})
146 }
147 }
148 sort.Sort(its)
149 sort.Sort(tags)
150 its = append(its, tags...)
151 itt := make([]list.Item, len(its))
152 for i, it := range its {
153 itt[i] = it
154 }
155 return b.list.SetItems(itt)
156}
157
158func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
159 cmds := make([]tea.Cmd, 0)
160 switch msg := msg.(type) {
161 case tea.WindowSizeMsg:
162 b.SetSize(msg.Width, msg.Height)
163
164 case tea.KeyMsg:
165 switch msg.String() {
166 case "B":
167 return b, b.reset()
168 case "enter", "right", "l":
169 if b.list.Index() >= 0 {
170 ref := b.list.SelectedItem().(item).Reference
171 return b.SetBranch(ref)
172 }
173 }
174 }
175
176 l, cmd := b.list.Update(msg)
177 b.list = l
178 cmds = append(cmds, cmd)
179
180 return b, tea.Batch(cmds...)
181}
182
183func (b *Bubble) View() string {
184 return b.list.View()
185}