source: node_modules/react-syntax-highlighter/src/highlight.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 11.2 KB
Line 
1import React from 'react';
2import createElement from './create-element';
3import checkForListedLanguage from './checkForListedLanguage';
4
5const newLineRegex = /\n/g;
6function getNewLines(str) {
7 return str.match(newLineRegex);
8}
9
10function getAllLineNumbers({ lines, startingLineNumber, style }) {
11 return lines.map((_, i) => {
12 const number = i + startingLineNumber;
13 return (
14 <span
15 key={`line-${i}`}
16 className="react-syntax-highlighter-line-number"
17 style={typeof style === 'function' ? style(number) : style}
18 >
19 {`${number}\n`}
20 </span>
21 );
22 });
23}
24
25function AllLineNumbers({
26 codeString,
27 codeStyle,
28 containerStyle = { float: 'left', paddingRight: '10px' },
29 numberStyle = {},
30 startingLineNumber
31}) {
32 return (
33 <code style={Object.assign({}, codeStyle, containerStyle)}>
34 {getAllLineNumbers({
35 lines: codeString.replace(/\n$/, '').split('\n'),
36 style: numberStyle,
37 startingLineNumber
38 })}
39 </code>
40 );
41}
42
43function getEmWidthOfNumber(num) {
44 return `${num.toString().length}.25em`;
45}
46
47function getInlineLineNumber(lineNumber, inlineLineNumberStyle) {
48 return {
49 type: 'element',
50 tagName: 'span',
51 properties: {
52 key: `line-number--${lineNumber}`,
53 className: [
54 'comment',
55 'linenumber',
56 'react-syntax-highlighter-line-number'
57 ],
58 style: inlineLineNumberStyle
59 },
60 children: [
61 {
62 type: 'text',
63 value: lineNumber
64 }
65 ]
66 };
67}
68
69function assembleLineNumberStyles(
70 lineNumberStyle,
71 lineNumber,
72 largestLineNumber
73) {
74 // minimally necessary styling for line numbers
75 const defaultLineNumberStyle = {
76 display: 'inline-block',
77 minWidth: getEmWidthOfNumber(largestLineNumber),
78 paddingRight: '1em',
79 textAlign: 'right',
80 userSelect: 'none'
81 };
82 // prep custom styling
83 const customLineNumberStyle =
84 typeof lineNumberStyle === 'function'
85 ? lineNumberStyle(lineNumber)
86 : lineNumberStyle;
87 // combine
88 const assembledStyle = {
89 ...defaultLineNumberStyle,
90 ...customLineNumberStyle
91 };
92 return assembledStyle;
93}
94
95function createLineElement({
96 children,
97 lineNumber,
98 lineNumberStyle,
99 largestLineNumber,
100 showInlineLineNumbers,
101 lineProps = {},
102 className = [],
103 showLineNumbers,
104 wrapLongLines
105}) {
106 const properties =
107 typeof lineProps === 'function' ? lineProps(lineNumber) : lineProps;
108 properties['className'] = className;
109
110 if (lineNumber && showInlineLineNumbers) {
111 const inlineLineNumberStyle = assembleLineNumberStyles(
112 lineNumberStyle,
113 lineNumber,
114 largestLineNumber
115 );
116 children.unshift(getInlineLineNumber(lineNumber, inlineLineNumberStyle));
117 }
118
119 if (wrapLongLines & showLineNumbers) {
120 properties.style = { ...properties.style, display: 'flex' };
121 }
122
123 return {
124 type: 'element',
125 tagName: 'span',
126 properties,
127 children
128 };
129}
130
131function flattenCodeTree(tree, className = [], newTree = []) {
132 for (let i = 0; i < tree.length; i++) {
133 const node = tree[i];
134 if (node.type === 'text') {
135 newTree.push(
136 createLineElement({
137 children: [node],
138 className: [...new Set(className)]
139 })
140 );
141 } else if (node.children) {
142 const classNames = className.concat(node.properties.className);
143 flattenCodeTree(node.children, classNames).forEach(i => newTree.push(i));
144 }
145 }
146 return newTree;
147}
148
149function processLines(
150 codeTree,
151 wrapLines,
152 lineProps,
153 showLineNumbers,
154 showInlineLineNumbers,
155 startingLineNumber,
156 largestLineNumber,
157 lineNumberStyle,
158 wrapLongLines
159) {
160 const tree = flattenCodeTree(codeTree.value);
161 const newTree = [];
162 let lastLineBreakIndex = -1;
163 let index = 0;
164
165 function createWrappedLine(children, lineNumber, className = []) {
166 return createLineElement({
167 children,
168 lineNumber,
169 lineNumberStyle,
170 largestLineNumber,
171 showInlineLineNumbers,
172 lineProps,
173 className,
174 showLineNumbers,
175 wrapLongLines
176 });
177 }
178
179 function createUnwrappedLine(children, lineNumber) {
180 if (showLineNumbers && lineNumber && showInlineLineNumbers) {
181 const inlineLineNumberStyle = assembleLineNumberStyles(
182 lineNumberStyle,
183 lineNumber,
184 largestLineNumber
185 );
186 children.unshift(getInlineLineNumber(lineNumber, inlineLineNumberStyle));
187 }
188 return children;
189 }
190
191 function createLine(children, lineNumber, className = []) {
192 return wrapLines || className.length > 0
193 ? createWrappedLine(children, lineNumber, className)
194 : createUnwrappedLine(children, lineNumber);
195 }
196
197 while (index < tree.length) {
198 const node = tree[index];
199 const value = node.children[0].value;
200 const newLines = getNewLines(value);
201
202 if (newLines) {
203 const splitValue = value.split('\n');
204 splitValue.forEach((text, i) => {
205 const lineNumber =
206 showLineNumbers && newTree.length + startingLineNumber;
207 const newChild = { type: 'text', value: `${text}\n` };
208
209 // if it's the first line
210 if (i === 0) {
211 const children = tree.slice(lastLineBreakIndex + 1, index).concat(
212 createLineElement({
213 children: [newChild],
214 className: node.properties.className
215 })
216 );
217
218 const line = createLine(children, lineNumber);
219 newTree.push(line);
220
221 // if it's the last line
222 } else if (i === splitValue.length - 1) {
223 const stringChild =
224 tree[index + 1] &&
225 tree[index + 1].children &&
226 tree[index + 1].children[0];
227 const lastLineInPreviousSpan = { type: 'text', value: `${text}` };
228 if (stringChild) {
229 const newElem = createLineElement({
230 children: [lastLineInPreviousSpan],
231 className: node.properties.className
232 });
233 tree.splice(index + 1, 0, newElem);
234 } else {
235 const children = [lastLineInPreviousSpan];
236 const line = createLine(
237 children,
238 lineNumber,
239 node.properties.className
240 );
241 newTree.push(line);
242 }
243
244 // if it's neither the first nor the last line
245 } else {
246 const children = [newChild];
247 const line = createLine(
248 children,
249 lineNumber,
250 node.properties.className
251 );
252 newTree.push(line);
253 }
254 });
255 lastLineBreakIndex = index;
256 }
257 index++;
258 }
259
260 if (lastLineBreakIndex !== tree.length - 1) {
261 const children = tree.slice(lastLineBreakIndex + 1, tree.length);
262 if (children && children.length) {
263 const lineNumber = showLineNumbers && newTree.length + startingLineNumber;
264 const line = createLine(children, lineNumber);
265 newTree.push(line);
266 }
267 }
268
269 return wrapLines ? newTree : [].concat(...newTree);
270}
271
272function defaultRenderer({ rows, stylesheet, useInlineStyles }) {
273 return rows.map((node, i) =>
274 createElement({
275 node,
276 stylesheet,
277 useInlineStyles,
278 key: `code-segement${i}`
279 })
280 );
281}
282
283// only highlight.js has the highlightAuto method
284function isHighlightJs(astGenerator) {
285 return astGenerator && typeof astGenerator.highlightAuto !== 'undefined';
286}
287
288function getCodeTree({ astGenerator, language, code, defaultCodeValue }) {
289 // figure out whether we're using lowlight/highlight or refractor/prism
290 // then attempt highlighting accordingly
291
292 // lowlight/highlight?
293 if (isHighlightJs(astGenerator)) {
294 const hasLanguage = checkForListedLanguage(astGenerator, language);
295 if (language === 'text') {
296 return { value: defaultCodeValue, language: 'text' };
297 } else if (hasLanguage) {
298 return astGenerator.highlight(language, code);
299 } else {
300 return astGenerator.highlightAuto(code);
301 }
302 }
303
304 // must be refractor/prism, then
305 try {
306 return language && language !== 'text'
307 ? { value: astGenerator.highlight(code, language) }
308 : { value: defaultCodeValue };
309 } catch (e) {
310 return { value: defaultCodeValue };
311 }
312}
313
314export default function(defaultAstGenerator, defaultStyle) {
315 return function SyntaxHighlighter({
316 language,
317 children,
318 style = defaultStyle,
319 customStyle = {},
320 codeTagProps = {
321 className: language ? `language-${language}` : undefined,
322 style: {
323 ...style['code[class*="language-"]'],
324 ...style[`code[class*="language-${language}"]`]
325 }
326 },
327 useInlineStyles = true,
328 showLineNumbers = false,
329 showInlineLineNumbers = true,
330 startingLineNumber = 1,
331 lineNumberContainerStyle,
332 lineNumberStyle = {},
333 wrapLines,
334 wrapLongLines = false,
335 lineProps = {},
336 renderer,
337 PreTag = 'pre',
338 CodeTag = 'code',
339 code = (Array.isArray(children) ? children[0] : children) || '',
340 astGenerator,
341 ...rest
342 }) {
343 astGenerator = astGenerator || defaultAstGenerator;
344
345 const allLineNumbers = showLineNumbers ? (
346 <AllLineNumbers
347 containerStyle={lineNumberContainerStyle}
348 codeStyle={codeTagProps.style || {}}
349 numberStyle={lineNumberStyle}
350 startingLineNumber={startingLineNumber}
351 codeString={code}
352 />
353 ) : null;
354
355 const defaultPreStyle = style.hljs ||
356 style['pre[class*="language-"]'] || { backgroundColor: '#fff' };
357 const generatorClassName = isHighlightJs(astGenerator) ? 'hljs' : 'prismjs';
358 const preProps = useInlineStyles
359 ? Object.assign({}, rest, {
360 style: Object.assign({}, defaultPreStyle, customStyle)
361 })
362 : Object.assign({}, rest, {
363 className: rest.className
364 ? `${generatorClassName} ${rest.className}`
365 : generatorClassName,
366 style: Object.assign({}, customStyle)
367 });
368
369 if (wrapLongLines) {
370 codeTagProps.style = { ...codeTagProps.style, whiteSpace: 'pre-wrap' };
371 } else {
372 codeTagProps.style = { ...codeTagProps.style, whiteSpace: 'pre' };
373 }
374
375 if (!astGenerator) {
376 return (
377 <PreTag {...preProps}>
378 {allLineNumbers}
379 <CodeTag {...codeTagProps}>{code}</CodeTag>
380 </PreTag>
381 );
382 }
383
384 /*
385 * Some custom renderers rely on individual row elements so we need to turn wrapLines on
386 * if renderer is provided and wrapLines is undefined.
387 */
388 if ((wrapLines === undefined && renderer) || wrapLongLines)
389 wrapLines = true;
390
391 renderer = renderer || defaultRenderer;
392 const defaultCodeValue = [{ type: 'text', value: code }];
393 const codeTree = getCodeTree({
394 astGenerator,
395 language,
396 code,
397 defaultCodeValue
398 });
399 if (codeTree.language === null) {
400 codeTree.value = defaultCodeValue;
401 }
402
403 // determine largest line number so that we can force minWidth on all linenumber elements
404 const largestLineNumber = codeTree.value.length + startingLineNumber;
405
406 const rows = processLines(
407 codeTree,
408 wrapLines,
409 lineProps,
410 showLineNumbers,
411 showInlineLineNumbers,
412 startingLineNumber,
413 largestLineNumber,
414 lineNumberStyle,
415 wrapLongLines
416 );
417
418 return (
419 <PreTag {...preProps}>
420 <CodeTag {...codeTagProps}>
421 {!showInlineLineNumbers && allLineNumbers}
422 {renderer({ rows, stylesheet: style, useInlineStyles })}
423 </CodeTag>
424 </PreTag>
425 );
426 };
427}
Note: See TracBrowser for help on using the repository browser.