1package repo
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/git"
14 "github.com/charmbracelet/soft-serve/ui/common"
15)
16
17// LogItem is a item in the log list that displays a git commit.
18type LogItem struct {
19 *git.Commit
20 copied time.Time
21}
22
23// ID implements selector.IdentifiableItem.
24func (i LogItem) ID() string {
25 return i.Hash()
26}
27
28func (i LogItem) Hash() string {
29 return i.Commit.ID.String()
30}
31
32// Title returns the item title. Implements list.DefaultItem.
33func (i LogItem) Title() string {
34 if i.Commit != nil {
35 return strings.Split(i.Commit.Message, "\n")[0]
36 }
37 return ""
38}
39
40// Description returns the item description. Implements list.DefaultItem.
41func (i LogItem) Description() string { return "" }
42
43// FilterValue implements list.Item.
44func (i LogItem) FilterValue() string { return i.Title() }
45
46// LogItemDelegate is the delegate for LogItem.
47type LogItemDelegate struct {
48 common *common.Common
49}
50
51// Height returns the item height. Implements list.ItemDelegate.
52func (d LogItemDelegate) Height() int { return 2 }
53
54// Spacing returns the item spacing. Implements list.ItemDelegate.
55func (d LogItemDelegate) Spacing() int { return 1 }
56
57// Update updates the item. Implements list.ItemDelegate.
58func (d LogItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
59 idx := m.Index()
60 item, ok := m.SelectedItem().(LogItem)
61 if !ok {
62 return nil
63 }
64 switch msg := msg.(type) {
65 case tea.KeyMsg:
66 switch {
67 case key.Matches(msg, d.common.KeyMap.Copy):
68 item.copied = time.Now()
69 d.common.Copy.Copy(item.Hash())
70 return m.SetItem(idx, item)
71 }
72 }
73 return nil
74}
75
76var (
77 faint = func(s string) string { return lipgloss.NewStyle().Faint(true).Render(s) }
78)
79
80// Render renders the item. Implements list.ItemDelegate.
81func (d LogItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
82 styles := d.common.Styles
83 i, ok := listItem.(LogItem)
84 if !ok {
85 return
86 }
87 if i.Commit == nil {
88 return
89 }
90
91 width := lipgloss.Width
92 titleStyle := styles.LogItemTitle.Copy()
93 style := styles.LogItemInactive
94 if index == m.Index() {
95 titleStyle.Bold(true)
96 style = styles.LogItemActive
97 }
98 hash := " " + i.Commit.ID.String()[:7]
99 if !i.copied.IsZero() && i.copied.Add(time.Second).After(time.Now()) {
100 hash = "copied"
101 }
102 title := titleStyle.Render(
103 common.TruncateString(i.Title(), m.Width()-style.GetHorizontalFrameSize()-width(hash)-2),
104 )
105 hashStyle := styles.LogItemHash.Copy().
106 Align(lipgloss.Right).
107 Width(m.Width() -
108 style.GetHorizontalFrameSize() -
109 width(title) -
110 // FIXME where this "1" is coming from?
111 1)
112 if index == m.Index() {
113 hashStyle = hashStyle.Bold(true)
114 }
115 hash = hashStyle.Render(hash)
116 author := i.Author.Name
117 commiter := i.Committer.Name
118 who := ""
119 if author != "" && commiter != "" {
120 who = commiter + faint(" committed")
121 if author != commiter {
122 who = author + faint(" authored and ") + who
123 }
124 who += " "
125 }
126 date := fmt.Sprintf("on %s", i.Committer.When.Format("Feb 02"))
127 date = faint(date)
128 if i.Committer.When.Year() != time.Now().Year() {
129 date += fmt.Sprintf(" %d", i.Committer.When.Year())
130 }
131 who += date
132 who = common.TruncateString(who, m.Width()-style.GetHorizontalFrameSize())
133 fmt.Fprint(w,
134 style.Render(
135 lipgloss.JoinVertical(lipgloss.Top,
136 lipgloss.JoinHorizontal(lipgloss.Left,
137 title,
138 hash,
139 ),
140 who,
141 ),
142 ),
143 )
144}