renderer.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package markdown
  2. import (
  3. "fmt"
  4. "io"
  5. "github.com/ayn2op/discordo/internal/config"
  6. "github.com/diamondburned/ningen/v3/discordmd"
  7. "github.com/yuin/goldmark/ast"
  8. gmr "github.com/yuin/goldmark/renderer"
  9. )
  10. var DefaultRenderer = newRenderer()
  11. type renderer struct {
  12. config *gmr.Config
  13. }
  14. func newRenderer() *renderer {
  15. config := gmr.NewConfig()
  16. return &renderer{config}
  17. }
  18. // AddOptions implements renderer.Renderer.
  19. func (r *renderer) AddOptions(opts ...gmr.Option) {
  20. for _, opt := range opts {
  21. opt.SetConfig(r.config)
  22. }
  23. }
  24. func (r *renderer) Render(w io.Writer, source []byte, n ast.Node) error {
  25. return ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
  26. switch n := n.(type) {
  27. case *ast.Document:
  28. // noop
  29. case *ast.Heading:
  30. r.renderHeading(w)
  31. case *ast.Text:
  32. r.renderText(w, n, entering, source)
  33. case *ast.FencedCodeBlock:
  34. r.renderFencedCodeBlock(w, n, entering, source)
  35. case *ast.AutoLink:
  36. r.renderAutoLink(w, n, entering, source)
  37. case *ast.Link:
  38. r.renderLink(w, n, entering)
  39. case *discordmd.Inline:
  40. r.renderInline(w, n, entering)
  41. case *discordmd.Mention:
  42. r.renderMention(w, n, entering)
  43. case *discordmd.Emoji:
  44. r.renderEmoji(w, n, entering)
  45. }
  46. return ast.WalkContinue, nil
  47. })
  48. }
  49. func (r *renderer) renderHeading(w io.Writer) {
  50. io.WriteString(w, "\n")
  51. }
  52. func (r *renderer) renderFencedCodeBlock(w io.Writer, n *ast.FencedCodeBlock, entering bool, source []byte) {
  53. io.WriteString(w, "\n")
  54. if entering {
  55. // body
  56. lines := n.Lines()
  57. for i := range lines.Len() {
  58. line := lines.At(i)
  59. io.WriteString(w, "| ")
  60. w.Write(line.Value(source))
  61. }
  62. }
  63. }
  64. func (r *renderer) renderAutoLink(w io.Writer, n *ast.AutoLink, entering bool, source []byte) {
  65. if entering {
  66. theme := r.config.Options["theme"].(config.MessagesTextTheme)
  67. fg, bg, _ := theme.URLStyle.Decompose()
  68. _, _ = fmt.Fprintf(w, "[%s:%s]", fg, bg)
  69. w.Write(n.URL(source))
  70. } else {
  71. io.WriteString(w, "[-:-]")
  72. }
  73. }
  74. func (r *renderer) renderLink(w io.Writer, n *ast.Link, entering bool) {
  75. if entering {
  76. theme := r.config.Options["theme"].(config.MessagesTextTheme)
  77. fg, bg, _ := theme.URLStyle.Decompose()
  78. _, _ = fmt.Fprintf(w, "[%s:%s::%s]", fg, bg, n.Destination)
  79. } else {
  80. io.WriteString(w, "[-:-::-]")
  81. }
  82. }
  83. func (r *renderer) renderText(w io.Writer, n *ast.Text, entering bool, source []byte) {
  84. if entering {
  85. w.Write(n.Segment.Value(source))
  86. switch {
  87. case n.HardLineBreak():
  88. io.WriteString(w, "\n\n")
  89. case n.SoftLineBreak():
  90. io.WriteString(w, "\n")
  91. }
  92. }
  93. }
  94. func (r *renderer) renderInline(w io.Writer, n *discordmd.Inline, entering bool) {
  95. if entering {
  96. switch n.Attr {
  97. case discordmd.AttrBold:
  98. io.WriteString(w, "[::b]")
  99. case discordmd.AttrItalics:
  100. io.WriteString(w, "[::i]")
  101. case discordmd.AttrUnderline:
  102. io.WriteString(w, "[::u]")
  103. case discordmd.AttrStrikethrough:
  104. io.WriteString(w, "[::s]")
  105. case discordmd.AttrMonospace:
  106. io.WriteString(w, "[::r]")
  107. }
  108. } else {
  109. switch n.Attr {
  110. case discordmd.AttrBold:
  111. io.WriteString(w, "[::B]")
  112. case discordmd.AttrItalics:
  113. io.WriteString(w, "[::I]")
  114. case discordmd.AttrUnderline:
  115. io.WriteString(w, "[::U]")
  116. case discordmd.AttrStrikethrough:
  117. io.WriteString(w, "[::S]")
  118. case discordmd.AttrMonospace:
  119. io.WriteString(w, "[::R]")
  120. }
  121. }
  122. }
  123. func (r *renderer) renderMention(w io.Writer, n *discordmd.Mention, entering bool) {
  124. if entering {
  125. theme := r.config.Options["theme"].(config.MessagesTextTheme)
  126. fg, bg, _ := theme.MentionStyle.Decompose()
  127. _, _ = fmt.Fprintf(w, "[%s:%s:b]", fg, bg)
  128. switch {
  129. case n.Channel != nil:
  130. io.WriteString(w, "#"+n.Channel.Name)
  131. case n.GuildUser != nil:
  132. username := n.GuildUser.DisplayOrUsername()
  133. theme := r.config.Options["theme"].(config.MessagesTextTheme)
  134. if theme.ShowNicknames && n.GuildUser.Member != nil && n.GuildUser.Member.Nick != "" {
  135. username = n.GuildUser.Member.Nick
  136. }
  137. io.WriteString(w, "@"+username)
  138. case n.GuildRole != nil:
  139. io.WriteString(w, "@"+n.GuildRole.Name)
  140. }
  141. } else {
  142. io.WriteString(w, "[-:-:B]")
  143. }
  144. }
  145. func (r *renderer) renderEmoji(w io.Writer, n *discordmd.Emoji, entering bool) {
  146. if entering {
  147. theme := r.config.Options["theme"].(config.MessagesTextTheme)
  148. fg, bg, _ := theme.EmojiStyle.Decompose()
  149. fmt.Fprintf(w, "[%s:%s]", fg, bg)
  150. io.WriteString(w, ":"+n.Name+":")
  151. } else {
  152. io.WriteString(w, "[-:-]")
  153. }
  154. }