1package repo
2
3import (
4 "fmt"
5 "io"
6 "io/fs"
7 "strings"
8
9 "github.com/charmbracelet/bubbles/key"
10 "github.com/charmbracelet/bubbles/list"
11 tea "github.com/charmbracelet/bubbletea"
12 "github.com/charmbracelet/lipgloss"
13 "github.com/charmbracelet/soft-serve/git"
14 "github.com/charmbracelet/soft-serve/ui/common"
15 "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 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 } else {
64 return cl[i].Title() < cl[j].Title()
65 }
66}
67
68// FileItemDelegate is the delegate for the file item list.
69type FileItemDelegate struct {
70 common *common.Common
71}
72
73// Height returns the height of the file item list. Implements list.ItemDelegate.
74func (d FileItemDelegate) Height() int { return 1 }
75
76// Spacing returns the spacing of the file item list. Implements list.ItemDelegate.
77func (d FileItemDelegate) Spacing() int { return 0 }
78
79// Update implements list.ItemDelegate.
80func (d FileItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
81 idx := m.Index()
82 item, ok := m.SelectedItem().(FileItem)
83 if !ok {
84 return nil
85 }
86 switch msg := msg.(type) {
87 case tea.KeyMsg:
88 switch {
89 case key.Matches(msg, d.common.KeyMap.Copy):
90 d.common.Copy.Copy(item.Title())
91 return m.SetItem(idx, item)
92 }
93 }
94 return nil
95}
96
97// Render implements list.ItemDelegate.
98func (d FileItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
99 s := d.common.Styles
100 i, ok := listItem.(FileItem)
101 if !ok {
102 return
103 }
104
105 name := i.Title()
106 size := humanize.Bytes(uint64(i.entry.Size()))
107 sizeLen := lipgloss.Width(size)
108 if i.entry.IsTree() {
109 size = strings.Repeat(" ", sizeLen)
110 name = s.TreeFileDir.Render(name)
111 }
112 var cs lipgloss.Style
113 mode := i.Mode()
114 if index == m.Index() {
115 cs = s.TreeItemActive
116 fmt.Fprint(w, s.TreeItemSelector.Render(">"))
117 } else {
118 cs = s.TreeItemInactive
119 fmt.Fprint(w, s.TreeItemSelector.Render(" "))
120 }
121 sizeStyle := s.TreeFileSize.Copy().
122 Width(8).
123 Align(lipgloss.Right).
124 MarginLeft(1)
125 leftMargin := s.TreeItemSelector.GetMarginLeft() +
126 s.TreeItemSelector.GetWidth() +
127 s.TreeFileMode.GetMarginLeft() +
128 s.TreeFileMode.GetWidth() +
129 cs.GetMarginLeft() +
130 sizeStyle.GetHorizontalFrameSize()
131 name = common.TruncateString(name, m.Width()-leftMargin)
132 fmt.Fprint(w,
133 s.TreeFileMode.Render(mode.String()),
134 sizeStyle.Render(size),
135 cs.Render(name),
136 )
137}