code_block.go (2635B)
1 package parser 2 3 import ( 4 "github.com/yuin/goldmark/ast" 5 "github.com/yuin/goldmark/text" 6 "github.com/yuin/goldmark/util" 7 ) 8 9 type codeBlockParser struct { 10 } 11 12 // CodeBlockParser is a BlockParser implementation that parses indented code blocks. 13 var defaultCodeBlockParser = &codeBlockParser{} 14 15 // NewCodeBlockParser returns a new BlockParser that 16 // parses code blocks. 17 func NewCodeBlockParser() BlockParser { 18 return defaultCodeBlockParser 19 } 20 21 func (b *codeBlockParser) Trigger() []byte { 22 return nil 23 } 24 25 func (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 44 func (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 67 func (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 83 func (b *codeBlockParser) CanInterruptParagraph() bool { 84 return false 85 } 86 87 func (b *codeBlockParser) CanAcceptIndentedLine() bool { 88 return true 89 } 90 91 func 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 }