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,
130 switchRefCmd(i.Reference),
131 tabs.SelectTabCmd(int(filesTab)),
132 )
133 }
134 case tea.KeyMsg:
135 switch msg.String() {
136 case "l", "right":
137 cmds = append(cmds, r.selector.SelectItem)
138 }
139 }
140 m, cmd := r.selector.Update(msg)
141 r.selector = m.(*selector.Selector)
142 if cmd != nil {
143 cmds = append(cmds, cmd)
144 }
145 return r, tea.Batch(cmds...)
146}
147
148// View implements tea.Model.
149func (r *Refs) View() string {
150 return r.selector.View()
151}
152
153// StatusBarValue implements statusbar.StatusBar.
154func (r *Refs) StatusBarValue() string {
155 if r.activeRef == nil {
156 return ""
157 }
158 return r.activeRef.Name().String()
159}
160
161// StatusBarInfo implements statusbar.StatusBar.
162func (r *Refs) StatusBarInfo() string {
163 totalPages := r.selector.TotalPages()
164 if totalPages > 1 {
165 return fmt.Sprintf("p. %d/%d", r.selector.Page()+1, totalPages)
166 }
167 return ""
168}
169
170func (r *Refs) updateItemsCmd() tea.Msg {
171 its := make(RefItems, 0)
172 refs, err := r.repo.References()
173 if err != nil {
174 return common.ErrorMsg(err)
175 }
176 for _, ref := range refs {
177 if strings.HasPrefix(ref.Name().String(), r.refPrefix) {
178 its = append(its, RefItem{Reference: ref})
179 }
180 }
181 sort.Sort(its)
182 items := make([]selector.IdentifiableItem, len(its))
183 for i, it := range its {
184 items[i] = it
185 }
186 return RefItemsMsg{
187 items: items,
188 prefix: r.refPrefix,
189 }
190}
191
192func switchRefCmd(ref *ggit.Reference) tea.Cmd {
193 return func() tea.Msg {
194 return RefMsg(ref)
195 }
196}