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