1
0

index.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { visit } from 'unist-util-visit'
  2. import toString from 'hast-util-to-string'
  3. import { refractor } from 'refractor'
  4. import rangeParser from 'parse-numeric-range'
  5. const getLanguage = (node) => {
  6. const className = node.properties.className || []
  7. for (const classListItem of className) {
  8. if (classListItem.slice(0, 9) === 'language-') {
  9. return classListItem.slice(9).toLowerCase()
  10. }
  11. }
  12. return null
  13. }
  14. // Create a closure that determines if we have to highlight the given index
  15. const calculateLinesToHighlight = (meta) => {
  16. const RE = /{([\d,-]+)}/
  17. if (RE.test(meta)) {
  18. const strlineNumbers = RE.exec(meta)[1]
  19. const lineNumbers = rangeParser(strlineNumbers)
  20. return (index) => lineNumbers.includes(index + 1)
  21. } else {
  22. return () => false
  23. }
  24. }
  25. const splitLine = (text) => {
  26. // Xdm Markdown parser every code line with \n
  27. const textArray = text.split(/\n/)
  28. // Remove last line \n which results in empty array
  29. if (textArray[textArray.length - 1].trim() === '') {
  30. textArray.pop()
  31. }
  32. return textArray.map((line) => {
  33. return {
  34. type: 'element',
  35. tagName: 'div',
  36. properties: { className: ['code-line'] },
  37. children: [{ type: 'text', value: line }],
  38. }
  39. })
  40. }
  41. const rehypePrism = (options) => {
  42. options = options || {}
  43. return (tree) => {
  44. visit(tree, 'element', visitor)
  45. }
  46. function visitor(node, index, parent) {
  47. if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') {
  48. return
  49. }
  50. const lang = getLanguage(node)
  51. const meta = node.data && node.data.meta ? node.data.meta : ''
  52. const shouldHighlightLine = calculateLinesToHighlight(meta)
  53. if (lang) {
  54. parent.properties.className = (parent.properties.className || []).concat('language-' + lang)
  55. }
  56. const codeLineArray = splitLine(toString(node))
  57. for (const [i, line] of codeLineArray.entries()) {
  58. // Code lines
  59. if (meta.includes('showLineNumbers') || options.showLineNumbers) {
  60. line.properties.line = [(i + 1).toString()]
  61. line.properties.className = [`${line.properties.className} line-number`]
  62. }
  63. // Line highlight
  64. if (shouldHighlightLine(i)) {
  65. line.properties.className = [`${line.properties.className} highlight-line`]
  66. }
  67. // Syntax highlight
  68. if (lang) {
  69. try {
  70. line.children = refractor.highlight(line.children[0].value, lang).children
  71. } catch (err) {
  72. // eslint-disable-next-line no-empty
  73. if (options.ignoreMissing && /Unknown language/.test(err.message)) {
  74. } else {
  75. throw err
  76. }
  77. }
  78. }
  79. }
  80. node.children = codeLineArray
  81. }
  82. }
  83. export default rehypePrism