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