bubble.go

  1package about
  2
  3import (
  4	"github.com/charmbracelet/bubbles/viewport"
  5	tea "github.com/charmbracelet/bubbletea"
  6	"github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/refs"
  7	"github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/types"
  8	vp "github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/viewport"
  9	"github.com/charmbracelet/soft-serve/internal/tui/style"
 10	"github.com/go-git/go-git/v5/plumbing"
 11	"github.com/muesli/reflow/wrap"
 12)
 13
 14type Bubble struct {
 15	readmeViewport *vp.ViewportBubble
 16	repo           types.Repo
 17	styles         *style.Styles
 18	height         int
 19	heightMargin   int
 20	width          int
 21	widthMargin    int
 22	ref            *plumbing.Reference
 23}
 24
 25func NewBubble(repo types.Repo, styles *style.Styles, width, wm, height, hm int) *Bubble {
 26	b := &Bubble{
 27		readmeViewport: &vp.ViewportBubble{
 28			Viewport: &viewport.Model{},
 29		},
 30		repo:         repo,
 31		styles:       styles,
 32		widthMargin:  wm,
 33		heightMargin: hm,
 34		ref:          repo.GetHEAD(),
 35	}
 36	b.SetSize(width, height)
 37	return b
 38}
 39func (b *Bubble) Init() tea.Cmd {
 40	return b.setupCmd
 41}
 42
 43func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 44	var cmds []tea.Cmd
 45	switch msg := msg.(type) {
 46	case tea.WindowSizeMsg:
 47		b.SetSize(msg.Width, msg.Height)
 48		// XXX: if we find that longer readmes take more than a few
 49		// milliseconds to render we may need to move Glamour rendering into a
 50		// command.
 51		md, err := b.glamourize()
 52		if err != nil {
 53			return b, nil
 54		}
 55		b.readmeViewport.Viewport.SetContent(md)
 56	case tea.KeyMsg:
 57		switch msg.String() {
 58		case "R":
 59			b.GotoTop()
 60		}
 61	case refs.RefMsg:
 62		b.ref = msg
 63		return b, b.setupCmd
 64	}
 65	rv, cmd := b.readmeViewport.Update(msg)
 66	b.readmeViewport = rv.(*vp.ViewportBubble)
 67	cmds = append(cmds, cmd)
 68	return b, tea.Batch(cmds...)
 69}
 70
 71func (b *Bubble) SetSize(w, h int) {
 72	b.width = w
 73	b.height = h
 74	b.readmeViewport.Viewport.Width = w - b.widthMargin
 75	b.readmeViewport.Viewport.Height = h - b.heightMargin
 76}
 77
 78func (b *Bubble) GotoTop() {
 79	b.readmeViewport.Viewport.GotoTop()
 80}
 81
 82func (b *Bubble) View() string {
 83	return b.readmeViewport.View()
 84}
 85
 86func (b *Bubble) Help() []types.HelpEntry {
 87	return nil
 88}
 89
 90func (b *Bubble) glamourize() (string, error) {
 91	w := b.width - b.widthMargin - b.styles.RepoBody.GetHorizontalFrameSize()
 92	rm := b.repo.GetReadme()
 93	if rm == "" {
 94		return b.styles.AboutNoReadme.Render("No readme found."), nil
 95	}
 96	f, err := types.RenderFile(b.repo.GetReadmePath(), rm, w)
 97	if err != nil {
 98		return "", err
 99	}
100	// For now, hard-wrap long lines in Glamour that would otherwise break the
101	// layout when wrapping. This may be due to #43 in Reflow, which has to do
102	// with a bug in the way lines longer than the given width are wrapped.
103	//
104	//     https://github.com/muesli/reflow/issues/43
105	//
106	// TODO: solve this upstream in Glamour/Reflow.
107	return wrap.String(f, w), nil
108}
109
110func (b *Bubble) setupCmd() tea.Msg {
111	md, err := b.glamourize()
112	if err != nil {
113		return types.ErrMsg{Err: err}
114	}
115	b.readmeViewport.Viewport.SetContent(md)
116	b.GotoTop()
117	return nil
118}