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}