All files / testing / highlight.ts

100.00% Branches 9/9
100.00% Lines 56/56
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
 
x43
x43
x43
x43
x43
 
 
 
 
 
 
 
 
 
x43
x406
x406
x2493
x831
x1251
x1251
x831
x406
x406
x406
x407
x407
x407
x406
x407
x407
x407
x406
x407
x407
x406
x406
 
 
x43
x995
x995
x995
x995
x2054
x2054
x2054
x12930
x2586
x2054
x2581
x2581
x2581
x2054
x1420
x995
 
 
x43
x570
x570
x570
x570
x570
x570
x570
x570
x570
x570



































































// Imports
import hljs from "highlight.js/lib/core"
import typescript from "highlight.js/lib/languages/typescript"
import { bgBlack, bgWhite, bgYellow, black, blue, cyan, gray, green, stripAnsiCode, underline, yellow } from "@std/fmt/colors"
import { unescape } from "@std/html/entities"
hljs.registerLanguage("typescript", typescript)

/**
 * Syntax highlights code strings within backticks with ANSI codes.
 *
 * ```ts
 * import { highlight } from "./highlight.ts"
 * console.log(highlight("`const foo = 'bar'`"))
 * ```
 */
export function highlight(text: string, { underline: underlined = false, header = "", type = "" } = {} as { underline?: boolean; header?: string; type?: string }): string {
  text = text
    .replace(/`([^`]*?)`/g, (_, content) => {
      let highlighted = process(unescape(hljs.highlight(content, { language: "typescript" }).value))
      if (underlined) {
        highlighted = underline(highlighted)
      }
      return highlighted
    })
  let background = bgWhite
  if (type === "warn") {
    text = yellow(stripAnsiCode(text))
    background = bgYellow
  }
  if (type === "debug") {
    text = gray(stripAnsiCode(text))
    background = bgBlack
  }
  if (header) {
    text = `${background(black(` ${header} `))} ${text}`
  }
  return text
}

/** Process rendered highlight.js html back to cli formatted highlighing. */
function process(html: string) {
  const stack = []
  const regex = /(?<open><span[^>]*class="(?<classname>[^"]*)"[^>]*>)|(?<close><\/span>)/gs
  let match = null as ReturnType<typeof regex.exec>
  while ((match = regex.exec(html)) !== null) {
    const [captured] = match
    const { open, close, classname } = match.groups!
    if (open) {
      stack.push({ classname, a: match.index, b: match.index + captured.length })
    }
    if (close) {
      const { a, b, classname } = stack.pop()!
      return process(`${html.substring(0, a)}${color(html.substring(b, match.index), classname)}${html.substring(match.index + close.length)}`)
    }
  }
  return html
}

/** Color content according to highlight.js classname. */
function color(content: string, classname: string) {
  return ({
    "hljs-comment": gray,
    "hljs-keyword": cyan,
    "hljs-string": green,
    "hljs-title function_": blue,
    "hljs-title class_": blue,
    "hljs-literal": yellow,
    "hljs-number": yellow,
  }[classname] ?? ((text: string) => text))(content)
}