refactor(markdown): export renderer and use type-safe config

This commit is contained in:
ayn2op 2025-11-11 23:13:22 -05:00
parent 662539b063
commit de52554f95
2 changed files with 30 additions and 35 deletions

View file

@ -29,7 +29,6 @@ import (
"github.com/skratchdot/open-golang/open"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/text"
"golang.design/x/clipboard"
)
@ -39,6 +38,8 @@ type messagesList struct {
cfg *config.Config
selectedMessageID discord.MessageID
renderer *markdown.Renderer
fetchingMembers struct {
mu sync.Mutex
value bool
@ -51,6 +52,7 @@ func newMessagesList(cfg *config.Config) *messagesList {
ml := &messagesList{
TextView: tview.NewTextView(),
cfg: cfg,
renderer: markdown.NewRenderer(cfg.Theme.MessagesList),
}
ml.Box = ui.ConfigureBox(ml.Box, &cfg.Theme)
@ -62,8 +64,6 @@ func newMessagesList(cfg *config.Config) *messagesList {
SetHighlightedFunc(ml.onHighlighted).
SetTitle("Messages").
SetInputCapture(ml.onInputCapture)
markdown.DefaultRenderer.AddOptions(renderer.WithOption("theme", cfg.Theme))
return ml
}
@ -168,7 +168,7 @@ func (ml *messagesList) drawContent(message discord.Message) {
c := []byte(tview.Escape(message.Content))
if app.cfg.Markdown {
ast := discordmd.ParseWithMessage(c, *discordState.Cabinet, &message, false)
markdown.DefaultRenderer.Render(ml, c, ast)
ml.renderer.Render(ml, c, ast)
} else {
ml.Write(c) // write the content as is
}

View file

@ -10,34 +10,24 @@ import (
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/tview"
"github.com/diamondburned/ningen/v3/discordmd"
"github.com/gdamore/tcell/v2"
"github.com/yuin/goldmark/ast"
gmr "github.com/yuin/goldmark/renderer"
)
var DefaultRenderer = newRenderer()
type renderer struct {
config *gmr.Config
type Renderer struct {
theme config.MessagesListTheme
listIx *int
listNested int
}
func newRenderer() *renderer {
config := gmr.NewConfig()
return &renderer{config: config}
func NewRenderer(theme config.MessagesListTheme) *Renderer {
return &Renderer{theme: theme}
}
// AddOptions implements renderer.Renderer.
func (r *renderer) AddOptions(opts ...gmr.Option) {
for _, opt := range opts {
opt.SetConfig(r.config)
}
}
func (r *Renderer) AddOptions(opts ...gmr.Option) {}
func (r *renderer) Render(w io.Writer, source []byte, node ast.Node) error {
theme := r.config.Options["theme"].(config.Theme)
func (r *Renderer) Render(w io.Writer, source []byte, node ast.Node) error {
return ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
switch node := node.(type) {
case *ast.Document:
@ -49,9 +39,9 @@ func (r *renderer) Render(w io.Writer, source []byte, node ast.Node) error {
case *ast.FencedCodeBlock:
r.renderFencedCodeBlock(w, node, entering, source)
case *ast.AutoLink:
r.renderAutoLink(w, node, entering, source, theme.MessagesList.URLStyle.Style)
r.renderAutoLink(w, node, entering, source)
case *ast.Link:
r.renderLink(w, node, entering, theme.MessagesList.URLStyle.Style)
r.renderLink(w, node, entering)
case *ast.List:
r.renderList(w, node, entering)
case *ast.ListItem:
@ -60,16 +50,16 @@ func (r *renderer) Render(w io.Writer, source []byte, node ast.Node) error {
case *discordmd.Inline:
r.renderInline(w, node, entering)
case *discordmd.Mention:
r.renderMention(w, node, entering, theme.MessagesList.MentionStyle.Style)
r.renderMention(w, node, entering)
case *discordmd.Emoji:
r.renderEmoji(w, node, entering, theme.MessagesList.EmojiStyle.Style)
r.renderEmoji(w, node, entering)
}
return ast.WalkContinue, nil
})
}
func (r *renderer) renderHeading(w io.Writer, node *ast.Heading, entering bool) {
func (r *Renderer) renderHeading(w io.Writer, node *ast.Heading, entering bool) {
if entering {
io.WriteString(w, strings.Repeat("#", node.Level))
io.WriteString(w, " ")
@ -78,7 +68,7 @@ func (r *renderer) renderHeading(w io.Writer, node *ast.Heading, entering bool)
}
}
func (r *renderer) renderFencedCodeBlock(w io.Writer, node *ast.FencedCodeBlock, entering bool, source []byte) {
func (r *Renderer) renderFencedCodeBlock(w io.Writer, node *ast.FencedCodeBlock, entering bool, source []byte) {
io.WriteString(w, tview.NewLine)
if entering {
@ -99,7 +89,9 @@ func (r *renderer) renderFencedCodeBlock(w io.Writer, node *ast.FencedCodeBlock,
}
}
func (r *renderer) renderAutoLink(w io.Writer, node *ast.AutoLink, entering bool, source []byte, urlStyle tcell.Style) {
func (r *Renderer) renderAutoLink(w io.Writer, node *ast.AutoLink, entering bool, source []byte) {
urlStyle := r.theme.URLStyle
if entering {
fg, bg, _ := urlStyle.Decompose()
fmt.Fprintf(w, "[%s:%s]", fg, bg)
@ -109,7 +101,8 @@ func (r *renderer) renderAutoLink(w io.Writer, node *ast.AutoLink, entering bool
}
}
func (r *renderer) renderLink(w io.Writer, node *ast.Link, entering bool, urlStyle tcell.Style) {
func (r *Renderer) renderLink(w io.Writer, node *ast.Link, entering bool) {
urlStyle := r.theme.URLStyle
if entering {
fg, bg, _ := urlStyle.Decompose()
fmt.Fprintf(w, "[%s:%s::%s]", fg, bg, node.Destination)
@ -118,7 +111,7 @@ func (r *renderer) renderLink(w io.Writer, node *ast.Link, entering bool, urlSty
}
}
func (r *renderer) renderList(w io.Writer, node *ast.List, entering bool) {
func (r *Renderer) renderList(w io.Writer, node *ast.List, entering bool) {
if node.IsOrdered() {
r.listIx = &node.Start
} else {
@ -133,7 +126,7 @@ func (r *renderer) renderList(w io.Writer, node *ast.List, entering bool) {
}
}
func (r *renderer) renderListItem(w io.Writer, entering bool) {
func (r *Renderer) renderListItem(w io.Writer, entering bool) {
if entering {
io.WriteString(w, strings.Repeat(" ", r.listNested-1))
@ -149,7 +142,7 @@ func (r *renderer) renderListItem(w io.Writer, entering bool) {
}
}
func (r *renderer) renderText(w io.Writer, node *ast.Text, entering bool, source []byte) {
func (r *Renderer) renderText(w io.Writer, node *ast.Text, entering bool, source []byte) {
if entering {
w.Write(node.Segment.Value(source))
switch {
@ -160,7 +153,7 @@ func (r *renderer) renderText(w io.Writer, node *ast.Text, entering bool, source
}
}
}
func (r *renderer) renderInline(w io.Writer, node *discordmd.Inline, entering bool) {
func (r *Renderer) renderInline(w io.Writer, node *discordmd.Inline, entering bool) {
if start, end := attrToTag(node.Attr); entering && start != "" {
io.WriteString(w, start)
} else {
@ -168,9 +161,10 @@ func (r *renderer) renderInline(w io.Writer, node *discordmd.Inline, entering bo
}
}
func (r *renderer) renderMention(w io.Writer, node *discordmd.Mention, entering bool, style tcell.Style) {
func (r *Renderer) renderMention(w io.Writer, node *discordmd.Mention, entering bool) {
mentionStyle := r.theme.MentionStyle
if entering {
fg, bg, _ := style.Decompose()
fg, bg, _ := mentionStyle.Decompose()
fmt.Fprintf(w, "[%s:%s:b]", fg, bg)
switch {
@ -191,7 +185,8 @@ func (r *renderer) renderMention(w io.Writer, node *discordmd.Mention, entering
}
}
func (r *renderer) renderEmoji(w io.Writer, node *discordmd.Emoji, entering bool, emojiStyle tcell.Style) {
func (r *Renderer) renderEmoji(w io.Writer, node *discordmd.Emoji, entering bool) {
emojiStyle := r.theme.EmojiStyle
if entering {
fg, bg, _ := emojiStyle.Decompose()
fmt.Fprintf(w, "[%s:%s]", fg, bg)