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