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 node.Lines().Append(segment)
39 reader.Advance(segment.Len() - 1)
40 return node, NoChildren
41
42}
43
44func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
45 line, segment := reader.PeekLine()
46 if util.IsBlank(line) {
47 node.Lines().Append(segment.TrimLeftSpaceWidth(4, reader.Source()))
48 return Continue | NoChildren
49 }
50 pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
51 if pos < 0 {
52 return Close
53 }
54 reader.AdvanceAndSetPadding(pos, padding)
55 _, segment = reader.PeekLine()
56
57 // if code block line starts with a tab, keep a tab as it is.
58 if segment.Padding != 0 {
59 preserveLeadingTabInCodeBlock(&segment, reader, 0)
60 }
61
62 node.Lines().Append(segment)
63 reader.Advance(segment.Len() - 1)
64 return Continue | NoChildren
65}
66
67func (b *codeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
68 // trim trailing blank lines
69 lines := node.Lines()
70 length := lines.Len() - 1
71 source := reader.Source()
72 for length >= 0 {
73 line := lines.At(length)
74 if util.IsBlank(line.Value(source)) {
75 length--
76 } else {
77 break
78 }
79 }
80 lines.SetSliced(0, length+1)
81}
82
83func (b *codeBlockParser) CanInterruptParagraph() bool {
84 return false
85}
86
87func (b *codeBlockParser) CanAcceptIndentedLine() bool {
88 return true
89}
90
91func preserveLeadingTabInCodeBlock(segment *text.Segment, reader text.Reader, indent int) {
92 offsetWithPadding := reader.LineOffset() + indent
93 sl, ss := reader.Position()
94 reader.SetPosition(sl, text.NewSegment(ss.Start-1, ss.Stop))
95 if offsetWithPadding == reader.LineOffset() {
96 segment.Padding = 0
97 segment.Start--
98 }
99 reader.SetPosition(sl, ss)
100}