forkjo/vendor/github.com/yuin/goldmark/parser/setext_headings.go

127 lines
3 KiB
Go
Raw Normal View History

package parser
import (
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
var temporaryParagraphKey = NewContextKey()
type setextHeadingParser struct {
HeadingConfig
}
func matchesSetextHeadingBar(line []byte) (byte, bool) {
start := 0
end := len(line)
space := util.TrimLeftLength(line, []byte{' '})
if space > 3 {
return 0, false
}
start += space
level1 := util.TrimLeftLength(line[start:end], []byte{'='})
c := byte('=')
var level2 int
if level1 == 0 {
level2 = util.TrimLeftLength(line[start:end], []byte{'-'})
c = '-'
}
if util.IsSpace(line[end-1]) {
end -= util.TrimRightSpaceLength(line[start:end])
}
if !((level1 > 0 && start+level1 == end) || (level2 > 0 && start+level2 == end)) {
return 0, false
}
return c, true
}
// NewSetextHeadingParser return a new BlockParser that can parse Setext headings.
func NewSetextHeadingParser(opts ...HeadingOption) BlockParser {
p := &setextHeadingParser{}
for _, o := range opts {
o.SetHeadingOption(&p.HeadingConfig)
}
return p
}
func (b *setextHeadingParser) Trigger() []byte {
return []byte{'-', '='}
}
func (b *setextHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
last := pc.LastOpenedBlock().Node
if last == nil {
return nil, NoChildren
}
paragraph, ok := last.(*ast.Paragraph)
if !ok || paragraph.Parent() != parent {
return nil, NoChildren
}
line, segment := reader.PeekLine()
c, ok := matchesSetextHeadingBar(line)
if !ok {
return nil, NoChildren
}
level := 1
if c == '-' {
level = 2
}
node := ast.NewHeading(level)
node.Lines().Append(segment)
pc.Set(temporaryParagraphKey, last)
return node, NoChildren | RequireParagraph
}
func (b *setextHeadingParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
return Close
}
func (b *setextHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
heading := node.(*ast.Heading)
segment := node.Lines().At(0)
heading.Lines().Clear()
tmp := pc.Get(temporaryParagraphKey).(*ast.Paragraph)
pc.Set(temporaryParagraphKey, nil)
if tmp.Lines().Len() == 0 {
next := heading.NextSibling()
segment = segment.TrimLeftSpace(reader.Source())
if next == nil || !ast.IsParagraph(next) {
para := ast.NewParagraph()
para.Lines().Append(segment)
heading.Parent().InsertAfter(heading.Parent(), heading, para)
} else {
next.(ast.Node).Lines().Unshift(segment)
}
heading.Parent().RemoveChild(heading.Parent(), heading)
} else {
heading.SetLines(tmp.Lines())
heading.SetBlankPreviousLines(tmp.HasBlankPreviousLines())
tp := tmp.Parent()
if tp != nil {
tp.RemoveChild(tp, tmp)
}
}
if b.Attribute {
parseLastLineAttributes(node, reader, pc)
}
if b.AutoHeadingID {
id, ok := node.AttributeString("id")
if !ok {
generateAutoHeadingID(heading, reader, pc)
} else {
pc.IDs().Put(id.([]byte))
}
}
}
func (b *setextHeadingParser) CanInterruptParagraph() bool {
return true
}
func (b *setextHeadingParser) CanAcceptIndentedLine() bool {
return false
}