rehype-highlight-code-lines
Advanced tools
Comparing version
@@ -5,2 +5,3 @@ import type { Plugin } from "unified"; | ||
showLineNumbers?: boolean; | ||
lineContainerTagName?: "div" | "span"; | ||
}; | ||
@@ -7,0 +8,0 @@ export declare function clsx(arr: (string | false | null | undefined | 0)[]): string[]; |
@@ -5,2 +5,3 @@ import { visit, CONTINUE } from "unist-util-visit"; | ||
showLineNumbers: false, | ||
lineContainerTagName: "span", | ||
}; | ||
@@ -20,13 +21,42 @@ // a simple util for our use case, like clsx package | ||
* | ||
* flatten deep nodes | ||
* | ||
*/ | ||
function flattenCodeTree(children, className = [], newTree = []) { | ||
for (let i = 0; i < children.length; i++) { | ||
const node = children[i]; | ||
console.log(node); | ||
if (node.type !== "element") { | ||
newTree = newTree.concat([node]); | ||
} | ||
else if (node.children.length === 1 && node.children[0].type !== "element") { | ||
// @ts-expect-error | ||
node.properties.className = className.concat(node.properties.className); | ||
newTree = newTree.concat([node]); | ||
} | ||
else { | ||
// @ts-expect-error | ||
const classNames = className.concat(node.properties?.className || []); | ||
newTree = newTree.concat(flattenCodeTree(node.children, classNames)); | ||
} | ||
} | ||
return newTree; | ||
} | ||
/** | ||
* | ||
* constructs the line element | ||
* | ||
*/ | ||
const createLine = (children, lineNumber, classNames) => { | ||
const createLine = (children, lineNumber, showLineNumbers, linesToBeHighlighted) => { | ||
return { | ||
type: "element", | ||
tagName: "div", | ||
tagName: settings.lineContainerTagName, | ||
children, | ||
properties: { | ||
className: classNames, | ||
...(lineNumber && { "data-line-number": lineNumber }), | ||
className: clsx([ | ||
"code-line", | ||
showLineNumbers && "numbered-code-line", | ||
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line", | ||
]), | ||
dataLineNumber: showLineNumbers ? lineNumber : undefined, | ||
}, | ||
@@ -37,3 +67,3 @@ }; | ||
const RE = /\r?\n|\r/g; | ||
function starryNightGutter(tree, showLineNumbers, linesToBeHighlighted) { | ||
function gutter(tree, showLineNumbers, linesToBeHighlighted) { | ||
const replacement = []; | ||
@@ -50,3 +80,3 @@ let index = -1; | ||
while (match) { | ||
// Nodes in this line. | ||
// Nodes in this line. (current child is exclusive) | ||
const line = tree.children.slice(start, index); | ||
@@ -62,17 +92,12 @@ /* v8 ignore start */ | ||
if (match.index > textStart) { | ||
line.push({ | ||
type: "text", | ||
value: child.value.slice(textStart, match.index), | ||
}); | ||
const value = child.value.slice(textStart, match.index); | ||
line.push({ type: "text", value }); | ||
} | ||
// Add a line, and the eol. | ||
// Add a line | ||
lineNumber += 1; | ||
replacement.push(createLine(line, showLineNumbers ? lineNumber : undefined, clsx([ | ||
"code-line", | ||
showLineNumbers && "numbered-code-line", | ||
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line", | ||
])), { | ||
type: "text", | ||
value: match[0], | ||
}); | ||
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted)); | ||
// Add eol if the tag name is "span" | ||
if (settings.lineContainerTagName === "span") { | ||
replacement.push({ type: "text", value: match[0] }); | ||
} | ||
start = index + 1; | ||
@@ -97,7 +122,3 @@ textStart = match.index + match[0].length; | ||
lineNumber += 1; | ||
replacement.push(createLine(line, showLineNumbers ? lineNumber : undefined, clsx([ | ||
"code-line", | ||
showLineNumbers && "numbered-code-line", | ||
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line", | ||
]))); | ||
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted)); | ||
} | ||
@@ -136,5 +157,5 @@ /* v8 ignore end */ | ||
meta = meta ? language + meta : language; | ||
const index = code.properties.className.findIndex(testingFunction); | ||
if (index > -1) { | ||
code.properties.className[index] = "language-unknown"; | ||
const idx = code.properties.className.findIndex(testingFunction); | ||
if (idx > -1) { | ||
code.properties.className[idx] = "language-unknown"; | ||
} | ||
@@ -153,4 +174,6 @@ } | ||
return; | ||
// flatten deeper nodes into first level <span>s an texts | ||
code.children = flattenCodeTree(code.children); | ||
// add wrapper for each line mutating the code element | ||
starryNightGutter(code, showLineNumbers, linesToBeHighlighted); | ||
gutter(code, showLineNumbers, linesToBeHighlighted); | ||
}); | ||
@@ -157,0 +180,0 @@ }; |
{ | ||
"name": "rehype-highlight-code-lines", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "Rehype plugin to add line numbers to code blocks and allow highlighting of desired code lines", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -11,3 +11,3 @@ # rehype-highlight-code-lines | ||
This package is a [unified][unified] ([rehype][rehype]) plugin **to add wrapper to each line in a code block, allowing numbering of the code block and highlighting desired lines of code**. | ||
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**. | ||
@@ -135,9 +135,12 @@ **[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. | ||
***Note:** `hljs` prefix comes from `rehype-highlight`*. | ||
## Options | ||
There is one **boolean** option. | ||
All options are **optional** and have **default values**. | ||
```typescript | ||
type HighlightLinesOptions = { | ||
showLineNumbers?: boolean; // the default is "false" | ||
showLineNumbers?: boolean; // default is "false" | ||
lineContainerTagName?: "div" | "span"; // default is "span" | ||
}; | ||
@@ -148,2 +151,30 @@ | ||
#### `showLineNumbers` | ||
It is a **boolean** option which is for all code blocks will be numbered. | ||
By default, it is `false`. | ||
```javascript | ||
use(rehypeHighlightLines, { | ||
showLineNumbers: true, | ||
}); | ||
``` | ||
Now, all code blocks will become numbered by line. | ||
#### `lineContainerTagName` | ||
It is a **union** type option which is **"div" | "span"** for providing custom HTML tag name for code lines. | ||
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. | ||
```javascript | ||
use(rehypeHighlightLines, { | ||
lineContainerTagName: "div", | ||
}); | ||
``` | ||
Now, the code line container's tag name will be `div`. | ||
### Examples: | ||
@@ -150,0 +181,0 @@ |
122
src/index.ts
@@ -6,4 +6,11 @@ import type { Plugin } from "unified"; | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
type Prettify<T> = { [K in keyof T]: T[K] } & {}; | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
type PartiallyRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>; | ||
export type HighlightLinesOptions = { | ||
showLineNumbers?: boolean; | ||
lineContainerTagName?: "div" | "span"; | ||
}; | ||
@@ -13,4 +20,9 @@ | ||
showLineNumbers: false, | ||
lineContainerTagName: "span", | ||
}; | ||
type PartiallyRequiredHighlightLinesOptions = Prettify< | ||
PartiallyRequired<HighlightLinesOptions, "showLineNumbers" | "lineContainerTagName"> | ||
>; | ||
type CodeData = ElementData & { | ||
@@ -31,6 +43,38 @@ meta?: string; | ||
const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => { | ||
const settings = Object.assign({}, DEFAULT_SETTINGS, options); | ||
const settings = Object.assign( | ||
{}, | ||
DEFAULT_SETTINGS, | ||
options, | ||
) as PartiallyRequiredHighlightLinesOptions; | ||
/** | ||
* | ||
* flatten deep nodes | ||
* | ||
*/ | ||
function flattenCodeTree( | ||
children: ElementContent[], | ||
className: string[] = [], | ||
newTree: ElementContent[] = [], | ||
): ElementContent[] { | ||
for (let i = 0; i < children.length; i++) { | ||
const node = children[i]; | ||
console.log(node); | ||
if (node.type !== "element") { | ||
newTree = newTree.concat([node]); | ||
} else if (node.children.length === 1 && node.children[0].type !== "element") { | ||
// @ts-expect-error | ||
node.properties.className = className.concat(node.properties.className); | ||
newTree = newTree.concat([node]); | ||
} else { | ||
// @ts-expect-error | ||
const classNames = className.concat(node.properties?.className || []); | ||
newTree = newTree.concat(flattenCodeTree(node.children, classNames)); | ||
} | ||
} | ||
return newTree; | ||
} | ||
/** | ||
* | ||
* constructs the line element | ||
@@ -41,12 +85,17 @@ * | ||
children: ElementContent[], | ||
lineNumber: number | undefined, | ||
classNames: string[], | ||
lineNumber: number, | ||
showLineNumbers: boolean, | ||
linesToBeHighlighted: number[], | ||
): Element => { | ||
return { | ||
type: "element", | ||
tagName: "div", | ||
tagName: settings.lineContainerTagName, | ||
children, | ||
properties: { | ||
className: classNames, | ||
...(lineNumber && { "data-line-number": lineNumber }), | ||
className: clsx([ | ||
"code-line", | ||
showLineNumbers && "numbered-code-line", | ||
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line", | ||
]), | ||
dataLineNumber: showLineNumbers ? lineNumber : undefined, | ||
}, | ||
@@ -59,8 +108,5 @@ }; | ||
function starryNightGutter( | ||
tree: Element, | ||
showLineNumbers: boolean, | ||
linesToBeHighlighted: number[], | ||
) { | ||
function gutter(tree: Element, showLineNumbers: boolean, linesToBeHighlighted: number[]) { | ||
const replacement: Array<ElementContent> = []; | ||
let index = -1; | ||
@@ -79,3 +125,3 @@ let start = 0; | ||
while (match) { | ||
// Nodes in this line. | ||
// Nodes in this line. (current child is exclusive) | ||
const line = tree.children.slice(start, index); | ||
@@ -95,26 +141,15 @@ | ||
if (match.index > textStart) { | ||
line.push({ | ||
type: "text", | ||
value: child.value.slice(textStart, match.index), | ||
}); | ||
const value = child.value.slice(textStart, match.index); | ||
line.push({ type: "text", value }); | ||
} | ||
// Add a line, and the eol. | ||
// Add a line | ||
lineNumber += 1; | ||
replacement.push( | ||
createLine( | ||
line, | ||
showLineNumbers ? lineNumber : undefined, | ||
clsx([ | ||
"code-line", | ||
showLineNumbers && "numbered-code-line", | ||
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line", | ||
]), | ||
), | ||
{ | ||
type: "text", | ||
value: match[0], | ||
}, | ||
); | ||
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted)); | ||
// Add eol if the tag name is "span" | ||
if (settings.lineContainerTagName === "span") { | ||
replacement.push({ type: "text", value: match[0] }); | ||
} | ||
start = index + 1; | ||
@@ -144,13 +179,3 @@ textStart = match.index + match[0].length; | ||
lineNumber += 1; | ||
replacement.push( | ||
createLine( | ||
line, | ||
showLineNumbers ? lineNumber : undefined, | ||
clsx([ | ||
"code-line", | ||
showLineNumbers && "numbered-code-line", | ||
linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line", | ||
]), | ||
), | ||
); | ||
replacement.push(createLine(line, lineNumber, showLineNumbers, linesToBeHighlighted)); | ||
} | ||
@@ -199,6 +224,6 @@ | ||
const index = code.properties.className.findIndex(testingFunction); | ||
const idx = code.properties.className.findIndex(testingFunction); | ||
if (index > -1) { | ||
code.properties.className[index] = "language-unknown"; | ||
if (idx > -1) { | ||
code.properties.className[idx] = "language-unknown"; | ||
} | ||
@@ -220,4 +245,7 @@ } | ||
// flatten deeper nodes into first level <span>s an texts | ||
code.children = flattenCodeTree(code.children); | ||
// add wrapper for each line mutating the code element | ||
starryNightGutter(code, showLineNumbers, linesToBeHighlighted); | ||
gutter(code, showLineNumbers, linesToBeHighlighted); | ||
}); | ||
@@ -224,0 +252,0 @@ }; |
Sorry, the diff of this file is not supported yet
34987
13.34%385
13.24%277
12.6%