2022-09-13 16:33:37 +00:00
|
|
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
2022-11-27 18:20:29 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2022-09-13 16:33:37 +00:00
|
|
|
|
|
|
|
package math
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
|
2024-12-06 12:00:24 +00:00
|
|
|
giteaUtil "code.gitea.io/gitea/modules/util"
|
|
|
|
|
2022-09-13 16:33:37 +00:00
|
|
|
"github.com/yuin/goldmark/ast"
|
|
|
|
"github.com/yuin/goldmark/parser"
|
|
|
|
"github.com/yuin/goldmark/text"
|
|
|
|
"github.com/yuin/goldmark/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
type blockParser struct {
|
2024-12-06 12:00:24 +00:00
|
|
|
parseDollars bool
|
2024-12-14 05:43:05 +00:00
|
|
|
parseSquare bool
|
2024-12-06 12:00:24 +00:00
|
|
|
endBytesDollars []byte
|
2024-12-14 05:43:05 +00:00
|
|
|
endBytesSquare []byte
|
2022-09-13 16:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewBlockParser creates a new math BlockParser
|
2024-12-14 05:43:05 +00:00
|
|
|
func NewBlockParser(parseDollars, parseSquare bool) parser.BlockParser {
|
2022-09-13 16:33:37 +00:00
|
|
|
return &blockParser{
|
2024-12-14 05:43:05 +00:00
|
|
|
parseDollars: parseDollars,
|
|
|
|
parseSquare: parseSquare,
|
2024-12-06 12:00:24 +00:00
|
|
|
endBytesDollars: []byte{'$', '$'},
|
2024-12-14 05:43:05 +00:00
|
|
|
endBytesSquare: []byte{'\\', ']'},
|
2022-09-13 16:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open parses the current line and returns a result of parsing.
|
|
|
|
func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
|
|
|
|
line, segment := reader.PeekLine()
|
|
|
|
pos := pc.BlockOffset()
|
|
|
|
if pos == -1 || len(line[pos:]) < 2 {
|
|
|
|
return nil, parser.NoChildren
|
|
|
|
}
|
|
|
|
|
2024-06-04 15:35:29 +00:00
|
|
|
var dollars bool
|
2022-09-13 16:33:37 +00:00
|
|
|
if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
|
|
|
|
dollars = true
|
2024-12-14 05:43:05 +00:00
|
|
|
} else if b.parseSquare && line[pos] == '\\' && line[pos+1] == '[' {
|
2024-06-04 15:35:29 +00:00
|
|
|
if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) {
|
|
|
|
// do not process escaped attention block: "> \[!NOTE\]"
|
|
|
|
return nil, parser.NoChildren
|
|
|
|
}
|
|
|
|
dollars = false
|
|
|
|
} else {
|
2022-09-13 16:33:37 +00:00
|
|
|
return nil, parser.NoChildren
|
|
|
|
}
|
|
|
|
|
|
|
|
node := NewBlock(dollars, pos)
|
|
|
|
|
|
|
|
// Now we need to check if the ending block is on the segment...
|
2024-12-14 05:43:05 +00:00
|
|
|
endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesSquare)
|
2022-09-13 16:33:37 +00:00
|
|
|
idx := bytes.Index(line[pos+2:], endBytes)
|
|
|
|
if idx >= 0 {
|
2024-12-14 05:43:05 +00:00
|
|
|
// for case: "$$ ... $$ any other text" (this case will be handled by the inline parser)
|
2024-12-06 04:29:09 +00:00
|
|
|
for i := pos + 2 + idx + 2; i < len(line); i++ {
|
2024-06-29 23:23:47 +00:00
|
|
|
if line[i] != ' ' && line[i] != '\n' {
|
|
|
|
return nil, parser.NoChildren
|
|
|
|
}
|
|
|
|
}
|
2024-12-06 04:29:09 +00:00
|
|
|
segment.Start += pos + 2
|
|
|
|
segment.Stop = segment.Start + idx
|
2022-09-13 16:33:37 +00:00
|
|
|
node.Lines().Append(segment)
|
|
|
|
node.Closed = true
|
2024-12-06 12:00:24 +00:00
|
|
|
node.Inline = true
|
2022-09-13 16:33:37 +00:00
|
|
|
return node, parser.Close | parser.NoChildren
|
|
|
|
}
|
|
|
|
|
2024-12-14 05:43:05 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-06 04:29:09 +00:00
|
|
|
segment.Start += pos + 2
|
2022-09-13 16:33:37 +00:00
|
|
|
node.Lines().Append(segment)
|
|
|
|
return node, parser.NoChildren
|
|
|
|
}
|
|
|
|
|
|
|
|
// Continue parses the current line and returns a result of parsing.
|
|
|
|
func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
|
|
|
|
block := node.(*Block)
|
|
|
|
if block.Closed {
|
|
|
|
return parser.Close
|
|
|
|
}
|
|
|
|
|
|
|
|
line, segment := reader.PeekLine()
|
2024-12-06 12:00:24 +00:00
|
|
|
w, pos := util.IndentWidth(line, reader.LineOffset())
|
2022-09-13 16:33:37 +00:00
|
|
|
if w < 4 {
|
2024-12-14 05:43:05 +00:00
|
|
|
endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesSquare)
|
2024-12-06 12:00:24 +00:00
|
|
|
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)
|
|
|
|
reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
|
2022-09-13 16:33:37 +00:00
|
|
|
return parser.Close
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-12-06 12:00:24 +00:00
|
|
|
start := segment.Start + giteaUtil.Iif(pos > block.Indent, block.Indent, pos)
|
|
|
|
seg := text.NewSegmentPadding(start, segment.Stop, segment.Padding)
|
2022-09-13 16:33:37 +00:00
|
|
|
node.Lines().Append(seg)
|
|
|
|
return parser.Continue | parser.NoChildren
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close will be called when the parser returns Close.
|
|
|
|
func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
|
|
|
|
// noop
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanInterruptParagraph returns true if the parser can interrupt paragraphs,
|
|
|
|
// otherwise false.
|
|
|
|
func (b *blockParser) CanInterruptParagraph() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanAcceptIndentedLine returns true if the parser can open new node when
|
|
|
|
// the given line is being indented more than 3 spaces.
|
|
|
|
func (b *blockParser) CanAcceptIndentedLine() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger returns a list of characters that triggers Parse method of
|
|
|
|
// this parser.
|
|
|
|
// If Trigger returns a nil, Open will be called with any lines.
|
|
|
|
//
|
|
|
|
// We leave this as nil as our parse method is quick enough
|
|
|
|
func (b *blockParser) Trigger() []byte {
|
|
|
|
return nil
|
|
|
|
}
|