setext_headings.go (3079B)
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 // NewSetextHeadingParser return a new BlockParser that can parse Setext headings. 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.(ast.Node).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 }