1package repo
2
3import (
4 "fmt"
5 "io"
6 "io/fs"
7 "strconv"
8 "strings"
9
10 "github.com/charmbracelet/bubbles/key"
11 "github.com/charmbracelet/bubbles/list"
12 tea "github.com/charmbracelet/bubbletea"
13 "github.com/charmbracelet/lipgloss"
14 "github.com/charmbracelet/soft-serve/git"
15 "github.com/charmbracelet/soft-serve/pkg/ui/common"
16 "github.com/dustin/go-humanize"
17)
18
19// FileItem is a list item for a file.
20type FileItem struct {
21 entry *git.TreeEntry
22}
23
24// ID returns the ID of the file item.
25func (i FileItem) ID() string {
26 name := i.entry.Name()
27 if n, err := strconv.Unquote(name); err == nil {
28 name = n
29 }
30 return name
31}
32
33// Title returns the title of the file item.
34func (i FileItem) Title() string {
35 return i.entry.Name()
36}
37
38// Description returns the description of the file item.
39func (i FileItem) Description() string {
40 return ""
41}
42
43// Mode returns the mode of the file item.
44func (i FileItem) Mode() fs.FileMode {
45 return i.entry.Mode()
46}
47
48// FilterValue implements list.Item.
49func (i FileItem) FilterValue() string { return i.Title() }
50
51// FileItems is a list of file items.
52type FileItems []FileItem
53
54// Len implements sort.Interface.
55func (cl FileItems) Len() int { return len(cl) }
56
57// Swap implements sort.Interface.
58func (cl FileItems) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
59
60// Less implements sort.Interface.
61func (cl FileItems) Less(i, j int) bool {
62 if cl[i].entry.IsTree() && cl[j].entry.IsTree() {
63 return cl[i].Title() < cl[j].Title()
64 } else if cl[i].entry.IsTree() {
65 return true
66 } else if cl[j].entry.IsTree() {
67 return false
68 }
69 return cl[i].Title() < cl[j].Title()
70}
71
72// FileItemDelegate is the delegate for the file item list.
73type FileItemDelegate struct {
74 common *common.Common
75}
76
77// Height returns the height of the file item list. Implements list.ItemDelegate.
78func (d FileItemDelegate) Height() int { return 1 }
79
80// Spacing returns the spacing of the file item list. Implements list.ItemDelegate.
81func (d FileItemDelegate) Spacing() int { return 0 }
82
83// Update implements list.ItemDelegate.
84func (d FileItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
85 item, ok := m.SelectedItem().(FileItem)
86 if !ok {
87 return nil
88 }
89 switch msg := msg.(type) {
90 case tea.KeyMsg:
91 switch {
92 case key.Matches(msg, d.common.KeyMap.Copy):
93 return copyCmd(item.entry.Name(), fmt.Sprintf("File name %q copied to clipboard", item.entry.Name()))
94 }
95 }
96 return nil
97}
98
99// Render implements list.ItemDelegate.
100func (d FileItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
101 i, ok := listItem.(FileItem)
102 if !ok {
103 return
104 }
105
106 s := d.common.Styles.Tree
107
108 name := i.Title()
109 size := humanize.Bytes(uint64(i.entry.Size()))
110 size = strings.ReplaceAll(size, " ", "")
111 sizeLen := lipgloss.Width(size)
112 if i.entry.IsTree() {
113 size = strings.Repeat(" ", sizeLen)
114 if index == m.Index() {
115 name = s.Active.FileDir.Render(name)
116 } else {
117 name = s.Normal.FileDir.Render(name)
118 }
119 }
120 var nameStyle, sizeStyle, modeStyle lipgloss.Style
121 mode := i.Mode()
122 if index == m.Index() {
123 nameStyle = s.Active.FileName
124 sizeStyle = s.Active.FileSize
125 modeStyle = s.Active.FileMode
126 fmt.Fprint(w, s.Selector.Render(">"))
127 } else {
128 nameStyle = s.Normal.FileName
129 sizeStyle = s.Normal.FileSize
130 modeStyle = s.Normal.FileMode
131 fmt.Fprint(w, s.Selector.Render(" "))
132 }
133 sizeStyle = sizeStyle.Copy().
134 Width(8).
135 Align(lipgloss.Right).
136 MarginLeft(1)
137 leftMargin := s.Selector.GetMarginLeft() +
138 s.Selector.GetWidth() +
139 s.Normal.FileMode.GetMarginLeft() +
140 s.Normal.FileMode.GetWidth() +
141 nameStyle.GetMarginLeft() +
142 sizeStyle.GetHorizontalFrameSize()
143 name = common.TruncateString(name, m.Width()-leftMargin)
144 name = nameStyle.Render(name)
145 size = sizeStyle.Render(size)
146 modeStr := modeStyle.Render(mode.String())
147 truncate := d.common.Renderer.NewStyle().MaxWidth(m.Width() -
148 s.Selector.GetHorizontalFrameSize() -
149 s.Selector.GetWidth())
150 fmt.Fprint(w,
151 d.common.Zone.Mark(
152 i.ID(),
153 truncate.Render(fmt.Sprintf("%s%s%s",
154 modeStr,
155 size,
156 name,
157 )),
158 ),
159 )
160}