rehype-highlight-code-lines 0.0.1 → 0.0.3
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 +34 -3
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +61 -29
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +86 -48
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[![typescript][badge-typescript]][typescript-url]
|
|
9
9
|
[![License][badge-license]][github-license-url]
|
|
10
10
|
|
|
11
|
-
This package is a [unified][unified] ([rehype][rehype]) plugin **to add
|
|
11
|
+
This package is a [unified][unified] ([rehype][rehype]) plugin **to add container to each line in a code block, allowing numbering of the code block and highlighting desired lines of code**.
|
|
12
12
|
|
|
13
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
|
|
|
@@ -131,18 +131,49 @@ Without `rehype-highlight-code-lines`, the lines of code wouldn't be in a `div`.
|
|
|
131
131
|
</pre>
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
***Note:** `hljs` prefix comes from `rehype-highlight`*.
|
|
135
|
+
|
|
134
136
|
## Options
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
All options are **optional** and have **default values**.
|
|
137
139
|
|
|
138
140
|
```typescript
|
|
139
141
|
type HighlightLinesOptions = {
|
|
140
|
-
showLineNumbers?: boolean; //
|
|
142
|
+
showLineNumbers?: boolean; // default is "false"
|
|
143
|
+
lineContainerTagName?: "div" | "span"; // default is "span"
|
|
141
144
|
};
|
|
142
145
|
|
|
143
146
|
use(rehypeHighlightLines, HighlightLinesOptions);
|
|
144
147
|
```
|
|
145
148
|
|
|
149
|
+
#### `showLineNumbers`
|
|
150
|
+
|
|
151
|
+
It is a **boolean** option which is for all code blocks will be numbered.
|
|
152
|
+
|
|
153
|
+
By default, it is `false`.
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
use(rehypeHighlightLines, {
|
|
157
|
+
showLineNumbers: true,
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Now, all code blocks will become numbered by line.
|
|
162
|
+
|
|
163
|
+
#### `lineContainerTagName`
|
|
164
|
+
|
|
165
|
+
It is a **union** type option which is **"div" | "span"** for providing custom HTML tag name for code lines.
|
|
166
|
+
|
|
167
|
+
By default, it is `span` which is **inline** level container. If you set it as `div`, the container will be **block** level, in perspective of CSS.
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
use(rehypeHighlightLines, {
|
|
171
|
+
lineContainerTagName: "div",
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Now, the code line container's tag name will be `div`.
|
|
176
|
+
|
|
146
177
|
### Examples:
|
|
147
178
|
|
|
148
179
|
```typescript
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Plugin } from "unified";
|
|
|
2
2
|
import type { Root } from "hast";
|
|
3
3
|
export type HighlightLinesOptions = {
|
|
4
4
|
showLineNumbers?: boolean;
|
|
5
|
+
lineContainerTagName?: "div" | "span";
|
|
5
6
|
};
|
|
6
7
|
export declare function clsx(arr: (string | false | null | undefined | 0)[]): string[];
|
|
7
8
|
/**
|
package/dist/esm/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { visit, CONTINUE } from "unist-util-visit";
|
|
|
2
2
|
import rangeParser from "parse-numeric-range";
|
|
3
3
|
const DEFAULT_SETTINGS = {
|
|
4
4
|
showLineNumbers: false,
|
|
5
|
+
lineContainerTagName: "span",
|
|
5
6
|
};
|
|
6
7
|
// a simple util for our use case, like clsx package
|
|
7
8
|
export function clsx(arr) {
|
|
@@ -14,25 +15,55 @@ export function clsx(arr) {
|
|
|
14
15
|
*/
|
|
15
16
|
const plugin = (options) => {
|
|
16
17
|
const settings = Object.assign({}, DEFAULT_SETTINGS, options);
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* flatten deep nodes
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
function flattenCodeTree(children, className = [], newTree = []) {
|
|
24
|
+
for (let i = 0; i < children.length; i++) {
|
|
25
|
+
const node = children[i];
|
|
26
|
+
if (node.type !== "element") {
|
|
27
|
+
newTree = newTree.concat([node]);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// @ts-expect-error
|
|
31
|
+
// /* v8 ignore next */
|
|
32
|
+
const classNames = className.concat(node.properties?.className || []);
|
|
33
|
+
if (node.children.length === 1 && node.children[0].type !== "element") {
|
|
34
|
+
node.properties.className = classNames;
|
|
35
|
+
newTree = newTree.concat([node]);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
newTree = newTree.concat(flattenCodeTree(node.children, classNames));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return newTree;
|
|
43
|
+
}
|
|
17
44
|
/**
|
|
18
45
|
*
|
|
19
46
|
* constructs the line element
|
|
20
47
|
*
|
|
21
48
|
*/
|
|
22
|
-
const createLine = (children, lineNumber,
|
|
49
|
+
const createLine = (children, lineNumber, showLineNumbers, linesToBeHighlighted) => {
|
|
23
50
|
return {
|
|
24
51
|
type: "element",
|
|
25
|
-
tagName:
|
|
52
|
+
tagName: settings.lineContainerTagName,
|
|
26
53
|
children,
|
|
27
54
|
properties: {
|
|
28
|
-
className:
|
|
29
|
-
|
|
55
|
+
className: clsx([
|
|
56
|
+
"code-line",
|
|
57
|
+
showLineNumbers && "numbered-code-line",
|
|
58
|
+
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
|
|
59
|
+
]),
|
|
60
|
+
dataLineNumber: showLineNumbers ? lineNumber : undefined,
|
|
30
61
|
},
|
|
31
62
|
};
|
|
32
63
|
};
|
|
33
64
|
// match all common types of line breaks
|
|
34
65
|
const RE = /\r?\n|\r/g;
|
|
35
|
-
function
|
|
66
|
+
function gutter(tree, showLineNumbers, linesToBeHighlighted) {
|
|
36
67
|
const replacement = [];
|
|
37
68
|
let index = -1;
|
|
38
69
|
let start = 0;
|
|
@@ -44,7 +75,7 @@ const plugin = (options) => {
|
|
|
44
75
|
let textStart = 0;
|
|
45
76
|
let match = RE.exec(child.value);
|
|
46
77
|
while (match) {
|
|
47
|
-
// Nodes in this line.
|
|
78
|
+
// Nodes in this line. (current child is exclusive)
|
|
48
79
|
const line = tree.children.slice(start, index);
|
|
49
80
|
/* v8 ignore start */
|
|
50
81
|
// Prepend text from a partial matched earlier text.
|
|
@@ -55,21 +86,16 @@ const plugin = (options) => {
|
|
|
55
86
|
/* v8 ignore end */
|
|
56
87
|
// Append text from this text.
|
|
57
88
|
if (match.index > textStart) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
value: child.value.slice(textStart, match.index),
|
|
61
|
-
});
|
|
89
|
+
const value = child.value.slice(textStart, match.index);
|
|
90
|
+
line.push({ type: "text", value });
|
|
62
91
|
}
|
|
63
|
-
// Add a line
|
|
92
|
+
// Add a line
|
|
64
93
|
lineNumber += 1;
|
|
65
|
-
replacement.push(createLine(line,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
type: "text",
|
|
71
|
-
value: match[0],
|
|
72
|
-
});
|
|
94
|
+
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
|
|
95
|
+
// Add eol if the tag name is "span"
|
|
96
|
+
if (settings.lineContainerTagName === "span") {
|
|
97
|
+
replacement.push({ type: "text", value: match[0] });
|
|
98
|
+
}
|
|
73
99
|
start = index + 1;
|
|
74
100
|
textStart = match.index + match[0].length;
|
|
75
101
|
match = RE.exec(child.value);
|
|
@@ -89,11 +115,7 @@ const plugin = (options) => {
|
|
|
89
115
|
}
|
|
90
116
|
if (line.length > 0) {
|
|
91
117
|
lineNumber += 1;
|
|
92
|
-
replacement.push(createLine(line,
|
|
93
|
-
"code-line",
|
|
94
|
-
showLineNumbers && "numbered-code-line",
|
|
95
|
-
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
|
|
96
|
-
])));
|
|
118
|
+
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
|
|
97
119
|
}
|
|
98
120
|
/* v8 ignore end */
|
|
99
121
|
// Replace children with new array.
|
|
@@ -127,24 +149,34 @@ const plugin = (options) => {
|
|
|
127
149
|
const language = className.slice(9).toLowerCase();
|
|
128
150
|
if (language.startsWith("{") || language.startsWith("showlinenumbers")) {
|
|
129
151
|
meta = meta ? language + meta : language;
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
code.properties.className[
|
|
152
|
+
const idx = code.properties.className.findIndex(testingFunction);
|
|
153
|
+
if (idx > -1) {
|
|
154
|
+
code.properties.className[idx] = "language-unknown";
|
|
133
155
|
}
|
|
134
156
|
}
|
|
135
157
|
}
|
|
136
158
|
}
|
|
159
|
+
if (settings.showLineNumbers) {
|
|
160
|
+
if (!meta) {
|
|
161
|
+
meta = "showlinenumbers";
|
|
162
|
+
}
|
|
163
|
+
else if (!meta.includes("showlinenumbers")) {
|
|
164
|
+
meta = meta + " showlinenumbers";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
137
167
|
if (!meta)
|
|
138
168
|
return;
|
|
139
|
-
const showLineNumbers =
|
|
169
|
+
const showLineNumbers = meta.includes("showlinenumbers");
|
|
140
170
|
// find number range string within curly braces and parse it
|
|
141
171
|
const RE = /{(?<lines>[\d\s,-]+)}/g;
|
|
142
172
|
const strLineNumbers = RE.exec(meta)?.groups?.lines?.replace(/\s/g, "");
|
|
143
173
|
const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
|
|
144
174
|
if (!showLineNumbers && linesToBeHighlighted.length === 0)
|
|
145
175
|
return;
|
|
176
|
+
// flatten deeper nodes into first level <span>s an texts
|
|
177
|
+
code.children = flattenCodeTree(code.children);
|
|
146
178
|
// add wrapper for each line mutating the code element
|
|
147
|
-
|
|
179
|
+
gutter(code, showLineNumbers, linesToBeHighlighted);
|
|
148
180
|
});
|
|
149
181
|
};
|
|
150
182
|
};
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,WAAW,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,WAAW,MAAM,qBAAqB,CAAC;AAa9C,MAAM,gBAAgB,GAA0B;IAC9C,eAAe,EAAE,KAAK;IACtB,oBAAoB,EAAE,MAAM;CAC7B,CAAC;AAUF,oDAAoD;AACpD,MAAM,UAAU,IAAI,CAAC,GAA8C;IACjE,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,MAAM,GAA2C,CAAC,OAAO,EAAE,EAAE;IACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAC5B,EAAE,EACF,gBAAgB,EAChB,OAAO,CACkC,CAAC;IAE5C;;;;OAIG;IACH,SAAS,eAAe,CACtB,QAA0B,EAC1B,YAAsB,EAAE,EACxB,UAA4B,EAAE;QAE9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC5B,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,mBAAmB;gBACnB,uBAAuB;gBACvB,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;gBAEtE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;oBACtE,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC;oBACvC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBACnC,CAAC;qBAAM,CAAC;oBACN,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACH,MAAM,UAAU,GAAG,CACjB,QAA0B,EAC1B,UAAkB,EAClB,eAAwB,EACxB,oBAA8B,EACrB,EAAE;QACX,OAAO;YACL,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,QAAQ,CAAC,oBAAoB;YACtC,QAAQ;YACR,UAAU,EAAE;gBACV,SAAS,EAAE,IAAI,CAAC;oBACd,WAAW;oBACX,eAAe,IAAI,oBAAoB;oBACvC,oBAAoB,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,uBAAuB;iBACrE,CAAC;gBACF,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;aACzD;SACF,CAAC;IACJ,CAAC,CAAC;IAEF,wCAAwC;IACxC,MAAM,EAAE,GAAG,WAAW,CAAC;IAEvB,SAAS,MAAM,CAAC,IAAa,EAAE,eAAwB,EAAE,oBAA8B;QACrF,MAAM,WAAW,GAA0B,EAAE,CAAC;QAE9C,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QACf,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,kBAAkB,GAAG,EAAE,CAAC;QAC5B,IAAI,UAAU,GAAG,CAAC,CAAC;QAEnB,OAAO,EAAE,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAEnC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC1B,IAAI,SAAS,GAAG,CAAC,CAAC;gBAClB,IAAI,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAEjC,OAAO,KAAK,EAAE,CAAC;oBACb,mDAAmD;oBACnD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;oBAE/C,qBAAqB;oBAErB,oDAAoD;oBACpD,IAAI,kBAAkB,EAAE,CAAC;wBACvB,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBAC1D,kBAAkB,GAAG,EAAE,CAAC;oBAC1B,CAAC;oBAED,mBAAmB;oBAEnB,8BAA8B;oBAC9B,IAAI,KAAK,CAAC,KAAK,GAAG,SAAS,EAAE,CAAC;wBAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;wBACxD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;oBACrC,CAAC;oBAED,aAAa;oBACb,UAAU,IAAI,CAAC,CAAC;oBAChB,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,CAAC,CAAC,CAAC;oBAEtF,oCAAoC;oBACpC,IAAI,QAAQ,CAAC,oBAAoB,KAAK,MAAM,EAAE,CAAC;wBAC7C,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBACtD,CAAC;oBAED,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC;oBAClB,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;oBAC1C,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC/B,CAAC;gBAED,4EAA4E;gBAC5E,IAAI,KAAK,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;oBACxB,kBAAkB,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBACpD,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAExC,qBAAqB;QAErB,oDAAoD;QACpD,IAAI,kBAAkB,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC1D,kBAAkB,GAAG,EAAE,CAAC;QAC1B,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,UAAU,IAAI,CAAC,CAAC;YAChB,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACxF,CAAC;QAED,mBAAmB;QAEnB,mCAAmC;QACnC,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC;IAC9B,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,IAAU,EAAa,EAAE;QAC/B,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,IAAI,EAAE,KAAK,EAAE,MAAM;YAClD,oBAAoB;YACpB,IAAI,CAAC,MAAM,IAAI,OAAO,KAAK,KAAK,WAAW;gBAAE,OAAO;YAEpD,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;gBAAE,OAAO,QAAQ,CAAC;YAE5C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAE9B,oBAAoB;YACpB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM;gBAAE,OAAO;YAExE,IAAI,IAAI,GAAI,IAAI,CAAC,IAAiB,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;YAE/D,wDAAwD;YACxD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7C,MAAM,eAAe,GAAG,CAAC,OAAwB,EAAqB,EAAE,CACtE,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;gBAEjE,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;gBAElE,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;oBAElD,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;wBACvE,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;wBAEzC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;wBAEjE,IAAI,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;4BACb,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC;wBACtD,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,QAAQ,CAAC,eAAe,EAAE,CAAC;gBAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,IAAI,GAAG,iBAAiB,CAAC;gBAC3B,CAAC;qBAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;oBAC7C,IAAI,GAAG,IAAI,GAAG,kBAAkB,CAAC;gBACnC,CAAC;YACH,CAAC;YAED,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;YAEzD,4DAA4D;YAC5D,MAAM,EAAE,GAAG,wBAAwB,CAAC;YACpC,MAAM,cAAc,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACxE,MAAM,oBAAoB,GAAG,cAAc,CAAC,CAAC,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAE/E,IAAI,CAAC,eAAe,IAAI,oBAAoB,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAElE,yDAAyD;YACzD,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAE/C,sDAAsD;YACtD,MAAM,CAAC,IAAI,EAAE,eAAe,EAAE,oBAAoB,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,14 +3,26 @@ import type { Root, Element, ElementContent, ElementData } from "hast";
|
|
|
3
3
|
import { type VisitorResult, visit, CONTINUE } from "unist-util-visit";
|
|
4
4
|
import rangeParser from "parse-numeric-range";
|
|
5
5
|
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
7
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
10
|
+
type PartiallyRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
|
11
|
+
|
|
6
12
|
export type HighlightLinesOptions = {
|
|
7
13
|
showLineNumbers?: boolean;
|
|
14
|
+
lineContainerTagName?: "div" | "span";
|
|
8
15
|
};
|
|
9
16
|
|
|
10
17
|
const DEFAULT_SETTINGS: HighlightLinesOptions = {
|
|
11
18
|
showLineNumbers: false,
|
|
19
|
+
lineContainerTagName: "span",
|
|
12
20
|
};
|
|
13
21
|
|
|
22
|
+
type PartiallyRequiredHighlightLinesOptions = Prettify<
|
|
23
|
+
PartiallyRequired<HighlightLinesOptions, "showLineNumbers" | "lineContainerTagName">
|
|
24
|
+
>;
|
|
25
|
+
|
|
14
26
|
type CodeData = ElementData & {
|
|
15
27
|
meta?: string;
|
|
16
28
|
};
|
|
@@ -26,7 +38,41 @@ export function clsx(arr: (string | false | null | undefined | 0)[]): string[] {
|
|
|
26
38
|
*
|
|
27
39
|
*/
|
|
28
40
|
const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
29
|
-
const settings = Object.assign(
|
|
41
|
+
const settings = Object.assign(
|
|
42
|
+
{},
|
|
43
|
+
DEFAULT_SETTINGS,
|
|
44
|
+
options,
|
|
45
|
+
) as PartiallyRequiredHighlightLinesOptions;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* flatten deep nodes
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
function flattenCodeTree(
|
|
53
|
+
children: ElementContent[],
|
|
54
|
+
className: string[] = [],
|
|
55
|
+
newTree: ElementContent[] = [],
|
|
56
|
+
): ElementContent[] {
|
|
57
|
+
for (let i = 0; i < children.length; i++) {
|
|
58
|
+
const node = children[i];
|
|
59
|
+
if (node.type !== "element") {
|
|
60
|
+
newTree = newTree.concat([node]);
|
|
61
|
+
} else {
|
|
62
|
+
// @ts-expect-error
|
|
63
|
+
// /* v8 ignore next */
|
|
64
|
+
const classNames = className.concat(node.properties?.className || []);
|
|
65
|
+
|
|
66
|
+
if (node.children.length === 1 && node.children[0].type !== "element") {
|
|
67
|
+
node.properties.className = classNames;
|
|
68
|
+
newTree = newTree.concat([node]);
|
|
69
|
+
} else {
|
|
70
|
+
newTree = newTree.concat(flattenCodeTree(node.children, classNames));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return newTree;
|
|
75
|
+
}
|
|
30
76
|
|
|
31
77
|
/**
|
|
32
78
|
*
|
|
@@ -35,16 +81,21 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
35
81
|
*/
|
|
36
82
|
const createLine = (
|
|
37
83
|
children: ElementContent[],
|
|
38
|
-
lineNumber: number
|
|
39
|
-
|
|
84
|
+
lineNumber: number,
|
|
85
|
+
showLineNumbers: boolean,
|
|
86
|
+
linesToBeHighlighted: number[],
|
|
40
87
|
): Element => {
|
|
41
88
|
return {
|
|
42
89
|
type: "element",
|
|
43
|
-
tagName:
|
|
90
|
+
tagName: settings.lineContainerTagName,
|
|
44
91
|
children,
|
|
45
92
|
properties: {
|
|
46
|
-
className:
|
|
47
|
-
|
|
93
|
+
className: clsx([
|
|
94
|
+
"code-line",
|
|
95
|
+
showLineNumbers && "numbered-code-line",
|
|
96
|
+
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
|
|
97
|
+
]),
|
|
98
|
+
dataLineNumber: showLineNumbers ? lineNumber : undefined,
|
|
48
99
|
},
|
|
49
100
|
};
|
|
50
101
|
};
|
|
@@ -52,12 +103,9 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
52
103
|
// match all common types of line breaks
|
|
53
104
|
const RE = /\r?\n|\r/g;
|
|
54
105
|
|
|
55
|
-
function
|
|
56
|
-
tree: Element,
|
|
57
|
-
showLineNumbers: boolean,
|
|
58
|
-
linesToBeHighlighted: number[],
|
|
59
|
-
) {
|
|
106
|
+
function gutter(tree: Element, showLineNumbers: boolean, linesToBeHighlighted: number[]) {
|
|
60
107
|
const replacement: Array<ElementContent> = [];
|
|
108
|
+
|
|
61
109
|
let index = -1;
|
|
62
110
|
let start = 0;
|
|
63
111
|
let startTextRemainder = "";
|
|
@@ -71,7 +119,7 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
71
119
|
let match = RE.exec(child.value);
|
|
72
120
|
|
|
73
121
|
while (match) {
|
|
74
|
-
// Nodes in this line.
|
|
122
|
+
// Nodes in this line. (current child is exclusive)
|
|
75
123
|
const line = tree.children.slice(start, index);
|
|
76
124
|
|
|
77
125
|
/* v8 ignore start */
|
|
@@ -86,29 +134,18 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
86
134
|
|
|
87
135
|
// Append text from this text.
|
|
88
136
|
if (match.index > textStart) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
value: child.value.slice(textStart, match.index),
|
|
92
|
-
});
|
|
137
|
+
const value = child.value.slice(textStart, match.index);
|
|
138
|
+
line.push({ type: "text", value });
|
|
93
139
|
}
|
|
94
140
|
|
|
95
|
-
// Add a line
|
|
141
|
+
// Add a line
|
|
96
142
|
lineNumber += 1;
|
|
97
|
-
replacement.push(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
showLineNumbers && "numbered-code-line",
|
|
104
|
-
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
|
|
105
|
-
]),
|
|
106
|
-
),
|
|
107
|
-
{
|
|
108
|
-
type: "text",
|
|
109
|
-
value: match[0],
|
|
110
|
-
},
|
|
111
|
-
);
|
|
143
|
+
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
|
|
144
|
+
|
|
145
|
+
// Add eol if the tag name is "span"
|
|
146
|
+
if (settings.lineContainerTagName === "span") {
|
|
147
|
+
replacement.push({ type: "text", value: match[0] });
|
|
148
|
+
}
|
|
112
149
|
|
|
113
150
|
start = index + 1;
|
|
114
151
|
textStart = match.index + match[0].length;
|
|
@@ -134,17 +171,7 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
134
171
|
|
|
135
172
|
if (line.length > 0) {
|
|
136
173
|
lineNumber += 1;
|
|
137
|
-
replacement.push(
|
|
138
|
-
createLine(
|
|
139
|
-
line,
|
|
140
|
-
showLineNumbers ? lineNumber : undefined,
|
|
141
|
-
clsx([
|
|
142
|
-
"code-line",
|
|
143
|
-
showLineNumbers && "numbered-code-line",
|
|
144
|
-
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
|
|
145
|
-
]),
|
|
146
|
-
),
|
|
147
|
-
);
|
|
174
|
+
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted));
|
|
148
175
|
}
|
|
149
176
|
|
|
150
177
|
/* v8 ignore end */
|
|
@@ -188,18 +215,26 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
188
215
|
if (language.startsWith("{") || language.startsWith("showlinenumbers")) {
|
|
189
216
|
meta = meta ? language + meta : language;
|
|
190
217
|
|
|
191
|
-
const
|
|
218
|
+
const idx = code.properties.className.findIndex(testingFunction);
|
|
192
219
|
|
|
193
|
-
if (
|
|
194
|
-
code.properties.className[
|
|
220
|
+
if (idx > -1) {
|
|
221
|
+
code.properties.className[idx] = "language-unknown";
|
|
195
222
|
}
|
|
196
223
|
}
|
|
197
224
|
}
|
|
198
225
|
}
|
|
199
226
|
|
|
227
|
+
if (settings.showLineNumbers) {
|
|
228
|
+
if (!meta) {
|
|
229
|
+
meta = "showlinenumbers";
|
|
230
|
+
} else if (!meta.includes("showlinenumbers")) {
|
|
231
|
+
meta = meta + " showlinenumbers";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
200
235
|
if (!meta) return;
|
|
201
236
|
|
|
202
|
-
const showLineNumbers =
|
|
237
|
+
const showLineNumbers = meta.includes("showlinenumbers");
|
|
203
238
|
|
|
204
239
|
// find number range string within curly braces and parse it
|
|
205
240
|
const RE = /{(?<lines>[\d\s,-]+)}/g;
|
|
@@ -208,8 +243,11 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
|
|
|
208
243
|
|
|
209
244
|
if (!showLineNumbers && linesToBeHighlighted.length === 0) return;
|
|
210
245
|
|
|
246
|
+
// flatten deeper nodes into first level <span>s an texts
|
|
247
|
+
code.children = flattenCodeTree(code.children);
|
|
248
|
+
|
|
211
249
|
// add wrapper for each line mutating the code element
|
|
212
|
-
|
|
250
|
+
gutter(code, showLineNumbers, linesToBeHighlighted);
|
|
213
251
|
});
|
|
214
252
|
};
|
|
215
253
|
};
|