1 package extension
2
3 import (
4 "github.com/yuin/goldmark"
5 gast "github.com/yuin/goldmark/ast"
6 "github.com/yuin/goldmark/extension/ast"
7 "github.com/yuin/goldmark/parser"
8 "github.com/yuin/goldmark/renderer"
9 "github.com/yuin/goldmark/renderer/html"
10 "github.com/yuin/goldmark/text"
11 "github.com/yuin/goldmark/util"
12 )
13
14 type definitionListParser struct {
15 }
16
17 var defaultDefinitionListParser = &definitionListParser{}
18
19
20
21 func NewDefinitionListParser() parser.BlockParser {
22 return defaultDefinitionListParser
23 }
24
25 func (b *definitionListParser) Trigger() []byte {
26 return []byte{':'}
27 }
28
29 func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
30 if _, ok := parent.(*ast.DefinitionList); ok {
31 return nil, parser.NoChildren
32 }
33 line, _ := reader.PeekLine()
34 pos := pc.BlockOffset()
35 indent := pc.BlockIndent()
36 if pos < 0 || line[pos] != ':' || indent != 0 {
37 return nil, parser.NoChildren
38 }
39
40 last := parent.LastChild()
41
42 w, _ := util.IndentWidth(line[pos+1:], pos+1)
43 if w < 1 {
44 return nil, parser.NoChildren
45 }
46 if w >= 8 {
47 w = 5
48 }
49 w += pos + 1
50
51 para, lastIsParagraph := last.(*gast.Paragraph)
52 var list *ast.DefinitionList
53 status := parser.HasChildren
54 var ok bool
55 if lastIsParagraph {
56 list, ok = last.PreviousSibling().(*ast.DefinitionList)
57 if ok {
58 list.Offset = w
59 list.TemporaryParagraph = para
60 } else {
61 list = ast.NewDefinitionList(w, para)
62 status |= parser.RequireParagraph
63 }
64 } else if list, ok = last.(*ast.DefinitionList); ok {
65 list.Offset = w
66 list.TemporaryParagraph = nil
67 } else {
68 return nil, parser.NoChildren
69 }
70
71 return list, status
72 }
73
74 func (b *definitionListParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
75 line, _ := reader.PeekLine()
76 if util.IsBlank(line) {
77 return parser.Continue | parser.HasChildren
78 }
79 list, _ := node.(*ast.DefinitionList)
80 w, _ := util.IndentWidth(line, reader.LineOffset())
81 if w < list.Offset {
82 return parser.Close
83 }
84 pos, padding := util.IndentPosition(line, reader.LineOffset(), list.Offset)
85 reader.AdvanceAndSetPadding(pos, padding)
86 return parser.Continue | parser.HasChildren
87 }
88
89 func (b *definitionListParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
90
91 }
92
93 func (b *definitionListParser) CanInterruptParagraph() bool {
94 return true
95 }
96
97 func (b *definitionListParser) CanAcceptIndentedLine() bool {
98 return false
99 }
100
101 type definitionDescriptionParser struct {
102 }
103
104 var defaultDefinitionDescriptionParser = &definitionDescriptionParser{}
105
106
107
108 func NewDefinitionDescriptionParser() parser.BlockParser {
109 return defaultDefinitionDescriptionParser
110 }
111
112 func (b *definitionDescriptionParser) Trigger() []byte {
113 return []byte{':'}
114 }
115
116 func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
117 line, _ := reader.PeekLine()
118 pos := pc.BlockOffset()
119 indent := pc.BlockIndent()
120 if pos < 0 || line[pos] != ':' || indent != 0 {
121 return nil, parser.NoChildren
122 }
123 list, _ := parent.(*ast.DefinitionList)
124 if list == nil {
125 return nil, parser.NoChildren
126 }
127 para := list.TemporaryParagraph
128 list.TemporaryParagraph = nil
129 if para != nil {
130 lines := para.Lines()
131 l := lines.Len()
132 for i := 0; i < l; i++ {
133 term := ast.NewDefinitionTerm()
134 segment := lines.At(i)
135 term.Lines().Append(segment.TrimRightSpace(reader.Source()))
136 list.AppendChild(list, term)
137 }
138 para.Parent().RemoveChild(para.Parent(), para)
139 }
140 cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
141 reader.AdvanceAndSetPadding(cpos+1, padding)
142
143 return ast.NewDefinitionDescription(), parser.HasChildren
144 }
145
146 func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
147
148
149 return parser.Continue | parser.HasChildren
150 }
151
152 func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
153 desc := node.(*ast.DefinitionDescription)
154 desc.IsTight = !desc.HasBlankPreviousLines()
155 if desc.IsTight {
156 for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
157 paragraph, ok := gc.(*gast.Paragraph)
158 if ok {
159 textBlock := gast.NewTextBlock()
160 textBlock.SetLines(paragraph.Lines())
161 desc.ReplaceChild(desc, paragraph, textBlock)
162 }
163 }
164 }
165 }
166
167 func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
168 return true
169 }
170
171 func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
172 return false
173 }
174
175
176
177 type DefinitionListHTMLRenderer struct {
178 html.Config
179 }
180
181
182 func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
183 r := &DefinitionListHTMLRenderer{
184 Config: html.NewConfig(),
185 }
186 for _, opt := range opts {
187 opt.SetHTMLOption(&r.Config)
188 }
189 return r
190 }
191
192
193 func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
194 reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
195 reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
196 reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
197 }
198
199
200 var DefinitionListAttributeFilter = html.GlobalAttributeFilter
201
202 func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
203 if entering {
204 if n.Attributes() != nil {
205 _, _ = w.WriteString("<dl")
206 html.RenderAttributes(w, n, DefinitionListAttributeFilter)
207 _, _ = w.WriteString(">\n")
208 } else {
209 _, _ = w.WriteString("<dl>\n")
210 }
211 } else {
212 _, _ = w.WriteString("</dl>\n")
213 }
214 return gast.WalkContinue, nil
215 }
216
217
218 var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
219
220 func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
221 if entering {
222 if n.Attributes() != nil {
223 _, _ = w.WriteString("<dt")
224 html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
225 _ = w.WriteByte('>')
226 } else {
227 _, _ = w.WriteString("<dt>")
228 }
229 } else {
230 _, _ = w.WriteString("</dt>\n")
231 }
232 return gast.WalkContinue, nil
233 }
234
235
236 var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
237
238 func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
239 if entering {
240 n := node.(*ast.DefinitionDescription)
241 _, _ = w.WriteString("<dd")
242 if n.Attributes() != nil {
243 html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
244 }
245 if n.IsTight {
246 _, _ = w.WriteString(">")
247 } else {
248 _, _ = w.WriteString(">\n")
249 }
250 } else {
251 _, _ = w.WriteString("</dd>\n")
252 }
253 return gast.WalkContinue, nil
254 }
255
256 type definitionList struct {
257 }
258
259
260 var DefinitionList = &definitionList{}
261
262 func (e *definitionList) Extend(m goldmark.Markdown) {
263 m.Parser().AddOptions(parser.WithBlockParsers(
264 util.Prioritized(NewDefinitionListParser(), 101),
265 util.Prioritized(NewDefinitionDescriptionParser(), 102),
266 ))
267 m.Renderer().AddOptions(renderer.WithNodeRenderers(
268 util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
269 ))
270 }
271
View as plain text