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/dustin/go-humanize"16 "github.com/muesli/reflow/truncate"17)1819// RefItem is a git reference item.20type RefItem struct {21 *git.Reference22 *git.Tag23 *git.Commit24}2526// ID implements selector.IdentifiableItem.27func (i RefItem) ID() string {28 return i.Reference.Name().String()29}3031// Title implements list.DefaultItem.32func (i RefItem) Title() string {33 return i.Reference.Name().Short()34}3536// Description implements list.DefaultItem.37func (i RefItem) Description() string {38 return ""39}4041// Short returns the short name of the reference.42func (i RefItem) Short() string {43 return i.Reference.Name().Short()44}4546// FilterValue implements list.Item.47func (i RefItem) FilterValue() string { return i.Short() }4849// RefItems is a list of git references.50type RefItems []RefItem5152// Len implements sort.Interface.53func (cl RefItems) Len() int { return len(cl) }5455// Swap implements sort.Interface.56func (cl RefItems) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }5758// Less implements sort.Interface.59func (cl RefItems) Less(i, j int) bool {60 if cl[i].Commit != nil && cl[j].Commit != nil {61 return cl[i].Commit.Author.When.After(cl[j].Commit.Author.When)62 } else if cl[i].Commit != nil && cl[j].Commit == nil {63 return true64 }65 return false66}6768// RefItemDelegate is the delegate for the ref item.69type RefItemDelegate struct {70 common *common.Common71}7273// Height implements list.ItemDelegate.74func (d RefItemDelegate) Height() int { return 1 }7576// Spacing implements list.ItemDelegate.77func (d RefItemDelegate) Spacing() int { return 0 }7879// Update implements list.ItemDelegate.80func (d RefItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {81 item, ok := m.SelectedItem().(RefItem)82 if !ok {83 return nil84 }85 switch msg := msg.(type) {86 case tea.KeyPressMsg:87 switch {88 case key.Matches(msg, d.common.KeyMap.Copy):89 return copyCmd(item.ID(), fmt.Sprintf("Reference %q copied to clipboard", item.ID()))90 }91 }92 return nil93}9495// Render implements list.ItemDelegate.96func (d RefItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {97 i, ok := listItem.(RefItem)98 if !ok {99 return100 }101102 isTag := i.Reference.IsTag()103 isActive := index == m.Index()104 s := d.common.Styles.Ref105 st := s.Normal106 selector := " "107 if isActive {108 st = s.Active109 selector = s.ItemSelector.String()110 }111112 horizontalFrameSize := st.Base.GetHorizontalFrameSize()113 var itemSt lipgloss.Style114 if isTag && isActive {115 itemSt = st.ItemTag116 } else if isTag {117 itemSt = st.ItemTag118 } else if isActive {119 itemSt = st.Item120 } else {121 itemSt = st.Item122 }123124 var sha string125 c := i.Commit126 if c != nil {127 sha = c.ID.String()[:7]128 }129130 ref := i.Short()131132 var desc string133 if isTag {134 if c != nil {135 date := c.Committer.When.Format("Jan 02")136 if c.Committer.When.Year() != time.Now().Year() {137 date += fmt.Sprintf(" %d", c.Committer.When.Year())138 }139 desc += " " + st.ItemDesc.Render(date)140 }141142 t := i.Tag143 if t != nil {144 msgSt := st.ItemDesc.Faint(false)145 msg := t.Message()146 nl := strings.Index(msg, "\n")147 if nl > 0 {148 msg = msg[:nl]149 }150 msg = strings.TrimSpace(msg)151 if msg != "" {152 msgMargin := m.Width() -153 horizontalFrameSize -154 lipgloss.Width(selector) -155 lipgloss.Width(ref) -156 lipgloss.Width(desc) -157 lipgloss.Width(sha) -158 3 // 3 is for the paddings and truncation symbol159 if msgMargin >= 0 {160 msg = common.TruncateString(msg, msgMargin)161 desc = " " + msgSt.Render(msg) + desc162 }163 }164 }165 } else if c != nil {166 onMargin := m.Width() -167 horizontalFrameSize -168 lipgloss.Width(selector) -169 lipgloss.Width(ref) -170 lipgloss.Width(desc) -171 lipgloss.Width(sha) -172 2 // 2 is for the padding and truncation symbol173 if onMargin >= 0 {174 on := common.TruncateString("updated "+humanize.Time(c.Committer.When), onMargin)175 desc += " " + st.ItemDesc.Render(on)176 }177 }178179 var hash string180 ref = itemSt.Render(ref)181 hashMargin := m.Width() -182 horizontalFrameSize -183 lipgloss.Width(selector) -184 lipgloss.Width(ref) -185 lipgloss.Width(desc) -186 lipgloss.Width(sha) -187 1 // 1 is for the left padding188 if hashMargin >= 0 {189 hash = strings.Repeat(" ", hashMargin) + st.ItemHash.190 Align(lipgloss.Right).191 PaddingLeft(1).192 Render(sha)193 }194 fmt.Fprint(w, //nolint:errcheck195 d.common.Zone.Mark(196 i.ID(),197 st.Base.Render(198 lipgloss.JoinHorizontal(lipgloss.Top,199 truncate.String(selector+ref+desc+hash,200 uint(m.Width()-horizontalFrameSize)), //nolint:gosec201 ),202 ),203 ),204 )205}