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(
117 parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
118 line, _ := reader.PeekLine()
119 pos := pc.BlockOffset()
120 indent := pc.BlockIndent()
121 if pos < 0 || line[pos] != ':' || indent != 0 {
122 return nil, parser.NoChildren
123 }
124 list, _ := parent.(*ast.DefinitionList)
125 if list == nil {
126 return nil, parser.NoChildren
127 }
128 para := list.TemporaryParagraph
129 list.TemporaryParagraph = nil
130 if para != nil {
131 lines := para.Lines()
132 l := lines.Len()
133 for i := 0; i < l; i++ {
134 term := ast.NewDefinitionTerm()
135 segment := lines.At(i)
136 term.Lines().Append(segment.TrimRightSpace(reader.Source()))
137 list.AppendChild(list, term)
138 }
139 para.Parent().RemoveChild(para.Parent(), para)
140 }
141 cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
142 reader.AdvanceAndSetPadding(cpos+1, padding)
143
144 return ast.NewDefinitionDescription(), parser.HasChildren
145 }
146
147 func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
148
149
150 return parser.Continue | parser.HasChildren
151 }
152
153 func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
154 desc := node.(*ast.DefinitionDescription)
155 desc.IsTight = !desc.HasBlankPreviousLines()
156 if desc.IsTight {
157 for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
158 paragraph, ok := gc.(*gast.Paragraph)
159 if ok {
160 textBlock := gast.NewTextBlock()
161 textBlock.SetLines(paragraph.Lines())
162 desc.ReplaceChild(desc, paragraph, textBlock)
163 }
164 }
165 }
166 }
167
168 func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
169 return true
170 }
171
172 func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
173 return false
174 }
175
176
177
178 type DefinitionListHTMLRenderer struct {
179 html.Config
180 }
181
182
183 func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
184 r := &DefinitionListHTMLRenderer{
185 Config: html.NewConfig(),
186 }
187 for _, opt := range opts {
188 opt.SetHTMLOption(&r.Config)
189 }
190 return r
191 }
192
193
194 func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
195 reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
196 reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
197 reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
198 }
199
200
201 var DefinitionListAttributeFilter = html.GlobalAttributeFilter
202
203 func (r *DefinitionListHTMLRenderer) renderDefinitionList(
204 w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
205 if entering {
206 if n.Attributes() != nil {
207 _, _ = w.WriteString("<dl")
208 html.RenderAttributes(w, n, DefinitionListAttributeFilter)
209 _, _ = w.WriteString(">\n")
210 } else {
211 _, _ = w.WriteString("<dl>\n")
212 }
213 } else {
214 _, _ = w.WriteString("</dl>\n")
215 }
216 return gast.WalkContinue, nil
217 }
218
219
220 var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
221
222 func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(
223 w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
224 if entering {
225 if n.Attributes() != nil {
226 _, _ = w.WriteString("<dt")
227 html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
228 _ = w.WriteByte('>')
229 } else {
230 _, _ = w.WriteString("<dt>")
231 }
232 } else {
233 _, _ = w.WriteString("</dt>\n")
234 }
235 return gast.WalkContinue, nil
236 }
237
238
239 var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
240
241 func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(
242 w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
243 if entering {
244 n := node.(*ast.DefinitionDescription)
245 _, _ = w.WriteString("<dd")
246 if n.Attributes() != nil {
247 html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
248 }
249 if n.IsTight {
250 _, _ = w.WriteString(">")
251 } else {
252 _, _ = w.WriteString(">\n")
253 }
254 } else {
255 _, _ = w.WriteString("</dd>\n")
256 }
257 return gast.WalkContinue, nil
258 }
259
260 type definitionList struct {
261 }
262
263
264 var DefinitionList = &definitionList{}
265
266 func (e *definitionList) Extend(m goldmark.Markdown) {
267 m.Parser().AddOptions(parser.WithBlockParsers(
268 util.Prioritized(NewDefinitionListParser(), 101),
269 util.Prioritized(NewDefinitionDescriptionParser(), 102),
270 ))
271 m.Renderer().AddOptions(renderer.WithNodeRenderers(
272 util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
273 ))
274 }
275
View as plain text