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}