filesitem.go

  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}