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}