rehype-highlight-code-lines 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -77
- package/dist/esm/index.d.ts +5 -1
- package/dist/esm/index.js +130 -98
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/package.json +30 -22
- package/src/index.ts +170 -117
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# rehype-highlight-code-lines
|
|
2
2
|
|
|
3
|
-
[![
|
|
4
|
-
[![
|
|
5
|
-
[![
|
|
6
|
-
[![
|
|
7
|
-
[![type-coverage]
|
|
8
|
-
[![typescript][badge-typescript]][typescript
|
|
9
|
-
[![
|
|
3
|
+
[![npm version][badge-npm-version]][url-npm-package]
|
|
4
|
+
[![npm downloads][badge-npm-download]][url-npm-package]
|
|
5
|
+
[![publish to npm][badge-publish-to-npm]][url-publish-github-actions]
|
|
6
|
+
[![code-coverage][badge-codecov]][url-codecov]
|
|
7
|
+
[![type-coverage][badge-type-coverage]][url-github-package]
|
|
8
|
+
[![typescript][badge-typescript]][url-typescript]
|
|
9
|
+
[![license][badge-license]][url-license]
|
|
10
10
|
|
|
11
|
-
This package is a [unified][unified] ([rehype][rehype]) plugin **
|
|
11
|
+
This package is a **[unified][unified]** (**[rehype][rehype]**) plugin that **wraps each line of code in a container, enabling code block numbering and line highlighting**.
|
|
12
12
|
|
|
13
|
-
**[unified][unified]** is a project that transforms content with abstract syntax trees (ASTs) using the new parser **[micromark][micromark]**. **[remark][remark]** adds support for markdown to unified. **[mdast][mdast]** is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree.
|
|
13
|
+
**[unified][unified]** is a project that transforms content with abstract syntax trees (ASTs) using the new parser **[micromark][micromark]**. **[remark][remark]** adds support for markdown to unified. **[mdast][mdast]** is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree. **[rehype][rehype]** is a tool that transforms HTML with plugins. **[hast][hast]** stands for HTML Abstract Syntax Tree (HAST) that rehype uses.
|
|
14
14
|
|
|
15
|
-
**This plugin
|
|
15
|
+
**This plugin enables line numbering for code blocks and highlights specific lines as needed.**
|
|
16
16
|
|
|
17
17
|
## When should I use this?
|
|
18
18
|
|
|
19
|
-
The `rehype-highlight-code-lines`
|
|
19
|
+
The `rehype-highlight-code-lines` is ideal for adding line numbers to code blocks and highlighting specific lines.
|
|
20
20
|
|
|
21
21
|
**The `rehype-highlight-code-lines` is NOT code highlighter and does NOT provide code highlighting!** You can use a code highlighter for example **[rehype-highlight][rehype-highlight]** to highlight the code, then use the `rehype-highlight-code-lines` **after**.
|
|
22
22
|
|
|
@@ -42,9 +42,9 @@ yarn add rehype-highlight-code-lines
|
|
|
42
42
|
|
|
43
43
|
## Usage
|
|
44
44
|
|
|
45
|
-
In a code fence,
|
|
46
|
-
+ specify a range of
|
|
47
|
-
+
|
|
45
|
+
In a code fence, right after the language of the code block:
|
|
46
|
+
+ Use curly braces `{}` to specify a range of line numbers to highlight specific lines.
|
|
47
|
+
+ Add `showLineNumbers` to enable line numbering.
|
|
48
48
|
|
|
49
49
|
**\`\`\`[language] {2,4-6} showLineNumbers**
|
|
50
50
|
|
|
@@ -54,7 +54,7 @@ In a code fence, just after the language of the code block,
|
|
|
54
54
|
|
|
55
55
|
**\`\`\`[language] showLineNumbers**
|
|
56
56
|
|
|
57
|
-
You can use the specifiers without a language
|
|
57
|
+
You can use the specifiers without a language:
|
|
58
58
|
|
|
59
59
|
**\`\`\`{5} showLineNumbers**
|
|
60
60
|
|
|
@@ -140,7 +140,6 @@ All options are **optional** and have **default values**.
|
|
|
140
140
|
```typescript
|
|
141
141
|
type HighlightLinesOptions = {
|
|
142
142
|
showLineNumbers?: boolean; // default is "false"
|
|
143
|
-
lineContainerTagName?: "div" | "span"; // default is "span"
|
|
144
143
|
};
|
|
145
144
|
|
|
146
145
|
use(rehypeHighlightLines, HighlightLinesOptions);
|
|
@@ -176,28 +175,27 @@ Sometimes you may want to start the line numbering from a specific number. In th
|
|
|
176
175
|
|
|
177
176
|
#### `lineContainerTagName`
|
|
178
177
|
|
|
179
|
-
It is a
|
|
178
|
+
**It is a deprecated option.** Marked as `@deprecated`, will be removed in the next versions.
|
|
180
179
|
|
|
181
|
-
|
|
180
|
+
It was a **union** type option which was **"div" | "span"** for providing custom HTML tag name for code lines.
|
|
181
|
+
|
|
182
|
+
By default, it is `span` which is **inline** level container. If you set it as `div`, the container will still be `span` after deprecation.
|
|
182
183
|
|
|
183
184
|
```javascript
|
|
184
185
|
use(rehypeHighlightLines, {
|
|
185
|
-
lineContainerTagName: "div",
|
|
186
|
+
lineContainerTagName: "div", // @deprecated, always "span"
|
|
186
187
|
});
|
|
187
188
|
```
|
|
188
189
|
|
|
189
|
-
Now, the code line container's tag name will be `div`.
|
|
190
|
-
|
|
191
190
|
### Examples:
|
|
192
191
|
|
|
193
192
|
```typescript
|
|
194
193
|
// line numbering will occur as per directive "showLineNumber" and code-line containers will be <span> inline element
|
|
195
194
|
use(rehypeHighlightLines);
|
|
196
195
|
|
|
197
|
-
// all code blocks will be numbered and code-line containers will be <
|
|
196
|
+
// all code blocks will be numbered by default and code-line containers will be <span> inline element
|
|
198
197
|
use(rehypeHighlightLines, {
|
|
199
198
|
showLineNumbers: true,
|
|
200
|
-
lineContainerTagName: "div",
|
|
201
199
|
});
|
|
202
200
|
```
|
|
203
201
|
|
|
@@ -210,32 +208,6 @@ use(rehypeHighlightLines, {
|
|
|
210
208
|
+ [demo blog application](https://next-mdx-remote-client-in-app-router.vercel.app/) using `next-mdx-remote-client` within `Next.js app router`
|
|
211
209
|
+ [demo blog application](https://next-mdx-remote-client-in-pages-router.vercel.app/) using `next-mdx-remote-client` within `Next.js pages router`
|
|
212
210
|
|
|
213
|
-
## Copying Code Block's Content Issue
|
|
214
|
-
|
|
215
|
-
When the line container is "div" block element, the end of line character (eol) at the end of line causes unwanted extra blank line. In order to fix the issue, I've **removed the eol when it is "div", but kept the eol when it is "span"**.
|
|
216
|
-
|
|
217
|
-
But, **the lack of the eol when "div" causes another issue.** If you implement a button for copying code block content, it doesn't work as expected since all code are printed in a single line. In order to work around the issue, you can implement an `onClick` of the `button` like below:
|
|
218
|
-
|
|
219
|
-
*I assume you use a `useRef` for `<pre>` element.*
|
|
220
|
-
|
|
221
|
-
```javascript
|
|
222
|
-
const onClick = () => {
|
|
223
|
-
if (!preRef.current) return;
|
|
224
|
-
|
|
225
|
-
// clone the <code> element in order not to cause any change in actual DOM
|
|
226
|
-
const code = preRef.current.getElementsByTagName("code")[0].cloneNode(true);
|
|
227
|
-
|
|
228
|
-
// add eol to each code-line since there is no eol at the end when they are div
|
|
229
|
-
Array.from((code as HTMLElement).querySelectorAll("div.code-line")).forEach(
|
|
230
|
-
(line) => {
|
|
231
|
-
line.innerHTML = line.innerHTML + "\r";
|
|
232
|
-
}
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
void navigator.clipboard.writeText(code.textContent ?? "");
|
|
236
|
-
};
|
|
237
|
-
```
|
|
238
|
-
|
|
239
211
|
## Styling
|
|
240
212
|
|
|
241
213
|
The following styles can be added for **line highlighting** and **line numbering** to work correctly:
|
|
@@ -275,29 +247,22 @@ pre > code {
|
|
|
275
247
|
}
|
|
276
248
|
|
|
277
249
|
.code-line {
|
|
250
|
+
min-width: 100%;
|
|
278
251
|
padding-left: 12px;
|
|
279
252
|
padding-right: 12px;
|
|
280
253
|
margin-left: -12px;
|
|
281
254
|
margin-right: -12px;
|
|
282
255
|
border-left: 4px solid transparent; /* prepare for highlighted code-lines */
|
|
283
|
-
}
|
|
284
256
|
|
|
285
|
-
div.code-line:empty {
|
|
286
|
-
/* it is necessary because there is no even eol character in div code-lines */
|
|
287
|
-
height: 15.5938px; /* calculated height */
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
span.code-line {
|
|
291
|
-
min-width: 100%;
|
|
292
257
|
display: inline-block;
|
|
293
258
|
}
|
|
294
259
|
|
|
295
260
|
.code-line.inserted {
|
|
296
|
-
background-color: var(--color-inserted-line); /* inserted code-line (+)
|
|
261
|
+
background-color: var(--color-inserted-line); /* inserted code-line (+) */
|
|
297
262
|
}
|
|
298
263
|
|
|
299
264
|
.code-line.deleted {
|
|
300
|
-
background-color: var(--color-deleted-line); /* deleted code-line (-)
|
|
265
|
+
background-color: var(--color-deleted-line); /* deleted code-line (-) */
|
|
301
266
|
}
|
|
302
267
|
|
|
303
268
|
.highlighted-code-line {
|
|
@@ -324,7 +289,7 @@ This plugin modifies the `hast` (HTML abstract syntax tree).
|
|
|
324
289
|
|
|
325
290
|
## Types
|
|
326
291
|
|
|
327
|
-
This package is fully typed with [TypeScript][typescript].
|
|
292
|
+
This package is fully typed with [TypeScript][url-typescript].
|
|
328
293
|
|
|
329
294
|
The plugin exports the type `HighlightLinesOptions`.
|
|
330
295
|
|
|
@@ -375,17 +340,6 @@ I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to
|
|
|
375
340
|
|
|
376
341
|
[MIT License](./LICENSE) © ipikuka
|
|
377
342
|
|
|
378
|
-
### Keywords
|
|
379
|
-
|
|
380
|
-
🟩 [unified][unifiednpm] 🟩 [rehype][rehypenpm] 🟩 [rehype plugin][rehypepluginnpm] 🟩 [hast][hastnpm] 🟩 [markdown][markdownnpm] 🟩 [rehype-highlight][rehypehighlightnpm]
|
|
381
|
-
|
|
382
|
-
[unifiednpm]: https://www.npmjs.com/search?q=keywords:unified
|
|
383
|
-
[rehypenpm]: https://www.npmjs.com/search?q=keywords:rehype
|
|
384
|
-
[rehypepluginnpm]: https://www.npmjs.com/search?q=keywords:rehype%20plugin
|
|
385
|
-
[hastnpm]: https://www.npmjs.com/search?q=keywords:hast
|
|
386
|
-
[markdownnpm]: https://www.npmjs.com/search?q=keywords:markdown
|
|
387
|
-
[rehypehighlightnpm]: https://www.npmjs.com/search?q=keywords:rehype-highlight
|
|
388
|
-
|
|
389
343
|
[unified]: https://github.com/unifiedjs/unified
|
|
390
344
|
[micromark]: https://github.com/micromark/micromark
|
|
391
345
|
[remark]: https://github.com/remarkjs/remark
|
|
@@ -394,18 +348,23 @@ I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to
|
|
|
394
348
|
[rehype]: https://github.com/rehypejs/rehype
|
|
395
349
|
[rehypeplugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md
|
|
396
350
|
[hast]: https://github.com/syntax-tree/hast
|
|
397
|
-
[typescript]: https://www.typescriptlang.org/
|
|
398
351
|
[rehype-highlight]: https://github.com/rehypejs/rehype-highlight
|
|
399
352
|
|
|
400
353
|
[badge-npm-version]: https://img.shields.io/npm/v/rehype-highlight-code-lines
|
|
401
354
|
[badge-npm-download]:https://img.shields.io/npm/dt/rehype-highlight-code-lines
|
|
402
|
-
[npm-package
|
|
355
|
+
[url-npm-package]: https://www.npmjs.com/package/rehype-highlight-code-lines
|
|
356
|
+
[url-github-package]: https://github.com/ipikuka/rehype-highlight-code-lines
|
|
403
357
|
|
|
404
358
|
[badge-license]: https://img.shields.io/github/license/ipikuka/rehype-highlight-code-lines
|
|
405
|
-
[
|
|
359
|
+
[url-license]: https://github.com/ipikuka/rehype-highlight-code-lines/blob/main/LICENSE
|
|
406
360
|
|
|
407
|
-
[badge-
|
|
408
|
-
[github-
|
|
361
|
+
[badge-publish-to-npm]: https://github.com/ipikuka/rehype-highlight-code-lines/actions/workflows/publish.yml/badge.svg
|
|
362
|
+
[url-publish-github-actions]: https://github.com/ipikuka/rehype-highlight-code-lines/actions/workflows/publish.yml
|
|
409
363
|
|
|
410
364
|
[badge-typescript]: https://img.shields.io/npm/types/rehype-highlight-code-lines
|
|
411
|
-
[typescript
|
|
365
|
+
[url-typescript]: https://www.typescriptlang.org
|
|
366
|
+
|
|
367
|
+
[badge-codecov]: https://codecov.io/gh/ipikuka/rehype-highlight-code-lines/graph/badge.svg?token=RKrZlvMHwq
|
|
368
|
+
[url-codecov]: https://codecov.io/gh/ipikuka/rehype-highlight-code-lines
|
|
369
|
+
|
|
370
|
+
[badge-type-coverage]: https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fipikuka%2Frehype-highlight-code-lines%2Fmain%2Fpackage.json
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,12 +2,16 @@ import type { Plugin } from "unified";
|
|
|
2
2
|
import type { Root } from "hast";
|
|
3
3
|
export type HighlightLinesOptions = {
|
|
4
4
|
showLineNumbers?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated container tag name is always "span"
|
|
7
|
+
* will be removed in the next versions
|
|
8
|
+
*/
|
|
5
9
|
lineContainerTagName?: "div" | "span";
|
|
6
10
|
};
|
|
7
11
|
export declare function clsx(arr: (string | false | null | undefined | 0)[]): string[];
|
|
8
12
|
/**
|
|
9
13
|
*
|
|
10
|
-
*
|
|
14
|
+
* add line numbers to code blocks and allow highlighting of desired code lines
|
|
11
15
|
*
|
|
12
16
|
*/
|
|
13
17
|
declare const plugin: Plugin<[HighlightLinesOptions?], Root>;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { visit
|
|
1
|
+
import { visit } from "unist-util-visit";
|
|
2
2
|
import rangeParser from "parse-numeric-range";
|
|
3
3
|
const DEFAULT_SETTINGS = {
|
|
4
4
|
showLineNumbers: false,
|
|
@@ -10,44 +10,62 @@ export function clsx(arr) {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* add line numbers to code blocks and allow highlighting of desired code lines
|
|
14
14
|
*
|
|
15
15
|
*/
|
|
16
16
|
const plugin = (options) => {
|
|
17
17
|
const settings = Object.assign({}, DEFAULT_SETTINGS, options);
|
|
18
18
|
/**
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* check code element children need flattening or not
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
function checkCodeTreeForFlatteningNeed(code) {
|
|
24
|
+
const elementContents = code.children;
|
|
25
|
+
// type ElementContent = Comment | Element | Text
|
|
26
|
+
for (const elemenContent of elementContents) {
|
|
27
|
+
if (elemenContent.type === "element")
|
|
28
|
+
if (elemenContent.children.length >= 1 && elemenContent.children[0].type === "element")
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
*
|
|
35
|
+
* flatten code element children, recursively
|
|
21
36
|
* inspired from https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/highlight.js
|
|
22
37
|
*
|
|
23
38
|
*/
|
|
24
|
-
function flattenCodeTree(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
newTree
|
|
39
|
+
function flattenCodeTree(code, className = []) {
|
|
40
|
+
const newTree = [];
|
|
41
|
+
for (const elementContent of code.children) {
|
|
42
|
+
if (elementContent.type === "comment" || elementContent.type === "text") {
|
|
43
|
+
newTree.push(elementContent);
|
|
29
44
|
}
|
|
30
45
|
else {
|
|
31
|
-
// @ts-expect-error
|
|
46
|
+
// @ts-expect-error className is different from other key of properties, and expected to be an array or undefined
|
|
32
47
|
// /* v8 ignore next */
|
|
33
|
-
const classNames = className.concat(
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
const classNames = className.concat(elementContent.properties.className || []);
|
|
49
|
+
if (elementContent.children.length === 1 &&
|
|
50
|
+
elementContent.children[0].type !== "element") {
|
|
51
|
+
elementContent.properties.className = classNames;
|
|
52
|
+
newTree.push(elementContent);
|
|
37
53
|
}
|
|
38
54
|
else {
|
|
39
|
-
newTree
|
|
55
|
+
newTree.push(...flattenCodeTree(elementContent, classNames));
|
|
40
56
|
}
|
|
41
57
|
}
|
|
42
58
|
}
|
|
59
|
+
// Mutate the original code object
|
|
60
|
+
code.children = newTree;
|
|
43
61
|
return newTree;
|
|
44
62
|
}
|
|
45
63
|
/**
|
|
46
64
|
*
|
|
47
|
-
*
|
|
65
|
+
* construct the line element
|
|
48
66
|
*
|
|
49
67
|
*/
|
|
50
|
-
const createLine = (children, lineNumber, startingNumber,
|
|
68
|
+
const createLine = (children, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted) => {
|
|
51
69
|
const firstChild = children[0];
|
|
52
70
|
const isAddition = firstChild?.type === "element" &&
|
|
53
71
|
Array.isArray(firstChild.properties.className) &&
|
|
@@ -57,23 +75,32 @@ const plugin = (options) => {
|
|
|
57
75
|
firstChild.properties.className.some((cls) => typeof cls === "string" && cls.includes("deletion"));
|
|
58
76
|
return {
|
|
59
77
|
type: "element",
|
|
60
|
-
tagName:
|
|
78
|
+
tagName: "span", // now it is always "span"
|
|
61
79
|
children,
|
|
62
80
|
properties: {
|
|
63
81
|
className: clsx([
|
|
64
82
|
"code-line",
|
|
65
|
-
|
|
83
|
+
directiveShowLineNumbers && "numbered-code-line",
|
|
66
84
|
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
|
|
67
85
|
isAddition && "inserted",
|
|
68
86
|
isDeletion && "deleted",
|
|
69
87
|
]),
|
|
70
|
-
dataLineNumber:
|
|
88
|
+
dataLineNumber: directiveShowLineNumbers ? startingNumber - 1 + lineNumber : undefined,
|
|
71
89
|
},
|
|
72
90
|
};
|
|
73
91
|
};
|
|
74
92
|
// match all common types of line breaks
|
|
75
|
-
const
|
|
76
|
-
|
|
93
|
+
const REGEX_LINE_BREAKS = /\r?\n|\r/g;
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* check the code line is empty or with value only spaces
|
|
97
|
+
*
|
|
98
|
+
*/
|
|
99
|
+
function isEmptyLine(line) {
|
|
100
|
+
return (line.length === 0 ||
|
|
101
|
+
(line.length === 1 && line[0].type === "text" && line[0].value.trim() === ""));
|
|
102
|
+
}
|
|
103
|
+
function gutter(tree, directiveShowLineNumbers, startingNumber, linesToBeHighlighted) {
|
|
77
104
|
const replacement = [];
|
|
78
105
|
let index = -1;
|
|
79
106
|
let start = 0;
|
|
@@ -81,56 +108,72 @@ const plugin = (options) => {
|
|
|
81
108
|
let lineNumber = 0;
|
|
82
109
|
while (++index < tree.children.length) {
|
|
83
110
|
const child = tree.children[index];
|
|
84
|
-
if (child.type
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
line.push({ type: "text", value });
|
|
101
|
-
}
|
|
102
|
-
// Add a line
|
|
103
|
-
lineNumber += 1;
|
|
104
|
-
replacement.push(createLine(line, lineNumber, startingNumber, showLineNumbers, linesToBeHighlighted));
|
|
105
|
-
// Add eol if the tag name is "span"
|
|
106
|
-
if (settings.lineContainerTagName === "span") {
|
|
107
|
-
replacement.push({ type: "text", value: match[0] });
|
|
108
|
-
}
|
|
109
|
-
start = index + 1;
|
|
110
|
-
textStart = match.index + match[0].length;
|
|
111
|
-
match = RE.exec(child.value);
|
|
111
|
+
if (child.type !== "text")
|
|
112
|
+
continue;
|
|
113
|
+
let textStart = 0;
|
|
114
|
+
let match = REGEX_LINE_BREAKS.exec(child.value);
|
|
115
|
+
while (match !== null) {
|
|
116
|
+
// Nodes in this line. (current child is exclusive)
|
|
117
|
+
const line = tree.children.slice(start, index);
|
|
118
|
+
// Prepend text from a partial matched earlier text.
|
|
119
|
+
if (startTextRemainder) {
|
|
120
|
+
line.unshift({ type: "text", value: startTextRemainder });
|
|
121
|
+
startTextRemainder = "";
|
|
122
|
+
}
|
|
123
|
+
// Append text from this text.
|
|
124
|
+
if (match.index > textStart) {
|
|
125
|
+
const value = child.value.slice(textStart, match.index);
|
|
126
|
+
line.push({ type: "text", value });
|
|
112
127
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
if (!isEmptyLine(line)) {
|
|
129
|
+
lineNumber += 1;
|
|
130
|
+
replacement.push(createLine(line, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted));
|
|
116
131
|
}
|
|
132
|
+
// Add eol
|
|
133
|
+
replacement.push({ type: "text", value: match[0] });
|
|
134
|
+
start = index + 1;
|
|
135
|
+
textStart = match.index + match[0].length;
|
|
136
|
+
// iterate the match
|
|
137
|
+
match = REGEX_LINE_BREAKS.exec(child.value);
|
|
138
|
+
}
|
|
139
|
+
// If we matched, make sure to not drop the text after the last line ending.
|
|
140
|
+
if (start === index + 1) {
|
|
141
|
+
startTextRemainder = child.value.slice(textStart);
|
|
117
142
|
}
|
|
118
143
|
}
|
|
119
144
|
const line = tree.children.slice(start);
|
|
120
|
-
/* v8 ignore start */
|
|
121
145
|
// Prepend text from a partial matched earlier text.
|
|
122
146
|
if (startTextRemainder) {
|
|
123
147
|
line.unshift({ type: "text", value: startTextRemainder });
|
|
124
148
|
startTextRemainder = "";
|
|
125
149
|
}
|
|
126
|
-
if (line
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
if (!isEmptyLine(line)) {
|
|
151
|
+
if (line.length > 0) {
|
|
152
|
+
lineNumber += 1;
|
|
153
|
+
replacement.push(createLine(line, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted));
|
|
154
|
+
}
|
|
129
155
|
}
|
|
130
|
-
/* v8 ignore end */
|
|
131
156
|
// Replace children with new array.
|
|
132
157
|
tree.children = replacement;
|
|
133
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
*
|
|
161
|
+
* get the programming language analyzing the classNames
|
|
162
|
+
*
|
|
163
|
+
*/
|
|
164
|
+
function getLanguage(classNames) {
|
|
165
|
+
const isLanguageString = (element) => {
|
|
166
|
+
return String(element).startsWith("language-") || String(element).startsWith("lang-");
|
|
167
|
+
};
|
|
168
|
+
const languageString = classNames?.find(isLanguageString);
|
|
169
|
+
if (languageString?.slice(0, 5) === "lang-") {
|
|
170
|
+
return languageString.slice(5).toLowerCase();
|
|
171
|
+
}
|
|
172
|
+
if (languageString?.slice(0, 9) === "language-") {
|
|
173
|
+
return languageString.slice(9).toLowerCase();
|
|
174
|
+
}
|
|
175
|
+
return languageString;
|
|
176
|
+
}
|
|
134
177
|
/**
|
|
135
178
|
* Transform.
|
|
136
179
|
*
|
|
@@ -141,48 +184,34 @@ const plugin = (options) => {
|
|
|
141
184
|
*/
|
|
142
185
|
return (tree) => {
|
|
143
186
|
visit(tree, "element", function (node, index, parent) {
|
|
144
|
-
|
|
145
|
-
if (!parent || typeof index === "undefined")
|
|
187
|
+
if (!parent || index === undefined || node.tagName !== "code") {
|
|
146
188
|
return;
|
|
147
|
-
if (node.tagName !== "pre")
|
|
148
|
-
return CONTINUE;
|
|
149
|
-
const code = node.children[0];
|
|
150
|
-
/* v8 ignore next */
|
|
151
|
-
if (!code || code.type !== "element" || code.tagName !== "code")
|
|
152
|
-
return;
|
|
153
|
-
let meta = code.data?.meta?.toLowerCase().trim();
|
|
154
|
-
// handle if there is no language provided in code block
|
|
155
|
-
if (Array.isArray(code.properties.className)) {
|
|
156
|
-
const testingFunction = (element) => typeof element === "string" && element.startsWith("language-");
|
|
157
|
-
const className = code.properties.className.find(testingFunction);
|
|
158
|
-
if (className) {
|
|
159
|
-
const language = className.slice(9).toLowerCase();
|
|
160
|
-
if (language.startsWith("{") ||
|
|
161
|
-
language.startsWith("showlinenumbers") ||
|
|
162
|
-
language.startsWith("nolinenumbers")) {
|
|
163
|
-
meta = meta ? language + meta : language;
|
|
164
|
-
const idx = code.properties.className.findIndex(testingFunction);
|
|
165
|
-
if (idx > -1) {
|
|
166
|
-
code.properties.className[idx] = "language-unknown";
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
189
|
}
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
meta = "showlinenumbers";
|
|
174
|
-
}
|
|
175
|
-
else if (!meta.includes("showlinenumbers")) {
|
|
176
|
-
meta = meta + " showlinenumbers";
|
|
177
|
-
}
|
|
190
|
+
if (parent.type !== "element" || parent.tagName !== "pre") {
|
|
191
|
+
return;
|
|
178
192
|
}
|
|
179
|
-
|
|
193
|
+
const code = node;
|
|
194
|
+
const classNames = code.properties.className;
|
|
195
|
+
// only for type narrowing
|
|
196
|
+
/* v8 ignore next */
|
|
197
|
+
if (!Array.isArray(classNames) && classNames !== undefined)
|
|
180
198
|
return;
|
|
181
|
-
|
|
199
|
+
let meta = code.data?.meta?.toLowerCase().trim() || "";
|
|
200
|
+
const language = getLanguage(classNames);
|
|
201
|
+
if (language?.startsWith("{") ||
|
|
202
|
+
language?.startsWith("showlinenumbers") ||
|
|
203
|
+
language?.startsWith("nolinenumbers")) {
|
|
204
|
+
// add specifiers to meta
|
|
205
|
+
meta = (language + " " + meta).trim();
|
|
206
|
+
// remove all classnames like hljs, lang-x, language-x, because of false positive
|
|
207
|
+
code.properties.className = undefined;
|
|
208
|
+
}
|
|
209
|
+
const directiveShowLineNumbers = meta.includes("nolinenumbers")
|
|
182
210
|
? false
|
|
183
|
-
: meta.includes("showlinenumbers");
|
|
211
|
+
: settings.showLineNumbers || meta.includes("showlinenumbers");
|
|
184
212
|
let startingNumber = 1;
|
|
185
|
-
if
|
|
213
|
+
// find the number where the line number starts, if exists
|
|
214
|
+
if (directiveShowLineNumbers) {
|
|
186
215
|
const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
|
|
187
216
|
const start = REGEX1.exec(meta)?.groups?.start;
|
|
188
217
|
if (start && !isNaN(Number(start)))
|
|
@@ -192,12 +221,15 @@ const plugin = (options) => {
|
|
|
192
221
|
const REGEX2 = /{(?<lines>[\d\s,-]+)}/g;
|
|
193
222
|
const strLineNumbers = REGEX2.exec(meta)?.groups?.lines?.replace(/\s/g, "");
|
|
194
223
|
const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
|
|
195
|
-
if
|
|
224
|
+
// if nothing to do for numbering and highlihting, just return
|
|
225
|
+
if (!directiveShowLineNumbers && linesToBeHighlighted.length === 0)
|
|
196
226
|
return;
|
|
197
|
-
// flatten deeper nodes into first level <span>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
227
|
+
// flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
|
|
228
|
+
if (checkCodeTreeForFlatteningNeed(code)) {
|
|
229
|
+
flattenCodeTree(code);
|
|
230
|
+
}
|
|
231
|
+
// add container for each line mutating the code element
|
|
232
|
+
gutter(code, directiveShowLineNumbers, startingNumber, linesToBeHighlighted);
|
|
201
233
|
});
|
|
202
234
|
};
|
|
203
235
|
};
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../../src/index.ts"],"version":"5.7.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rehype-highlight-code-lines",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Rehype plugin to add line numbers to code blocks and allow highlighting of desired code lines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./dist/esm/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"lint": "eslint .",
|
|
14
14
|
"test": "vitest --watch=false",
|
|
15
15
|
"test:watch": "vitest",
|
|
16
|
-
"test:file": "vitest test.spec.ts",
|
|
16
|
+
"test:file": "vitest test.html.spec.ts",
|
|
17
17
|
"prepack": "npm run build",
|
|
18
18
|
"prepublishOnly": "npm run test && npm run format && npm run test-coverage",
|
|
19
19
|
"test-coverage": "vitest run --coverage"
|
|
@@ -30,12 +30,17 @@
|
|
|
30
30
|
},
|
|
31
31
|
"keywords": [
|
|
32
32
|
"unified",
|
|
33
|
-
"hast",
|
|
34
33
|
"rehype",
|
|
34
|
+
"hast",
|
|
35
35
|
"markdown",
|
|
36
|
+
"mdx",
|
|
36
37
|
"plugin",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
38
|
+
"highlight",
|
|
39
|
+
"highlighting",
|
|
40
|
+
"rehype plugin",
|
|
41
|
+
"rehype highlight",
|
|
42
|
+
"code highlighting",
|
|
43
|
+
"syntax highlighting",
|
|
39
44
|
"line numbering",
|
|
40
45
|
"line highlighting"
|
|
41
46
|
],
|
|
@@ -46,26 +51,30 @@
|
|
|
46
51
|
"url": "https://github.com/ipikuka/rehype-highlight-code-lines/issues"
|
|
47
52
|
},
|
|
48
53
|
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^9.19.0",
|
|
49
55
|
"@types/dedent": "^0.7.2",
|
|
50
|
-
"@types/node": "^
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
-
"@vitest/coverage-v8": "^1.6.0",
|
|
56
|
+
"@types/node": "^22.13.1",
|
|
57
|
+
"@vitest/coverage-v8": "^3.0.5",
|
|
58
|
+
"@vitest/eslint-plugin": "^1.1.25",
|
|
54
59
|
"dedent": "^1.5.3",
|
|
55
|
-
"eslint": "^
|
|
56
|
-
"eslint-config-prettier": "^
|
|
57
|
-
"eslint-plugin-prettier": "^5.
|
|
58
|
-
"prettier": "^3.
|
|
59
|
-
"rehype
|
|
60
|
-
"rehype-
|
|
60
|
+
"eslint": "^9.19.0",
|
|
61
|
+
"eslint-config-prettier": "^10.0.1",
|
|
62
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
63
|
+
"prettier": "^3.4.2",
|
|
64
|
+
"rehype": "^13.0.2",
|
|
65
|
+
"rehype-highlight": "^7.0.2",
|
|
66
|
+
"rehype-parse": "^9.0.1",
|
|
67
|
+
"rehype-stringify": "^10.0.1",
|
|
61
68
|
"remark-gfm": "^4.0.0",
|
|
62
69
|
"remark-parse": "^11.0.0",
|
|
63
|
-
"remark-rehype": "^11.1.
|
|
64
|
-
"rimraf": "^5.0.
|
|
65
|
-
"type-coverage": "^2.29.
|
|
66
|
-
"typescript": "^5.
|
|
67
|
-
"
|
|
68
|
-
"
|
|
70
|
+
"remark-rehype": "^11.1.1",
|
|
71
|
+
"rimraf": "^5.0.10",
|
|
72
|
+
"type-coverage": "^2.29.7",
|
|
73
|
+
"typescript": "^5.7.3",
|
|
74
|
+
"typescript-eslint": "^8.23.0",
|
|
75
|
+
"unist-util-remove-position": "^5.0.0",
|
|
76
|
+
"vfile": "^6.0.3",
|
|
77
|
+
"vitest": "^3.0.5"
|
|
69
78
|
},
|
|
70
79
|
"dependencies": {
|
|
71
80
|
"@types/hast": "^3.0.4",
|
|
@@ -77,7 +86,6 @@
|
|
|
77
86
|
"atLeast": 100,
|
|
78
87
|
"detail": true,
|
|
79
88
|
"ignoreAsAssertion": true,
|
|
80
|
-
"ignoreCatch": true,
|
|
81
89
|
"strict": true
|
|
82
90
|
},
|
|
83
91
|
"sideEffects": false
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import type { Plugin } from "unified";
|
|
2
2
|
import type { Root, Element, ElementContent, ElementData } from "hast";
|
|
3
|
-
import { type VisitorResult
|
|
3
|
+
import { visit, type VisitorResult } from "unist-util-visit";
|
|
4
4
|
import rangeParser from "parse-numeric-range";
|
|
5
5
|
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
7
6
|
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
8
7
|
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
10
8
|
type PartiallyRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
|
11
9
|
|
|
12
10
|
export type HighlightLinesOptions = {
|
|
13
11
|
showLineNumbers?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated container tag name is always "span"
|
|
14
|
+
* will be removed in the next versions
|
|
15
|
+
*/
|
|
14
16
|
lineContainerTagName?: "div" | "span";
|
|
15
17
|
};
|
|
16
18
|
|
|
@@ -34,7 +36,7 @@ export function clsx(arr: (string | false | null | undefined | 0)[]): string[] {
|
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
*
|
|
37
|
-
*
|
|
39
|
+
* add line numbers to code blocks and allow highlighting of desired code lines
|
|
38
40
|
*
|
|
39
41
|
*/
|
|
40
42
|
const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
@@ -46,45 +48,66 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
*
|
|
49
|
-
*
|
|
51
|
+
* check code element children need flattening or not
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
54
|
+
function checkCodeTreeForFlatteningNeed(code: Element): boolean {
|
|
55
|
+
const elementContents = code.children;
|
|
56
|
+
|
|
57
|
+
// type ElementContent = Comment | Element | Text
|
|
58
|
+
for (const elemenContent of elementContents) {
|
|
59
|
+
if (elemenContent.type === "element")
|
|
60
|
+
if (elemenContent.children.length >= 1 && elemenContent.children[0].type === "element")
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
* flatten code element children, recursively
|
|
50
70
|
* inspired from https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/highlight.js
|
|
51
71
|
*
|
|
52
72
|
*/
|
|
53
|
-
function flattenCodeTree(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const node = children[i];
|
|
60
|
-
if (node.type !== "element") {
|
|
61
|
-
newTree = newTree.concat([node]);
|
|
73
|
+
function flattenCodeTree(code: Element, className: string[] = []): ElementContent[] {
|
|
74
|
+
const newTree: ElementContent[] = [];
|
|
75
|
+
|
|
76
|
+
for (const elementContent of code.children) {
|
|
77
|
+
if (elementContent.type === "comment" || elementContent.type === "text") {
|
|
78
|
+
newTree.push(elementContent);
|
|
62
79
|
} else {
|
|
63
|
-
// @ts-expect-error
|
|
80
|
+
// @ts-expect-error className is different from other key of properties, and expected to be an array or undefined
|
|
64
81
|
// /* v8 ignore next */
|
|
65
|
-
const classNames = className.concat(
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
const classNames = className.concat(elementContent.properties.className || []);
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
elementContent.children.length === 1 &&
|
|
86
|
+
elementContent.children[0].type !== "element"
|
|
87
|
+
) {
|
|
88
|
+
elementContent.properties.className = classNames;
|
|
89
|
+
newTree.push(elementContent);
|
|
70
90
|
} else {
|
|
71
|
-
newTree
|
|
91
|
+
newTree.push(...flattenCodeTree(elementContent, classNames));
|
|
72
92
|
}
|
|
73
93
|
}
|
|
74
94
|
}
|
|
95
|
+
|
|
96
|
+
// Mutate the original code object
|
|
97
|
+
code.children = newTree;
|
|
75
98
|
return newTree;
|
|
76
99
|
}
|
|
77
100
|
|
|
78
101
|
/**
|
|
79
102
|
*
|
|
80
|
-
*
|
|
103
|
+
* construct the line element
|
|
81
104
|
*
|
|
82
105
|
*/
|
|
83
106
|
const createLine = (
|
|
84
107
|
children: ElementContent[],
|
|
85
108
|
lineNumber: number,
|
|
86
109
|
startingNumber: number,
|
|
87
|
-
|
|
110
|
+
directiveShowLineNumbers: boolean,
|
|
88
111
|
linesToBeHighlighted: number[],
|
|
89
112
|
): Element => {
|
|
90
113
|
const firstChild = children[0];
|
|
@@ -105,31 +128,43 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
105
128
|
|
|
106
129
|
return {
|
|
107
130
|
type: "element",
|
|
108
|
-
tagName:
|
|
131
|
+
tagName: "span", // now it is always "span"
|
|
109
132
|
children,
|
|
110
133
|
properties: {
|
|
111
134
|
className: clsx([
|
|
112
135
|
"code-line",
|
|
113
|
-
|
|
136
|
+
directiveShowLineNumbers && "numbered-code-line",
|
|
114
137
|
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
|
|
115
138
|
isAddition && "inserted",
|
|
116
139
|
isDeletion && "deleted",
|
|
117
140
|
]),
|
|
118
|
-
dataLineNumber:
|
|
141
|
+
dataLineNumber: directiveShowLineNumbers ? startingNumber - 1 + lineNumber : undefined,
|
|
119
142
|
},
|
|
120
143
|
};
|
|
121
144
|
};
|
|
122
145
|
|
|
123
146
|
// match all common types of line breaks
|
|
124
|
-
const
|
|
147
|
+
const REGEX_LINE_BREAKS = /\r?\n|\r/g;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
*
|
|
151
|
+
* check the code line is empty or with value only spaces
|
|
152
|
+
*
|
|
153
|
+
*/
|
|
154
|
+
function isEmptyLine(line: ElementContent[]): boolean {
|
|
155
|
+
return (
|
|
156
|
+
line.length === 0 ||
|
|
157
|
+
(line.length === 1 && line[0].type === "text" && line[0].value.trim() === "")
|
|
158
|
+
);
|
|
159
|
+
}
|
|
125
160
|
|
|
126
161
|
function gutter(
|
|
127
162
|
tree: Element,
|
|
128
|
-
|
|
163
|
+
directiveShowLineNumbers: boolean,
|
|
129
164
|
startingNumber: number,
|
|
130
165
|
linesToBeHighlighted: number[],
|
|
131
166
|
) {
|
|
132
|
-
const replacement:
|
|
167
|
+
const replacement: ElementContent[] = [];
|
|
133
168
|
|
|
134
169
|
let index = -1;
|
|
135
170
|
let start = 0;
|
|
@@ -139,76 +174,106 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
139
174
|
while (++index < tree.children.length) {
|
|
140
175
|
const child = tree.children[index];
|
|
141
176
|
|
|
142
|
-
if (child.type
|
|
143
|
-
let textStart = 0;
|
|
144
|
-
let match = RE.exec(child.value);
|
|
145
|
-
|
|
146
|
-
while (match) {
|
|
147
|
-
// Nodes in this line. (current child is exclusive)
|
|
148
|
-
const line = tree.children.slice(start, index);
|
|
177
|
+
if (child.type !== "text") continue;
|
|
149
178
|
|
|
150
|
-
|
|
179
|
+
let textStart = 0;
|
|
180
|
+
let match = REGEX_LINE_BREAKS.exec(child.value);
|
|
151
181
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
startTextRemainder = "";
|
|
156
|
-
}
|
|
182
|
+
while (match !== null) {
|
|
183
|
+
// Nodes in this line. (current child is exclusive)
|
|
184
|
+
const line = tree.children.slice(start, index);
|
|
157
185
|
|
|
158
|
-
|
|
186
|
+
// Prepend text from a partial matched earlier text.
|
|
187
|
+
if (startTextRemainder) {
|
|
188
|
+
line.unshift({ type: "text", value: startTextRemainder });
|
|
189
|
+
startTextRemainder = "";
|
|
190
|
+
}
|
|
159
191
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
192
|
+
// Append text from this text.
|
|
193
|
+
if (match.index > textStart) {
|
|
194
|
+
const value = child.value.slice(textStart, match.index);
|
|
195
|
+
line.push({ type: "text", value });
|
|
196
|
+
}
|
|
165
197
|
|
|
166
|
-
|
|
198
|
+
if (!isEmptyLine(line)) {
|
|
167
199
|
lineNumber += 1;
|
|
168
200
|
replacement.push(
|
|
169
|
-
createLine(
|
|
201
|
+
createLine(
|
|
202
|
+
line,
|
|
203
|
+
lineNumber,
|
|
204
|
+
startingNumber,
|
|
205
|
+
directiveShowLineNumbers,
|
|
206
|
+
linesToBeHighlighted,
|
|
207
|
+
),
|
|
170
208
|
);
|
|
209
|
+
}
|
|
171
210
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
replacement.push({ type: "text", value: match[0] });
|
|
175
|
-
}
|
|
211
|
+
// Add eol
|
|
212
|
+
replacement.push({ type: "text", value: match[0] });
|
|
176
213
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
match = RE.exec(child.value);
|
|
180
|
-
}
|
|
214
|
+
start = index + 1;
|
|
215
|
+
textStart = match.index + match[0].length;
|
|
181
216
|
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
// iterate the match
|
|
218
|
+
match = REGEX_LINE_BREAKS.exec(child.value);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// If we matched, make sure to not drop the text after the last line ending.
|
|
222
|
+
if (start === index + 1) {
|
|
223
|
+
startTextRemainder = child.value.slice(textStart);
|
|
186
224
|
}
|
|
187
225
|
}
|
|
188
226
|
|
|
189
227
|
const line = tree.children.slice(start);
|
|
190
228
|
|
|
191
|
-
/* v8 ignore start */
|
|
192
|
-
|
|
193
229
|
// Prepend text from a partial matched earlier text.
|
|
194
230
|
if (startTextRemainder) {
|
|
195
231
|
line.unshift({ type: "text", value: startTextRemainder });
|
|
196
232
|
startTextRemainder = "";
|
|
197
233
|
}
|
|
198
234
|
|
|
199
|
-
if (line
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
235
|
+
if (!isEmptyLine(line)) {
|
|
236
|
+
if (line.length > 0) {
|
|
237
|
+
lineNumber += 1;
|
|
238
|
+
replacement.push(
|
|
239
|
+
createLine(
|
|
240
|
+
line,
|
|
241
|
+
lineNumber,
|
|
242
|
+
startingNumber,
|
|
243
|
+
directiveShowLineNumbers,
|
|
244
|
+
linesToBeHighlighted,
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
}
|
|
204
248
|
}
|
|
205
249
|
|
|
206
|
-
/* v8 ignore end */
|
|
207
|
-
|
|
208
250
|
// Replace children with new array.
|
|
209
251
|
tree.children = replacement;
|
|
210
252
|
}
|
|
211
253
|
|
|
254
|
+
/**
|
|
255
|
+
*
|
|
256
|
+
* get the programming language analyzing the classNames
|
|
257
|
+
*
|
|
258
|
+
*/
|
|
259
|
+
function getLanguage(classNames: (string | number)[] | undefined): string | undefined {
|
|
260
|
+
const isLanguageString = (element: string | number): element is string => {
|
|
261
|
+
return String(element).startsWith("language-") || String(element).startsWith("lang-");
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const languageString = classNames?.find(isLanguageString);
|
|
265
|
+
|
|
266
|
+
if (languageString?.slice(0, 5) === "lang-") {
|
|
267
|
+
return languageString.slice(5).toLowerCase();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (languageString?.slice(0, 9) === "language-") {
|
|
271
|
+
return languageString.slice(9).toLowerCase();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return languageString;
|
|
275
|
+
}
|
|
276
|
+
|
|
212
277
|
/**
|
|
213
278
|
* Transform.
|
|
214
279
|
*
|
|
@@ -219,61 +284,46 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
219
284
|
*/
|
|
220
285
|
return (tree: Root): undefined => {
|
|
221
286
|
visit(tree, "element", function (node, index, parent): VisitorResult {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (node.tagName !== "pre") return CONTINUE;
|
|
226
|
-
|
|
227
|
-
const code = node.children[0];
|
|
228
|
-
|
|
229
|
-
/* v8 ignore next */
|
|
230
|
-
if (!code || code.type !== "element" || code.tagName !== "code") return;
|
|
287
|
+
if (!parent || index === undefined || node.tagName !== "code") {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
231
290
|
|
|
232
|
-
|
|
291
|
+
if (parent.type !== "element" || parent.tagName !== "pre") {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
233
294
|
|
|
234
|
-
|
|
235
|
-
if (Array.isArray(code.properties.className)) {
|
|
236
|
-
const testingFunction = (element: string | number): element is string =>
|
|
237
|
-
typeof element === "string" && element.startsWith("language-");
|
|
295
|
+
const code = node;
|
|
238
296
|
|
|
239
|
-
|
|
297
|
+
const classNames = code.properties.className;
|
|
240
298
|
|
|
241
|
-
|
|
242
|
-
|
|
299
|
+
// only for type narrowing
|
|
300
|
+
/* v8 ignore next */
|
|
301
|
+
if (!Array.isArray(classNames) && classNames !== undefined) return;
|
|
243
302
|
|
|
244
|
-
|
|
245
|
-
language.startsWith("{") ||
|
|
246
|
-
language.startsWith("showlinenumbers") ||
|
|
247
|
-
language.startsWith("nolinenumbers")
|
|
248
|
-
) {
|
|
249
|
-
meta = meta ? language + meta : language;
|
|
303
|
+
let meta = (code.data as CodeData)?.meta?.toLowerCase().trim() || "";
|
|
250
304
|
|
|
251
|
-
|
|
305
|
+
const language = getLanguage(classNames);
|
|
252
306
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
307
|
+
if (
|
|
308
|
+
language?.startsWith("{") ||
|
|
309
|
+
language?.startsWith("showlinenumbers") ||
|
|
310
|
+
language?.startsWith("nolinenumbers")
|
|
311
|
+
) {
|
|
312
|
+
// add specifiers to meta
|
|
313
|
+
meta = (language + " " + meta).trim();
|
|
259
314
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
meta = "showlinenumbers";
|
|
263
|
-
} else if (!meta.includes("showlinenumbers")) {
|
|
264
|
-
meta = meta + " showlinenumbers";
|
|
265
|
-
}
|
|
315
|
+
// remove all classnames like hljs, lang-x, language-x, because of false positive
|
|
316
|
+
code.properties.className = undefined;
|
|
266
317
|
}
|
|
267
318
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const showLineNumbers = meta.includes("nolinenumbers")
|
|
319
|
+
const directiveShowLineNumbers = meta.includes("nolinenumbers")
|
|
271
320
|
? false
|
|
272
|
-
: meta.includes("showlinenumbers");
|
|
321
|
+
: settings.showLineNumbers || meta.includes("showlinenumbers");
|
|
273
322
|
|
|
274
323
|
let startingNumber = 1;
|
|
275
324
|
|
|
276
|
-
if
|
|
325
|
+
// find the number where the line number starts, if exists
|
|
326
|
+
if (directiveShowLineNumbers) {
|
|
277
327
|
const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
|
|
278
328
|
const start = REGEX1.exec(meta)?.groups?.start;
|
|
279
329
|
if (start && !isNaN(Number(start))) startingNumber = Number(start);
|
|
@@ -284,13 +334,16 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
284
334
|
const strLineNumbers = REGEX2.exec(meta)?.groups?.lines?.replace(/\s/g, "");
|
|
285
335
|
const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
|
|
286
336
|
|
|
287
|
-
if
|
|
337
|
+
// if nothing to do for numbering and highlihting, just return
|
|
338
|
+
if (!directiveShowLineNumbers && linesToBeHighlighted.length === 0) return;
|
|
288
339
|
|
|
289
|
-
// flatten deeper nodes into first level <span>
|
|
290
|
-
|
|
340
|
+
// flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
|
|
341
|
+
if (checkCodeTreeForFlatteningNeed(code)) {
|
|
342
|
+
flattenCodeTree(code);
|
|
343
|
+
}
|
|
291
344
|
|
|
292
|
-
// add
|
|
293
|
-
gutter(code,
|
|
345
|
+
// add container for each line mutating the code element
|
|
346
|
+
gutter(code, directiveShowLineNumbers, startingNumber, linesToBeHighlighted);
|
|
294
347
|
});
|
|
295
348
|
};
|
|
296
349
|
};
|