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
77var (
78 faint = func(s string) string { return lipgloss.NewStyle().Faint(true).Render(s) }
79)
80
81// Render renders the item. Implements list.ItemDelegate.
82func (d LogItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
83 styles := d.common.Styles
84 i, ok := listItem.(LogItem)
85 if !ok {
86 return
87 }
88 if i.Commit == nil {
89 return
90 }
91
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(),
104 m.Width()-
105 style.GetHorizontalFrameSize()-
106 // 9 is the length of the hash (7) + the left padding (1) + the
107 // title truncation symbol (1)
108 9),
109 )
110 hashStyle := styles.LogItemHash.Copy().
111 Align(lipgloss.Right).
112 PaddingLeft(1).
113 Width(m.Width() -
114 style.GetHorizontalFrameSize() -
115 lipgloss.Width(title) - 1) // 1 is for the left padding
116 if index == m.Index() {
117 hashStyle = hashStyle.Bold(true)
118 }
119 hash = hashStyle.Render(hash)
120 if m.Width()-style.GetHorizontalFrameSize()-hashStyle.GetHorizontalFrameSize()-hashStyle.GetWidth() <= 0 {
121 hash = ""
122 title = titleStyle.Render(
123 common.TruncateString(i.Title(),
124 m.Width()-style.GetHorizontalFrameSize()),
125 )
126 }
127 author := i.Author.Name
128 commiter := i.Committer.Name
129 who := ""
130 if author != "" && commiter != "" {
131 who = commiter + faint(" committed")
132 if author != commiter {
133 who = author + faint(" authored and ") + who
134 }
135 who += " "
136 }
137 date := fmt.Sprintf("on %s", i.Committer.When.Format("Feb 02"))
138 date = faint(date)
139 if i.Committer.When.Year() != time.Now().Year() {
140 date += fmt.Sprintf(" %d", i.Committer.When.Year())
141 }
142 who += date
143 who = common.TruncateString(who, m.Width()-style.GetHorizontalFrameSize())
144 fmt.Fprint(w,
145 style.Render(
146 lipgloss.JoinVertical(lipgloss.Top,
147 truncate.String(fmt.Sprintf("%s%s",
148 title,
149 hash,
150 ), uint(m.Width()-style.GetHorizontalFrameSize())),
151 who,
152 ),
153 ),
154 )
155}