code.gitea.io/gitea@v1.22.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 var dollars bool 35 if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' { 36 dollars = true 37 } else if line[pos] == '\\' && line[pos+1] == '[' { 38 if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) { 39 // do not process escaped attention block: "> \[!NOTE\]" 40 return nil, parser.NoChildren 41 } 42 dollars = false 43 } else { 44 return nil, parser.NoChildren 45 } 46 47 node := NewBlock(dollars, pos) 48 49 // Now we need to check if the ending block is on the segment... 50 endBytes := []byte{'\\', ']'} 51 if dollars { 52 endBytes = []byte{'$', '$'} 53 } 54 idx := bytes.Index(line[pos+2:], endBytes) 55 if idx >= 0 { 56 segment.Stop = segment.Start + idx + 2 57 reader.Advance(segment.Len() - 1) 58 segment.Start += 2 59 node.Lines().Append(segment) 60 node.Closed = true 61 return node, parser.Close | parser.NoChildren 62 } 63 64 reader.Advance(segment.Len() - 1) 65 segment.Start += 2 66 node.Lines().Append(segment) 67 return node, parser.NoChildren 68 } 69 70 // Continue parses the current line and returns a result of parsing. 71 func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { 72 block := node.(*Block) 73 if block.Closed { 74 return parser.Close 75 } 76 77 line, segment := reader.PeekLine() 78 w, pos := util.IndentWidth(line, 0) 79 if w < 4 { 80 if block.Dollars { 81 i := pos 82 for ; i < len(line) && line[i] == '$'; i++ { 83 } 84 length := i - pos 85 if length >= 2 && util.IsBlank(line[i:]) { 86 reader.Advance(segment.Stop - segment.Start - segment.Padding) 87 block.Closed = true 88 return parser.Close 89 } 90 } else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) { 91 reader.Advance(segment.Stop - segment.Start - segment.Padding) 92 block.Closed = true 93 return parser.Close 94 } 95 } 96 97 pos, padding := util.IndentPosition(line, 0, block.Indent) 98 seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding) 99 node.Lines().Append(seg) 100 reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding) 101 return parser.Continue | parser.NoChildren 102 } 103 104 // Close will be called when the parser returns Close. 105 func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) { 106 // noop 107 } 108 109 // CanInterruptParagraph returns true if the parser can interrupt paragraphs, 110 // otherwise false. 111 func (b *blockParser) CanInterruptParagraph() bool { 112 return true 113 } 114 115 // CanAcceptIndentedLine returns true if the parser can open new node when 116 // the given line is being indented more than 3 spaces. 117 func (b *blockParser) CanAcceptIndentedLine() bool { 118 return false 119 } 120 121 // Trigger returns a list of characters that triggers Parse method of 122 // this parser. 123 // If Trigger returns a nil, Open will be called with any lines. 124 // 125 // We leave this as nil as our parse method is quick enough 126 func (b *blockParser) Trigger() []byte { 127 return nil 128 }