fcode_block.go

  1package parser
  2
  3import (
  4	"bytes"
  5
  6	"github.com/yuin/goldmark/ast"
  7	"github.com/yuin/goldmark/text"
  8	"github.com/yuin/goldmark/util"
  9)
 10
 11type fencedCodeBlockParser struct {
 12}
 13
 14var defaultFencedCodeBlockParser = &fencedCodeBlockParser{}
 15
 16// NewFencedCodeBlockParser returns a new BlockParser that
 17// parses fenced code blocks.
 18func NewFencedCodeBlockParser() BlockParser {
 19	return defaultFencedCodeBlockParser
 20}
 21
 22type fenceData struct {
 23	char   byte
 24	indent int
 25	length int
 26	node   ast.Node
 27}
 28
 29var fencedCodeBlockInfoKey = NewContextKey()
 30
 31func (b *fencedCodeBlockParser) Trigger() []byte {
 32	return []byte{'~', '`'}
 33}
 34
 35func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
 36	line, segment := reader.PeekLine()
 37	pos := pc.BlockOffset()
 38	if pos < 0 || (line[pos] != '`' && line[pos] != '~') {
 39		return nil, NoChildren
 40	}
 41	findent := pos
 42	fenceChar := line[pos]
 43	i := pos
 44	for ; i < len(line) && line[i] == fenceChar; i++ {
 45	}
 46	oFenceLength := i - pos
 47	if oFenceLength < 3 {
 48		return nil, NoChildren
 49	}
 50	var info *ast.Text
 51	if i < len(line)-1 {
 52		rest := line[i:]
 53		left := util.TrimLeftSpaceLength(rest)
 54		right := util.TrimRightSpaceLength(rest)
 55		if left < len(rest)-right {
 56			infoStart, infoStop := segment.Start-segment.Padding+i+left, segment.Stop-right
 57			value := rest[left : len(rest)-right]
 58			if fenceChar == '`' && bytes.IndexByte(value, '`') > -1 {
 59				return nil, NoChildren
 60			} else if infoStart != infoStop {
 61				info = ast.NewTextSegment(text.NewSegment(infoStart, infoStop))
 62			}
 63		}
 64	}
 65	node := ast.NewFencedCodeBlock(info)
 66	pc.Set(fencedCodeBlockInfoKey, &fenceData{fenceChar, findent, oFenceLength, node})
 67	return node, NoChildren
 68
 69}
 70
 71func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
 72	line, segment := reader.PeekLine()
 73	fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
 74
 75	w, pos := util.IndentWidth(line, reader.LineOffset())
 76	if w < 4 {
 77		i := pos
 78		for ; i < len(line) && line[i] == fdata.char; i++ {
 79		}
 80		length := i - pos
 81		if length >= fdata.length && util.IsBlank(line[i:]) {
 82			newline := 1
 83			if line[len(line)-1] != '\n' {
 84				newline = 0
 85			}
 86			reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
 87			return Close
 88		}
 89	}
 90	pos, padding := util.IndentPositionPadding(line, reader.LineOffset(), segment.Padding, fdata.indent)
 91	if pos < 0 {
 92		pos = util.FirstNonSpacePosition(line)
 93		if pos < 0 {
 94			pos = 0
 95		}
 96		padding = 0
 97	}
 98	seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
 99	// if code block line starts with a tab, keep a tab as it is.
100	if padding != 0 {
101		preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
102	}
103	seg.ForceNewline = true // EOF as newline
104	node.Lines().Append(seg)
105	reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
106	return Continue | NoChildren
107}
108
109func (b *fencedCodeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
110	fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
111	if fdata.node == node {
112		pc.Set(fencedCodeBlockInfoKey, nil)
113	}
114}
115
116func (b *fencedCodeBlockParser) CanInterruptParagraph() bool {
117	return true
118}
119
120func (b *fencedCodeBlockParser) CanAcceptIndentedLine() bool {
121	return false
122}