Skip to content

Commit 87e0903

Browse files
authored
Merge pull request #10 from timlrx/fix/multiline
fix: multiline code
2 parents cfb6fec + 3243be2 commit 87e0903

File tree

4 files changed

+382
-351
lines changed

4 files changed

+382
-351
lines changed

index.js

+95-21
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
/**
2-
* @typedef {import('unist').Node & {properties: Object<any, any>}} Node
3-
* @typedef {import('unist').Parent & {properties: Object<any, any>}} Parent
2+
* @typedef {import('hast').Node & {properties: Object<any, any>}} Node
3+
* @typedef {import('hast').Parent & {properties: Object<any, any>}} Parent
4+
* @typedef {import('hast').Root} Root
45
* @typedef {import('unist-util-visit').Visitor<Node>} Visitor
6+
* @typedef Options options
7+
* Configuration.
8+
* @property {boolean} [showLineNumbers]
9+
* Set `showLineNumbers` to `true` to always display line number
10+
* @property {boolean} [ignoreMissing]
11+
* Set `ignoreMissing` to `true` to ignore unsupported languages and line highlighting when no language is specified
512
*/
613

714
import { visit } from 'unist-util-visit'
815
import { toString } from 'hast-util-to-string'
916
import { refractor } from 'refractor/lib/all.js'
17+
import { toHtml } from 'hast-util-to-html'
18+
import { filter } from 'unist-util-filter'
19+
import { unified } from 'unified'
20+
import parse from 'rehype-parse'
1021
import rangeParser from 'parse-numeric-range'
1122

1223
/**
@@ -73,20 +84,68 @@ const splitLine = (text) => {
7384
}
7485

7586
/**
76-
* Rehype plugin that highlights code blocks with refractor (prismjs)
77-
*
78-
* Set `showLineNumbers` to `true` to always display line number
87+
* Split line to div node with className `code-line`
7988
*
80-
* Set `ignoreMissing` to `true` to ignore unsupported languages and line highlighting when no language is specified
89+
* @param {import('refractor').RefractorRoot} ast
90+
* @return {Root}
91+
*/
92+
const getNodePosition = (ast) => {
93+
// @ts-ignore
94+
let html = toHtml(ast)
95+
const hast = unified().use(parse, { emitParseErrors: true, fragment: true }).parse(html)
96+
return hast
97+
}
98+
99+
/**
100+
* Split multiline text nodes into individual nodes with positioning
81101
*
82-
* @typedef {{ showLineNumbers?: boolean, ignoreMissing?: boolean }} RehypePrismOptions
83-
* @param {RehypePrismOptions} options
84-
* @return {Visitor}
102+
* @param {Parent['children']} ast
103+
* @return {Parent['children']}
85104
*/
86-
const rehypePrism = (options) => {
87-
options = options || {}
105+
const splitTextByLine = (ast) => {
106+
//@ts-ignore
107+
return ast.reduce((result, node) => {
108+
if (node.type === 'text') {
109+
if (node.value.indexOf('\n') === -1) {
110+
result.push(node)
111+
return result
112+
}
113+
114+
const lines = node.value.split('\n')
115+
for (const [i, line] of lines.entries()) {
116+
result.push({
117+
type: 'text',
118+
value: i === lines.length - 1 ? line : line + '\n',
119+
position: {
120+
start: { line: node.position.start.line + i },
121+
end: { line: node.position.start.line + i },
122+
},
123+
})
124+
}
125+
126+
return result
127+
}
128+
129+
if (node.children) {
130+
// @ts-ignore
131+
node.children = splitTextByLine(node.children)
132+
result.push(node)
133+
return result
134+
}
88135

136+
result.push(node)
137+
return result
138+
}, [])
139+
}
140+
141+
/**
142+
* Rehype plugin that highlights code blocks with refractor (prismjs)
143+
*
144+
* @type {import('unified').Plugin<[Options?], Root>}
145+
*/
146+
const rehypePrism = (options = {}) => {
89147
return (tree) => {
148+
// @ts-ignore
90149
visit(tree, 'element', visitor)
91150
}
92151

@@ -112,6 +171,25 @@ const rehypePrism = (options) => {
112171
meta = `${lang} ${meta}`
113172
}
114173

174+
let refractorRoot
175+
let langError = false
176+
177+
// Syntax highlight
178+
if (lang) {
179+
try {
180+
// @ts-ignore
181+
refractorRoot = refractor.highlight(toString(node), lang)
182+
refractorRoot = getNodePosition(refractorRoot)
183+
refractorRoot.children = splitTextByLine(refractorRoot.children)
184+
} catch (err) {
185+
if (options.ignoreMissing && /Unknown language/.test(err.message)) {
186+
langError = true
187+
} else {
188+
throw err
189+
}
190+
}
191+
}
192+
115193
const shouldHighlightLine = calculateLinesToHighlight(meta)
116194
// @ts-ignore
117195
const codeLineArray = splitLine(toString(node))
@@ -129,16 +207,12 @@ const rehypePrism = (options) => {
129207
}
130208

131209
// Syntax highlight
132-
if (lang && line.children) {
133-
try {
134-
line.children = refractor.highlight(line.children[0].value, lang).children
135-
} catch (err) {
136-
// eslint-disable-next-line no-empty
137-
if (options.ignoreMissing && /Unknown language/.test(err.message)) {
138-
} else {
139-
throw err
140-
}
141-
}
210+
if (lang && line.children && !langError) {
211+
const treeExtract = filter(
212+
refractorRoot,
213+
(node) => node.position.start.line <= i + 1 && node.position.end.line >= i + 1
214+
)
215+
line.children = treeExtract.children
142216
}
143217
}
144218

0 commit comments

Comments
 (0)