Skip to content

Commit 93df41f

Browse files
zeripathwxiaoguang
andauthored
Fix slight bug in katex (#21171)
There is a small bug in #20571 whereby `$a a$b b$` will not be correctly detected as a math inline block of `a a$b b`. This PR fixes this. Also reenable test cases as per #21340 Signed-off-by: Andrew Thornton <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 2d2cf58 commit 93df41f

File tree

7 files changed

+153
-69
lines changed

7 files changed

+153
-69
lines changed

Diff for: modules/markup/html_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package markup_test
77
import (
88
"context"
99
"io"
10+
"os"
1011
"strings"
1112
"testing"
1213

@@ -32,6 +33,7 @@ func TestMain(m *testing.M) {
3233
if err := git.InitSimple(context.Background()); err != nil {
3334
log.Fatal("git init failed, err: %v", err)
3435
}
36+
os.Exit(m.Run())
3537
}
3638

3739
func TestRender_Commits(t *testing.T) {
@@ -336,7 +338,7 @@ func TestRender_emoji(t *testing.T) {
336338
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
337339
test(
338340
"😎🤪🔐🤑❓",
339-
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="question mark">❓</span></p>`)
341+
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="red question mark">❓</span></p>`)
340342

341343
// should match nothing
342344
test(

Diff for: modules/markup/markdown/markdown_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package markdown_test
66

77
import (
88
"context"
9+
"os"
910
"strings"
1011
"testing"
1112

@@ -37,6 +38,7 @@ func TestMain(m *testing.M) {
3738
if err := git.InitSimple(context.Background()); err != nil {
3839
log.Fatal("git init failed, err: %v", err)
3940
}
41+
os.Exit(m.Run())
4042
}
4143

4244
func TestRender_StandardLinks(t *testing.T) {
@@ -426,3 +428,51 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
426428
assert.NoError(t, err)
427429
assert.Equal(t, expected, res)
428430
}
431+
432+
func TestMathBlock(t *testing.T) {
433+
const nl = "\n"
434+
testcases := []struct {
435+
testcase string
436+
expected string
437+
}{
438+
{
439+
"$a$",
440+
`<p><code class="language-math is-loading">a</code></p>` + nl,
441+
},
442+
{
443+
"$ a $",
444+
`<p><code class="language-math is-loading">a</code></p>` + nl,
445+
},
446+
{
447+
"$a$ $b$",
448+
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
449+
},
450+
{
451+
`\(a\) \(b\)`,
452+
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
453+
},
454+
{
455+
`$a a$b b$`,
456+
`<p><code class="language-math is-loading">a a$b b</code></p>` + nl,
457+
},
458+
{
459+
`a a$b b`,
460+
`<p>a a$b b</p>` + nl,
461+
},
462+
{
463+
`a$b $a a$b b$`,
464+
`<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl,
465+
},
466+
{
467+
"$$a$$",
468+
`<pre class="code-block is-loading"><code class="chroma language-math display">a</code></pre>` + nl,
469+
},
470+
}
471+
472+
for _, test := range testcases {
473+
res, err := RenderString(&markup.RenderContext{}, test.testcase)
474+
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
475+
assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
476+
477+
}
478+
}

Diff for: modules/markup/markdown/math/inline_parser.go

+34-13
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func NewInlineBracketParser() parser.InlineParser {
3737
return defaultInlineBracketParser
3838
}
3939

40-
// Trigger triggers this parser on $
40+
// Trigger triggers this parser on $ or \
4141
func (parser *inlineParser) Trigger() []byte {
4242
return parser.start[0:1]
4343
}
@@ -50,29 +50,50 @@ func isAlphanumeric(b byte) bool {
5050
// Parse parses the current line and returns a result of parsing.
5151
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
5252
line, _ := block.PeekLine()
53-
opener := bytes.Index(line, parser.start)
54-
if opener < 0 {
55-
return nil
56-
}
57-
if opener != 0 && isAlphanumeric(line[opener-1]) {
53+
54+
if !bytes.HasPrefix(line, parser.start) {
55+
// We'll catch this one on the next time round
5856
return nil
5957
}
6058

61-
opener += len(parser.start)
62-
ender := bytes.Index(line[opener:], parser.end)
63-
if ender < 0 {
59+
precedingCharacter := block.PrecendingCharacter()
60+
if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {
61+
// need to exclude things like `a$` from being considered a start
6462
return nil
6563
}
66-
if len(line) > opener+ender+len(parser.end) && isAlphanumeric(line[opener+ender+len(parser.end)]) {
67-
return nil
64+
65+
// move the opener marker point at the start of the text
66+
opener := len(parser.start)
67+
68+
// Now look for an ending line
69+
ender := opener
70+
for {
71+
pos := bytes.Index(line[ender:], parser.end)
72+
if pos < 0 {
73+
return nil
74+
}
75+
76+
ender += pos
77+
78+
// Now we want to check the character at the end of our parser section
79+
// that is ender + len(parser.end)
80+
pos = ender + len(parser.end)
81+
if len(line) <= pos {
82+
break
83+
}
84+
if !isAlphanumeric(line[pos]) {
85+
break
86+
}
87+
// move the pointer onwards
88+
ender += len(parser.end)
6889
}
6990

7091
block.Advance(opener)
7192
_, pos := block.Position()
7293
node := NewInline()
73-
segment := pos.WithStop(pos.Start + ender)
94+
segment := pos.WithStop(pos.Start + ender - opener)
7495
node.AppendChild(node, ast.NewRawTextSegment(segment))
75-
block.Advance(ender + len(parser.end))
96+
block.Advance(ender - opener + len(parser.end))
7697

7798
trimBlock(node, block)
7899
return node

Diff for: modules/markup/markdown/meta.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ func ExtractMetadataBytes(contents []byte, out interface{}) ([]byte, error) {
8888
line := contents[start:end]
8989
if isYAMLSeparator(line) {
9090
front = contents[frontMatterStart:start]
91-
body = contents[end+1:]
91+
if end+1 < len(contents) {
92+
body = contents[end+1:]
93+
}
9294
break
9395
}
9496
}

Diff for: modules/markup/markdown/meta_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func TestExtractMetadataBytes(t *testing.T) {
6161
var meta structs.IssueTemplate
6262
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
6363
assert.NoError(t, err)
64-
assert.Equal(t, bodyTest, body)
64+
assert.Equal(t, bodyTest, string(body))
6565
assert.Equal(t, metaTest, meta)
6666
assert.True(t, validateMetadata(meta))
6767
})
@@ -82,7 +82,7 @@ func TestExtractMetadataBytes(t *testing.T) {
8282
var meta structs.IssueTemplate
8383
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
8484
assert.NoError(t, err)
85-
assert.Equal(t, "", body)
85+
assert.Equal(t, "", string(body))
8686
assert.Equal(t, metaTest, meta)
8787
assert.True(t, validateMetadata(meta))
8888
})

Diff for: modules/markup/markdown/renderconfig.go

+52-44
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
package markdown
66

77
import (
8+
"fmt"
89
"strings"
910

10-
"code.gitea.io/gitea/modules/log"
11-
1211
"github.com/yuin/goldmark/ast"
1312
"gopkg.in/yaml.v3"
1413
)
@@ -33,72 +32,81 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
3332
}
3433
rc.yamlNode = value
3534

36-
type basicRenderConfig struct {
37-
Gitea *yaml.Node `yaml:"gitea"`
38-
TOC bool `yaml:"include_toc"`
39-
Lang string `yaml:"lang"`
35+
type commonRenderConfig struct {
36+
TOC bool `yaml:"include_toc"`
37+
Lang string `yaml:"lang"`
4038
}
41-
42-
var basic basicRenderConfig
43-
44-
err := value.Decode(&basic)
45-
if err != nil {
46-
return err
39+
var basic commonRenderConfig
40+
if err := value.Decode(&basic); err != nil {
41+
return fmt.Errorf("unable to decode into commonRenderConfig %w", err)
4742
}
4843

4944
if basic.Lang != "" {
5045
rc.Lang = basic.Lang
5146
}
5247

5348
rc.TOC = basic.TOC
54-
if basic.Gitea == nil {
55-
return nil
49+
50+
type controlStringRenderConfig struct {
51+
Gitea string `yaml:"gitea"`
5652
}
5753

58-
var control *string
59-
if err := basic.Gitea.Decode(&control); err == nil && control != nil {
60-
log.Info("control %v", control)
61-
switch strings.TrimSpace(strings.ToLower(*control)) {
62-
case "none":
63-
rc.Meta = "none"
64-
case "table":
65-
rc.Meta = "table"
66-
default: // "details"
67-
rc.Meta = "details"
54+
var stringBasic controlStringRenderConfig
55+
56+
if err := value.Decode(&stringBasic); err == nil {
57+
if stringBasic.Gitea != "" {
58+
switch strings.TrimSpace(strings.ToLower(stringBasic.Gitea)) {
59+
case "none":
60+
rc.Meta = "none"
61+
case "table":
62+
rc.Meta = "table"
63+
default: // "details"
64+
rc.Meta = "details"
65+
}
6866
}
6967
return nil
7068
}
7169

7270
type giteaControl struct {
73-
Meta string `yaml:"meta"`
74-
Icon string `yaml:"details_icon"`
75-
TOC *yaml.Node `yaml:"include_toc"`
76-
Lang string `yaml:"lang"`
71+
Meta *string `yaml:"meta"`
72+
Icon *string `yaml:"details_icon"`
73+
TOC *bool `yaml:"include_toc"`
74+
Lang *string `yaml:"lang"`
75+
}
76+
77+
type complexGiteaConfig struct {
78+
Gitea *giteaControl `yaml:"gitea"`
79+
}
80+
var complex complexGiteaConfig
81+
if err := value.Decode(&complex); err != nil {
82+
return fmt.Errorf("unable to decode into complexRenderConfig %w", err)
7783
}
7884

79-
var controlStruct *giteaControl
80-
if err := basic.Gitea.Decode(controlStruct); err != nil || controlStruct == nil {
81-
return err
85+
if complex.Gitea == nil {
86+
return nil
8287
}
8388

84-
switch strings.TrimSpace(strings.ToLower(controlStruct.Meta)) {
85-
case "none":
86-
rc.Meta = "none"
87-
case "table":
88-
rc.Meta = "table"
89-
default: // "details"
90-
rc.Meta = "details"
89+
if complex.Gitea.Meta != nil {
90+
switch strings.TrimSpace(strings.ToLower(*complex.Gitea.Meta)) {
91+
case "none":
92+
rc.Meta = "none"
93+
case "table":
94+
rc.Meta = "table"
95+
default: // "details"
96+
rc.Meta = "details"
97+
}
9198
}
9299

93-
rc.Icon = strings.TrimSpace(strings.ToLower(controlStruct.Icon))
100+
if complex.Gitea.Icon != nil {
101+
rc.Icon = strings.TrimSpace(strings.ToLower(*complex.Gitea.Icon))
102+
}
94103

95-
if controlStruct.Lang != "" {
96-
rc.Lang = controlStruct.Lang
104+
if complex.Gitea.Lang != nil && *complex.Gitea.Lang != "" {
105+
rc.Lang = *complex.Gitea.Lang
97106
}
98107

99-
var toc bool
100-
if err := controlStruct.TOC.Decode(&toc); err == nil {
101-
rc.TOC = toc
108+
if complex.Gitea.TOC != nil {
109+
rc.TOC = *complex.Gitea.TOC
102110
}
103111

104112
return nil

Diff for: modules/markup/markdown/renderconfig_test.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package markdown
66

77
import (
8+
"strings"
89
"testing"
910

1011
"gopkg.in/yaml.v3"
@@ -81,19 +82,19 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
8182
TOC: true,
8283
Lang: "testlang",
8384
}, `
84-
include_toc: true
85-
lang: testlang
86-
`,
85+
include_toc: true
86+
lang: testlang
87+
`,
8788
},
8889
{
8990
"complexlang", &RenderConfig{
9091
Meta: "table",
9192
Icon: "table",
9293
Lang: "testlang",
9394
}, `
94-
gitea:
95-
lang: testlang
96-
`,
95+
gitea:
96+
lang: testlang
97+
`,
9798
},
9899
{
99100
"complexlang2", &RenderConfig{
@@ -140,8 +141,8 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
140141
Icon: "table",
141142
Lang: "",
142143
}
143-
if err := yaml.Unmarshal([]byte(tt.args), got); err != nil {
144-
t.Errorf("RenderConfig.UnmarshalYAML() error = %v", err)
144+
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
145+
t.Errorf("RenderConfig.UnmarshalYAML() error = %v\n%q", err, tt.args)
145146
return
146147
}
147148

0 commit comments

Comments
 (0)