1package repo
2
3import (
4 "fmt"
5 "io"
6 "io/fs"
7
8 "github.com/charmbracelet/bubbles/key"
9 "github.com/charmbracelet/bubbles/list"
10 tea "github.com/charmbracelet/bubbletea"
11 "github.com/charmbracelet/lipgloss"
12 "github.com/charmbracelet/soft-serve/git"
13 "github.com/charmbracelet/soft-serve/ui/common"
14 "github.com/dustin/go-humanize"
15)
16
17// FileItem is a list item for a file.
18type FileItem struct {
19 entry *git.TreeEntry
20}
21
22// ID returns the ID of the file item.
23func (i FileItem) ID() string {
24 return i.entry.Name()
25}
26
27// Title returns the title of the file item.
28func (i FileItem) Title() string {
29 return i.entry.Name()
30}
31
32// Description returns the description of the file item.
33func (i FileItem) Description() string {
34 return ""
35}
36
37// Mode returns the mode of the file item.
38func (i FileItem) Mode() fs.FileMode {
39 return i.entry.Mode()
40}
41
42// FilterValue implements list.Item.
43func (i FileItem) FilterValue() string { return i.Title() }
44
45// FileItems is a list of file items.
46type FileItems []FileItem
47
48// Len implements sort.Interface.
49func (cl FileItems) Len() int { return len(cl) }
50
51// Swap implements sort.Interface.
52func (cl FileItems) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
53
54// Less implements sort.Interface.
55func (cl FileItems) Less(i, j int) bool {
56 if cl[i].entry.IsTree() && cl[j].entry.IsTree() {
57 return cl[i].Title() < cl[j].Title()
58 } else if cl[i].entry.IsTree() {
59 return true
60 } else if cl[j].entry.IsTree() {
61 return false
62 } else {
63 return cl[i].Title() < cl[j].Title()
64 }
65}
66
67// FileItemDelegate is the delegate for the file item list.
68type FileItemDelegate struct {
69 common *common.Common
70}
71
72// Height returns the height of the file item list. Implements list.ItemDelegate.
73func (d FileItemDelegate) Height() int { return 1 }
74
75// Spacing returns the spacing of the file item list. Implements list.ItemDelegate.
76func (d FileItemDelegate) Spacing() int { return 0 }
77
78// Update implements list.ItemDelegate.
79func (d FileItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
80 idx := m.Index()
81 item, ok := m.SelectedItem().(FileItem)
82 if !ok {
83 return nil
84 }
85 switch msg := msg.(type) {
86 case tea.KeyMsg:
87 switch {
88 case key.Matches(msg, d.common.KeyMap.Copy):
89 d.common.Copy.Copy(item.Title())
90 return m.SetItem(idx, item)
91 }
92 }
93 return nil
94}
95
96// Render implements list.ItemDelegate.
97func (d FileItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
98 s := d.common.Styles
99 i, ok := listItem.(FileItem)
100 if !ok {
101 return
102 }
103
104 name := i.Title()
105 size := humanize.Bytes(uint64(i.entry.Size()))
106 if i.entry.IsTree() {
107 size = ""
108 name = s.TreeFileDir.Render(name)
109 }
110 var cs lipgloss.Style
111 mode := i.Mode()
112 if index == m.Index() {
113 cs = s.TreeItemActive
114 fmt.Fprint(w, s.TreeItemSelector.Render(">"))
115 } else {
116 cs = s.TreeItemInactive
117 fmt.Fprint(w, s.TreeItemSelector.Render(" "))
118 }
119 leftMargin := s.TreeItemSelector.GetMarginLeft() +
120 s.TreeItemSelector.GetWidth() +
121 s.TreeFileMode.GetMarginLeft() +
122 s.TreeFileMode.GetWidth() +
123 cs.GetMarginLeft()
124 rightMargin := s.TreeFileSize.GetMarginLeft() + lipgloss.Width(size)
125 name = truncateString(name, m.Width()-leftMargin-rightMargin, "…")
126 sizeStyle := s.TreeFileSize.Copy().
127 Width(m.Width() -
128 leftMargin -
129 s.TreeFileSize.GetMarginLeft() -
130 lipgloss.Width(name)).
131 Align(lipgloss.Right)
132 fmt.Fprint(w, s.TreeFileMode.Render(mode.String())+
133 cs.Render(name)+
134 sizeStyle.Render(size))
135}