filesitem.go

  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}