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}