bubble.go

  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/bubbles/git/types"
 11	"github.com/charmbracelet/soft-serve/internal/tui/style"
 12	"github.com/go-git/go-git/v5/plumbing"
 13)
 14
 15type RefMsg = *plumbing.Reference
 16
 17type item struct {
 18	*plumbing.Reference
 19}
 20
 21func (i item) Short() string {
 22	return i.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].Name().Short() < cl[j].Name().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.Name().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 = types.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         types.Repo
 70	list         list.Model
 71	style        *style.Styles
 72	width        int
 73	widthMargin  int
 74	height       int
 75	heightMargin int
 76}
 77
 78func NewBubble(repo types.Repo, style *style.Styles, width, widthMargin, height, heightMargin int) *Bubble {
 79	l := list.NewModel([]list.Item{}, itemDelegate{style}, width-widthMargin, height-heightMargin)
 80	l.SetShowFilter(false)
 81	l.SetShowHelp(false)
 82	l.SetShowPagination(false)
 83	l.SetShowStatusBar(false)
 84	l.SetShowTitle(false)
 85	l.SetFilteringEnabled(false)
 86	l.DisableQuitKeybindings()
 87	b := &Bubble{
 88		repo:         repo,
 89		style:        style,
 90		width:        width,
 91		height:       height,
 92		widthMargin:  widthMargin,
 93		heightMargin: heightMargin,
 94		list:         l,
 95	}
 96	b.SetSize(width, height)
 97	return b
 98}
 99
100func (b *Bubble) SetBranch(ref *plumbing.Reference) (tea.Model, tea.Cmd) {
101	return b, func() tea.Msg {
102		b.repo.SetReference(ref)
103		return RefMsg(ref)
104	}
105}
106
107func (b *Bubble) Init() tea.Cmd {
108	return b.updateItems()
109}
110
111func (b *Bubble) SetSize(width, height int) {
112	b.width = width
113	b.height = height
114	b.list.SetSize(width-b.widthMargin, height-b.heightMargin)
115}
116
117func (b *Bubble) Help() []types.HelpEntry {
118	return nil
119}
120
121func (b *Bubble) updateItems() tea.Cmd {
122	its := make(items, 0)
123	tags := make(items, 0)
124	ri, err := b.repo.Repository().References()
125	if err != nil {
126		return nil
127	}
128	if err = ri.ForEach(func(r *plumbing.Reference) error {
129		if r.Type() == plumbing.HashReference {
130			if r.Name().IsTag() {
131				tags = append(tags, item{r})
132			} else {
133				its = append(its, item{r})
134			}
135		}
136		return nil
137	}); err != nil {
138		return nil
139	}
140	sort.Sort(its)
141	sort.Sort(tags)
142	its = append(its, tags...)
143	itt := make([]list.Item, len(its))
144	for i, it := range its {
145		itt[i] = it
146	}
147	return b.list.SetItems(itt)
148}
149
150func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
151	cmds := make([]tea.Cmd, 0)
152	switch msg := msg.(type) {
153	case tea.WindowSizeMsg:
154		b.SetSize(msg.Width, msg.Height)
155
156	case tea.KeyMsg:
157		switch msg.String() {
158		case "B":
159			cmds = append(cmds, b.updateItems())
160		case "enter", "right", "l":
161			if b.list.Index() >= 0 {
162				ref := b.list.SelectedItem().(item).Reference
163				return b.SetBranch(ref)
164			}
165		}
166	}
167
168	l, cmd := b.list.Update(msg)
169	b.list = l
170	cmds = append(cmds, cmd)
171
172	return b, tea.Batch(cmds...)
173}
174
175func (b *Bubble) View() string {
176	return b.list.View()
177}