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/server/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}
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 item, ok := m.SelectedItem().(LogItem)
60 if !ok {
61 return nil
62 }
63 switch msg := msg.(type) {
64 case tea.KeyMsg:
65 switch {
66 case key.Matches(msg, d.common.KeyMap.Copy):
67 return copyCmd(item.Hash(), "Commit hash copied to clipboard")
68 }
69 }
70 return nil
71}
72
73// Render renders the item. Implements list.ItemDelegate.
74func (d LogItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
75 i, ok := listItem.(LogItem)
76 if !ok {
77 return
78 }
79 if i.Commit == nil {
80 return
81 }
82
83 styles := d.common.Styles.LogItem.Normal
84 if index == m.Index() {
85 styles = d.common.Styles.LogItem.Active
86 }
87
88 horizontalFrameSize := styles.Base.GetHorizontalFrameSize()
89
90 hash := i.Commit.ID.String()[:7]
91 title := styles.Title.Render(
92 common.TruncateString(i.Title(),
93 m.Width()-
94 horizontalFrameSize-
95 // 9 is the length of the hash (7) + the left padding (1) + the
96 // title truncation symbol (1)
97 9),
98 )
99 hashStyle := styles.Hash.Copy().
100 Align(lipgloss.Right).
101 PaddingLeft(1).
102 Width(m.Width() -
103 horizontalFrameSize -
104 lipgloss.Width(title) - 1) // 1 is for the left padding
105 if index == m.Index() {
106 hashStyle = hashStyle.Bold(true)
107 }
108 hash = hashStyle.Render(hash)
109 if m.Width()-horizontalFrameSize-hashStyle.GetHorizontalFrameSize()-hashStyle.GetWidth() <= 0 {
110 hash = ""
111 title = styles.Title.Render(
112 common.TruncateString(i.Title(),
113 m.Width()-horizontalFrameSize),
114 )
115 }
116 author := i.Author.Name
117 committer := i.Committer.Name
118 who := ""
119 if author != "" && committer != "" {
120 who = styles.Keyword.Render(committer) + styles.Desc.Render(" committed")
121 if author != committer {
122 who = styles.Keyword.Render(author) + styles.Desc.Render(" authored and ") + who
123 }
124 who += " "
125 }
126 date := i.Committer.When.Format("Jan 02")
127 if i.Committer.When.Year() != time.Now().Year() {
128 date += fmt.Sprintf(" %d", i.Committer.When.Year())
129 }
130 who += styles.Desc.Render("on ") + styles.Keyword.Render(date)
131 who = common.TruncateString(who, m.Width()-horizontalFrameSize)
132 fmt.Fprint(w,
133 d.common.Zone.Mark(
134 i.ID(),
135 styles.Base.Render(
136 lipgloss.JoinVertical(lipgloss.Top,
137 truncate.String(fmt.Sprintf("%s%s",
138 title,
139 hash,
140 ), uint(m.Width()-horizontalFrameSize)),
141 who,
142 ),
143 ),
144 ),
145 )
146}