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 styles := d.common.Styles
80 i, ok := listItem.(LogItem)
81 if !ok {
82 return
83 }
84 if i.Commit == nil {
85 return
86 }
87
88 var titleStyler,
89 descStyler,
90 keywordStyler func(string) string
91 style := styles.LogItemInactive
92
93 if index == m.Index() {
94 titleStyler = styles.LogItemTitleActive.Render
95 descStyler = styles.LogItemDescActive.Render
96 keywordStyler = styles.LogItemKeywordActive.Render
97 style = styles.LogItemActive
98 } else {
99 titleStyler = styles.LogItemTitleInactive.Render
100 descStyler = styles.LogItemDescInactive.Render
101 keywordStyler = styles.LogItemKeywordInactive.Render
102 }
103
104 hash := i.Commit.ID.String()[:7]
105 if !i.copied.IsZero() && i.copied.Add(time.Second).After(time.Now()) {
106 hash = "copied"
107 }
108 title := titleStyler(
109 common.TruncateString(i.Title(),
110 m.Width()-
111 style.GetHorizontalFrameSize()-
112 // 9 is the length of the hash (7) + the left padding (1) + the
113 // title truncation symbol (1)
114 9),
115 )
116 hashStyle := styles.LogItemHash.Copy().
117 Align(lipgloss.Right).
118 PaddingLeft(1).
119 Width(m.Width() -
120 style.GetHorizontalFrameSize() -
121 lipgloss.Width(title) - 1) // 1 is for the left padding
122 if index == m.Index() {
123 hashStyle = hashStyle.Bold(true)
124 }
125 hash = hashStyle.Render(hash)
126 if m.Width()-style.GetHorizontalFrameSize()-hashStyle.GetHorizontalFrameSize()-hashStyle.GetWidth() <= 0 {
127 hash = ""
128 title = titleStyler(
129 common.TruncateString(i.Title(),
130 m.Width()-style.GetHorizontalFrameSize()),
131 )
132 }
133 author := i.Author.Name
134 committer := i.Committer.Name
135 who := ""
136 if author != "" && committer != "" {
137 who = keywordStyler(committer) + descStyler(" committed")
138 if author != committer {
139 who = keywordStyler(author) + descStyler(" authored and ") + who
140 }
141 who += " "
142 }
143 date := i.Committer.When.Format("Feb 02")
144 if i.Committer.When.Year() != time.Now().Year() {
145 date += fmt.Sprintf(" %d", i.Committer.When.Year())
146 }
147 who += descStyler("on ") + keywordStyler(date)
148 who = common.TruncateString(who, m.Width()-style.GetHorizontalFrameSize())
149 fmt.Fprint(w,
150 style.Render(
151 lipgloss.JoinVertical(lipgloss.Top,
152 truncate.String(fmt.Sprintf("%s%s",
153 title,
154 hash,
155 ), uint(m.Width()-style.GetHorizontalFrameSize())),
156 who,
157 ),
158 ),
159 )
160}