...
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 var temporaryParagraphKey = NewContextKey()
10
11 type setextHeadingParser struct {
12 HeadingConfig
13 }
14
15 func matchesSetextHeadingBar(line []byte) (byte, bool) {
16 start := 0
17 end := len(line)
18 space := util.TrimLeftLength(line, []byte{' '})
19 if space > 3 {
20 return 0, false
21 }
22 start += space
23 level1 := util.TrimLeftLength(line[start:end], []byte{'='})
24 c := byte('=')
25 var level2 int
26 if level1 == 0 {
27 level2 = util.TrimLeftLength(line[start:end], []byte{'-'})
28 c = '-'
29 }
30 if util.IsSpace(line[end-1]) {
31 end -= util.TrimRightSpaceLength(line[start:end])
32 }
33 if !((level1 > 0 && start+level1 == end) || (level2 > 0 && start+level2 == end)) {
34 return 0, false
35 }
36 return c, true
37 }
38
39
40 func NewSetextHeadingParser(opts ...HeadingOption) BlockParser {
41 p := &setextHeadingParser{}
42 for _, o := range opts {
43 o.SetHeadingOption(&p.HeadingConfig)
44 }
45 return p
46 }
47
48 func (b *setextHeadingParser) Trigger() []byte {
49 return []byte{'-', '='}
50 }
51
52 func (b *setextHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
53 last := pc.LastOpenedBlock().Node
54 if last == nil {
55 return nil, NoChildren
56 }
57 paragraph, ok := last.(*ast.Paragraph)
58 if !ok || paragraph.Parent() != parent {
59 return nil, NoChildren
60 }
61 line, segment := reader.PeekLine()
62 c, ok := matchesSetextHeadingBar(line)
63 if !ok {
64 return nil, NoChildren
65 }
66 level := 1
67 if c == '-' {
68 level = 2
69 }
70 node := ast.NewHeading(level)
71 node.Lines().Append(segment)
72 pc.Set(temporaryParagraphKey, last)
73 return node, NoChildren | RequireParagraph
74 }
75
76 func (b *setextHeadingParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
77 return Close
78 }
79
80 func (b *setextHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
81 heading := node.(*ast.Heading)
82 segment := node.Lines().At(0)
83 heading.Lines().Clear()
84 tmp := pc.Get(temporaryParagraphKey).(*ast.Paragraph)
85 pc.Set(temporaryParagraphKey, nil)
86 if tmp.Lines().Len() == 0 {
87 next := heading.NextSibling()
88 segment = segment.TrimLeftSpace(reader.Source())
89 if next == nil || !ast.IsParagraph(next) {
90 para := ast.NewParagraph()
91 para.Lines().Append(segment)
92 heading.Parent().InsertAfter(heading.Parent(), heading, para)
93 } else {
94 next.Lines().Unshift(segment)
95 }
96 heading.Parent().RemoveChild(heading.Parent(), heading)
97 } else {
98 heading.SetLines(tmp.Lines())
99 heading.SetBlankPreviousLines(tmp.HasBlankPreviousLines())
100 tp := tmp.Parent()
101 if tp != nil {
102 tp.RemoveChild(tp, tmp)
103 }
104 }
105
106 if b.Attribute {
107 parseLastLineAttributes(node, reader, pc)
108 }
109
110 if b.AutoHeadingID {
111 id, ok := node.AttributeString("id")
112 if !ok {
113 generateAutoHeadingID(heading, reader, pc)
114 } else {
115 pc.IDs().Put(id.([]byte))
116 }
117 }
118 }
119
120 func (b *setextHeadingParser) CanInterruptParagraph() bool {
121 return true
122 }
123
124 func (b *setextHeadingParser) CanAcceptIndentedLine() bool {
125 return false
126 }
127
View as plain text