From 3c4a06273f776df340459c3775d90eb8d20b71e5 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Fri, 6 Dec 2024 20:00:24 +0800
Subject: [PATCH] Refactor markdown render (#32736)

and add some tests
---
 modules/markup/markdown/markdown_math_test.go | 78 +++++++++++++++++--
 modules/markup/markdown/math/block_node.go    |  1 +
 modules/markup/markdown/math/block_parser.go  | 40 +++++-----
 .../markup/markdown/math/block_renderer.go    |  6 +-
 modules/markup/markdown/math/inline_parser.go |  8 +-
 5 files changed, 101 insertions(+), 32 deletions(-)

diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go
index 0e5adeeac8..e371b1c74a 100644
--- a/modules/markup/markdown/markdown_math_test.go
+++ b/modules/markup/markdown/markdown_math_test.go
@@ -68,7 +68,7 @@ func TestMathRender(t *testing.T) {
 		},
 		{
 			"$$a$$",
-			`<pre class="code-block is-loading"><code class="chroma language-math display">a</code></pre>` + nl,
+			`<code class="chroma language-math display">a</code>` + nl,
 		},
 		{
 			"$$a$$ test",
@@ -79,9 +79,13 @@ func TestMathRender(t *testing.T) {
 			`<p>test <code class="language-math display is-loading">a</code></p>` + nl,
 		},
 		{
-			"foo $x=\\$$ bar",
+			`foo $x=\$$ bar`,
 			`<p>foo <code class="language-math is-loading">x=\$</code> bar</p>` + nl,
 		},
+		{
+			`$\text{$b$}$`,
+			`<p><code class="language-math is-loading">\text{$b$}</code></p>` + nl,
+		},
 	}
 
 	for _, test := range testcases {
@@ -124,14 +128,36 @@ func TestMathRenderBlockIndent(t *testing.T) {
 `,
 		},
 		{
-			"indent-2",
+			"indent-2-mismatch",
 			`
   \[
-  \alpha
+a
+ b
+  c
+   d
   \]
 `,
 			`<pre class="code-block is-loading"><code class="chroma language-math display">
-\alpha
+a
+b
+c
+ d
+</code></pre>
+`,
+		},
+		{
+			"indent-2",
+			`
+  \[
+  a
+   b
+  c
+  \]
+`,
+			`<pre class="code-block is-loading"><code class="chroma language-math display">
+a
+ b
+c
 </code></pre>
 `,
 		},
@@ -139,7 +165,7 @@ func TestMathRenderBlockIndent(t *testing.T) {
 			"indent-0-oneline",
 			`$$ x $$
 foo`,
-			`<pre class="code-block is-loading"><code class="chroma language-math display"> x </code></pre>
+			`<code class="chroma language-math display"> x </code>
 <p>foo</p>
 `,
 		},
@@ -147,8 +173,46 @@ foo`,
 			"indent-3-oneline",
 			`   $$ x $$<SPACE>
 foo`,
-			`<pre class="code-block is-loading"><code class="chroma language-math display"> x </code></pre>
+			`<code class="chroma language-math display"> x </code>
 <p>foo</p>
+`,
+		},
+		{
+			"quote-block",
+			`
+> \[
+> a
+> \]
+> \[
+> b
+> \]
+`,
+			`<blockquote>
+<pre class="code-block is-loading"><code class="chroma language-math display">
+a
+</code></pre>
+<pre class="code-block is-loading"><code class="chroma language-math display">
+b
+</code></pre>
+</blockquote>
+`,
+		},
+		{
+			"list-block",
+			`
+1. a
+   \[
+   x
+   \]
+2. b`,
+			`<ol>
+<li>a
+<pre class="code-block is-loading"><code class="chroma language-math display">
+x
+</code></pre>
+</li>
+<li>b</li>
+</ol>
 `,
 		},
 	}
diff --git a/modules/markup/markdown/math/block_node.go b/modules/markup/markdown/math/block_node.go
index 10d17ff8d3..d2293133cd 100644
--- a/modules/markup/markdown/math/block_node.go
+++ b/modules/markup/markdown/math/block_node.go
@@ -11,6 +11,7 @@ type Block struct {
 	Dollars bool
 	Indent  int
 	Closed  bool
+	Inline  bool
 }
 
 // KindBlock is the node kind for math blocks
diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go
index f31cfb09ad..3f37ce8333 100644
--- a/modules/markup/markdown/math/block_parser.go
+++ b/modules/markup/markdown/math/block_parser.go
@@ -6,6 +6,8 @@ package math
 import (
 	"bytes"
 
+	giteaUtil "code.gitea.io/gitea/modules/util"
+
 	"github.com/yuin/goldmark/ast"
 	"github.com/yuin/goldmark/parser"
 	"github.com/yuin/goldmark/text"
@@ -13,13 +15,17 @@ import (
 )
 
 type blockParser struct {
-	parseDollars bool
+	parseDollars    bool
+	endBytesDollars []byte
+	endBytesBracket []byte
 }
 
 // NewBlockParser creates a new math BlockParser
 func NewBlockParser(parseDollarBlocks bool) parser.BlockParser {
 	return &blockParser{
-		parseDollars: parseDollarBlocks,
+		parseDollars:    parseDollarBlocks,
+		endBytesDollars: []byte{'$', '$'},
+		endBytesBracket: []byte{'\\', ']'},
 	}
 }
 
@@ -47,10 +53,7 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
 	node := NewBlock(dollars, pos)
 
 	// Now we need to check if the ending block is on the segment...
-	endBytes := []byte{'\\', ']'}
-	if dollars {
-		endBytes = []byte{'$', '$'}
-	}
+	endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesBracket)
 	idx := bytes.Index(line[pos+2:], endBytes)
 	if idx >= 0 {
 		// for case $$ ... $$ any other text
@@ -63,6 +66,7 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
 		segment.Stop = segment.Start + idx
 		node.Lines().Append(segment)
 		node.Closed = true
+		node.Inline = true
 		return node, parser.Close | parser.NoChildren
 	}
 
@@ -79,27 +83,19 @@ func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Cont
 	}
 
 	line, segment := reader.PeekLine()
-	w, pos := util.IndentWidth(line, 0)
+	w, pos := util.IndentWidth(line, reader.LineOffset())
 	if w < 4 {
-		if block.Dollars {
-			i := pos
-			for ; i < len(line) && line[i] == '$'; i++ {
-			}
-			length := i - pos
-			if length >= 2 && util.IsBlank(line[i:]) {
-				reader.Advance(segment.Stop - segment.Start - segment.Padding)
-				block.Closed = true
+		endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesBracket)
+		if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) {
+			if util.IsBlank(line[pos+len(endBytes):]) {
+				newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1)
+				reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
 				return parser.Close
 			}
-		} else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) {
-			reader.Advance(segment.Stop - segment.Start - segment.Padding)
-			block.Closed = true
-			return parser.Close
 		}
 	}
-
-	pos, padding := util.IndentPosition(line, 0, block.Indent)
-	seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
+	start := segment.Start + giteaUtil.Iif(pos > block.Indent, block.Indent, pos)
+	seg := text.NewSegmentPadding(start, segment.Stop, segment.Padding)
 	node.Lines().Append(seg)
 	return parser.Continue | parser.NoChildren
 }
diff --git a/modules/markup/markdown/math/block_renderer.go b/modules/markup/markdown/math/block_renderer.go
index 0d2a966102..a770efa01c 100644
--- a/modules/markup/markdown/math/block_renderer.go
+++ b/modules/markup/markdown/math/block_renderer.go
@@ -5,6 +5,7 @@ package math
 
 import (
 	"code.gitea.io/gitea/modules/markup/internal"
+	giteaUtil "code.gitea.io/gitea/modules/util"
 
 	gast "github.com/yuin/goldmark/ast"
 	"github.com/yuin/goldmark/renderer"
@@ -37,10 +38,11 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node)
 func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
 	n := node.(*Block)
 	if entering {
-		_ = r.renderInternal.FormatWithSafeAttrs(w, `<pre class="code-block is-loading"><code class="chroma language-math display">`)
+		code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="chroma language-math display">`
+		_ = r.renderInternal.FormatWithSafeAttrs(w, code)
 		r.writeLines(w, source, n)
 	} else {
-		_, _ = w.WriteString(`</code></pre>` + "\n")
+		_, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")
 	}
 	return gast.WalkContinue, nil
 }
diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go
index 56ae3d57eb..191d1e5a31 100644
--- a/modules/markup/markdown/math/inline_parser.go
+++ b/modules/markup/markdown/math/inline_parser.go
@@ -79,9 +79,10 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
 	opener := len(parser.start)
 
 	// Now look for an ending line
+	depth := 0
 	ender := -1
 	for i := opener; i < len(line); i++ {
-		if bytes.HasPrefix(line[i:], parser.end) {
+		if depth == 0 && bytes.HasPrefix(line[i:], parser.end) {
 			succeedingCharacter := byte(0)
 			if i+len(parser.end) < len(line) {
 				succeedingCharacter = line[i+len(parser.end)]
@@ -99,6 +100,11 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
 			i++
 			continue
 		}
+		if line[i] == '{' {
+			depth++
+		} else if line[i] == '}' {
+			depth--
+		}
 	}
 	if ender == -1 {
 		return nil