1package repo23import (4 "fmt"5 "io"6 "strings"7 "time"89 "charm.land/bubbles/v2/key"10 "charm.land/bubbles/v2/list"11 tea "charm.land/bubbletea/v2"12 "charm.land/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)1718// LogItem is a item in the log list that displays a git commit.19type LogItem struct {20 *git.Commit21}2223// ID implements selector.IdentifiableItem.24func (i LogItem) ID() string {25 return i.Hash()26}2728// Hash returns the commit hash.29func (i LogItem) Hash() string {30 return i.Commit.ID.String()31}3233// 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}4041// Description returns the item description. Implements list.DefaultItem.42func (i LogItem) Description() string { return "" }4344// FilterValue implements list.Item.45func (i LogItem) FilterValue() string { return i.Title() }4647// LogItemDelegate is the delegate for LogItem.48type LogItemDelegate struct {49 common *common.Common50}5152// Height returns the item height. Implements list.ItemDelegate.53func (d LogItemDelegate) Height() int { return 2 }5455// Spacing returns the item spacing. Implements list.ItemDelegate.56func (d LogItemDelegate) Spacing() int { return 1 }5758// 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 nil63 }64 switch msg := msg.(type) {65 case tea.KeyPressMsg: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 nil72}7374// 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 return79 }80 if i.Commit == nil {81 return82 }8384 styles := d.common.Styles.LogItem.Normal85 if index == m.Index() {86 styles = d.common.Styles.LogItem.Active87 }8889 horizontalFrameSize := styles.Base.GetHorizontalFrameSize()9091 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) + the97 // 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 padding106 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.Name118 committer := i.Committer.Name119 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 ") + who124 }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:errcheck134 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:gosec142 who,143 ),144 ),145 ),146 )147}