Refactor markdown render (#32736)

and add some tests
This commit is contained in:
wxiaoguang 2024-12-06 20:00:24 +08:00 committed by GitHub
parent b32f0cdfa0
commit 3c4a06273f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 101 additions and 32 deletions

View File

@ -68,7 +68,7 @@ func TestMathRender(t *testing.T) {
}, },
{ {
"$$a$$", "$$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", "$$a$$ test",
@ -79,9 +79,13 @@ func TestMathRender(t *testing.T) {
`<p>test <code class="language-math display is-loading">a</code></p>` + nl, `<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, `<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 { 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"> `<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> </code></pre>
`, `,
}, },
@ -139,7 +165,7 @@ func TestMathRenderBlockIndent(t *testing.T) {
"indent-0-oneline", "indent-0-oneline",
`$$ x $$ `$$ x $$
foo`, 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> <p>foo</p>
`, `,
}, },
@ -147,8 +173,46 @@ foo`,
"indent-3-oneline", "indent-3-oneline",
` $$ x $$<SPACE> ` $$ x $$<SPACE>
foo`, 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> <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>
`, `,
}, },
} }

View File

@ -11,6 +11,7 @@ type Block struct {
Dollars bool Dollars bool
Indent int Indent int
Closed bool Closed bool
Inline bool
} }
// KindBlock is the node kind for math blocks // KindBlock is the node kind for math blocks

View File

@ -6,6 +6,8 @@ package math
import ( import (
"bytes" "bytes"
giteaUtil "code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
@ -13,13 +15,17 @@ import (
) )
type blockParser struct { type blockParser struct {
parseDollars bool parseDollars bool
endBytesDollars []byte
endBytesBracket []byte
} }
// NewBlockParser creates a new math BlockParser // NewBlockParser creates a new math BlockParser
func NewBlockParser(parseDollarBlocks bool) parser.BlockParser { func NewBlockParser(parseDollarBlocks bool) parser.BlockParser {
return &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) node := NewBlock(dollars, pos)
// Now we need to check if the ending block is on the segment... // Now we need to check if the ending block is on the segment...
endBytes := []byte{'\\', ']'} endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesBracket)
if dollars {
endBytes = []byte{'$', '$'}
}
idx := bytes.Index(line[pos+2:], endBytes) idx := bytes.Index(line[pos+2:], endBytes)
if idx >= 0 { if idx >= 0 {
// for case $$ ... $$ any other text // 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 segment.Stop = segment.Start + idx
node.Lines().Append(segment) node.Lines().Append(segment)
node.Closed = true node.Closed = true
node.Inline = true
return node, parser.Close | parser.NoChildren 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() line, segment := reader.PeekLine()
w, pos := util.IndentWidth(line, 0) w, pos := util.IndentWidth(line, reader.LineOffset())
if w < 4 { if w < 4 {
if block.Dollars { endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesBracket)
i := pos if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) {
for ; i < len(line) && line[i] == '$'; i++ { if util.IsBlank(line[pos+len(endBytes):]) {
} newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1)
length := i - pos reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
if length >= 2 && util.IsBlank(line[i:]) {
reader.Advance(segment.Stop - segment.Start - segment.Padding)
block.Closed = true
return parser.Close 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
} }
} }
start := segment.Start + giteaUtil.Iif(pos > block.Indent, block.Indent, pos)
pos, padding := util.IndentPosition(line, 0, block.Indent) seg := text.NewSegmentPadding(start, segment.Stop, segment.Padding)
seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
node.Lines().Append(seg) node.Lines().Append(seg)
return parser.Continue | parser.NoChildren return parser.Continue | parser.NoChildren
} }

View File

@ -5,6 +5,7 @@ package math
import ( import (
"code.gitea.io/gitea/modules/markup/internal" "code.gitea.io/gitea/modules/markup/internal"
giteaUtil "code.gitea.io/gitea/modules/util"
gast "github.com/yuin/goldmark/ast" gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/renderer" "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) { func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
n := node.(*Block) n := node.(*Block)
if entering { 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) r.writeLines(w, source, n)
} else { } else {
_, _ = w.WriteString(`</code></pre>` + "\n") _, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }

View File

@ -79,9 +79,10 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
opener := len(parser.start) opener := len(parser.start)
// Now look for an ending line // Now look for an ending line
depth := 0
ender := -1 ender := -1
for i := opener; i < len(line); i++ { 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) succeedingCharacter := byte(0)
if i+len(parser.end) < len(line) { if i+len(parser.end) < len(line) {
succeedingCharacter = line[i+len(parser.end)] succeedingCharacter = line[i+len(parser.end)]
@ -99,6 +100,11 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
i++ i++
continue continue
} }
if line[i] == '{' {
depth++
} else if line[i] == '}' {
depth--
}
} }
if ender == -1 { if ender == -1 {
return nil return nil