1package parser
2
3import (
4 "github.com/yuin/goldmark/ast"
5 "github.com/yuin/goldmark/text"
6 "github.com/yuin/goldmark/util"
7)
8
9type codeBlockParser struct {
10}
11
12// CodeBlockParser is a BlockParser implementation that parses indented code blocks.
13var defaultCodeBlockParser = &codeBlockParser{}
14
15// NewCodeBlockParser returns a new BlockParser that
16// parses code blocks.
17func NewCodeBlockParser() BlockParser {
18 return defaultCodeBlockParser
19}
20
21func (b *codeBlockParser) Trigger() []byte {
22 return nil
23}
24
25func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
26 line, segment := reader.PeekLine()
27 pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
28 if pos < 0 || util.IsBlank(line) {
29 return nil, NoChildren
30 }
31 node := ast.NewCodeBlock()
32 reader.AdvanceAndSetPadding(pos, padding)
33 _, segment = reader.PeekLine()
34 // if code block line starts with a tab, keep a tab as it is.
35 if segment.Padding != 0 {
36 preserveLeadingTabInCodeBlock(&segment, reader, 0)
37 }
38 segment.ForceNewline = true
39 node.Lines().Append(segment)
40 reader.Advance(segment.Len() - 1)
41 return node, NoChildren
42
43}
44
45func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
46 line, segment := reader.PeekLine()
47 if util.IsBlank(line) {
48 node.Lines().Append(segment.TrimLeftSpaceWidth(4, reader.Source()))
49 return Continue | NoChildren
50 }
51 pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
52 if pos < 0 {
53 return Close
54 }
55 reader.AdvanceAndSetPadding(pos, padding)
56 _, segment = reader.PeekLine()
57
58 // if code block line starts with a tab, keep a tab as it is.
59 if segment.Padding != 0 {
60 preserveLeadingTabInCodeBlock(&segment, reader, 0)
61 }
62
63 segment.ForceNewline = true
64 node.Lines().Append(segment)
65 reader.Advance(segment.Len() - 1)
66 return Continue | NoChildren
67}
68
69func (b *codeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
70 // trim trailing blank lines
71 lines := node.Lines()
72 length := lines.Len() - 1
73 source := reader.Source()
74 for length >= 0 {
75 line := lines.At(length)
76 if util.IsBlank(line.Value(source)) {
77 length--
78 } else {
79 break
80 }
81 }
82 lines.SetSliced(0, length+1)
83}
84
85func (b *codeBlockParser) CanInterruptParagraph() bool {
86 return false
87}
88
89func (b *codeBlockParser) CanAcceptIndentedLine() bool {
90 return true
91}
92
93func preserveLeadingTabInCodeBlock(segment *text.Segment, reader text.Reader, indent int) {
94 offsetWithPadding := reader.LineOffset() + indent
95 sl, ss := reader.Position()
96 reader.SetPosition(sl, text.NewSegment(ss.Start-1, ss.Stop))
97 if offsetWithPadding == reader.LineOffset() {
98 segment.Padding = 0
99 segment.Start--
100 }
101 reader.SetPosition(sl, ss)
102}