diff options
Diffstat (limited to 'vendor/github.com/alecthomas/chroma/v2/formatters')
| -rw-r--r-- | vendor/github.com/alecthomas/chroma/v2/formatters/html/html.go | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/vendor/github.com/alecthomas/chroma/v2/formatters/html/html.go b/vendor/github.com/alecthomas/chroma/v2/formatters/html/html.go new file mode 100644 index 0000000..0a45d87 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/formatters/html/html.go | |||
| @@ -0,0 +1,563 @@ | |||
| 1 | package html | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | "html" | ||
| 6 | "io" | ||
| 7 | "sort" | ||
| 8 | "strings" | ||
| 9 | |||
| 10 | "github.com/alecthomas/chroma/v2" | ||
| 11 | ) | ||
| 12 | |||
| 13 | // Option sets an option of the HTML formatter. | ||
| 14 | type Option func(f *Formatter) | ||
| 15 | |||
| 16 | // Standalone configures the HTML formatter for generating a standalone HTML document. | ||
| 17 | func Standalone(b bool) Option { return func(f *Formatter) { f.standalone = b } } | ||
| 18 | |||
| 19 | // ClassPrefix sets the CSS class prefix. | ||
| 20 | func ClassPrefix(prefix string) Option { return func(f *Formatter) { f.prefix = prefix } } | ||
| 21 | |||
| 22 | // WithClasses emits HTML using CSS classes, rather than inline styles. | ||
| 23 | func WithClasses(b bool) Option { return func(f *Formatter) { f.Classes = b } } | ||
| 24 | |||
| 25 | // WithAllClasses disables an optimisation that omits redundant CSS classes. | ||
| 26 | func WithAllClasses(b bool) Option { return func(f *Formatter) { f.allClasses = b } } | ||
| 27 | |||
| 28 | // WithCustomCSS sets user's custom CSS styles. | ||
| 29 | func WithCustomCSS(css map[chroma.TokenType]string) Option { | ||
| 30 | return func(f *Formatter) { | ||
| 31 | f.customCSS = css | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | // TabWidth sets the number of characters for a tab. Defaults to 8. | ||
| 36 | func TabWidth(width int) Option { return func(f *Formatter) { f.tabWidth = width } } | ||
| 37 | |||
| 38 | // PreventSurroundingPre prevents the surrounding pre tags around the generated code. | ||
| 39 | func PreventSurroundingPre(b bool) Option { | ||
| 40 | return func(f *Formatter) { | ||
| 41 | f.preventSurroundingPre = b | ||
| 42 | |||
| 43 | if b { | ||
| 44 | f.preWrapper = nopPreWrapper | ||
| 45 | } else { | ||
| 46 | f.preWrapper = defaultPreWrapper | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | // InlineCode creates inline code wrapped in a code tag. | ||
| 52 | func InlineCode(b bool) Option { | ||
| 53 | return func(f *Formatter) { | ||
| 54 | f.inlineCode = b | ||
| 55 | f.preWrapper = preWrapper{ | ||
| 56 | start: func(code bool, styleAttr string) string { | ||
| 57 | if code { | ||
| 58 | return fmt.Sprintf(`<code%s>`, styleAttr) | ||
| 59 | } | ||
| 60 | |||
| 61 | return `` | ||
| 62 | }, | ||
| 63 | end: func(code bool) string { | ||
| 64 | if code { | ||
| 65 | return `</code>` | ||
| 66 | } | ||
| 67 | |||
| 68 | return `` | ||
| 69 | }, | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | // WithPreWrapper allows control of the surrounding pre tags. | ||
| 75 | func WithPreWrapper(wrapper PreWrapper) Option { | ||
| 76 | return func(f *Formatter) { | ||
| 77 | f.preWrapper = wrapper | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | // WrapLongLines wraps long lines. | ||
| 82 | func WrapLongLines(b bool) Option { | ||
| 83 | return func(f *Formatter) { | ||
| 84 | f.wrapLongLines = b | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | // WithLineNumbers formats output with line numbers. | ||
| 89 | func WithLineNumbers(b bool) Option { | ||
| 90 | return func(f *Formatter) { | ||
| 91 | f.lineNumbers = b | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | // LineNumbersInTable will, when combined with WithLineNumbers, separate the line numbers | ||
| 96 | // and code in table td's, which make them copy-and-paste friendly. | ||
| 97 | func LineNumbersInTable(b bool) Option { | ||
| 98 | return func(f *Formatter) { | ||
| 99 | f.lineNumbersInTable = b | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | // LinkableLineNumbers decorates the line numbers HTML elements with an "id" | ||
| 104 | // attribute so they can be linked. | ||
| 105 | func LinkableLineNumbers(b bool, prefix string) Option { | ||
| 106 | return func(f *Formatter) { | ||
| 107 | f.linkableLineNumbers = b | ||
| 108 | f.lineNumbersIDPrefix = prefix | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | // HighlightLines higlights the given line ranges with the Highlight style. | ||
| 113 | // | ||
| 114 | // A range is the beginning and ending of a range as 1-based line numbers, inclusive. | ||
| 115 | func HighlightLines(ranges [][2]int) Option { | ||
| 116 | return func(f *Formatter) { | ||
| 117 | f.highlightRanges = ranges | ||
| 118 | sort.Sort(f.highlightRanges) | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | // BaseLineNumber sets the initial number to start line numbering at. Defaults to 1. | ||
| 123 | func BaseLineNumber(n int) Option { | ||
| 124 | return func(f *Formatter) { | ||
| 125 | f.baseLineNumber = n | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | // New HTML formatter. | ||
| 130 | func New(options ...Option) *Formatter { | ||
| 131 | f := &Formatter{ | ||
| 132 | baseLineNumber: 1, | ||
| 133 | preWrapper: defaultPreWrapper, | ||
| 134 | } | ||
| 135 | for _, option := range options { | ||
| 136 | option(f) | ||
| 137 | } | ||
| 138 | return f | ||
| 139 | } | ||
| 140 | |||
| 141 | // PreWrapper defines the operations supported in WithPreWrapper. | ||
| 142 | type PreWrapper interface { | ||
| 143 | // Start is called to write a start <pre> element. | ||
| 144 | // The code flag tells whether this block surrounds | ||
| 145 | // highlighted code. This will be false when surrounding | ||
| 146 | // line numbers. | ||
| 147 | Start(code bool, styleAttr string) string | ||
| 148 | |||
| 149 | // End is called to write the end </pre> element. | ||
| 150 | End(code bool) string | ||
| 151 | } | ||
| 152 | |||
| 153 | type preWrapper struct { | ||
| 154 | start func(code bool, styleAttr string) string | ||
| 155 | end func(code bool) string | ||
| 156 | } | ||
| 157 | |||
| 158 | func (p preWrapper) Start(code bool, styleAttr string) string { | ||
| 159 | return p.start(code, styleAttr) | ||
| 160 | } | ||
| 161 | |||
| 162 | func (p preWrapper) End(code bool) string { | ||
| 163 | return p.end(code) | ||
| 164 | } | ||
| 165 | |||
| 166 | var ( | ||
| 167 | nopPreWrapper = preWrapper{ | ||
| 168 | start: func(code bool, styleAttr string) string { return "" }, | ||
| 169 | end: func(code bool) string { return "" }, | ||
| 170 | } | ||
| 171 | defaultPreWrapper = preWrapper{ | ||
| 172 | start: func(code bool, styleAttr string) string { | ||
| 173 | if code { | ||
| 174 | return fmt.Sprintf(`<pre tabindex="0"%s><code>`, styleAttr) | ||
| 175 | } | ||
| 176 | |||
| 177 | return fmt.Sprintf(`<pre tabindex="0"%s>`, styleAttr) | ||
| 178 | }, | ||
| 179 | end: func(code bool) string { | ||
| 180 | if code { | ||
| 181 | return `</code></pre>` | ||
| 182 | } | ||
| 183 | |||
| 184 | return `</pre>` | ||
| 185 | }, | ||
| 186 | } | ||
| 187 | ) | ||
| 188 | |||
| 189 | // Formatter that generates HTML. | ||
| 190 | type Formatter struct { | ||
| 191 | standalone bool | ||
| 192 | prefix string | ||
| 193 | Classes bool // Exported field to detect when classes are being used | ||
| 194 | allClasses bool | ||
| 195 | customCSS map[chroma.TokenType]string | ||
| 196 | preWrapper PreWrapper | ||
| 197 | inlineCode bool | ||
| 198 | preventSurroundingPre bool | ||
| 199 | tabWidth int | ||
| 200 | wrapLongLines bool | ||
| 201 | lineNumbers bool | ||
| 202 | lineNumbersInTable bool | ||
| 203 | linkableLineNumbers bool | ||
| 204 | lineNumbersIDPrefix string | ||
| 205 | highlightRanges highlightRanges | ||
| 206 | baseLineNumber int | ||
| 207 | } | ||
| 208 | |||
| 209 | type highlightRanges [][2]int | ||
| 210 | |||
| 211 | func (h highlightRanges) Len() int { return len(h) } | ||
| 212 | func (h highlightRanges) Swap(i, j int) { h[i], h[j] = h[j], h[i] } | ||
| 213 | func (h highlightRanges) Less(i, j int) bool { return h[i][0] < h[j][0] } | ||
| 214 | |||
| 215 | func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) { | ||
| 216 | return f.writeHTML(w, style, iterator.Tokens()) | ||
| 217 | } | ||
| 218 | |||
| 219 | // We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked). | ||
| 220 | // | ||
| 221 | // OTOH we need to be super careful about correct escaping... | ||
| 222 | func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.Token) (err error) { // nolint: gocyclo | ||
| 223 | css := f.styleToCSS(style) | ||
| 224 | if !f.Classes { | ||
| 225 | for t, style := range css { | ||
| 226 | css[t] = compressStyle(style) | ||
| 227 | } | ||
| 228 | } | ||
| 229 | if f.standalone { | ||
| 230 | fmt.Fprint(w, "<html>\n") | ||
| 231 | if f.Classes { | ||
| 232 | fmt.Fprint(w, "<style type=\"text/css\">\n") | ||
| 233 | err = f.WriteCSS(w, style) | ||
| 234 | if err != nil { | ||
| 235 | return err | ||
| 236 | } | ||
| 237 | fmt.Fprintf(w, "body { %s; }\n", css[chroma.Background]) | ||
| 238 | fmt.Fprint(w, "</style>") | ||
| 239 | } | ||
| 240 | fmt.Fprintf(w, "<body%s>\n", f.styleAttr(css, chroma.Background)) | ||
| 241 | } | ||
| 242 | |||
| 243 | wrapInTable := f.lineNumbers && f.lineNumbersInTable | ||
| 244 | |||
| 245 | lines := chroma.SplitTokensIntoLines(tokens) | ||
| 246 | lineDigits := len(fmt.Sprintf("%d", f.baseLineNumber+len(lines)-1)) | ||
| 247 | highlightIndex := 0 | ||
| 248 | |||
| 249 | if wrapInTable { | ||
| 250 | // List line numbers in its own <td> | ||
| 251 | fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.PreWrapper)) | ||
| 252 | fmt.Fprintf(w, "<table%s><tr>", f.styleAttr(css, chroma.LineTable)) | ||
| 253 | fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD)) | ||
| 254 | fmt.Fprintf(w, f.preWrapper.Start(false, f.styleAttr(css, chroma.PreWrapper))) | ||
| 255 | for index := range lines { | ||
| 256 | line := f.baseLineNumber + index | ||
| 257 | highlight, next := f.shouldHighlight(highlightIndex, line) | ||
| 258 | if next { | ||
| 259 | highlightIndex++ | ||
| 260 | } | ||
| 261 | if highlight { | ||
| 262 | fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight)) | ||
| 263 | } | ||
| 264 | |||
| 265 | fmt.Fprintf(w, "<span%s%s>%s\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line)) | ||
| 266 | |||
| 267 | if highlight { | ||
| 268 | fmt.Fprintf(w, "</span>") | ||
| 269 | } | ||
| 270 | } | ||
| 271 | fmt.Fprint(w, f.preWrapper.End(false)) | ||
| 272 | fmt.Fprint(w, "</td>\n") | ||
| 273 | fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%")) | ||
| 274 | } | ||
| 275 | |||
| 276 | fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.PreWrapper))) | ||
| 277 | |||
| 278 | highlightIndex = 0 | ||
| 279 | for index, tokens := range lines { | ||
| 280 | // 1-based line number. | ||
| 281 | line := f.baseLineNumber + index | ||
| 282 | highlight, next := f.shouldHighlight(highlightIndex, line) | ||
| 283 | if next { | ||
| 284 | highlightIndex++ | ||
| 285 | } | ||
| 286 | |||
| 287 | if !(f.preventSurroundingPre || f.inlineCode) { | ||
| 288 | // Start of Line | ||
| 289 | fmt.Fprint(w, `<span`) | ||
| 290 | |||
| 291 | if highlight { | ||
| 292 | // Line + LineHighlight | ||
| 293 | if f.Classes { | ||
| 294 | fmt.Fprintf(w, ` class="%s %s"`, f.class(chroma.Line), f.class(chroma.LineHighlight)) | ||
| 295 | } else { | ||
| 296 | fmt.Fprintf(w, ` style="%s %s"`, css[chroma.Line], css[chroma.LineHighlight]) | ||
| 297 | } | ||
| 298 | fmt.Fprint(w, `>`) | ||
| 299 | } else { | ||
| 300 | fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line)) | ||
| 301 | } | ||
| 302 | |||
| 303 | // Line number | ||
| 304 | if f.lineNumbers && !wrapInTable { | ||
| 305 | fmt.Fprintf(w, "<span%s%s>%s</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line)) | ||
| 306 | } | ||
| 307 | |||
| 308 | fmt.Fprintf(w, `<span%s>`, f.styleAttr(css, chroma.CodeLine)) | ||
| 309 | } | ||
| 310 | |||
| 311 | for _, token := range tokens { | ||
| 312 | html := html.EscapeString(token.String()) | ||
| 313 | attr := f.styleAttr(css, token.Type) | ||
| 314 | if attr != "" { | ||
| 315 | html = fmt.Sprintf("<span%s>%s</span>", attr, html) | ||
| 316 | } | ||
| 317 | fmt.Fprint(w, html) | ||
| 318 | } | ||
| 319 | |||
| 320 | if !(f.preventSurroundingPre || f.inlineCode) { | ||
| 321 | fmt.Fprint(w, `</span>`) // End of CodeLine | ||
| 322 | |||
| 323 | fmt.Fprint(w, `</span>`) // End of Line | ||
| 324 | } | ||
| 325 | } | ||
| 326 | fmt.Fprintf(w, f.preWrapper.End(true)) | ||
| 327 | |||
| 328 | if wrapInTable { | ||
| 329 | fmt.Fprint(w, "</td></tr></table>\n") | ||
| 330 | fmt.Fprint(w, "</div>\n") | ||
| 331 | } | ||
| 332 | |||
| 333 | if f.standalone { | ||
| 334 | fmt.Fprint(w, "\n</body>\n") | ||
| 335 | fmt.Fprint(w, "</html>\n") | ||
| 336 | } | ||
| 337 | |||
| 338 | return nil | ||
| 339 | } | ||
| 340 | |||
| 341 | func (f *Formatter) lineIDAttribute(line int) string { | ||
| 342 | if !f.linkableLineNumbers { | ||
| 343 | return "" | ||
| 344 | } | ||
| 345 | return fmt.Sprintf(" id=\"%s\"", f.lineID(line)) | ||
| 346 | } | ||
| 347 | |||
| 348 | func (f *Formatter) lineTitleWithLinkIfNeeded(lineDigits, line int) string { | ||
| 349 | title := fmt.Sprintf("%*d", lineDigits, line) | ||
| 350 | if !f.linkableLineNumbers { | ||
| 351 | return title | ||
| 352 | } | ||
| 353 | return fmt.Sprintf("<a style=\"outline: none; text-decoration:none; color:inherit\" href=\"#%s\">%s</a>", f.lineID(line), title) | ||
| 354 | } | ||
| 355 | |||
| 356 | func (f *Formatter) lineID(line int) string { | ||
| 357 | return fmt.Sprintf("%s%d", f.lineNumbersIDPrefix, line) | ||
| 358 | } | ||
| 359 | |||
| 360 | func (f *Formatter) shouldHighlight(highlightIndex, line int) (bool, bool) { | ||
| 361 | next := false | ||
| 362 | for highlightIndex < len(f.highlightRanges) && line > f.highlightRanges[highlightIndex][1] { | ||
| 363 | highlightIndex++ | ||
| 364 | next = true | ||
| 365 | } | ||
| 366 | if highlightIndex < len(f.highlightRanges) { | ||
| 367 | hrange := f.highlightRanges[highlightIndex] | ||
| 368 | if line >= hrange[0] && line <= hrange[1] { | ||
| 369 | return true, next | ||
| 370 | } | ||
| 371 | } | ||
| 372 | return false, next | ||
| 373 | } | ||
| 374 | |||
| 375 | func (f *Formatter) class(t chroma.TokenType) string { | ||
| 376 | for t != 0 { | ||
| 377 | if cls, ok := chroma.StandardTypes[t]; ok { | ||
| 378 | if cls != "" { | ||
| 379 | return f.prefix + cls | ||
| 380 | } | ||
| 381 | return "" | ||
| 382 | } | ||
| 383 | t = t.Parent() | ||
| 384 | } | ||
| 385 | if cls := chroma.StandardTypes[t]; cls != "" { | ||
| 386 | return f.prefix + cls | ||
| 387 | } | ||
| 388 | return "" | ||
| 389 | } | ||
| 390 | |||
| 391 | func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType, extraCSS ...string) string { | ||
| 392 | if f.Classes { | ||
| 393 | cls := f.class(tt) | ||
| 394 | if cls == "" { | ||
| 395 | return "" | ||
| 396 | } | ||
| 397 | return fmt.Sprintf(` class="%s"`, cls) | ||
| 398 | } | ||
| 399 | if _, ok := styles[tt]; !ok { | ||
| 400 | tt = tt.SubCategory() | ||
| 401 | if _, ok := styles[tt]; !ok { | ||
| 402 | tt = tt.Category() | ||
| 403 | if _, ok := styles[tt]; !ok { | ||
| 404 | return "" | ||
| 405 | } | ||
| 406 | } | ||
| 407 | } | ||
| 408 | css := []string{styles[tt]} | ||
| 409 | css = append(css, extraCSS...) | ||
| 410 | return fmt.Sprintf(` style="%s"`, strings.Join(css, ";")) | ||
| 411 | } | ||
| 412 | |||
| 413 | func (f *Formatter) tabWidthStyle() string { | ||
| 414 | if f.tabWidth != 0 && f.tabWidth != 8 { | ||
| 415 | return fmt.Sprintf("-moz-tab-size: %[1]d; -o-tab-size: %[1]d; tab-size: %[1]d;", f.tabWidth) | ||
| 416 | } | ||
| 417 | return "" | ||
| 418 | } | ||
| 419 | |||
| 420 | // WriteCSS writes CSS style definitions (without any surrounding HTML). | ||
| 421 | func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error { | ||
| 422 | css := f.styleToCSS(style) | ||
| 423 | // Special-case background as it is mapped to the outer ".chroma" class. | ||
| 424 | if _, err := fmt.Fprintf(w, "/* %s */ .%sbg { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil { | ||
| 425 | return err | ||
| 426 | } | ||
| 427 | // Special-case PreWrapper as it is the ".chroma" class. | ||
| 428 | if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.PreWrapper, f.prefix, css[chroma.PreWrapper]); err != nil { | ||
| 429 | return err | ||
| 430 | } | ||
| 431 | // Special-case code column of table to expand width. | ||
| 432 | if f.lineNumbers && f.lineNumbersInTable { | ||
| 433 | if _, err := fmt.Fprintf(w, "/* %s */ .%schroma .%s:last-child { width: 100%%; }", | ||
| 434 | chroma.LineTableTD, f.prefix, f.class(chroma.LineTableTD)); err != nil { | ||
| 435 | return err | ||
| 436 | } | ||
| 437 | } | ||
| 438 | // Special-case line number highlighting when targeted. | ||
| 439 | if f.lineNumbers || f.lineNumbersInTable { | ||
| 440 | targetedLineCSS := StyleEntryToCSS(style.Get(chroma.LineHighlight)) | ||
| 441 | for _, tt := range []chroma.TokenType{chroma.LineNumbers, chroma.LineNumbersTable} { | ||
| 442 | fmt.Fprintf(w, "/* %s targeted by URL anchor */ .%schroma .%s:target { %s }\n", tt, f.prefix, f.class(tt), targetedLineCSS) | ||
| 443 | } | ||
| 444 | } | ||
| 445 | tts := []int{} | ||
| 446 | for tt := range css { | ||
| 447 | tts = append(tts, int(tt)) | ||
| 448 | } | ||
| 449 | sort.Ints(tts) | ||
| 450 | for _, ti := range tts { | ||
| 451 | tt := chroma.TokenType(ti) | ||
| 452 | switch tt { | ||
| 453 | case chroma.Background, chroma.PreWrapper: | ||
| 454 | continue | ||
| 455 | } | ||
| 456 | class := f.class(tt) | ||
| 457 | if class == "" { | ||
| 458 | continue | ||
| 459 | } | ||
| 460 | styles := css[tt] | ||
| 461 | if _, err := fmt.Fprintf(w, "/* %s */ .%schroma .%s { %s }\n", tt, f.prefix, class, styles); err != nil { | ||
| 462 | return err | ||
| 463 | } | ||
| 464 | } | ||
| 465 | return nil | ||
| 466 | } | ||
| 467 | |||
| 468 | func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string { | ||
| 469 | classes := map[chroma.TokenType]string{} | ||
| 470 | bg := style.Get(chroma.Background) | ||
| 471 | // Convert the style. | ||
| 472 | for t := range chroma.StandardTypes { | ||
| 473 | entry := style.Get(t) | ||
| 474 | if t != chroma.Background { | ||
| 475 | entry = entry.Sub(bg) | ||
| 476 | } | ||
| 477 | |||
| 478 | // Inherit from custom CSS provided by user | ||
| 479 | tokenCategory := t.Category() | ||
| 480 | tokenSubCategory := t.SubCategory() | ||
| 481 | if t != tokenCategory { | ||
| 482 | if css, ok := f.customCSS[tokenCategory]; ok { | ||
| 483 | classes[t] = css | ||
| 484 | } | ||
| 485 | } | ||
| 486 | if tokenCategory != tokenSubCategory { | ||
| 487 | if css, ok := f.customCSS[tokenSubCategory]; ok { | ||
| 488 | classes[t] += css | ||
| 489 | } | ||
| 490 | } | ||
| 491 | // Add custom CSS provided by user | ||
| 492 | if css, ok := f.customCSS[t]; ok { | ||
| 493 | classes[t] += css | ||
| 494 | } | ||
| 495 | |||
| 496 | if !f.allClasses && entry.IsZero() && classes[t] == `` { | ||
| 497 | continue | ||
| 498 | } | ||
| 499 | |||
| 500 | styleEntryCSS := StyleEntryToCSS(entry) | ||
| 501 | if styleEntryCSS != `` && classes[t] != `` { | ||
| 502 | styleEntryCSS += `;` | ||
| 503 | } | ||
| 504 | classes[t] = styleEntryCSS + classes[t] | ||
| 505 | } | ||
| 506 | classes[chroma.Background] += `;` + f.tabWidthStyle() | ||
| 507 | classes[chroma.PreWrapper] += classes[chroma.Background] | ||
| 508 | // Make PreWrapper a grid to show highlight style with full width. | ||
| 509 | if len(f.highlightRanges) > 0 && f.customCSS[chroma.PreWrapper] == `` { | ||
| 510 | classes[chroma.PreWrapper] += `display: grid;` | ||
| 511 | } | ||
| 512 | // Make PreWrapper wrap long lines. | ||
| 513 | if f.wrapLongLines { | ||
| 514 | classes[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;` | ||
| 515 | } | ||
| 516 | lineNumbersStyle := `white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;` | ||
| 517 | // All rules begin with default rules followed by user provided rules | ||
| 518 | classes[chroma.Line] = `display: flex;` + classes[chroma.Line] | ||
| 519 | classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers] | ||
| 520 | classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable] | ||
| 521 | classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTable] | ||
| 522 | classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD] | ||
| 523 | return classes | ||
| 524 | } | ||
| 525 | |||
| 526 | // StyleEntryToCSS converts a chroma.StyleEntry to CSS attributes. | ||
| 527 | func StyleEntryToCSS(e chroma.StyleEntry) string { | ||
| 528 | styles := []string{} | ||
| 529 | if e.Colour.IsSet() { | ||
| 530 | styles = append(styles, "color: "+e.Colour.String()) | ||
| 531 | } | ||
| 532 | if e.Background.IsSet() { | ||
| 533 | styles = append(styles, "background-color: "+e.Background.String()) | ||
| 534 | } | ||
| 535 | if e.Bold == chroma.Yes { | ||
| 536 | styles = append(styles, "font-weight: bold") | ||
| 537 | } | ||
| 538 | if e.Italic == chroma.Yes { | ||
| 539 | styles = append(styles, "font-style: italic") | ||
| 540 | } | ||
| 541 | if e.Underline == chroma.Yes { | ||
| 542 | styles = append(styles, "text-decoration: underline") | ||
| 543 | } | ||
| 544 | return strings.Join(styles, "; ") | ||
| 545 | } | ||
| 546 | |||
| 547 | // Compress CSS attributes - remove spaces, transform 6-digit colours to 3. | ||
| 548 | func compressStyle(s string) string { | ||
| 549 | parts := strings.Split(s, ";") | ||
| 550 | out := []string{} | ||
| 551 | for _, p := range parts { | ||
| 552 | p = strings.Join(strings.Fields(p), " ") | ||
| 553 | p = strings.Replace(p, ": ", ":", 1) | ||
| 554 | if strings.Contains(p, "#") { | ||
| 555 | c := p[len(p)-6:] | ||
| 556 | if c[0] == c[1] && c[2] == c[3] && c[4] == c[5] { | ||
| 557 | p = p[:len(p)-6] + c[0:1] + c[2:3] + c[4:5] | ||
| 558 | } | ||
| 559 | } | ||
| 560 | out = append(out, p) | ||
| 561 | } | ||
| 562 | return strings.Join(out, ";") | ||
| 563 | } | ||
