okami.mom/glamour

git clone https://git.nkp.pet/okami.mom/glamour

nekomimi.pettake code block text from Segments.Value and hang list items through IndentWriter052c391

main
10.3 KiB460 linesraw
1package ansi
2
3import (
4	"bytes"
5	"html"
6	"io"
7	"strings"
8
9	"charm.land/glamour/v2/internal/autolink"
10	east "github.com/yuin/goldmark-emoji/ast"
11	"github.com/yuin/goldmark/ast"
12	astext "github.com/yuin/goldmark/extension/ast"
13)
14
15// ElementRenderer is called when entering a markdown node.
16type ElementRenderer interface {
17	Render(w io.Writer, ctx RenderContext) error
18}
19
20// StyleOverriderElementRenderer is called when entering a markdown node with a specific style.
21type StyleOverriderElementRenderer interface {
22	StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error
23}
24
25// ElementFinisher is called when leaving a markdown node.
26type ElementFinisher interface {
27	Finish(w io.Writer, ctx RenderContext) error
28}
29
30// An Element is used to instruct the renderer how to handle individual markdown
31// nodes.
32type Element struct {
33	Entering string
34	Exiting  string
35	Renderer ElementRenderer
36	Finisher ElementFinisher
37}
38
39// NewElement returns the appropriate render Element for a given node.
40func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
41	ctx := tr.context
42	if element, ok := ctx.options.Elements[node.Kind()]; ok {
43		return element(node, source, ctx)
44	}
45
46	switch node.Kind() {
47	// Document
48	case ast.KindDocument:
49		e := &BlockElement{
50			Block:  &bytes.Buffer{},
51			Style:  ctx.options.Styles.Document,
52			Margin: true,
53		}
54		return Element{
55			Renderer: e,
56			Finisher: e,
57		}
58
59	// Heading
60	case ast.KindHeading:
61		n := node.(*ast.Heading)
62		he := &HeadingElement{
63			Level: n.Level,
64			First: node.PreviousSibling() == nil,
65		}
66		return Element{
67			Exiting:  "",
68			Renderer: he,
69			Finisher: he,
70		}
71
72	// Paragraph
73	case ast.KindParagraph:
74		return Element{
75			Renderer: &ParagraphElement{
76				First: node.PreviousSibling() == nil,
77			},
78			Finisher: &ParagraphElement{},
79		}
80
81	// Blockquote
82	case ast.KindBlockquote:
83		e := &BlockElement{
84			Block:  &bytes.Buffer{},
85			Style:  cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.BlockQuote, false),
86			Margin: true,
87		}
88		return Element{
89			Entering: "\n",
90			Renderer: e,
91			Finisher: e,
92		}
93
94	// Lists
95	case ast.KindList:
96		s := ctx.options.Styles.List.StyleBlock
97		if s.Indent == nil {
98			var i uint
99			s.Indent = &i
100		}
101		n := node.Parent()
102		for n != nil {
103			if n.Kind() == ast.KindList {
104				i := ctx.options.Styles.List.LevelIndent
105				s.Indent = &i
106				break
107			}
108			n = n.Parent()
109		}
110
111		e := &BlockElement{
112			Block:   &bytes.Buffer{},
113			Style:   cascadeStyle(ctx.blockStack.Current().Style, s, false),
114			Margin:  true,
115			Newline: true,
116		}
117		return Element{
118			Entering: "\n",
119			Renderer: e,
120			Finisher: e,
121		}
122
123	case ast.KindListItem:
124		var l uint
125		var e uint
126		l = 1
127		n := node
128		for n.PreviousSibling() != nil && (n.PreviousSibling().Kind() == ast.KindListItem) {
129			l++
130			n = n.PreviousSibling()
131		}
132		if node.Parent().(*ast.List).IsOrdered() {
133			e = l
134			if node.Parent().(*ast.List).Start != 1 {
135				e += uint(node.Parent().(*ast.List).Start) - 1
136			}
137		}
138
139		post := "\n"
140		if (node.LastChild() != nil && node.LastChild().Kind() == ast.KindList) ||
141			node.NextSibling() == nil {
142			post = ""
143		}
144
145		var marker ElementRenderer = &ItemElement{
146			IsOrdered:   node.Parent().(*ast.List).IsOrdered(),
147			Enumeration: e,
148		}
149		if node.FirstChild() != nil &&
150			node.FirstChild().FirstChild() != nil &&
151			node.FirstChild().FirstChild().Kind() == astext.KindTaskCheckBox {
152			nc := node.FirstChild().FirstChild().(*astext.TaskCheckBox)
153			marker = &TaskElement{Checked: nc.IsChecked}
154		}
155		item := &ListItemElement{Marker: marker}
156		return Element{
157			Exiting:  post,
158			Renderer: item,
159			Finisher: item,
160		}
161
162	// Text Elements
163	case ast.KindText:
164		n := node.(*ast.Text)
165		s := string(n.Segment.Value(source))
166
167		if n.HardLineBreak() || (n.SoftLineBreak()) {
168			s += "\n"
169		}
170		return Element{
171			Renderer: &BaseElement{
172				Token: html.UnescapeString(s),
173				Style: ctx.options.Styles.Text,
174			},
175		}
176
177	case ast.KindEmphasis:
178		n := node.(*ast.Emphasis)
179		var children []ElementRenderer
180		nn := n.FirstChild()
181		for nn != nil {
182			children = append(children, tr.NewElement(nn, source).Renderer)
183			nn = nn.NextSibling()
184		}
185		return Element{
186			Renderer: &EmphasisElement{
187				Level:    n.Level,
188				Children: children,
189			},
190		}
191
192	case astext.KindStrikethrough:
193		n := node.(*astext.Strikethrough)
194		s := string(n.Text(source)) //nolint: staticcheck
195		style := ctx.options.Styles.Strikethrough
196
197		return Element{
198			Renderer: &BaseElement{
199				Token: html.UnescapeString(s),
200				Style: style,
201			},
202		}
203
204	case ast.KindThematicBreak:
205		return Element{
206			Entering: "",
207			Exiting:  "",
208			Renderer: &BaseElement{
209				Style: ctx.options.Styles.HorizontalRule,
210			},
211		}
212
213	// Links
214	case ast.KindLink:
215		n := node.(*ast.Link)
216		isFooterLinks := !ctx.options.InlineTableLinks && isInsideTable(node)
217
218		var children []ElementRenderer
219		content, err := nodeContent(node, source)
220
221		if isFooterLinks && err == nil {
222			text := string(content)
223			tl := tableLink{
224				content:  text,
225				href:     string(n.Destination),
226				title:    string(n.Title),
227				linkType: linkTypeRegular,
228			}
229			text = linkWithSuffix(tl, ctx.table.tableLinks)
230			children = []ElementRenderer{&BaseElement{Token: text}}
231		} else {
232			nn := n.FirstChild()
233			for nn != nil {
234				children = append(children, tr.NewElement(nn, source).Renderer)
235				nn = nn.NextSibling()
236			}
237		}
238
239		return Element{
240			Renderer: &LinkElement{
241				BaseURL:  ctx.options.BaseURL,
242				URL:      string(n.Destination),
243				Children: children,
244				SkipHref: isFooterLinks,
245			},
246		}
247	case ast.KindAutoLink:
248		n := node.(*ast.AutoLink)
249		u := string(n.URL(source))
250		isFooterLinks := !ctx.options.InlineTableLinks && isInsideTable(node)
251
252		var children []ElementRenderer
253		nn := n.FirstChild()
254		for nn != nil {
255			children = append(children, tr.NewElement(nn, source).Renderer)
256			nn = nn.NextSibling()
257		}
258
259		if len(children) == 0 {
260			children = append(children, &BaseElement{Token: u})
261		}
262
263		if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(u), "mailto:") {
264			u = "mailto:" + u
265		}
266
267		var renderer ElementRenderer
268		if isFooterLinks {
269			domain := linkDomain(u)
270			tl := tableLink{
271				content:  domain,
272				href:     u,
273				linkType: linkTypeAuto,
274			}
275			if shortned, ok := autolink.Detect(u); ok {
276				tl.content = shortned
277			}
278			text := linkWithSuffix(tl, ctx.table.tableLinks)
279
280			renderer = &LinkElement{
281				Children: []ElementRenderer{&BaseElement{Token: text}},
282				URL:      u,
283				SkipHref: true,
284			}
285		} else {
286			isEmail := n.AutoLinkType == ast.AutoLinkEmail
287			renderer = &LinkElement{
288				Children: children,
289				URL:      u,
290				SkipText: !isEmail, // For non-email links, skip text (show only href)
291				SkipHref: isEmail,  // For email links, skip href (hide mailto: URL)
292			}
293		}
294		return Element{Renderer: renderer}
295
296	// Images
297	case ast.KindImage:
298		n := node.(*ast.Image)
299		text := string(n.Text(source)) //nolint: staticcheck
300		isFooterLinks := !ctx.options.InlineTableLinks && isInsideTable(node)
301
302		if isFooterLinks {
303			if text == "" {
304				text = linkDomain(string(n.Destination))
305			}
306			tl := tableLink{
307				title:    string(n.Title),
308				content:  text,
309				href:     string(n.Destination),
310				linkType: linkTypeImage,
311			}
312			text = linkWithSuffix(tl, ctx.table.tableImages)
313		}
314
315		return Element{
316			Renderer: &ImageElement{
317				Text:     text,
318				BaseURL:  ctx.options.BaseURL,
319				URL:      string(n.Destination),
320				TextOnly: isFooterLinks,
321			},
322		}
323
324	// Code
325	case ast.KindFencedCodeBlock:
326		n := node.(*ast.FencedCodeBlock)
327		return Element{
328			Entering: "\n",
329			Renderer: &CodeBlockElement{
330				Code:     string(n.Lines().Value(source)),
331				Language: string(n.Language(source)),
332			},
333		}
334
335	case ast.KindCodeBlock:
336		n := node.(*ast.CodeBlock)
337		return Element{
338			Entering: "\n",
339			Renderer: &CodeBlockElement{
340				Code: string(n.Lines().Value(source)),
341			},
342		}
343
344	case ast.KindCodeSpan:
345		n := node.(*ast.CodeSpan)
346		s := string(n.Text(source)) //nolint: staticcheck
347		return Element{
348			Renderer: &CodeSpanElement{
349				Text:  html.UnescapeString(s),
350				Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.Code, false).StylePrimitive,
351			},
352		}
353
354	// Tables
355	case astext.KindTable:
356		table := node.(*astext.Table)
357		te := &TableElement{
358			table:  table,
359			source: source,
360		}
361		return Element{
362			Entering: "\n",
363			Exiting:  "\n",
364			Renderer: te,
365			Finisher: te,
366		}
367
368	case astext.KindTableCell:
369		n := node.(*astext.TableCell)
370		var children []ElementRenderer
371		nn := n.FirstChild()
372		for nn != nil {
373			children = append(children, tr.NewElement(nn, source).Renderer)
374			nn = nn.NextSibling()
375		}
376
377		r := &TableCellElement{
378			Children: children,
379			Head:     node.Parent().Kind() == astext.KindTableHeader,
380		}
381		return Element{
382			Renderer: r,
383		}
384
385	case astext.KindTableHeader:
386		return Element{
387			Finisher: &TableHeadElement{},
388		}
389	case astext.KindTableRow:
390		return Element{
391			Finisher: &TableRowElement{},
392		}
393
394	// HTML Elements
395	case ast.KindHTMLBlock:
396		n := node.(*ast.HTMLBlock)
397		return Element{
398			Renderer: &BaseElement{
399				Token: ctx.SanitizeHTML(string(n.Text(source)), true), //nolint: staticcheck
400				Style: ctx.options.Styles.HTMLBlock.StylePrimitive,
401			},
402		}
403	case ast.KindRawHTML:
404		n := node.(*ast.RawHTML)
405		return Element{
406			Renderer: &BaseElement{
407				Token: ctx.SanitizeHTML(string(n.Text(source)), true), //nolint: staticcheck
408				Style: ctx.options.Styles.HTMLSpan.StylePrimitive,
409			},
410		}
411
412	// Definition Lists
413	case astext.KindDefinitionList:
414		e := &BlockElement{
415			Block:   &bytes.Buffer{},
416			Style:   cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.DefinitionList, false),
417			Margin:  true,
418			Newline: true,
419		}
420		return Element{
421			Renderer: e,
422			Finisher: e,
423		}
424
425	case astext.KindDefinitionTerm:
426		return Element{
427			Entering: "\n",
428			Renderer: &BaseElement{
429				Style: ctx.options.Styles.DefinitionTerm,
430			},
431		}
432
433	case astext.KindDefinitionDescription:
434		return Element{
435			Exiting: "\n",
436			Renderer: &BaseElement{
437				Style: ctx.options.Styles.DefinitionDescription,
438			},
439		}
440
441	// Handled by parents
442	case astext.KindTaskCheckBox:
443		// handled by KindListItem
444		return Element{}
445	case ast.KindTextBlock:
446		return Element{}
447
448	case east.KindEmoji:
449		n := node.(*east.Emoji)
450		return Element{
451			Renderer: &BaseElement{
452				Token: string(n.Value.Unicode),
453			},
454		}
455
456	// Unknown kinds render nothing; their children are still walked.
457	default:
458		return Element{}
459	}
460}