1package repo
  2
  3import (
  4	"fmt"
  5	"sort"
  6	"strings"
  7
  8	"github.com/charmbracelet/bubbles/key"
  9	tea "github.com/charmbracelet/bubbletea"
 10	ggit "github.com/charmbracelet/soft-serve/git"
 11	"github.com/charmbracelet/soft-serve/ui/common"
 12	"github.com/charmbracelet/soft-serve/ui/components/selector"
 13	"github.com/charmbracelet/soft-serve/ui/components/tabs"
 14	"github.com/charmbracelet/soft-serve/ui/git"
 15)
 16
 17// RefItemsMsg is a message that contains a list of RefItem.
 18type RefItemsMsg struct {
 19	prefix string
 20	items  []selector.IdentifiableItem
 21}
 22
 23// Refs is a component that displays a list of references.
 24type Refs struct {
 25	common    common.Common
 26	selector  *selector.Selector
 27	repo      git.GitRepo
 28	ref       *ggit.Reference
 29	activeRef *ggit.Reference
 30	refPrefix string
 31}
 32
 33// NewRefs creates a new Refs component.
 34func NewRefs(common common.Common, refPrefix string) *Refs {
 35	r := &Refs{
 36		common:    common,
 37		refPrefix: refPrefix,
 38	}
 39	s := selector.New(common, []selector.IdentifiableItem{}, RefItemDelegate{&common})
 40	s.SetShowFilter(false)
 41	s.SetShowHelp(false)
 42	s.SetShowPagination(false)
 43	s.SetShowStatusBar(false)
 44	s.SetShowTitle(false)
 45	s.SetFilteringEnabled(false)
 46	s.DisableQuitKeybindings()
 47	r.selector = s
 48	return r
 49}
 50
 51// SetSize implements common.Component.
 52func (r *Refs) SetSize(width, height int) {
 53	r.common.SetSize(width, height)
 54	r.selector.SetSize(width, height)
 55}
 56
 57// ShortHelp implements help.KeyMap.
 58func (r *Refs) ShortHelp() []key.Binding {
 59	copyKey := r.common.KeyMap.Copy
 60	copyKey.SetHelp("c", "copy ref")
 61	k := r.selector.KeyMap
 62	return []key.Binding{
 63		r.common.KeyMap.SelectItem,
 64		k.CursorUp,
 65		k.CursorDown,
 66		copyKey,
 67	}
 68}
 69
 70// FullHelp implements help.KeyMap.
 71func (r *Refs) FullHelp() [][]key.Binding {
 72	copyKey := r.common.KeyMap.Copy
 73	copyKey.SetHelp("c", "copy ref")
 74	k := r.selector.KeyMap
 75	return [][]key.Binding{
 76		{r.common.KeyMap.SelectItem},
 77		{
 78			k.CursorUp,
 79			k.CursorDown,
 80			k.NextPage,
 81			k.PrevPage,
 82		},
 83		{
 84			k.GoToStart,
 85			k.GoToEnd,
 86			copyKey,
 87		},
 88	}
 89}
 90
 91// Init implements tea.Model.
 92func (r *Refs) Init() tea.Cmd {
 93	return r.updateItemsCmd
 94}
 95
 96// Update implements tea.Model.
 97func (r *Refs) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 98	cmds := make([]tea.Cmd, 0)
 99	switch msg := msg.(type) {
100	case RepoMsg:
101		r.selector.Select(0)
102		r.repo = git.GitRepo(msg)
103		cmds = append(cmds, r.Init())
104	case RefMsg:
105		r.ref = msg
106		cmds = append(cmds, r.Init())
107	case RefItemsMsg:
108		if r.refPrefix == msg.prefix {
109			cmds = append(cmds, r.selector.SetItems(msg.items))
110			i := r.selector.SelectedItem()
111			if i != nil {
112				r.activeRef = i.(RefItem).Reference
113			}
114		}
115	case selector.ActiveMsg:
116		switch sel := msg.IdentifiableItem.(type) {
117		case RefItem:
118			r.activeRef = sel.Reference
119		}
120		cmds = append(cmds, updateStatusBarCmd)
121	case selector.SelectMsg:
122		switch i := msg.IdentifiableItem.(type) {
123		case RefItem:
124			cmds = append(cmds, switchRefCmd(i.Reference))
125		}
126	// FileItemsMsg indicates that the Files model has updated the items and
127	// it's time to switch tabs.
128	case FileItemsMsg:
129		cmds = append(cmds, tabs.SelectTabCmd(int(filesTab)))
130	case tea.KeyMsg:
131		switch msg.String() {
132		case "l", "right":
133			cmds = append(cmds, r.selector.SelectItem)
134		}
135	}
136	m, cmd := r.selector.Update(msg)
137	r.selector = m.(*selector.Selector)
138	if cmd != nil {
139		cmds = append(cmds, cmd)
140	}
141	return r, tea.Batch(cmds...)
142}
143
144// View implements tea.Model.
145func (r *Refs) View() string {
146	return r.selector.View()
147}
148
149// StausBarValue implements statusbar.StatusBar.
150func (r *Refs) StatusBarValue() string {
151	if r.activeRef == nil {
152		return ""
153	}
154	return r.activeRef.Name().String()
155}
156
157// StatusBarInfo implements statusbar.StatusBar.
158func (r *Refs) StatusBarInfo() string {
159	totalPages := r.selector.TotalPages()
160	if totalPages > 1 {
161		return fmt.Sprintf("p. %d/%d", r.selector.Page()+1, totalPages)
162	}
163	return ""
164}
165
166func (r *Refs) updateItemsCmd() tea.Msg {
167	its := make(RefItems, 0)
168	refs, err := r.repo.References()
169	if err != nil {
170		return common.ErrorMsg(err)
171	}
172	for _, ref := range refs {
173		if strings.HasPrefix(ref.Name().String(), r.refPrefix) {
174			its = append(its, RefItem{Reference: ref})
175		}
176	}
177	sort.Sort(its)
178	items := make([]selector.IdentifiableItem, len(its))
179	for i, it := range its {
180		items[i] = it
181	}
182	return RefItemsMsg{
183		items:  items,
184		prefix: r.refPrefix,
185	}
186}
187
188func switchRefCmd(ref *ggit.Reference) tea.Cmd {
189	return func() tea.Msg {
190		return RefMsg(ref)
191	}
192}