...
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
17
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
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 }
122
View as plain text