1package repo
  2
  3import (
  4	"fmt"
  5	"strconv"
  6
  7	tea "github.com/charmbracelet/bubbletea"
  8	"github.com/charmbracelet/lipgloss"
  9	gitui "github.com/charmbracelet/soft-serve/internal/tui/bubbles/git"
 10	gittypes "github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/types"
 11	"github.com/charmbracelet/soft-serve/internal/tui/style"
 12	"github.com/go-git/go-git/v5/plumbing"
 13	"github.com/muesli/reflow/truncate"
 14	"github.com/muesli/reflow/wrap"
 15)
 16
 17const (
 18	repoNameMaxWidth = 32
 19)
 20
 21type Bubble struct {
 22	name         string
 23	host         string
 24	port         int
 25	repo         gittypes.Repo
 26	styles       *style.Styles
 27	width        int
 28	widthMargin  int
 29	height       int
 30	heightMargin int
 31	box          *gitui.Bubble
 32
 33	Active bool
 34}
 35
 36func NewBubble(repo gittypes.Repo, host string, port int, styles *style.Styles, width, wm, height, hm int) *Bubble {
 37	b := &Bubble{
 38		name:         repo.Name(),
 39		host:         host,
 40		port:         port,
 41		width:        width,
 42		widthMargin:  wm,
 43		height:       height,
 44		heightMargin: hm,
 45		styles:       styles,
 46	}
 47	b.repo = repo
 48	b.box = gitui.NewBubble(repo, styles, width, wm+styles.RepoBody.GetHorizontalBorderSize(), height, hm+lipgloss.Height(b.headerView())-styles.RepoBody.GetVerticalBorderSize())
 49	return b
 50}
 51
 52func (b *Bubble) Init() tea.Cmd {
 53	return b.box.Init()
 54}
 55
 56func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 57	switch msg := msg.(type) {
 58	case tea.WindowSizeMsg:
 59		b.width = msg.Width
 60		b.height = msg.Height
 61	}
 62	box, cmd := b.box.Update(msg)
 63	b.box = box.(*gitui.Bubble)
 64	return b, cmd
 65}
 66
 67func (b *Bubble) Help() []gittypes.HelpEntry {
 68	return b.box.Help()
 69}
 70
 71func (b Bubble) headerView() string {
 72	// Render repo title
 73	title := b.name
 74	if title == "config" {
 75		title = "Home"
 76	}
 77	title = truncate.StringWithTail(title, repoNameMaxWidth, "…")
 78	title = b.styles.RepoTitle.Render(title)
 79
 80	// Render clone command
 81	var note string
 82	if b.name == "config" {
 83		note = ""
 84	} else {
 85		note = fmt.Sprintf("git clone %s", b.sshAddress())
 86	}
 87	noteWidth := b.width -
 88		b.widthMargin -
 89		lipgloss.Width(title) -
 90		b.styles.RepoTitleBox.GetHorizontalFrameSize()
 91	// Hard-wrap the clone command only, without the usual word-wrapping. since
 92	// a long repo name isn't going to be a series of space-separated "words",
 93	// we'll always want it to be perfectly hard-wrapped.
 94	note = wrap.String(note, noteWidth-b.styles.RepoNote.GetHorizontalFrameSize())
 95	note = b.styles.RepoNote.Copy().Width(noteWidth).Render(note)
 96
 97	// Render borders on name and command
 98	height := gittypes.Max(lipgloss.Height(title), lipgloss.Height(note))
 99	titleBoxStyle := b.styles.RepoTitleBox.Copy().Height(height)
100	noteBoxStyle := b.styles.RepoNoteBox.Copy().Height(height)
101	if b.Active {
102		titleBoxStyle = titleBoxStyle.BorderForeground(b.styles.ActiveBorderColor)
103		noteBoxStyle = noteBoxStyle.BorderForeground(b.styles.ActiveBorderColor)
104	}
105	title = titleBoxStyle.Render(title)
106	note = noteBoxStyle.Render(note)
107
108	// Render
109	return lipgloss.JoinHorizontal(lipgloss.Top, title, note)
110}
111
112func (b *Bubble) View() string {
113	header := b.headerView()
114	bs := b.styles.RepoBody.Copy()
115	if b.Active {
116		bs = bs.BorderForeground(b.styles.ActiveBorderColor)
117	}
118	body := bs.Width(b.width - b.widthMargin - b.styles.RepoBody.GetVerticalFrameSize()).
119		Height(b.height - b.heightMargin - lipgloss.Height(header)).
120		Render(b.box.View())
121	return header + body
122}
123
124func (b *Bubble) Reference() plumbing.ReferenceName {
125	return b.box.Reference()
126}
127
128func (b Bubble) sshAddress() string {
129	p := ":" + strconv.Itoa(int(b.port))
130	if p == ":22" {
131		p = ""
132	}
133	return fmt.Sprintf("ssh://%s%s/%s", b.host, p, b.name)
134}