fcode_block.go (3194B)
1 package parser 2 3 import ( 4 "bytes" 5 6 "github.com/yuin/goldmark/ast" 7 "github.com/yuin/goldmark/text" 8 "github.com/yuin/goldmark/util" 9 ) 10 11 type fencedCodeBlockParser struct { 12 } 13 14 var defaultFencedCodeBlockParser = &fencedCodeBlockParser{} 15 16 // NewFencedCodeBlockParser returns a new BlockParser that 17 // parses fenced code blocks. 18 func NewFencedCodeBlockParser() BlockParser { 19 return defaultFencedCodeBlockParser 20 } 21 22 type fenceData struct { 23 char byte 24 indent int 25 length int 26 node ast.Node 27 } 28 29 var fencedCodeBlockInfoKey = NewContextKey() 30 31 func (b *fencedCodeBlockParser) Trigger() []byte { 32 return []byte{'~', '`'} 33 } 34 35 func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) { 36 line, segment := reader.PeekLine() 37 pos := pc.BlockOffset() 38 if pos < 0 || (line[pos] != '`' && line[pos] != '~') { 39 return nil, NoChildren 40 } 41 findent := pos 42 fenceChar := line[pos] 43 i := pos 44 for ; i < len(line) && line[i] == fenceChar; i++ { 45 } 46 oFenceLength := i - pos 47 if oFenceLength < 3 { 48 return nil, NoChildren 49 } 50 var info *ast.Text 51 if i < len(line)-1 { 52 rest := line[i:] 53 left := util.TrimLeftSpaceLength(rest) 54 right := util.TrimRightSpaceLength(rest) 55 if left < len(rest)-right { 56 infoStart, infoStop := segment.Start-segment.Padding+i+left, segment.Stop-right 57 value := rest[left : len(rest)-right] 58 if fenceChar == '`' && bytes.IndexByte(value, '`') > -1 { 59 return nil, NoChildren 60 } else if infoStart != infoStop { 61 info = ast.NewTextSegment(text.NewSegment(infoStart, infoStop)) 62 } 63 } 64 } 65 node := ast.NewFencedCodeBlock(info) 66 pc.Set(fencedCodeBlockInfoKey, &fenceData{fenceChar, findent, oFenceLength, node}) 67 return node, NoChildren 68 69 } 70 71 func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State { 72 line, segment := reader.PeekLine() 73 fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData) 74 75 w, pos := util.IndentWidth(line, reader.LineOffset()) 76 if w < 4 { 77 i := pos 78 for ; i < len(line) && line[i] == fdata.char; i++ { 79 } 80 length := i - pos 81 if length >= fdata.length && util.IsBlank(line[i:]) { 82 newline := 1 83 if line[len(line)-1] != '\n' { 84 newline = 0 85 } 86 reader.Advance(segment.Stop - segment.Start - newline + segment.Padding) 87 return Close 88 } 89 } 90 pos, padding := util.IndentPositionPadding(line, reader.LineOffset(), segment.Padding, fdata.indent) 91 if pos < 0 { 92 pos = util.FirstNonSpacePosition(line) 93 if pos < 0 { 94 pos = 0 95 } 96 padding = 0 97 } 98 seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding) 99 // if code block line starts with a tab, keep a tab as it is. 100 if padding != 0 { 101 preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent) 102 } 103 node.Lines().Append(seg) 104 reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding) 105 return Continue | NoChildren 106 } 107 108 func (b *fencedCodeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) { 109 fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData) 110 if fdata.node == node { 111 pc.Set(fencedCodeBlockInfoKey, nil) 112 } 113 } 114 115 func (b *fencedCodeBlockParser) CanInterruptParagraph() bool { 116 return true 117 } 118 119 func (b *fencedCodeBlockParser) CanAcceptIndentedLine() bool { 120 return false 121 }