logitem.go

  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	var titleStyler,
 89		descStyler,
 90		keywordStyler func(string) string
 91	style := styles.LogItemInactive
 92
 93	if index == m.Index() {
 94		titleStyler = styles.LogItemTitleActive.Render
 95		descStyler = styles.LogItemDescActive.Render
 96		keywordStyler = styles.LogItemKeywordActive.Render
 97		style = styles.LogItemActive
 98	} else {
 99		titleStyler = styles.LogItemTitleInactive.Render
100		descStyler = styles.LogItemDescInactive.Render
101		keywordStyler = styles.LogItemKeywordInactive.Render
102	}
103
104	hash := i.Commit.ID.String()[:7]
105	if !i.copied.IsZero() && i.copied.Add(time.Second).After(time.Now()) {
106		hash = "copied"
107	}
108	title := titleStyler(
109		common.TruncateString(i.Title(),
110			m.Width()-
111				style.GetHorizontalFrameSize()-
112				// 9 is the length of the hash (7) + the left padding (1) + the
113				// title truncation symbol (1)
114				9),
115	)
116	hashStyle := styles.LogItemHash.Copy().
117		Align(lipgloss.Right).
118		PaddingLeft(1).
119		Width(m.Width() -
120			style.GetHorizontalFrameSize() -
121			lipgloss.Width(title) - 1) // 1 is for the left padding
122	if index == m.Index() {
123		hashStyle = hashStyle.Bold(true)
124	}
125	hash = hashStyle.Render(hash)
126	if m.Width()-style.GetHorizontalFrameSize()-hashStyle.GetHorizontalFrameSize()-hashStyle.GetWidth() <= 0 {
127		hash = ""
128		title = titleStyler(
129			common.TruncateString(i.Title(),
130				m.Width()-style.GetHorizontalFrameSize()),
131		)
132	}
133	author := i.Author.Name
134	committer := i.Committer.Name
135	who := ""
136	if author != "" && committer != "" {
137		who = keywordStyler(committer) + descStyler(" committed")
138		if author != committer {
139			who = keywordStyler(author) + descStyler(" authored and ") + who
140		}
141		who += " "
142	}
143	date := i.Committer.When.Format("Feb 02")
144	if i.Committer.When.Year() != time.Now().Year() {
145		date += fmt.Sprintf(" %d", i.Committer.When.Year())
146	}
147	who += descStyler("on ") + keywordStyler(date)
148	who = common.TruncateString(who, m.Width()-style.GetHorizontalFrameSize())
149	fmt.Fprint(w,
150		style.Render(
151			lipgloss.JoinVertical(lipgloss.Top,
152				truncate.String(fmt.Sprintf("%s%s",
153					title,
154					hash,
155				), uint(m.Width()-style.GetHorizontalFrameSize())),
156				who,
157			),
158		),
159	)
160}