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