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}