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/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}