logitem.go

  1package repo
  2
  3import (
  4	"fmt"
  5	"io"
  6	"strings"
  7	"time"
  8
  9	"github.com/charmbracelet/bubbles/v2/key"
 10	"github.com/charmbracelet/bubbles/v2/list"
 11	tea "github.com/charmbracelet/bubbletea/v2"
 12	"github.com/charmbracelet/lipgloss/v2"
 13	"github.com/charmbracelet/soft-serve/git"
 14	"github.com/charmbracelet/soft-serve/pkg/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
 28// Hash returns the commit hash.
 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	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			return copyCmd(item.Hash(), "Commit hash copied to clipboard")
 69		}
 70	}
 71	return nil
 72}
 73
 74// Render renders the item. Implements list.ItemDelegate.
 75func (d LogItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
 76	i, ok := listItem.(LogItem)
 77	if !ok {
 78		return
 79	}
 80	if i.Commit == nil {
 81		return
 82	}
 83
 84	styles := d.common.Styles.LogItem.Normal
 85	if index == m.Index() {
 86		styles = d.common.Styles.LogItem.Active
 87	}
 88
 89	horizontalFrameSize := styles.Base.GetHorizontalFrameSize()
 90
 91	hash := i.Commit.ID.String()[:7]
 92	title := styles.Title.Render(
 93		common.TruncateString(i.Title(),
 94			m.Width()-
 95				horizontalFrameSize-
 96				// 9 is the length of the hash (7) + the left padding (1) + the
 97				// title truncation symbol (1)
 98				9),
 99	)
100	hashStyle := styles.Hash.
101		Align(lipgloss.Right).
102		PaddingLeft(1).
103		Width(m.Width() -
104			horizontalFrameSize -
105			lipgloss.Width(title) - 1) // 1 is for the left padding
106	if index == m.Index() {
107		hashStyle = hashStyle.Bold(true)
108	}
109	hash = hashStyle.Render(hash)
110	if m.Width()-horizontalFrameSize-hashStyle.GetHorizontalFrameSize()-hashStyle.GetWidth() <= 0 {
111		hash = ""
112		title = styles.Title.Render(
113			common.TruncateString(i.Title(),
114				m.Width()-horizontalFrameSize),
115		)
116	}
117	author := i.Author.Name
118	committer := i.Committer.Name
119	who := ""
120	if author != "" && committer != "" {
121		who = styles.Keyword.Render(committer) + styles.Desc.Render(" committed")
122		if author != committer {
123			who = styles.Keyword.Render(author) + styles.Desc.Render(" authored and ") + who
124		}
125		who += " "
126	}
127	date := i.Committer.When.Format("Jan 02")
128	if i.Committer.When.Year() != time.Now().Year() {
129		date += fmt.Sprintf(" %d", i.Committer.When.Year())
130	}
131	who += styles.Desc.Render("on ") + styles.Keyword.Render(date)
132	who = common.TruncateString(who, m.Width()-horizontalFrameSize)
133	fmt.Fprint(w, //nolint:errcheck
134		d.common.Zone.Mark(
135			i.ID(),
136			styles.Base.Render(
137				lipgloss.JoinVertical(lipgloss.Left,
138					truncate.String(fmt.Sprintf("%s%s",
139						title,
140						hash,
141					), uint(m.Width()-horizontalFrameSize)), //nolint:gosec
142					who,
143				),
144			),
145		),
146	)
147}