-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathindex.js
128 lines (107 loc) · 3.56 KB
/
index.js
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
'use strict';
const rangeParser = require('parse-numeric-range');
const rehype = require('rehype');
const visit = require('unist-util-visit');
const nodeToString = require('hast-util-to-string');
const unified = require('unified');
const parse = require('rehype-parse');
const refractor = require('refractor');
const addMarkers = require('./add-markers');
/**
* This module walks through the node tree and does:
* - gets the class name
* - parses the class and extracts the highlight lines directive and the language name
* - highlights the code using refractor
* - if markers are present then:
* - converts AST to HTML
* - then applies some fixes to make line highlighting work with JSX found here: https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-prismjs/src/directives.js#L113-L119
* - add markers using: https://github.com/rexxars/react-refractor/blob/master/src/addMarkers.js
* - converts the code back from HTML to AST
* - sets the code as value
*/
module.exports = (options = {}) => {
return tree => {
visit(tree, 'element', visitor);
};
function visitor(node, index, parent) {
if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') {
return;
}
const className = getLangClass(node);
const { highlightLines, splitLanguage } = parseLineNumberRange(className);
const lang = getLanguage(splitLanguage);
const markers = highlightLines;
if (lang === null) {
return;
}
let result;
try {
parent.properties.className = (parent.properties.className || [])
.concat('language-' + lang);
result = refractor.highlight(nodeToString(node), lang);
if (markers && markers.length > 0) {
// This blocks attempts this fix:
// https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-prismjs/src/directives.js#L113-L119
const PLAIN_TEXT_WITH_LF_TEST = /<span class="token plain-text">[^<]*\n[^<]*<\/span>/g;
// AST to HTML
let html_ = rehype()
.stringify({ type: 'root', children: result })
.toString();
// Fix JSX issue
html_ = html_.replace(PLAIN_TEXT_WITH_LF_TEST, match => {
return match.replace(
/\n/g,
'</span>\n<span class="token plain-text">'
);
});
// HTML to AST
const hast_ = unified()
.use(parse, { emitParseErrors: true, fragment: true })
.parse(html_);
// Add markers
result = addMarkers(hast_.children, { markers });
}
} catch (err) {
if (options.ignoreMissing && /Unknown language/.test(err.message)) {
return;
}
throw err;
}
node.children = result;
}
};
const parseLineNumberRange = language => {
if (!language) {
return '';
}
if (language.split('{').length > 1) {
let [splitLanguage, ...options] = language.split('{');
let highlightLines = [];
options.forEach(option => {
option = option.slice(0, -1);
if (rangeParser.parse(option).length > 0) {
highlightLines = rangeParser.parse(option).filter(n => n > 0);
}
});
return {
splitLanguage,
highlightLines
};
}
return { splitLanguage: language };
};
function getLangClass(node) {
const className = node.properties.className || [];
for (const item of className) {
if (item.slice(0, 9) === 'language-') {
return item;
}
}
return null;
}
function getLanguage(className = '') {
if (className.slice(0, 9) === 'language-') {
return className.slice(9).toLowerCase();
}
return null;
}