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)
16
17// LogItem is a item in the log list that displays a git commit.
18type LogItem struct {
19 *git.Commit
20 copied time.Time
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 idx := m.Index()
60 item, ok := m.SelectedItem().(LogItem)
61 if !ok {
62 return nil
63 }
64 switch msg := msg.(type) {
65 case tea.KeyMsg:
66 switch {
67 case key.Matches(msg, d.common.KeyMap.Copy):
68 item.copied = time.Now()
69 d.common.Copy.Copy(item.Hash())
70 return m.SetItem(idx, item)
71 }
72 }
73 return nil
74}
75
76var (
77 highlight = lipgloss.NewStyle().Foreground(lipgloss.Color("#F1F1F1"))
78)
79
80// Render renders the item. Implements list.ItemDelegate.
81func (d LogItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
82 styles := d.common.Styles
83 i, ok := listItem.(LogItem)
84 if !ok {
85 return
86 }
87 if i.Commit == nil {
88 return
89 }
90
91 width := lipgloss.Width
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(), m.Width()-style.GetHorizontalFrameSize()-width(hash)-2),
104 )
105 hashStyle := styles.LogItemHash.Copy().
106 Align(lipgloss.Right).
107 Width(m.Width() -
108 style.GetHorizontalFrameSize() -
109 width(title) -
110 // FIXME where this "1" is coming from?
111 1)
112 if index == m.Index() {
113 hashStyle = hashStyle.Bold(true)
114 }
115 hash = hashStyle.Render(hash)
116 author := highlight.Render(i.Author.Name)
117 commiter := highlight.Render(i.Committer.Name)
118 who := ""
119 if author != "" && commiter != "" {
120 who = fmt.Sprintf("%s committed", commiter)
121 if author != commiter {
122 who = fmt.Sprintf("%s authored and %s", author, who)
123 }
124 who += " "
125 }
126 date := fmt.Sprintf("on %s", i.Committer.When.Format("Feb 02"))
127 if i.Committer.When.Year() != time.Now().Year() {
128 date += fmt.Sprintf(" %d", i.Committer.When.Year())
129 }
130 who += date
131 who = common.TruncateString(who, m.Width()-style.GetHorizontalFrameSize())
132 fmt.Fprint(w,
133 style.Render(
134 lipgloss.JoinVertical(lipgloss.Top,
135 lipgloss.JoinHorizontal(lipgloss.Left,
136 title,
137 hash,
138 ),
139 who,
140 ),
141 ),
142 )
143}