code.gitea.io/gitea@v1.19.3/modules/markup/markdown/math/block_parser.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package math 5 6 import ( 7 "bytes" 8 9 "github.com/yuin/goldmark/ast" 10 "github.com/yuin/goldmark/parser" 11 "github.com/yuin/goldmark/text" 12 "github.com/yuin/goldmark/util" 13 ) 14 15 type blockParser struct { 16 parseDollars bool 17 } 18 19 // NewBlockParser creates a new math BlockParser 20 func NewBlockParser(parseDollarBlocks bool) parser.BlockParser { 21 return &blockParser{ 22 parseDollars: parseDollarBlocks, 23 } 24 } 25 26 // Open parses the current line and returns a result of parsing. 27 func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) { 28 line, segment := reader.PeekLine() 29 pos := pc.BlockOffset() 30 if pos == -1 || len(line[pos:]) < 2 { 31 return nil, parser.NoChildren 32 } 33 34 dollars := false 35 if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' { 36 dollars = true 37 } else if line[pos] != '\\' || line[pos+1] != '[' { 38 return nil, parser.NoChildren 39 } 40 41 node := NewBlock(dollars, pos) 42 43 // Now we need to check if the ending block is on the segment... 44 endBytes := []byte{'\\', ']'} 45 if dollars { 46 endBytes = []byte{'$', '$'} 47 } 48 idx := bytes.Index(line[pos+2:], endBytes) 49 if idx >= 0 { 50 segment.Stop = segment.Start + idx + 2 51 reader.Advance(segment.Len() - 1) 52 segment.Start += 2 53 node.Lines().Append(segment) 54 node.Closed = true 55 return node, parser.Close | parser.NoChildren 56 } 57 58 reader.Advance(segment.Len() - 1) 59 segment.Start += 2 60 node.Lines().Append(segment) 61 return node, parser.NoChildren 62 } 63 64 // Continue parses the current line and returns a result of parsing. 65 func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { 66 block := node.(*Block) 67 if block.Closed { 68 return parser.Close 69 } 70 71 line, segment := reader.PeekLine() 72 w, pos := util.IndentWidth(line, 0) 73 if w < 4 { 74 if block.Dollars { 75 i := pos 76 for ; i < len(line) && line[i] == '$'; i++ { 77 } 78 length := i - pos 79 if length >= 2 && util.IsBlank(line[i:]) { 80 reader.Advance(segment.Stop - segment.Start - segment.Padding) 81 block.Closed = true 82 return parser.Close 83 } 84 } else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) { 85 reader.Advance(segment.Stop - segment.Start - segment.Padding) 86 block.Closed = true 87 return parser.Close 88 } 89 } 90 91 pos, padding := util.IndentPosition(line, 0, block.Indent) 92 seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding) 93 node.Lines().Append(seg) 94 reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding) 95 return parser.Continue | parser.NoChildren 96 } 97 98 // Close will be called when the parser returns Close. 99 func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) { 100 // noop 101 } 102 103 // CanInterruptParagraph returns true if the parser can interrupt paragraphs, 104 // otherwise false. 105 func (b *blockParser) CanInterruptParagraph() bool { 106 return true 107 } 108 109 // CanAcceptIndentedLine returns true if the parser can open new node when 110 // the given line is being indented more than 3 spaces. 111 func (b *blockParser) CanAcceptIndentedLine() bool { 112 return false 113 } 114 115 // Trigger returns a list of characters that triggers Parse method of 116 // this parser. 117 // If Trigger returns a nil, Open will be called with any lines. 118 // 119 // We leave this as nil as our parse method is quick enough 120 func (b *blockParser) Trigger() []byte { 121 return nil 122 }