1package selection
2
3import (
4 "fmt"
5 "io"
6 "strings"
7 "time"
8
9 "github.com/charmbracelet/bubbles/key"
10 "github.com/charmbracelet/bubbles/list"
11 tea "github.com/charmbracelet/bubbletea"
12 "github.com/charmbracelet/lipgloss"
13 "github.com/charmbracelet/soft-serve/ui/common"
14 "github.com/charmbracelet/soft-serve/ui/git"
15 "github.com/dustin/go-humanize"
16)
17
18// Item represents a single item in the selector.
19type Item struct {
20 repo git.GitRepo
21 lastUpdate time.Time
22 cmd string
23 copied time.Time
24}
25
26// ID implements selector.IdentifiableItem.
27func (i Item) ID() string {
28 return i.repo.Repo()
29}
30
31// Title returns the item title. Implements list.DefaultItem.
32func (i Item) Title() string { return i.repo.Name() }
33
34// Description returns the item description. Implements list.DefaultItem.
35func (i Item) Description() string { return i.repo.Description() }
36
37// FilterValue implements list.Item.
38func (i Item) FilterValue() string { return i.Title() }
39
40// Command returns the item Command view.
41func (i Item) Command() string {
42 return i.cmd
43}
44
45// ItemDelegate is the delegate for the item.
46type ItemDelegate struct {
47 common *common.Common
48 activeBox *box
49}
50
51// Width returns the item width.
52func (d ItemDelegate) Width() int {
53 width := d.common.Styles.MenuItem.GetHorizontalFrameSize() + d.common.Styles.MenuItem.GetWidth()
54 return width
55}
56
57// Height returns the item height. Implements list.ItemDelegate.
58func (d ItemDelegate) Height() int {
59 height := d.common.Styles.MenuItem.GetVerticalFrameSize() + d.common.Styles.MenuItem.GetHeight()
60 return height
61}
62
63// Spacing returns the spacing between items. Implements list.ItemDelegate.
64func (d ItemDelegate) Spacing() int { return 0 }
65
66// Update implements list.ItemDelegate.
67func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
68 idx := m.Index()
69 item, ok := m.SelectedItem().(Item)
70 if !ok {
71 return nil
72 }
73 switch msg := msg.(type) {
74 case tea.KeyMsg:
75 switch {
76 case key.Matches(msg, d.common.KeyMap.Copy):
77 item.copied = time.Now()
78 d.common.Copy.Copy(item.Command())
79 return m.SetItem(idx, item)
80 }
81 }
82 return nil
83}
84
85// Render implements list.ItemDelegate.
86func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
87 styles := d.common.Styles
88 i := listItem.(Item)
89 s := strings.Builder{}
90 var matchedRunes []int
91
92 // Conditions
93 var (
94 isSelected = index == m.Index()
95 isFiltered = m.FilterState() == list.Filtering || m.FilterState() == list.FilterApplied
96 )
97
98 itemStyle := styles.MenuItem.Copy()
99 if isSelected {
100 itemStyle = itemStyle.BorderForeground(styles.ActiveBorderColor)
101 if d.activeBox != nil && *d.activeBox == readmeBox {
102 // TODO make this into its own color
103 itemStyle = itemStyle.BorderForeground(lipgloss.Color("15"))
104 }
105 }
106
107 title := i.Title()
108 updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.lastUpdate))
109 updated := styles.MenuLastUpdate.
110 Copy().
111 Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(title)).
112 Render(updatedStr)
113 titleStyle := lipgloss.NewStyle().
114 Align(lipgloss.Left).
115 Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(updated))
116
117 if isFiltered && index < len(m.VisibleItems()) {
118 // Get indices of matched characters
119 matchedRunes = m.MatchesForItem(index)
120 }
121
122 if isFiltered {
123 unmatched := lipgloss.NewStyle().Inline(true)
124 matched := unmatched.Copy().Underline(true)
125 title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
126 }
127 title = titleStyle.Render(title)
128
129 s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated))
130 s.WriteString("\n")
131 s.WriteString(i.Description())
132 s.WriteString("\n\n")
133 cmdStyle := styles.RepoCommand.Copy()
134 cmd := cmdStyle.Render(i.Command())
135 if !i.copied.IsZero() && i.copied.Add(time.Second).After(time.Now()) {
136 cmd = cmdStyle.Render("Copied!")
137 }
138 s.WriteString(cmd)
139 w.Write([]byte(itemStyle.Render(s.String())))
140}