diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 1a88d5d44a..671276e45c 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -555,6 +555,14 @@ func TestMathBlock(t *testing.T) { "$a$ ($b$) [$c$] {$d$}", `

a (b) [$c$] {$d$}

` + nl, }, + { + "$$a$$ test", + `

a test

` + nl, + }, + { + "test $$a$$", + `

test a

` + nl, + }, } for _, test := range testcases { diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go index 37f6caf11c..a23f48b637 100644 --- a/modules/markup/markdown/math/block_parser.go +++ b/modules/markup/markdown/math/block_parser.go @@ -53,6 +53,12 @@ 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++ { + if line[i] != ' ' && line[i] != '\n' { + return nil, parser.NoChildren + } + } segment.Stop = segment.Start + idx + 2 reader.Advance(segment.Len() - 1) segment.Start += 2 diff --git a/modules/markup/markdown/math/inline_block_node.go b/modules/markup/markdown/math/inline_block_node.go new file mode 100644 index 0000000000..c92d0c8d84 --- /dev/null +++ b/modules/markup/markdown/math/inline_block_node.go @@ -0,0 +1,31 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package math + +import ( + "github.com/yuin/goldmark/ast" +) + +// InlineBlock represents inline math e.g. $$...$$ +type InlineBlock struct { + Inline +} + +// InlineBlock implements InlineBlock. +func (n *InlineBlock) InlineBlock() {} + +// KindInlineBlock is the kind for math inline block +var KindInlineBlock = ast.NewNodeKind("MathInlineBlock") + +// Kind returns KindInlineBlock +func (n *InlineBlock) Kind() ast.NodeKind { + return KindInlineBlock +} + +// NewInlineBlock creates a new ast math inline block node +func NewInlineBlock() *InlineBlock { + return &InlineBlock{ + Inline{}, + } +} diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go index 614cf329af..b11195d551 100644 --- a/modules/markup/markdown/math/inline_parser.go +++ b/modules/markup/markdown/math/inline_parser.go @@ -21,11 +21,20 @@ var defaultInlineDollarParser = &inlineParser{ end: []byte{'$'}, } +var defaultDualDollarParser = &inlineParser{ + start: []byte{'$', '$'}, + end: []byte{'$', '$'}, +} + // NewInlineDollarParser returns a new inline parser func NewInlineDollarParser() parser.InlineParser { return defaultInlineDollarParser } +func NewInlineDualDollarParser() parser.InlineParser { + return defaultDualDollarParser +} + var defaultInlineBracketParser = &inlineParser{ start: []byte{'\\', '('}, end: []byte{'\\', ')'}, @@ -38,7 +47,7 @@ func NewInlineBracketParser() parser.InlineParser { // Trigger triggers this parser on $ or \ func (parser *inlineParser) Trigger() []byte { - return parser.start[0:1] + return parser.start } func isPunctuation(b byte) bool { @@ -88,7 +97,11 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. break } suceedingCharacter := line[pos] - if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') && !isBracket(suceedingCharacter) { + // check valid ending character + if !isPunctuation(suceedingCharacter) && + !(suceedingCharacter == ' ') && + !(suceedingCharacter == '\n') && + !isBracket(suceedingCharacter) { return nil } if line[ender-1] != '\\' { @@ -101,12 +114,21 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. block.Advance(opener) _, pos := block.Position() - node := NewInline() + var node ast.Node + if parser == defaultDualDollarParser { + node = NewInlineBlock() + } else { + node = NewInline() + } segment := pos.WithStop(pos.Start + ender - opener) node.AppendChild(node, ast.NewRawTextSegment(segment)) block.Advance(ender - opener + len(parser.end)) - trimBlock(node, block) + if parser == defaultDualDollarParser { + trimBlock(&(node.(*InlineBlock)).Inline, block) + } else { + trimBlock(node.(*Inline), block) + } return node } diff --git a/modules/markup/markdown/math/inline_renderer.go b/modules/markup/markdown/math/inline_renderer.go index b4e9ade0ae..96848099cc 100644 --- a/modules/markup/markdown/math/inline_renderer.go +++ b/modules/markup/markdown/math/inline_renderer.go @@ -21,7 +21,11 @@ func NewInlineRenderer() renderer.NodeRenderer { func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - _, _ = w.WriteString(``) + extraClass := "" + if _, ok := n.(*InlineBlock); ok { + extraClass = "display " + } + _, _ = w.WriteString(``) for c := n.FirstChild(); c != nil; c = c.NextSibling() { segment := c.(*ast.Text).Segment value := util.EscapeHTML(segment.Value(source)) @@ -43,4 +47,5 @@ func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Nod // RegisterFuncs registers the renderer for inline math nodes func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(KindInline, r.renderInline) + reg.Register(KindInlineBlock, r.renderInline) } diff --git a/modules/markup/markdown/math/math.go b/modules/markup/markdown/math/math.go index 8a50753574..3d9f376bc6 100644 --- a/modules/markup/markdown/math/math.go +++ b/modules/markup/markdown/math/math.go @@ -96,7 +96,8 @@ func (e *Extension) Extend(m goldmark.Markdown) { util.Prioritized(NewInlineBracketParser(), 501), } if e.parseDollarInline { - inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 501)) + inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 503), + util.Prioritized(NewInlineDualDollarParser(), 502)) } m.Parser().AddOptions(parser.WithInlineParsers(inlines...))