소스 검색

Refactor tag rendering to avoid recreating goldmark instance for each message

Tulir Asokan 2 년 전
부모
커밋
e7615ef4be
3개의 변경된 파일45개의 추가작업 그리고 32개의 파일을 삭제
  1. 23 16
      formatter.go
  2. 4 2
      formatter_everyone.go
  3. 18 14
      formatter_tag.go

+ 23 - 16
formatter.go

@@ -33,9 +33,16 @@ import (
 	"maunium.net/go/mautrix/util/variationselector"
 )
 
-var discordExtensions = goldmark.WithExtensions(extension.Strikethrough, mdext.SimpleSpoiler, mdext.DiscordUnderline, &DiscordEveryone{})
+// escapeFixer is a hacky partial fix for the difference in escaping markdown, used with escapeReplacement
+//
+// Discord allows escaping with just one backslash, e.g. \__a__,
+// but standard markdown requires both to be escaped (\_\_a__)
 var escapeFixer = regexp.MustCompile(`\\(__[^_]|\*\*[^*])`)
 
+func escapeReplacement(s string) string {
+	return s[:2] + `\` + s[2:]
+}
+
 // indentableParagraphParser is the default paragraph parser with CanAcceptIndentedLine.
 // Used when disabling CodeBlockParser (as disabling it without a replacement will make indented blocks disappear).
 type indentableParagraphParser struct {
@@ -48,24 +55,24 @@ func (b *indentableParagraphParser) CanAcceptIndentedLine() bool {
 	return true
 }
 
+var discordRenderer = goldmark.New(
+	goldmark.WithParser(mdext.ParserWithoutFeatures(
+		parser.NewListParser(), parser.NewListItemParser(), parser.NewHTMLBlockParser(), parser.NewRawHTMLParser(),
+		parser.NewSetextHeadingParser(), parser.NewATXHeadingParser(), parser.NewThematicBreakParser(),
+		parser.NewLinkParser(), parser.NewCodeBlockParser(),
+	)),
+	goldmark.WithParserOptions(parser.WithBlockParsers(util.Prioritized(defaultIndentableParagraphParser, 500))),
+	format.HTMLOptions,
+	goldmark.WithExtensions(extension.Strikethrough, mdext.SimpleSpoiler, mdext.DiscordUnderline, ExtDiscordEveryone, ExtDiscordTag),
+)
+
 func (portal *Portal) renderDiscordMarkdownOnlyHTML(text string) string {
-	text = escapeFixer.ReplaceAllStringFunc(text, func(s string) string {
-		return s[:2] + `\` + s[2:]
-	})
-
-	mdRenderer := goldmark.New(
-		goldmark.WithParser(mdext.ParserWithoutFeatures(
-			parser.NewListParser(), parser.NewListItemParser(), parser.NewHTMLBlockParser(), parser.NewRawHTMLParser(),
-			parser.NewSetextHeadingParser(), parser.NewATXHeadingParser(), parser.NewThematicBreakParser(),
-			parser.NewLinkParser(), parser.NewCodeBlockParser(),
-		)),
-		goldmark.WithParserOptions(parser.WithBlockParsers(util.Prioritized(defaultIndentableParagraphParser, 500))),
-		format.HTMLOptions, discordExtensions,
-		goldmark.WithExtensions(&DiscordTag{portal}),
-	)
+	text = escapeFixer.ReplaceAllStringFunc(text, escapeReplacement)
 
 	var buf strings.Builder
-	err := mdRenderer.Convert([]byte(text), &buf)
+	ctx := parser.NewContext()
+	ctx.Set(parserContextPortal, portal)
+	err := discordRenderer.Convert([]byte(text), &buf, parser.WithContext(ctx))
 	if err != nil {
 		panic(fmt.Errorf("markdown parser errored: %w", err))
 	}

+ 4 - 2
formatter_everyone.go

@@ -96,9 +96,11 @@ func (r *discordEveryoneHTMLRenderer) renderDiscordEveryone(w util.BufWriter, so
 	return
 }
 
-type DiscordEveryone struct{}
+type discordEveryone struct{}
 
-func (e *DiscordEveryone) Extend(m goldmark.Markdown) {
+var ExtDiscordEveryone = &discordEveryone{}
+
+func (e *discordEveryone) Extend(m goldmark.Markdown) {
 	m.Parser().AddOptions(parser.WithInlineParsers(
 		util.Prioritized(defaultDiscordEveryoneParser, 600),
 	))

+ 18 - 14
formatter_tag.go

@@ -37,7 +37,8 @@ import (
 
 type astDiscordTag struct {
 	ast.BaseInline
-	id int64
+	portal *Portal
+	id     int64
 }
 
 var _ ast.Node = (*astDiscordTag)(nil)
@@ -143,7 +144,10 @@ func (s *discordTagParser) Trigger() []byte {
 	return []byte{'<'}
 }
 
+var parserContextPortal = parser.NewContextKey()
+
 func (s *discordTagParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
+	portal := pc.Get(parserContextPortal).(*Portal)
 	//before := block.PrecendingCharacter()
 	line, _ := block.PeekLine()
 	match := discordTagRegex.FindSubmatch(line)
@@ -157,7 +161,7 @@ func (s *discordTagParser) Parse(parent ast.Node, block text.Reader, pc parser.C
 	if err != nil {
 		return nil
 	}
-	tag := astDiscordTag{id: id}
+	tag := astDiscordTag{id: id, portal: portal}
 	tagName := string(match[1])
 	switch {
 	case tagName == "@":
@@ -199,9 +203,9 @@ func (s *discordTagParser) CloseBlock(parent ast.Node, pc parser.Context) {
 	// nothing to do
 }
 
-type discordTagHTMLRenderer struct {
-	portal *Portal
-}
+type discordTagHTMLRenderer struct{}
+
+var defaultDiscordTagHTMLRenderer = &discordTagHTMLRenderer{}
 
 func (r *discordTagHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 	reg.Register(astKindDiscordTag, r.renderDiscordMention)
@@ -259,17 +263,17 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
 	}
 	switch node := n.(type) {
 	case *astDiscordUserMention:
-		puppet := r.portal.bridge.GetPuppetByID(strconv.FormatInt(node.id, 10))
+		puppet := node.portal.bridge.GetPuppetByID(strconv.FormatInt(node.id, 10))
 		_, _ = fmt.Fprintf(w, `<a href="https://matrix.to/#/%s">%s</a>`, puppet.MXID, puppet.Name)
 		return
 	case *astDiscordRoleMention:
-		role := r.portal.bridge.DB.Role.GetByID(r.portal.GuildID, strconv.FormatInt(node.id, 10))
+		role := node.portal.bridge.DB.Role.GetByID(node.portal.GuildID, strconv.FormatInt(node.id, 10))
 		if role != nil {
 			_, _ = fmt.Fprintf(w, `<font color="#%06x"><strong>@%s</strong></font>`, role.Color, role.Name)
 			return
 		}
 	case *astDiscordChannelMention:
-		portal := r.portal.bridge.GetExistingPortalByID(database.PortalKey{
+		portal := node.portal.bridge.GetExistingPortalByID(database.PortalKey{
 			ChannelID: strconv.FormatInt(node.id, 10),
 			Receiver:  "",
 		})
@@ -282,7 +286,7 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
 			return
 		}
 	case *astDiscordCustomEmoji:
-		reactionMXC := r.portal.getEmojiMXCByDiscordID(strconv.FormatInt(node.id, 10), node.name, node.animated)
+		reactionMXC := node.portal.getEmojiMXCByDiscordID(strconv.FormatInt(node.id, 10), node.name, node.animated)
 		if !reactionMXC.IsEmpty() {
 			_, _ = fmt.Fprintf(w, `<img data-mx-emoticon src="%[1]s" alt="%[2]s" title="%[2]s" height="32"/>`, reactionMXC.String(), node.name)
 			return
@@ -310,15 +314,15 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
 	return
 }
 
-type DiscordTag struct {
-	Portal *Portal
-}
+type discordTag struct{}
+
+var ExtDiscordTag = &discordTag{}
 
-func (e *DiscordTag) Extend(m goldmark.Markdown) {
+func (e *discordTag) Extend(m goldmark.Markdown) {
 	m.Parser().AddOptions(parser.WithInlineParsers(
 		util.Prioritized(defaultDiscordTagParser, 600),
 	))
 	m.Renderer().AddOptions(renderer.WithNodeRenderers(
-		util.Prioritized(&discordTagHTMLRenderer{e.Portal}, 600),
+		util.Prioritized(defaultDiscordTagHTMLRenderer, 600),
 	))
 }