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