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