code_block.go

  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}