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