From 0f18046df4a9560351fcd8a7d9c9a80adf10efc2 Mon Sep 17 00:00:00 2001
From: wxiaoguang {Attention} text .$a$ $a a$b b$ a a$b b a$b $a a$b b$ a$x$ $x$a test foo foo foo .$a$ $a a$b b$ a a$b b a$b $a a$b b$ a$x$ $x$a test
")
+
+ test(`> [!note]`, renderAttention("note", "octicon-info")+"\n")
+ test(`> [!tip]`, renderAttention("tip", "octicon-light-bulb")+"\n")
+ test(`> [!important]`, renderAttention("important", "octicon-report")+"\n")
+ test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n")
+ test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n")
+
+ // escaped by mdformat
+ test(`> \[!NOTE\]`, renderAttention("note", "octicon-info")+"\n")
+
+ // legacy GitHub style
+ test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n")
+}
diff --git a/modules/markup/markdown/markdown_benchmark_test.go b/modules/markup/markdown/markdown_benchmark_test.go
new file mode 100644
index 0000000000..0f7e3eea6f
--- /dev/null
+++ b/modules/markup/markdown/markdown_benchmark_test.go
@@ -0,0 +1,25 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markdown_test
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/markdown"
+)
+
+func BenchmarkSpecializedMarkdown(b *testing.B) {
+ // 240856 4719 ns/op
+ for i := 0; i < b.N; i++ {
+ markdown.SpecializedMarkdown(&markup.RenderContext{})
+ }
+}
+
+func BenchmarkMarkdownRender(b *testing.B) {
+ // 23202 50840 ns/op
+ for i := 0; i < b.N; i++ {
+ _, _ = markdown.RenderString(markup.NewTestRenderContext(), "https://example.com\n- a\n- b\n")
+ }
+}
diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go
new file mode 100644
index 0000000000..0e5adeeac8
--- /dev/null
+++ b/modules/markup/markdown/markdown_math_test.go
@@ -0,0 +1,163 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markdown
+
+import (
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/modules/markup"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMathRender(t *testing.T) {
+ const nl = "\n"
+ testcases := []struct {
+ testcase string
+ expected string
+ }{
+ {
+ "$a$",
+ `aaa ba ba.a (b) [$c$] {$d$}
` + nl,
+ },
+ {
+ "$$a$$ test",
+ `aa testax=\$ bar
+`,
+ },
+ {
+ "indent-1",
+ `
+ \[
+ \alpha
+ \]
+`,
+ `
+\alpha
+
+`,
+ },
+ {
+ "indent-2",
+ `
+ \[
+ \alpha
+ \]
+`,
+ `
+\alpha
+
+`,
+ },
+ {
+ "indent-0-oneline",
+ `$$ x $$
+foo`,
+ `
+\alpha
+
+ x
+ x aaa ba ba.
` + nl,
- },
- {
- "$a$ ($b$) [$c$] {$d$}",
- `aa (b) [$c$] {$d$}a testa
") - - test(`> [!note]`, renderAttention("note", "octicon-info")+"\n") - test(`> [!tip]`, renderAttention("tip", "octicon-light-bulb")+"\n") - test(`> [!important]`, renderAttention("important", "octicon-report")+"\n") - test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n") - test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n") - - // escaped by mdformat - test(`> \[!NOTE\]`, renderAttention("note", "octicon-info")+"\n") - - // legacy GitHub style - test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n") -} - -func BenchmarkSpecializedMarkdown(b *testing.B) { - // 240856 4719 ns/op - for i := 0; i < b.N; i++ { - markdown.SpecializedMarkdown(&markup.RenderContext{}) - } -} - -func BenchmarkMarkdownRender(b *testing.B) { - // 23202 50840 ns/op - for i := 0; i < b.N; i++ { - _, _ = markdown.RenderString(markup.NewTestRenderContext(), "https://example.com\n- a\n- b\n") - } -} diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go index a23f48b637..f31cfb09ad 100644 --- a/modules/markup/markdown/math/block_parser.go +++ b/modules/markup/markdown/math/block_parser.go @@ -54,21 +54,19 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex idx := bytes.Index(line[pos+2:], endBytes) if idx >= 0 { // for case $$ ... $$ any other text - for i := pos + idx + 4; i < len(line); i++ { + for i := pos + 2 + idx + 2; i < len(line); i++ { if line[i] != ' ' && line[i] != '\n' { return nil, parser.NoChildren } } - segment.Stop = segment.Start + idx + 2 - reader.Advance(segment.Len() - 1) - segment.Start += 2 + segment.Start += pos + 2 + segment.Stop = segment.Start + idx node.Lines().Append(segment) node.Closed = true return node, parser.Close | parser.NoChildren } - reader.Advance(segment.Len() - 1) - segment.Start += 2 + segment.Start += pos + 2 node.Lines().Append(segment) return node, parser.NoChildren } @@ -103,7 +101,6 @@ func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Cont pos, padding := util.IndentPosition(line, 0, block.Indent) seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding) node.Lines().Append(seg) - reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding) return parser.Continue | parser.NoChildren } diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go index b11195d551..56ae3d57eb 100644 --- a/modules/markup/markdown/math/inline_parser.go +++ b/modules/markup/markdown/math/inline_parser.go @@ -26,7 +26,6 @@ var defaultDualDollarParser = &inlineParser{ end: []byte{'$', '$'}, } -// NewInlineDollarParser returns a new inline parser func NewInlineDollarParser() parser.InlineParser { return defaultInlineDollarParser } @@ -40,7 +39,6 @@ var defaultInlineBracketParser = &inlineParser{ end: []byte{'\\', ')'}, } -// NewInlineDollarParser returns a new inline parser func NewInlineBracketParser() parser.InlineParser { return defaultInlineBracketParser } @@ -81,35 +79,29 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. opener := len(parser.start) // Now look for an ending line - ender := opener - for { - pos := bytes.Index(line[ender:], parser.end) - if pos < 0 { - return nil - } - - ender += pos - - // Now we want to check the character at the end of our parser section - // that is ender + len(parser.end) and check if char before ender is '\' - pos = ender + len(parser.end) - if len(line) <= pos { + ender := -1 + for i := opener; i < len(line); i++ { + if bytes.HasPrefix(line[i:], parser.end) { + succeedingCharacter := byte(0) + if i+len(parser.end) < len(line) { + succeedingCharacter = line[i+len(parser.end)] + } + // check valid ending character + isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) || + succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 + if !isValidEndingChar { + break + } + ender = i break } - suceedingCharacter := line[pos] - // check valid ending character - if !isPunctuation(suceedingCharacter) && - !(suceedingCharacter == ' ') && - !(suceedingCharacter == '\n') && - !isBracket(suceedingCharacter) { - return nil + if line[i] == '\\' { + i++ + continue } - if line[ender-1] != '\\' { - break - } - - // move the pointer onwards - ender += len(parser.end) + } + if ender == -1 { + return nil } block.Advance(opener){Attention}
` - tmpl = strings.ReplaceAll(tmpl, "{attention}", attention) - tmpl = strings.ReplaceAll(tmpl, "{icon}", icon) - tmpl = strings.ReplaceAll(tmpl, "{Attention}", cases.Title(language.English).String(attention)) - return tmpl - } - - test := func(input, expected string) { - result, err := markdown.RenderString(markup.NewTestRenderContext(), input) - assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result))) - } - - test(` -> [!NOTE] -> text -`, renderAttention("note", "octicon-info")+"\ntext
\n