aboutsummaryrefslogtreecommitdiffstats
path: root/modules/markup/markdown/math
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2024-12-14 13:43:05 +0800
committerGitHub <noreply@github.com>2024-12-14 13:43:05 +0800
commitcc5ff98e0d510c1923ad7cabc3e339f9cf0b570f (patch)
tree36e6c850a686beda2ae7cca8ed38d6234fbc6968 /modules/markup/markdown/math
parent82c59d52ea650ce42bbca2c6740d9449d06e77be (diff)
downloadgitea-cc5ff98e0d510c1923ad7cabc3e339f9cf0b570f.tar.gz
gitea-cc5ff98e0d510c1923ad7cabc3e339f9cf0b570f.zip
Refactor markdown math render (#32831)
Add more tests
Diffstat (limited to 'modules/markup/markdown/math')
-rw-r--r--modules/markup/markdown/math/block_parser.go25
-rw-r--r--modules/markup/markdown/math/inline_block_node.go31
-rw-r--r--modules/markup/markdown/math/inline_node.go2
-rw-r--r--modules/markup/markdown/math/inline_parser.go90
-rw-r--r--modules/markup/markdown/math/inline_renderer.go1
-rw-r--r--modules/markup/markdown/math/math.go68
6 files changed, 91 insertions, 126 deletions
diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go
index 3f37ce8333..2c5553550a 100644
--- a/modules/markup/markdown/math/block_parser.go
+++ b/modules/markup/markdown/math/block_parser.go
@@ -16,16 +16,18 @@ import (
type blockParser struct {
parseDollars bool
+ parseSquare bool
endBytesDollars []byte
- endBytesBracket []byte
+ endBytesSquare []byte
}
// NewBlockParser creates a new math BlockParser
-func NewBlockParser(parseDollarBlocks bool) parser.BlockParser {
+func NewBlockParser(parseDollars, parseSquare bool) parser.BlockParser {
return &blockParser{
- parseDollars: parseDollarBlocks,
+ parseDollars: parseDollars,
+ parseSquare: parseSquare,
endBytesDollars: []byte{'$', '$'},
- endBytesBracket: []byte{'\\', ']'},
+ endBytesSquare: []byte{'\\', ']'},
}
}
@@ -40,7 +42,7 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
var dollars bool
if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
dollars = true
- } else if line[pos] == '\\' && line[pos+1] == '[' {
+ } else if b.parseSquare && line[pos] == '\\' && line[pos+1] == '[' {
if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) {
// do not process escaped attention block: "> \[!NOTE\]"
return nil, parser.NoChildren
@@ -53,10 +55,10 @@ 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 := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesBracket)
+ endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesSquare)
idx := bytes.Index(line[pos+2:], endBytes)
if idx >= 0 {
- // for case $$ ... $$ any other text
+ // for case: "$$ ... $$ any other text" (this case will be handled by the inline parser)
for i := pos + 2 + idx + 2; i < len(line); i++ {
if line[i] != ' ' && line[i] != '\n' {
return nil, parser.NoChildren
@@ -70,6 +72,13 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
return node, parser.Close | parser.NoChildren
}
+ // for case "\[ ... ]" (no close marker on the same line)
+ for i := pos + 2 + idx + 2; i < len(line); i++ {
+ if line[i] != ' ' && line[i] != '\n' {
+ return nil, parser.NoChildren
+ }
+ }
+
segment.Start += pos + 2
node.Lines().Append(segment)
return node, parser.NoChildren
@@ -85,7 +94,7 @@ func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Cont
line, segment := reader.PeekLine()
w, pos := util.IndentWidth(line, reader.LineOffset())
if w < 4 {
- endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesBracket)
+ endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesSquare)
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)
diff --git a/modules/markup/markdown/math/inline_block_node.go b/modules/markup/markdown/math/inline_block_node.go
deleted file mode 100644
index c92d0c8d84..0000000000
--- a/modules/markup/markdown/math/inline_block_node.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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_node.go b/modules/markup/markdown/math/inline_node.go
index 2221a251bf..1e4034d54b 100644
--- a/modules/markup/markdown/math/inline_node.go
+++ b/modules/markup/markdown/math/inline_node.go
@@ -8,7 +8,7 @@ import (
"github.com/yuin/goldmark/util"
)
-// Inline represents inline math e.g. $...$ or \(...\)
+// Inline struct represents inline math e.g. $...$ or \(...\)
type Inline struct {
ast.BaseInline
}
diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go
index 191d1e5a31..a57abe9f9b 100644
--- a/modules/markup/markdown/math/inline_parser.go
+++ b/modules/markup/markdown/math/inline_parser.go
@@ -12,31 +12,25 @@ import (
)
type inlineParser struct {
- start []byte
- end []byte
+ trigger []byte
+ endBytesSingleDollar []byte
+ endBytesDoubleDollar []byte
+ endBytesBracket []byte
}
var defaultInlineDollarParser = &inlineParser{
- start: []byte{'$'},
- end: []byte{'$'},
-}
-
-var defaultDualDollarParser = &inlineParser{
- start: []byte{'$', '$'},
- end: []byte{'$', '$'},
+ trigger: []byte{'$'},
+ endBytesSingleDollar: []byte{'$'},
+ endBytesDoubleDollar: []byte{'$', '$'},
}
func NewInlineDollarParser() parser.InlineParser {
return defaultInlineDollarParser
}
-func NewInlineDualDollarParser() parser.InlineParser {
- return defaultDualDollarParser
-}
-
var defaultInlineBracketParser = &inlineParser{
- start: []byte{'\\', '('},
- end: []byte{'\\', ')'},
+ trigger: []byte{'\\', '('},
+ endBytesBracket: []byte{'\\', ')'},
}
func NewInlineBracketParser() parser.InlineParser {
@@ -45,7 +39,7 @@ func NewInlineBracketParser() parser.InlineParser {
// Trigger triggers this parser on $ or \
func (parser *inlineParser) Trigger() []byte {
- return parser.start
+ return parser.trigger
}
func isPunctuation(b byte) bool {
@@ -64,33 +58,60 @@ func isAlphanumeric(b byte) bool {
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine()
- if !bytes.HasPrefix(line, parser.start) {
+ if !bytes.HasPrefix(line, parser.trigger) {
// We'll catch this one on the next time round
return nil
}
- precedingCharacter := block.PrecendingCharacter()
- if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
- // need to exclude things like `a$` from being considered a start
- return nil
+ var startMarkLen int
+ var stopMark []byte
+ checkSurrounding := true
+ if line[0] == '$' {
+ startMarkLen = 1
+ stopMark = parser.endBytesSingleDollar
+ if len(line) > 1 {
+ if line[1] == '$' {
+ startMarkLen = 2
+ stopMark = parser.endBytesDoubleDollar
+ } else if line[1] == '`' {
+ pos := 1
+ for ; pos < len(line) && line[pos] == '`'; pos++ {
+ }
+ startMarkLen = pos
+ stopMark = bytes.Repeat([]byte{'`'}, pos)
+ stopMark[len(stopMark)-1] = '$'
+ checkSurrounding = false
+ }
+ }
+ } else {
+ startMarkLen = 2
+ stopMark = parser.endBytesBracket
+ }
+
+ if checkSurrounding {
+ precedingCharacter := block.PrecendingCharacter()
+ if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
+ // need to exclude things like `a$` from being considered a start
+ return nil
+ }
}
// move the opener marker point at the start of the text
- opener := len(parser.start)
+ opener := startMarkLen
// Now look for an ending line
depth := 0
ender := -1
for i := opener; i < len(line); i++ {
- if depth == 0 && bytes.HasPrefix(line[i:], parser.end) {
+ if depth == 0 && bytes.HasPrefix(line[i:], stopMark) {
succeedingCharacter := byte(0)
- if i+len(parser.end) < len(line) {
- succeedingCharacter = line[i+len(parser.end)]
+ if i+len(stopMark) < len(line) {
+ succeedingCharacter = line[i+len(stopMark)]
}
// check valid ending character
isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) ||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
- if !isValidEndingChar {
+ if checkSurrounding && !isValidEndingChar {
break
}
ender = i
@@ -112,21 +133,12 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
block.Advance(opener)
_, pos := block.Position()
- var node ast.Node
- if parser == defaultDualDollarParser {
- node = NewInlineBlock()
- } else {
- node = NewInline()
- }
+ node := NewInline()
+
segment := pos.WithStop(pos.Start + ender - opener)
node.AppendChild(node, ast.NewRawTextSegment(segment))
- block.Advance(ender - opener + len(parser.end))
-
- if parser == defaultDualDollarParser {
- trimBlock(&(node.(*InlineBlock)).Inline, block)
- } else {
- trimBlock(node.(*Inline), block)
- }
+ block.Advance(ender - opener + len(stopMark))
+ trimBlock(node, block)
return node
}
diff --git a/modules/markup/markdown/math/inline_renderer.go b/modules/markup/markdown/math/inline_renderer.go
index 4e0531cf40..d000a7b317 100644
--- a/modules/markup/markdown/math/inline_renderer.go
+++ b/modules/markup/markdown/math/inline_renderer.go
@@ -50,5 +50,4 @@ 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 7e8defcd4a..a6ff593d62 100644
--- a/modules/markup/markdown/math/math.go
+++ b/modules/markup/markdown/math/math.go
@@ -5,6 +5,7 @@ package math
import (
"code.gitea.io/gitea/modules/markup/internal"
+ giteaUtil "code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser"
@@ -12,70 +13,45 @@ import (
"github.com/yuin/goldmark/util"
)
-// Extension is a math extension
-type Extension struct {
- renderInternal *internal.RenderInternal
- enabled bool
- parseDollarInline bool
- parseDollarBlock bool
-}
-
-// Option is the interface Options should implement
-type Option interface {
- SetOption(e *Extension)
-}
-
-type extensionFunc func(e *Extension)
-
-func (fn extensionFunc) SetOption(e *Extension) {
- fn(e)
+type Options struct {
+ Enabled bool
+ ParseDollarInline bool
+ ParseDollarBlock bool
+ ParseSquareBlock bool
}
-// Enabled enables or disables this extension
-func Enabled(enable ...bool) Option {
- value := true
- if len(enable) > 0 {
- value = enable[0]
- }
- return extensionFunc(func(e *Extension) {
- e.enabled = value
- })
+// Extension is a math extension
+type Extension struct {
+ renderInternal *internal.RenderInternal
+ options Options
}
// NewExtension creates a new math extension with the provided options
-func NewExtension(renderInternal *internal.RenderInternal, opts ...Option) *Extension {
+func NewExtension(renderInternal *internal.RenderInternal, opts ...Options) *Extension {
+ opt := giteaUtil.OptionalArg(opts)
r := &Extension{
- renderInternal: renderInternal,
- enabled: true,
- parseDollarBlock: true,
- parseDollarInline: true,
- }
-
- for _, o := range opts {
- o.SetOption(r)
+ renderInternal: renderInternal,
+ options: opt,
}
return r
}
// Extend extends goldmark with our parsers and renderers
func (e *Extension) Extend(m goldmark.Markdown) {
- if !e.enabled {
+ if !e.options.Enabled {
return
}
- m.Parser().AddOptions(parser.WithBlockParsers(
- util.Prioritized(NewBlockParser(e.parseDollarBlock), 701),
- ))
-
- inlines := []util.PrioritizedValue{
- util.Prioritized(NewInlineBracketParser(), 501),
- }
- if e.parseDollarInline {
- inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 503),
- util.Prioritized(NewInlineDualDollarParser(), 502))
+ inlines := []util.PrioritizedValue{util.Prioritized(NewInlineBracketParser(), 501)}
+ if e.options.ParseDollarInline {
+ inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 502))
}
m.Parser().AddOptions(parser.WithInlineParsers(inlines...))
+ m.Parser().AddOptions(parser.WithBlockParsers(
+ util.Prioritized(NewBlockParser(e.options.ParseDollarBlock, e.options.ParseSquareBlock), 701),
+ ))
+
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewBlockRenderer(e.renderInternal), 501),
util.Prioritized(NewInlineRenderer(e.renderInternal), 502),