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 width := lipgloss.Width
89 titleStyle := styles.LogItemTitle.Copy()
90 style := styles.LogItemInactive
91 if index == m.Index() {
92 titleStyle.Bold(true)
93 style = styles.LogItemActive
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 := titleStyle.Render(
100 truncateString(i.Title(), m.Width()-style.GetHorizontalFrameSize()-width(hash)-2, "…"),
101 )
102 hash = styles.LogItemHash.Copy().
103 Align(lipgloss.Right).
104 Width(m.Width() -
105 style.GetHorizontalFrameSize() -
106 width(title) -
107 // FIXME where this "1" is coming from?
108 1).
109 Render(hash)
110 author := i.Author.Name
111 commiter := i.Committer.Name
112 who := ""
113 if author != "" && commiter != "" {
114 who = fmt.Sprintf("%s committed", commiter)
115 if author != commiter {
116 who = fmt.Sprintf("%s authored and %s", author, who)
117 }
118 who += " "
119 }
120 date := fmt.Sprintf("on %s", i.Committer.When.Format("Feb 02"))
121 if i.Committer.When.Year() != time.Now().Year() {
122 date += fmt.Sprintf(" %d", i.Committer.When.Year())
123 }
124 who += date
125 who = truncateString(who, m.Width()-style.GetHorizontalFrameSize(), "…")
126 fmt.Fprint(w,
127 style.Render(
128 lipgloss.JoinVertical(lipgloss.Top,
129 lipgloss.JoinHorizontal(lipgloss.Left,
130 title,
131 hash,
132 ),
133 who,
134 ),
135 ),
136 )
137}
138
139func truncateString(s string, max int, tail string) string {
140 if max < 0 {
141 max = 0
142 }
143 return truncate.StringWithTail(s, uint(max), tail)
144}