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
 29// Hash returns the commit hash.
 30func (i LogItem) Hash() string {
 31	return i.Commit.ID.String()
 32}
 33
 34// Title returns the item title. Implements list.DefaultItem.
 35func (i LogItem) Title() string {
 36	if i.Commit != nil {
 37		return strings.Split(i.Commit.Message, "\n")[0]
 38	}
 39	return ""
 40}
 41
 42// Description returns the item description. Implements list.DefaultItem.
 43func (i LogItem) Description() string { return "" }
 44
 45// FilterValue implements list.Item.
 46func (i LogItem) FilterValue() string { return i.Title() }
 47
 48// LogItemDelegate is the delegate for LogItem.
 49type LogItemDelegate struct {
 50	common *common.Common
 51}
 52
 53// Height returns the item height. Implements list.ItemDelegate.
 54func (d LogItemDelegate) Height() int { return 2 }
 55
 56// Spacing returns the item spacing. Implements list.ItemDelegate.
 57func (d LogItemDelegate) Spacing() int { return 1 }
 58
 59// Update updates the item. Implements list.ItemDelegate.
 60func (d LogItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
 61	idx := m.Index()
 62	item, ok := m.SelectedItem().(LogItem)
 63	if !ok {
 64		return nil
 65	}
 66	switch msg := msg.(type) {
 67	case tea.KeyMsg:
 68		switch {
 69		case key.Matches(msg, d.common.KeyMap.Copy):
 70			item.copied = time.Now()
 71			d.common.Copy.Copy(item.Hash())
 72			return m.SetItem(idx, item)
 73		}
 74	}
 75	return nil
 76}
 77
 78// Render renders the item. Implements list.ItemDelegate.
 79func (d LogItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
 80	i, ok := listItem.(LogItem)
 81	if !ok {
 82		return
 83	}
 84	if i.Commit == nil {
 85		return
 86	}
 87
 88	styles := d.common.Styles.LogItem.Normal
 89	if index == m.Index() {
 90		styles = d.common.Styles.LogItem.Active
 91	}
 92
 93	horizontalFrameSize := styles.Base.GetHorizontalFrameSize()
 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 := styles.Title.Render(
100		common.TruncateString(i.Title(),
101			m.Width()-
102				horizontalFrameSize-
103				// 9 is the length of the hash (7) + the left padding (1) + the
104				// title truncation symbol (1)
105				9),
106	)
107	hashStyle := styles.Hash.Copy().
108		Align(lipgloss.Right).
109		PaddingLeft(1).
110		Width(m.Width() -
111			horizontalFrameSize -
112			lipgloss.Width(title) - 1) // 1 is for the left padding
113	if index == m.Index() {
114		hashStyle = hashStyle.Bold(true)
115	}
116	hash = hashStyle.Render(hash)
117	if m.Width()-horizontalFrameSize-hashStyle.GetHorizontalFrameSize()-hashStyle.GetWidth() <= 0 {
118		hash = ""
119		title = styles.Title.Render(
120			common.TruncateString(i.Title(),
121				m.Width()-horizontalFrameSize),
122		)
123	}
124	author := i.Author.Name
125	committer := i.Committer.Name
126	who := ""
127	if author != "" && committer != "" {
128		who = styles.Keyword.Render(committer) + styles.Desc.Render(" committed")
129		if author != committer {
130			who = styles.Keyword.Render(author) + styles.Desc.Render(" authored and ") + who
131		}
132		who += " "
133	}
134	date := i.Committer.When.Format("Jan 02")
135	if i.Committer.When.Year() != time.Now().Year() {
136		date += fmt.Sprintf(" %d", i.Committer.When.Year())
137	}
138	who += styles.Desc.Render("on ") + styles.Keyword.Render(date)
139	who = common.TruncateString(who, m.Width()-horizontalFrameSize)
140	fmt.Fprint(w,
141		d.common.Zone.Mark(
142			i.ID(),
143			styles.Base.Render(
144				lipgloss.JoinVertical(lipgloss.Top,
145					truncate.String(fmt.Sprintf("%s%s",
146						title,
147						hash,
148					), uint(m.Width()-horizontalFrameSize)),
149					who,
150				),
151			),
152		),
153	)
154}