react-codemirror-runmode 2.2.0 → 2.3.0
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/.oxfmtrc.json +9 -0
- package/.oxlintrc.json +31 -0
- package/README.md +42 -6
- package/dist/highlight.d.ts +14 -3
- package/dist/highlight.js +42 -9
- package/dist/react-highlighter.d.ts +16 -3
- package/dist/react-highlighter.js +3 -3
- package/dist/src/highlight.d.ts +16 -0
- package/dist/src/highlight.js +76 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/react-highlighter.d.ts +24 -0
- package/dist/src/react-highlighter.js +14 -0
- package/package.json +34 -36
- package/pnpm-workspace.yaml +43 -0
- package/src/highlight.ts +82 -26
- package/src/react-highlighter.tsx +22 -10
- package/test/index.spec.tsx +94 -8
- package/tsconfig.build.json +3 -0
- package/vite.config.ts +1 -2
- package/.prettierignore +0 -137
- package/.prettierrc.json +0 -10
- package/eslint.config.js +0 -45
package/.oxfmtrc.json
ADDED
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["typescript", "unicorn", "oxc", "react"],
|
|
4
|
+
"categories": {
|
|
5
|
+
"correctness": "error"
|
|
6
|
+
},
|
|
7
|
+
"settings": {
|
|
8
|
+
"react": {
|
|
9
|
+
"version": "19"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"rules": {
|
|
13
|
+
"typescript/no-explicit-any": "off",
|
|
14
|
+
"typescript/ban-ts-comment": "off",
|
|
15
|
+
"typescript/no-this-alias": "off",
|
|
16
|
+
"typescript/no-unused-vars": [
|
|
17
|
+
"warn",
|
|
18
|
+
{
|
|
19
|
+
"argsIgnorePattern": "^_",
|
|
20
|
+
"varsIgnorePattern": "^_",
|
|
21
|
+
"caughtErrorsIgnorePattern": "^_"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"no-useless-escape": "off",
|
|
25
|
+
"no-unused-expressions": "off",
|
|
26
|
+
"prefer-const": "error",
|
|
27
|
+
"no-unused-vars": "off",
|
|
28
|
+
"react-hooks/exhaustive-deps": "off"
|
|
29
|
+
},
|
|
30
|
+
"ignorePatterns": ["dist/**", "node_modules/**"]
|
|
31
|
+
}
|
package/README.md
CHANGED
|
@@ -37,28 +37,64 @@ You can apply custom themes using CodeMirror's theme system. This component uses
|
|
|
37
37
|
Props:
|
|
38
38
|
|
|
39
39
|
- `lang`: `string` - The name of the language
|
|
40
|
-
- `theme`: [`Highlighter`](https://lezer.codemirror.net/docs/ref/#highlight.Highlighter) - The highlight style
|
|
40
|
+
- `theme`: [`Highlighter`](https://lezer.codemirror.net/docs/ref/#highlight.Highlighter)` | readonly Highlighter[]` - The highlight style. Pass an array to combine several highlighters; their emitted classes are merged per token (e.g. a generic token theme plus a markdown-specific one).
|
|
41
41
|
- `children`: `string` - The code to highlight
|
|
42
42
|
- `fallbackLanguage`: `Language` - Optional fallback language to use if the specified language isn't found
|
|
43
43
|
- `languages`: `LanguageDescription[]` - Optional custom list of language descriptions
|
|
44
|
+
- `markdownConfig`: `MarkdownConfig` - Optional markdown parser options, applied only when `lang` is `markdown`/`md`. See [Markdown highlighting](#markdown-highlighting).
|
|
44
45
|
|
|
45
|
-
### `highlightCode<o>(languageName, input,
|
|
46
|
+
### `highlightCode<o>(languageName, input, highlighter, fallbackLanguage?, languages?, callback, markdownConfig?): Promise<Output[]>`
|
|
46
47
|
|
|
47
48
|
Parameters:
|
|
48
49
|
|
|
49
50
|
- `languageName`: `string` - The name of the language
|
|
50
51
|
- `input`: `string` - The code to highlight
|
|
51
|
-
- `highlighter`: [`Highlighter`](https://lezer.codemirror.net/docs/ref/#highlight.Highlighter) - The highlight style
|
|
52
|
+
- `highlighter`: [`Highlighter`](https://lezer.codemirror.net/docs/ref/#highlight.Highlighter)` | readonly Highlighter[]` - The highlight style, or an array of styles whose classes are merged
|
|
52
53
|
- `fallbackLanguage`: `Language` - Optional fallback language to use if the specified language isn't found
|
|
53
54
|
- `languages`: `LanguageDescription[]` - Optional custom list of language descriptions
|
|
54
55
|
- `callback`: `(text: string, style: string | null, from: number, to: number) => Output)` - A callback function that converts the parsed tokens
|
|
56
|
+
- `markdownConfig`: `MarkdownConfig` - Optional markdown parser options, applied only when `languageName` is `markdown`/`md`
|
|
55
57
|
|
|
56
|
-
### `getCodeParser(languageName,
|
|
58
|
+
### `getCodeParser(input, languageName, fallbackLanguage?, languages?, markdownConfig?): Promise<Parser | null>`
|
|
57
59
|
|
|
58
60
|
Parameters:
|
|
59
61
|
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
+
- `input`: `string` - The code to highlight (used to preload nested code-fence languages for markdown)
|
|
63
|
+
- `languageName`: `string` - The name of the language
|
|
64
|
+
- `fallbackLanguage?`: `Language` - A fallback language (Optional)
|
|
65
|
+
- `languages?`: `LanguageDescription[]` - Optional custom list of language descriptions
|
|
66
|
+
- `markdownConfig?`: `MarkdownConfig` - Optional markdown parser options, applied only when `languageName` is `markdown`/`md`
|
|
67
|
+
|
|
68
|
+
## Markdown highlighting
|
|
69
|
+
|
|
70
|
+
When `lang` is `markdown` (or `md`), the markdown source is parsed with
|
|
71
|
+
[`@codemirror/lang-markdown`](https://github.com/codemirror/lang-markdown). By default this
|
|
72
|
+
uses a **CommonMark** base with no extensions. Pass `markdownConfig` to opt into GFM and/or
|
|
73
|
+
custom [Lezer markdown extensions](https://github.com/lezer-parser/markdown#user-content-markdownextension):
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import { Highlighter } from 'react-codemirror-runmode'
|
|
77
|
+
import { markdownLanguage } from '@codemirror/lang-markdown'
|
|
78
|
+
|
|
79
|
+
// GFM (tables, strikethrough, task lists, autolinks) + custom extensions,
|
|
80
|
+
// with two highlighters whose classes are merged per token.
|
|
81
|
+
;<Highlighter
|
|
82
|
+
lang="markdown"
|
|
83
|
+
theme={[tokenHighlighter, mdTokenHighlighter]}
|
|
84
|
+
markdownConfig={{
|
|
85
|
+
base: markdownLanguage,
|
|
86
|
+
extensions: [
|
|
87
|
+
/* your @lezer/markdown extensions */
|
|
88
|
+
]
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{markdownSource}
|
|
92
|
+
</Highlighter>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`MarkdownConfig` mirrors the `base` and `extensions` options of `markdown()`. Prefer a stable
|
|
96
|
+
reference for `markdownConfig` (e.g. a module-level constant) to avoid re-highlighting on every
|
|
97
|
+
render.
|
|
62
98
|
|
|
63
99
|
## License
|
|
64
100
|
|
package/dist/highlight.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { markdown } from '@codemirror/lang-markdown';
|
|
2
2
|
import { Language, LanguageDescription } from '@codemirror/language';
|
|
3
|
+
import { Parser } from '@lezer/common';
|
|
3
4
|
import { Highlighter } from '@lezer/highlight';
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for the markdown parser used when highlighting `markdown`/`md`
|
|
7
|
+
* input. Mirrors the relevant subset of `@codemirror/lang-markdown`'s `markdown()`
|
|
8
|
+
* options so callers can opt into GFM (`base: markdownLanguage`) and custom Lezer
|
|
9
|
+
* markdown extensions (e.g. app-specific node props, GFM alerts).
|
|
10
|
+
*
|
|
11
|
+
* Defaults match `@codemirror/lang-markdown`: a CommonMark base with no extensions.
|
|
12
|
+
*/
|
|
13
|
+
export type MarkdownConfig = Pick<NonNullable<Parameters<typeof markdown>[0]>, 'base' | 'extensions'>;
|
|
14
|
+
export declare function getMarkdownParser(input: string, languages?: LanguageDescription[], markdownConfig?: MarkdownConfig): Promise<Parser>;
|
|
15
|
+
export declare function getCodeParser(input: string, languageName: string, fallbackLanguage?: Language, languages?: LanguageDescription[], markdownConfig?: MarkdownConfig): Promise<Parser | null>;
|
|
16
|
+
export declare function highlightCode<Output>(languageName: string, input: string, highlighter: Highlighter | readonly Highlighter[], fallbackLanguage: Language | undefined, languages: LanguageDescription[] | undefined, callback: (text: string, style: string | null, from: number, to: number) => Output, markdownConfig?: MarkdownConfig): Promise<Output[]>;
|
package/dist/highlight.js
CHANGED
|
@@ -1,13 +1,46 @@
|
|
|
1
|
+
import { markdown } from '@codemirror/lang-markdown';
|
|
1
2
|
import { LanguageDescription } from '@codemirror/language';
|
|
2
|
-
import { highlightTree } from '@lezer/highlight';
|
|
3
3
|
import { languages as builtinLanguages } from '@codemirror/language-data';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { highlightTree } from '@lezer/highlight';
|
|
5
|
+
/**
|
|
6
|
+
* Extract language names from code blocks in markdown text
|
|
7
|
+
*/
|
|
8
|
+
function extractCodeBlockLanguages(text) {
|
|
9
|
+
const codeBlockRegex = /```(\w+)/g;
|
|
10
|
+
const languages = [];
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
13
|
+
languages.push(match[1]);
|
|
14
|
+
}
|
|
15
|
+
return languages;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Pre-load parsers for a list of language names
|
|
19
|
+
*/
|
|
20
|
+
async function preloadLanguageParsers(languageNames, languages) {
|
|
21
|
+
return (await Promise.all(languageNames.map(async (langName) => {
|
|
22
|
+
const found = LanguageDescription.matchLanguageName(languages, langName, true);
|
|
23
|
+
if (found instanceof LanguageDescription) {
|
|
24
|
+
if (!found.support)
|
|
25
|
+
await found.load();
|
|
26
|
+
if (found.support) {
|
|
27
|
+
return found;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}))).filter((desc) => !!desc);
|
|
31
|
+
}
|
|
32
|
+
export async function getMarkdownParser(input, languages = builtinLanguages, markdownConfig = {}) {
|
|
33
|
+
const codeBlockLanguages = extractCodeBlockLanguages(input);
|
|
34
|
+
const preloadedLanguages = await preloadLanguageParsers(codeBlockLanguages || [], languages);
|
|
35
|
+
const langSupport = markdown({
|
|
36
|
+
...markdownConfig,
|
|
37
|
+
codeLanguages: preloadedLanguages
|
|
38
|
+
});
|
|
39
|
+
return langSupport.language.parser;
|
|
40
|
+
}
|
|
41
|
+
export async function getCodeParser(input, languageName, fallbackLanguage, languages = builtinLanguages, markdownConfig) {
|
|
6
42
|
if (languageName === 'markdown' || languageName === 'md') {
|
|
7
|
-
|
|
8
|
-
codeLanguages: builtinLanguages
|
|
9
|
-
});
|
|
10
|
-
return mdSupport.language.parser;
|
|
43
|
+
return await getMarkdownParser(input, languages, markdownConfig);
|
|
11
44
|
}
|
|
12
45
|
else {
|
|
13
46
|
const found = LanguageDescription.matchLanguageName(languages, languageName, true);
|
|
@@ -21,8 +54,8 @@ export async function getCodeParser(languageName, fallbackLanguage, languages =
|
|
|
21
54
|
}
|
|
22
55
|
return fallbackLanguage ? fallbackLanguage.parser : null;
|
|
23
56
|
}
|
|
24
|
-
export async function highlightCode(languageName, input, highlighter, fallbackLanguage, languages, callback) {
|
|
25
|
-
const parser = await getCodeParser(languageName, fallbackLanguage, languages);
|
|
57
|
+
export async function highlightCode(languageName, input, highlighter, fallbackLanguage, languages, callback, markdownConfig) {
|
|
58
|
+
const parser = await getCodeParser(input, languageName, fallbackLanguage, languages, markdownConfig);
|
|
26
59
|
if (parser) {
|
|
27
60
|
const tree = parser.parse(input);
|
|
28
61
|
const output = [];
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { Highlighter as LezerHighlighter } from '@lezer/highlight';
|
|
3
1
|
import type { Language, LanguageDescription } from '@codemirror/language';
|
|
2
|
+
import type { Highlighter as LezerHighlighter } from '@lezer/highlight';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { type MarkdownConfig } from './highlight.js';
|
|
4
5
|
export type HighlighterProps = {
|
|
5
6
|
lang: string;
|
|
6
7
|
children: string;
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* One highlighter, or several whose emitted classes are merged per token.
|
|
10
|
+
* Pass an array to combine, e.g. a generic token theme with a
|
|
11
|
+
* markdown-specific one.
|
|
12
|
+
*/
|
|
13
|
+
theme: LezerHighlighter | readonly LezerHighlighter[];
|
|
8
14
|
fallbackLanguage?: Language;
|
|
9
15
|
languages?: LanguageDescription[];
|
|
16
|
+
/**
|
|
17
|
+
* Markdown parser options, used only when `lang` is `markdown`/`md`. Enables
|
|
18
|
+
* GFM (`base: markdownLanguage`) and custom Lezer markdown extensions. Pass a
|
|
19
|
+
* stable reference (e.g. a module-level constant) to avoid re-highlighting on
|
|
20
|
+
* every render.
|
|
21
|
+
*/
|
|
22
|
+
markdownConfig?: MarkdownConfig;
|
|
10
23
|
};
|
|
11
24
|
export declare const Highlighter: React.NamedExoticComponent<HighlighterProps>;
|
|
@@ -2,13 +2,13 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
2
2
|
import { memo, useEffect, useState } from 'react';
|
|
3
3
|
import { highlightCode } from './highlight.js';
|
|
4
4
|
export const Highlighter = memo((props) => {
|
|
5
|
-
const { lang, children: code, theme, fallbackLanguage, languages } = props;
|
|
5
|
+
const { lang, children: code, theme, fallbackLanguage, languages, markdownConfig } = props;
|
|
6
6
|
const [highlightedCode, setHighlightedCode] = useState(null);
|
|
7
7
|
useEffect(() => {
|
|
8
8
|
highlightCode(lang, code, theme, fallbackLanguage, languages, (text, style, from) => {
|
|
9
9
|
return (_jsx("span", { className: style || '', children: text }, from));
|
|
10
|
-
}).then(setHighlightedCode);
|
|
11
|
-
}, [lang, code, theme]);
|
|
10
|
+
}, markdownConfig).then(setHighlightedCode);
|
|
11
|
+
}, [lang, code, theme, markdownConfig]);
|
|
12
12
|
return _jsx(_Fragment, { children: highlightedCode || code });
|
|
13
13
|
});
|
|
14
14
|
Highlighter.displayName = 'Highlighter';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { markdown } from '@codemirror/lang-markdown';
|
|
2
|
+
import { Language, LanguageDescription } from '@codemirror/language';
|
|
3
|
+
import { Parser } from '@lezer/common';
|
|
4
|
+
import { Highlighter } from '@lezer/highlight';
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for the markdown parser used when highlighting `markdown`/`md`
|
|
7
|
+
* input. Mirrors the relevant subset of `@codemirror/lang-markdown`'s `markdown()`
|
|
8
|
+
* options so callers can opt into GFM (`base: markdownLanguage`) and custom Lezer
|
|
9
|
+
* markdown extensions (e.g. app-specific node props, GFM alerts).
|
|
10
|
+
*
|
|
11
|
+
* Defaults match `@codemirror/lang-markdown`: a CommonMark base with no extensions.
|
|
12
|
+
*/
|
|
13
|
+
export type MarkdownConfig = Pick<NonNullable<Parameters<typeof markdown>[0]>, 'base' | 'extensions'>;
|
|
14
|
+
export declare function getMarkdownParser(input: string, languages?: LanguageDescription[], markdownConfig?: MarkdownConfig): Promise<Parser>;
|
|
15
|
+
export declare function getCodeParser(input: string, languageName: string, fallbackLanguage?: Language, languages?: LanguageDescription[], markdownConfig?: MarkdownConfig): Promise<Parser | null>;
|
|
16
|
+
export declare function highlightCode<Output>(languageName: string, input: string, highlighter: Highlighter | readonly Highlighter[], fallbackLanguage: Language | undefined, languages: LanguageDescription[] | undefined, callback: (text: string, style: string | null, from: number, to: number) => Output, markdownConfig?: MarkdownConfig): Promise<Output[]>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { markdown } from '@codemirror/lang-markdown';
|
|
2
|
+
import { LanguageDescription } from '@codemirror/language';
|
|
3
|
+
import { languages as builtinLanguages } from '@codemirror/language-data';
|
|
4
|
+
import { highlightTree } from '@lezer/highlight';
|
|
5
|
+
/**
|
|
6
|
+
* Extract language names from code blocks in markdown text
|
|
7
|
+
*/
|
|
8
|
+
function extractCodeBlockLanguages(text) {
|
|
9
|
+
const codeBlockRegex = /```(\w+)/g;
|
|
10
|
+
const languages = [];
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
13
|
+
languages.push(match[1]);
|
|
14
|
+
}
|
|
15
|
+
return languages;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Pre-load parsers for a list of language names
|
|
19
|
+
*/
|
|
20
|
+
async function preloadLanguageParsers(languageNames, languages) {
|
|
21
|
+
return (await Promise.all(languageNames.map(async (langName) => {
|
|
22
|
+
const found = LanguageDescription.matchLanguageName(languages, langName, true);
|
|
23
|
+
if (found instanceof LanguageDescription) {
|
|
24
|
+
if (!found.support)
|
|
25
|
+
await found.load();
|
|
26
|
+
if (found.support) {
|
|
27
|
+
return found;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}))).filter((desc) => !!desc);
|
|
31
|
+
}
|
|
32
|
+
export async function getMarkdownParser(input, languages = builtinLanguages, markdownConfig = {}) {
|
|
33
|
+
const codeBlockLanguages = extractCodeBlockLanguages(input);
|
|
34
|
+
const preloadedLanguages = await preloadLanguageParsers(codeBlockLanguages || [], languages);
|
|
35
|
+
const langSupport = markdown({
|
|
36
|
+
...markdownConfig,
|
|
37
|
+
codeLanguages: preloadedLanguages
|
|
38
|
+
});
|
|
39
|
+
return langSupport.language.parser;
|
|
40
|
+
}
|
|
41
|
+
export async function getCodeParser(input, languageName, fallbackLanguage, languages = builtinLanguages, markdownConfig) {
|
|
42
|
+
if (languageName === 'markdown' || languageName === 'md') {
|
|
43
|
+
return await getMarkdownParser(input, languages, markdownConfig);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const found = LanguageDescription.matchLanguageName(languages, languageName, true);
|
|
47
|
+
if (found instanceof LanguageDescription) {
|
|
48
|
+
if (!found.support)
|
|
49
|
+
await found.load();
|
|
50
|
+
return found.support ? found.support.language.parser : null;
|
|
51
|
+
}
|
|
52
|
+
else if (found)
|
|
53
|
+
return found.parser;
|
|
54
|
+
}
|
|
55
|
+
return fallbackLanguage ? fallbackLanguage.parser : null;
|
|
56
|
+
}
|
|
57
|
+
export async function highlightCode(languageName, input, highlighter, fallbackLanguage, languages, callback, markdownConfig) {
|
|
58
|
+
const parser = await getCodeParser(input, languageName, fallbackLanguage, languages, markdownConfig);
|
|
59
|
+
if (parser) {
|
|
60
|
+
const tree = parser.parse(input);
|
|
61
|
+
const output = [];
|
|
62
|
+
let pos = 0;
|
|
63
|
+
highlightTree(tree, highlighter, (from, to, classes) => {
|
|
64
|
+
if (from > pos)
|
|
65
|
+
output.push(callback(input.slice(pos, from), null, pos, from));
|
|
66
|
+
output.push(callback(input.slice(from, to), classes, from, to));
|
|
67
|
+
pos = to;
|
|
68
|
+
});
|
|
69
|
+
pos != tree.length &&
|
|
70
|
+
output.push(callback(input.slice(pos, tree.length), null, pos, tree.length));
|
|
71
|
+
return output;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return [callback(input, null, 0, input.length)];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Language, LanguageDescription } from '@codemirror/language';
|
|
2
|
+
import type { Highlighter as LezerHighlighter } from '@lezer/highlight';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { type MarkdownConfig } from './highlight.js';
|
|
5
|
+
export type HighlighterProps = {
|
|
6
|
+
lang: string;
|
|
7
|
+
children: string;
|
|
8
|
+
/**
|
|
9
|
+
* One highlighter, or several whose emitted classes are merged per token.
|
|
10
|
+
* Pass an array to combine, e.g. a generic token theme with a
|
|
11
|
+
* markdown-specific one.
|
|
12
|
+
*/
|
|
13
|
+
theme: LezerHighlighter | readonly LezerHighlighter[];
|
|
14
|
+
fallbackLanguage?: Language;
|
|
15
|
+
languages?: LanguageDescription[];
|
|
16
|
+
/**
|
|
17
|
+
* Markdown parser options, used only when `lang` is `markdown`/`md`. Enables
|
|
18
|
+
* GFM (`base: markdownLanguage`) and custom Lezer markdown extensions. Pass a
|
|
19
|
+
* stable reference (e.g. a module-level constant) to avoid re-highlighting on
|
|
20
|
+
* every render.
|
|
21
|
+
*/
|
|
22
|
+
markdownConfig?: MarkdownConfig;
|
|
23
|
+
};
|
|
24
|
+
export declare const Highlighter: React.NamedExoticComponent<HighlighterProps>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useEffect, useState } from 'react';
|
|
3
|
+
import { highlightCode } from './highlight.js';
|
|
4
|
+
export const Highlighter = memo((props) => {
|
|
5
|
+
const { lang, children: code, theme, fallbackLanguage, languages, markdownConfig } = props;
|
|
6
|
+
const [highlightedCode, setHighlightedCode] = useState(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
highlightCode(lang, code, theme, fallbackLanguage, languages, (text, style, from) => {
|
|
9
|
+
return (_jsx("span", { className: style || '', children: text }, from));
|
|
10
|
+
}, markdownConfig).then(setHighlightedCode);
|
|
11
|
+
}, [lang, code, theme, markdownConfig]);
|
|
12
|
+
return _jsx(_Fragment, { children: highlightedCode || code });
|
|
13
|
+
});
|
|
14
|
+
Highlighter.displayName = 'Highlighter';
|
package/package.json
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-codemirror-runmode",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Syntax highlighting for react, utilizing CodeMirror's parser",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"codemirror",
|
|
7
|
+
"highlight",
|
|
8
|
+
"react",
|
|
9
|
+
"syntax"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Takuya Matsuyama <hi@craftz.dog>",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/craftzdog/react-codemirror-runmode.git"
|
|
16
|
+
},
|
|
5
17
|
"type": "module",
|
|
6
18
|
"main": "dist/index.js",
|
|
7
19
|
"exports": {
|
|
@@ -13,50 +25,36 @@
|
|
|
13
25
|
"scripts": {
|
|
14
26
|
"build": "tsc --project tsconfig.build.json",
|
|
15
27
|
"test": "vitest",
|
|
16
|
-
"lint": "
|
|
28
|
+
"lint": "oxlint",
|
|
17
29
|
"typecheck": "tsc --noEmit",
|
|
18
|
-
"format": "
|
|
19
|
-
"format:check": "
|
|
30
|
+
"format": "oxfmt",
|
|
31
|
+
"format:check": "oxfmt --check",
|
|
20
32
|
"prepublishOnly": "npm-run-all lint format:check build && npm run test run"
|
|
21
33
|
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"react",
|
|
24
|
-
"codemirror",
|
|
25
|
-
"syntax",
|
|
26
|
-
"highlight"
|
|
27
|
-
],
|
|
28
|
-
"author": "Takuya Matsuyama <hi@craftz.dog>",
|
|
29
|
-
"license": "MIT",
|
|
30
|
-
"repository": {
|
|
31
|
-
"type": "git",
|
|
32
|
-
"url": "https://github.com/craftzdog/react-codemirror-runmode.git"
|
|
33
|
-
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@codemirror/
|
|
35
|
+
"@codemirror/lang-markdown": "^6.5.0",
|
|
36
|
+
"@codemirror/language": "^6.12.3",
|
|
36
37
|
"@codemirror/language-data": "^6.5.2",
|
|
37
|
-
"@lezer/common": "^1.
|
|
38
|
+
"@lezer/common": "^1.5.2",
|
|
38
39
|
"@lezer/highlight": "^1.2.3"
|
|
39
40
|
},
|
|
40
|
-
"peerDependencies": {
|
|
41
|
-
"react": ">= 18"
|
|
42
|
-
},
|
|
43
41
|
"devDependencies": {
|
|
44
42
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
45
|
-
"@testing-library/react": "^16.3.
|
|
46
|
-
"@types/node": "^
|
|
47
|
-
"@types/react": "^19.2.
|
|
48
|
-
"@vitejs/plugin-react": "^
|
|
49
|
-
"
|
|
50
|
-
"eslint-config-prettier": "^10.1.8",
|
|
51
|
-
"eslint-plugin-react": "^7.37.5",
|
|
52
|
-
"jsdom": "^27.3.0",
|
|
43
|
+
"@testing-library/react": "^16.3.2",
|
|
44
|
+
"@types/node": "^26.0.0",
|
|
45
|
+
"@types/react": "^19.2.17",
|
|
46
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
47
|
+
"jsdom": "^29.1.1",
|
|
53
48
|
"npm-run-all": "^4.1.5",
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"react
|
|
57
|
-
"
|
|
58
|
-
"typescript
|
|
59
|
-
"vite": "^
|
|
60
|
-
"vitest": "^4.
|
|
49
|
+
"oxfmt": "0.56.0",
|
|
50
|
+
"oxlint": "1.71.0",
|
|
51
|
+
"react": "^19.2.7",
|
|
52
|
+
"react-dom": "^19.2.7",
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vite": "^8.0.16",
|
|
55
|
+
"vitest": "^4.1.9"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": ">= 18"
|
|
61
59
|
}
|
|
62
60
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
allowBuilds:
|
|
2
|
+
esbuild: true
|
|
3
|
+
minimumReleaseAgeExclude:
|
|
4
|
+
- '@oxfmt/binding-android-arm-eabi@0.56.0'
|
|
5
|
+
- '@oxfmt/binding-android-arm64@0.56.0'
|
|
6
|
+
- '@oxfmt/binding-darwin-arm64@0.56.0'
|
|
7
|
+
- '@oxfmt/binding-darwin-x64@0.56.0'
|
|
8
|
+
- '@oxfmt/binding-freebsd-x64@0.56.0'
|
|
9
|
+
- '@oxfmt/binding-linux-arm-gnueabihf@0.56.0'
|
|
10
|
+
- '@oxfmt/binding-linux-arm-musleabihf@0.56.0'
|
|
11
|
+
- '@oxfmt/binding-linux-arm64-gnu@0.56.0'
|
|
12
|
+
- '@oxfmt/binding-linux-arm64-musl@0.56.0'
|
|
13
|
+
- '@oxfmt/binding-linux-ppc64-gnu@0.56.0'
|
|
14
|
+
- '@oxfmt/binding-linux-riscv64-gnu@0.56.0'
|
|
15
|
+
- '@oxfmt/binding-linux-riscv64-musl@0.56.0'
|
|
16
|
+
- '@oxfmt/binding-linux-s390x-gnu@0.56.0'
|
|
17
|
+
- '@oxfmt/binding-linux-x64-gnu@0.56.0'
|
|
18
|
+
- '@oxfmt/binding-linux-x64-musl@0.56.0'
|
|
19
|
+
- '@oxfmt/binding-openharmony-arm64@0.56.0'
|
|
20
|
+
- '@oxfmt/binding-win32-arm64-msvc@0.56.0'
|
|
21
|
+
- '@oxfmt/binding-win32-ia32-msvc@0.56.0'
|
|
22
|
+
- '@oxfmt/binding-win32-x64-msvc@0.56.0'
|
|
23
|
+
- '@oxlint/binding-android-arm-eabi@1.71.0'
|
|
24
|
+
- '@oxlint/binding-android-arm64@1.71.0'
|
|
25
|
+
- '@oxlint/binding-darwin-arm64@1.71.0'
|
|
26
|
+
- '@oxlint/binding-darwin-x64@1.71.0'
|
|
27
|
+
- '@oxlint/binding-freebsd-x64@1.71.0'
|
|
28
|
+
- '@oxlint/binding-linux-arm-gnueabihf@1.71.0'
|
|
29
|
+
- '@oxlint/binding-linux-arm-musleabihf@1.71.0'
|
|
30
|
+
- '@oxlint/binding-linux-arm64-gnu@1.71.0'
|
|
31
|
+
- '@oxlint/binding-linux-arm64-musl@1.71.0'
|
|
32
|
+
- '@oxlint/binding-linux-ppc64-gnu@1.71.0'
|
|
33
|
+
- '@oxlint/binding-linux-riscv64-gnu@1.71.0'
|
|
34
|
+
- '@oxlint/binding-linux-riscv64-musl@1.71.0'
|
|
35
|
+
- '@oxlint/binding-linux-s390x-gnu@1.71.0'
|
|
36
|
+
- '@oxlint/binding-linux-x64-gnu@1.71.0'
|
|
37
|
+
- '@oxlint/binding-linux-x64-musl@1.71.0'
|
|
38
|
+
- '@oxlint/binding-openharmony-arm64@1.71.0'
|
|
39
|
+
- '@oxlint/binding-win32-arm64-msvc@1.71.0'
|
|
40
|
+
- '@oxlint/binding-win32-ia32-msvc@1.71.0'
|
|
41
|
+
- '@oxlint/binding-win32-x64-msvc@1.71.0'
|
|
42
|
+
- oxfmt@0.56.0
|
|
43
|
+
- oxlint@1.71.0
|
package/src/highlight.ts
CHANGED
|
@@ -1,25 +1,82 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { markdown } from '@codemirror/lang-markdown'
|
|
2
2
|
import { Language, LanguageDescription } from '@codemirror/language'
|
|
3
|
-
import { Highlighter, highlightTree } from '@lezer/highlight'
|
|
4
3
|
import { languages as builtinLanguages } from '@codemirror/language-data'
|
|
5
|
-
import {
|
|
4
|
+
import { Parser } from '@lezer/common'
|
|
5
|
+
import { Highlighter, highlightTree } from '@lezer/highlight'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for the markdown parser used when highlighting `markdown`/`md`
|
|
9
|
+
* input. Mirrors the relevant subset of `@codemirror/lang-markdown`'s `markdown()`
|
|
10
|
+
* options so callers can opt into GFM (`base: markdownLanguage`) and custom Lezer
|
|
11
|
+
* markdown extensions (e.g. app-specific node props, GFM alerts).
|
|
12
|
+
*
|
|
13
|
+
* Defaults match `@codemirror/lang-markdown`: a CommonMark base with no extensions.
|
|
14
|
+
*/
|
|
15
|
+
export type MarkdownConfig = Pick<
|
|
16
|
+
NonNullable<Parameters<typeof markdown>[0]>,
|
|
17
|
+
'base' | 'extensions'
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract language names from code blocks in markdown text
|
|
22
|
+
*/
|
|
23
|
+
function extractCodeBlockLanguages(text: string): string[] {
|
|
24
|
+
const codeBlockRegex = /```(\w+)/g
|
|
25
|
+
const languages: string[] = []
|
|
26
|
+
let match
|
|
27
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
28
|
+
languages.push(match[1])
|
|
29
|
+
}
|
|
30
|
+
return languages
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pre-load parsers for a list of language names
|
|
35
|
+
*/
|
|
36
|
+
async function preloadLanguageParsers(
|
|
37
|
+
languageNames: string[],
|
|
38
|
+
languages: LanguageDescription[]
|
|
39
|
+
): Promise<LanguageDescription[]> {
|
|
40
|
+
return (
|
|
41
|
+
await Promise.all(
|
|
42
|
+
languageNames.map(async langName => {
|
|
43
|
+
const found = LanguageDescription.matchLanguageName(languages, langName, true)
|
|
44
|
+
if (found instanceof LanguageDescription) {
|
|
45
|
+
if (!found.support) await found.load()
|
|
46
|
+
if (found.support) {
|
|
47
|
+
return found
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
).filter((desc): desc is LanguageDescription => !!desc)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getMarkdownParser(
|
|
56
|
+
input: string,
|
|
57
|
+
languages: LanguageDescription[] = builtinLanguages,
|
|
58
|
+
markdownConfig: MarkdownConfig = {}
|
|
59
|
+
): Promise<Parser> {
|
|
60
|
+
const codeBlockLanguages = extractCodeBlockLanguages(input)
|
|
61
|
+
const preloadedLanguages = await preloadLanguageParsers(codeBlockLanguages || [], languages)
|
|
62
|
+
const langSupport = markdown({
|
|
63
|
+
...markdownConfig,
|
|
64
|
+
codeLanguages: preloadedLanguages
|
|
65
|
+
})
|
|
66
|
+
return langSupport.language.parser
|
|
67
|
+
}
|
|
6
68
|
|
|
7
69
|
export async function getCodeParser(
|
|
70
|
+
input: string,
|
|
8
71
|
languageName: string,
|
|
9
72
|
fallbackLanguage?: Language,
|
|
10
|
-
languages: LanguageDescription[] = builtinLanguages
|
|
73
|
+
languages: LanguageDescription[] = builtinLanguages,
|
|
74
|
+
markdownConfig?: MarkdownConfig
|
|
11
75
|
): Promise<Parser | null> {
|
|
12
76
|
if (languageName === 'markdown' || languageName === 'md') {
|
|
13
|
-
|
|
14
|
-
codeLanguages: builtinLanguages
|
|
15
|
-
})
|
|
16
|
-
return mdSupport.language.parser
|
|
77
|
+
return await getMarkdownParser(input, languages, markdownConfig)
|
|
17
78
|
} else {
|
|
18
|
-
const found = LanguageDescription.matchLanguageName(
|
|
19
|
-
languages,
|
|
20
|
-
languageName,
|
|
21
|
-
true
|
|
22
|
-
)
|
|
79
|
+
const found = LanguageDescription.matchLanguageName(languages, languageName, true)
|
|
23
80
|
if (found instanceof LanguageDescription) {
|
|
24
81
|
if (!found.support) await found.load()
|
|
25
82
|
return found.support ? found.support.language.parser : null
|
|
@@ -31,31 +88,30 @@ export async function getCodeParser(
|
|
|
31
88
|
export async function highlightCode<Output>(
|
|
32
89
|
languageName: string,
|
|
33
90
|
input: string,
|
|
34
|
-
highlighter: Highlighter,
|
|
91
|
+
highlighter: Highlighter | readonly Highlighter[],
|
|
35
92
|
fallbackLanguage: Language | undefined,
|
|
36
93
|
languages: LanguageDescription[] | undefined,
|
|
37
|
-
callback: (
|
|
38
|
-
|
|
39
|
-
style: string | null,
|
|
40
|
-
from: number,
|
|
41
|
-
to: number
|
|
42
|
-
) => Output
|
|
94
|
+
callback: (text: string, style: string | null, from: number, to: number) => Output,
|
|
95
|
+
markdownConfig?: MarkdownConfig
|
|
43
96
|
): Promise<Output[]> {
|
|
44
|
-
const parser = await getCodeParser(
|
|
97
|
+
const parser = await getCodeParser(
|
|
98
|
+
input,
|
|
99
|
+
languageName,
|
|
100
|
+
fallbackLanguage,
|
|
101
|
+
languages,
|
|
102
|
+
markdownConfig
|
|
103
|
+
)
|
|
45
104
|
if (parser) {
|
|
46
105
|
const tree = parser.parse(input)
|
|
47
106
|
const output: Array<Output> = []
|
|
48
107
|
let pos = 0
|
|
49
108
|
highlightTree(tree, highlighter, (from, to, classes) => {
|
|
50
|
-
if (from > pos)
|
|
51
|
-
output.push(callback(input.slice(pos, from), null, pos, from))
|
|
109
|
+
if (from > pos) output.push(callback(input.slice(pos, from), null, pos, from))
|
|
52
110
|
output.push(callback(input.slice(from, to), classes, from, to))
|
|
53
111
|
pos = to
|
|
54
112
|
})
|
|
55
113
|
pos != tree.length &&
|
|
56
|
-
output.push(
|
|
57
|
-
callback(input.slice(pos, tree.length), null, pos, tree.length)
|
|
58
|
-
)
|
|
114
|
+
output.push(callback(input.slice(pos, tree.length), null, pos, tree.length))
|
|
59
115
|
return output
|
|
60
116
|
} else {
|
|
61
117
|
return [callback(input, null, 0, input.length)]
|
|
@@ -1,21 +1,32 @@
|
|
|
1
|
-
import React, { memo, useEffect, useState } from 'react'
|
|
2
|
-
import { highlightCode } from './highlight.js'
|
|
3
|
-
import type { Highlighter as LezerHighlighter } from '@lezer/highlight'
|
|
4
1
|
import type { Language, LanguageDescription } from '@codemirror/language'
|
|
2
|
+
import type { Highlighter as LezerHighlighter } from '@lezer/highlight'
|
|
3
|
+
import React, { memo, useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { highlightCode, type MarkdownConfig } from './highlight.js'
|
|
5
6
|
|
|
6
7
|
export type HighlighterProps = {
|
|
7
8
|
lang: string
|
|
8
9
|
children: string
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* One highlighter, or several whose emitted classes are merged per token.
|
|
12
|
+
* Pass an array to combine, e.g. a generic token theme with a
|
|
13
|
+
* markdown-specific one.
|
|
14
|
+
*/
|
|
15
|
+
theme: LezerHighlighter | readonly LezerHighlighter[]
|
|
10
16
|
fallbackLanguage?: Language
|
|
11
17
|
languages?: LanguageDescription[]
|
|
18
|
+
/**
|
|
19
|
+
* Markdown parser options, used only when `lang` is `markdown`/`md`. Enables
|
|
20
|
+
* GFM (`base: markdownLanguage`) and custom Lezer markdown extensions. Pass a
|
|
21
|
+
* stable reference (e.g. a module-level constant) to avoid re-highlighting on
|
|
22
|
+
* every render.
|
|
23
|
+
*/
|
|
24
|
+
markdownConfig?: MarkdownConfig
|
|
12
25
|
}
|
|
13
26
|
|
|
14
27
|
export const Highlighter = memo<HighlighterProps>((props: HighlighterProps) => {
|
|
15
|
-
const { lang, children: code, theme, fallbackLanguage, languages } = props
|
|
16
|
-
const [highlightedCode, setHighlightedCode] = useState<
|
|
17
|
-
React.ReactNode[] | null
|
|
18
|
-
>(null)
|
|
28
|
+
const { lang, children: code, theme, fallbackLanguage, languages, markdownConfig } = props
|
|
29
|
+
const [highlightedCode, setHighlightedCode] = useState<React.ReactNode[] | null>(null)
|
|
19
30
|
|
|
20
31
|
useEffect(() => {
|
|
21
32
|
highlightCode(
|
|
@@ -30,9 +41,10 @@ export const Highlighter = memo<HighlighterProps>((props: HighlighterProps) => {
|
|
|
30
41
|
{text}
|
|
31
42
|
</span>
|
|
32
43
|
)
|
|
33
|
-
}
|
|
44
|
+
},
|
|
45
|
+
markdownConfig
|
|
34
46
|
).then(setHighlightedCode)
|
|
35
|
-
}, [lang, code, theme])
|
|
47
|
+
}, [lang, code, theme, markdownConfig])
|
|
36
48
|
|
|
37
49
|
return <>{highlightedCode || code}</>
|
|
38
50
|
})
|
package/test/index.spec.tsx
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
+
import { markdownLanguage } from '@codemirror/lang-markdown'
|
|
2
|
+
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark'
|
|
3
|
+
import { Parser } from '@lezer/common'
|
|
4
|
+
import { tagHighlighter, tags } from '@lezer/highlight'
|
|
5
|
+
import { render, screen } from '@testing-library/react'
|
|
1
6
|
// @vitest-environment jsdom
|
|
2
7
|
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark'
|
|
8
|
+
|
|
5
9
|
import { getCodeParser, highlightCode, Highlighter } from '../src'
|
|
6
|
-
import { render, screen } from '@testing-library/react'
|
|
7
10
|
|
|
8
|
-
const sleep = (msec: number) =>
|
|
9
|
-
new Promise(resolve => setTimeout(resolve, msec))
|
|
11
|
+
const sleep = (msec: number) => new Promise(resolve => setTimeout(resolve, msec))
|
|
10
12
|
|
|
11
13
|
describe('getCodeParser', () => {
|
|
12
14
|
it('loads a JavaScript parser', async () => {
|
|
13
|
-
const parser = await getCodeParser('js')
|
|
15
|
+
const parser = await getCodeParser('', 'js')
|
|
14
16
|
expect(parser).toBeInstanceOf(Parser)
|
|
15
17
|
})
|
|
16
18
|
it('loads a Python parser', async () => {
|
|
17
|
-
const parser = await getCodeParser('python')
|
|
19
|
+
const parser = await getCodeParser('', 'python')
|
|
18
20
|
expect(parser).toBeInstanceOf(Parser)
|
|
19
21
|
})
|
|
20
22
|
it('returns null for non-existing languages', async () => {
|
|
21
|
-
const parser = await getCodeParser('foobar123')
|
|
23
|
+
const parser = await getCodeParser('', 'foobar123')
|
|
22
24
|
expect(parser).toBeNull()
|
|
23
25
|
})
|
|
24
26
|
})
|
|
@@ -99,6 +101,90 @@ describe('Highlight codeblocks', () => {
|
|
|
99
101
|
expect(numberToken).toBeDefined()
|
|
100
102
|
expect(numberToken?.style).not.toBeNull()
|
|
101
103
|
})
|
|
104
|
+
|
|
105
|
+
it('highlights typescript codeblocks in markdown', async () => {
|
|
106
|
+
const mdCode = '```typescript\nconst x: number = 123\n```'
|
|
107
|
+
const highlighted = await highlightCode(
|
|
108
|
+
'markdown',
|
|
109
|
+
mdCode,
|
|
110
|
+
oneDarkHighlightStyle,
|
|
111
|
+
undefined,
|
|
112
|
+
undefined,
|
|
113
|
+
(text, style, from, to) => ({ text, style, from, to })
|
|
114
|
+
)
|
|
115
|
+
expect(highlighted.length).toBeGreaterThan(0)
|
|
116
|
+
|
|
117
|
+
// Find the language info token
|
|
118
|
+
const langInfo = highlighted.find(t => t.text === 'typescript')
|
|
119
|
+
expect(langInfo).toBeDefined()
|
|
120
|
+
|
|
121
|
+
// Find the TS keyword 'const'
|
|
122
|
+
const constToken = highlighted.find(t => t.text === 'const')
|
|
123
|
+
expect(constToken).toBeDefined()
|
|
124
|
+
expect(constToken?.style).not.toBeNull()
|
|
125
|
+
|
|
126
|
+
// Find the type annotation 'number'
|
|
127
|
+
const typeToken = highlighted.find(t => t.text === 'number')
|
|
128
|
+
expect(typeToken).toBeDefined()
|
|
129
|
+
expect(typeToken?.style).not.toBeNull()
|
|
130
|
+
|
|
131
|
+
// Find the number '123'
|
|
132
|
+
const numberToken = highlighted.find(t => t.text === '123')
|
|
133
|
+
expect(numberToken).toBeDefined()
|
|
134
|
+
expect(numberToken?.style).not.toBeNull()
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('markdownConfig (GFM)', () => {
|
|
139
|
+
const strikeHighlighter = tagHighlighter([{ tag: tags.strikethrough, class: 'strike' }])
|
|
140
|
+
|
|
141
|
+
it('does not tokenize GFM strikethrough with the default CommonMark base', async () => {
|
|
142
|
+
const highlighted = await highlightCode(
|
|
143
|
+
'markdown',
|
|
144
|
+
'~~gone~~',
|
|
145
|
+
strikeHighlighter,
|
|
146
|
+
undefined,
|
|
147
|
+
undefined,
|
|
148
|
+
(text, style) => ({ text, style })
|
|
149
|
+
)
|
|
150
|
+
expect(highlighted.some(t => t.style === 'strike')).toBe(false)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('tokenizes GFM strikethrough when base is markdownLanguage', async () => {
|
|
154
|
+
const highlighted = await highlightCode(
|
|
155
|
+
'markdown',
|
|
156
|
+
'~~gone~~',
|
|
157
|
+
strikeHighlighter,
|
|
158
|
+
undefined,
|
|
159
|
+
undefined,
|
|
160
|
+
(text, style) => ({ text, style }),
|
|
161
|
+
{ base: markdownLanguage }
|
|
162
|
+
)
|
|
163
|
+
const struck = highlighted.find(t => t.style === 'strike')
|
|
164
|
+
expect(struck).toBeDefined()
|
|
165
|
+
expect(struck?.text).toContain('gone')
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('multiple highlighters', () => {
|
|
170
|
+
const highlighterA = tagHighlighter([{ tag: tags.strikethrough, class: 'a-strike' }])
|
|
171
|
+
const highlighterB = tagHighlighter([{ tag: tags.strikethrough, class: 'b-strike' }])
|
|
172
|
+
|
|
173
|
+
it('merges classes emitted by an array of highlighters', async () => {
|
|
174
|
+
const highlighted = await highlightCode(
|
|
175
|
+
'markdown',
|
|
176
|
+
'~~gone~~',
|
|
177
|
+
[highlighterA, highlighterB],
|
|
178
|
+
undefined,
|
|
179
|
+
undefined,
|
|
180
|
+
(text, style) => ({ text, style }),
|
|
181
|
+
{ base: markdownLanguage }
|
|
182
|
+
)
|
|
183
|
+
const struck = highlighted.find(t => t.style?.includes('a-strike'))
|
|
184
|
+
expect(struck).toBeDefined()
|
|
185
|
+
expect(struck?.style).toContain('a-strike')
|
|
186
|
+
expect(struck?.style).toContain('b-strike')
|
|
187
|
+
})
|
|
102
188
|
})
|
|
103
189
|
|
|
104
190
|
describe('React Highlighter', () => {
|
package/tsconfig.build.json
CHANGED
package/vite.config.ts
CHANGED
package/.prettierignore
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# Logs
|
|
2
|
-
logs
|
|
3
|
-
*.log
|
|
4
|
-
npm-debug.log*
|
|
5
|
-
yarn-debug.log*
|
|
6
|
-
yarn-error.log*
|
|
7
|
-
lerna-debug.log*
|
|
8
|
-
.pnpm-debug.log*
|
|
9
|
-
|
|
10
|
-
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
11
|
-
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
12
|
-
|
|
13
|
-
# Runtime data
|
|
14
|
-
pids
|
|
15
|
-
*.pid
|
|
16
|
-
*.seed
|
|
17
|
-
*.pid.lock
|
|
18
|
-
|
|
19
|
-
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
20
|
-
lib-cov
|
|
21
|
-
|
|
22
|
-
# Coverage directory used by tools like istanbul
|
|
23
|
-
coverage
|
|
24
|
-
*.lcov
|
|
25
|
-
|
|
26
|
-
# nyc test coverage
|
|
27
|
-
.nyc_output
|
|
28
|
-
|
|
29
|
-
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
30
|
-
.grunt
|
|
31
|
-
|
|
32
|
-
# Bower dependency directory (https://bower.io/)
|
|
33
|
-
bower_components
|
|
34
|
-
|
|
35
|
-
# node-waf configuration
|
|
36
|
-
.lock-wscript
|
|
37
|
-
|
|
38
|
-
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
39
|
-
build/Release
|
|
40
|
-
|
|
41
|
-
# Dependency directories
|
|
42
|
-
node_modules/
|
|
43
|
-
jspm_packages/
|
|
44
|
-
|
|
45
|
-
# Snowpack dependency directory (https://snowpack.dev/)
|
|
46
|
-
web_modules/
|
|
47
|
-
|
|
48
|
-
# TypeScript cache
|
|
49
|
-
*.tsbuildinfo
|
|
50
|
-
|
|
51
|
-
# Optional npm cache directory
|
|
52
|
-
.npm
|
|
53
|
-
|
|
54
|
-
# Optional eslint cache
|
|
55
|
-
.eslintcache
|
|
56
|
-
|
|
57
|
-
# Optional stylelint cache
|
|
58
|
-
.stylelintcache
|
|
59
|
-
|
|
60
|
-
# Microbundle cache
|
|
61
|
-
.rpt2_cache/
|
|
62
|
-
.rts2_cache_cjs/
|
|
63
|
-
.rts2_cache_es/
|
|
64
|
-
.rts2_cache_umd/
|
|
65
|
-
|
|
66
|
-
# Optional REPL history
|
|
67
|
-
.node_repl_history
|
|
68
|
-
|
|
69
|
-
# Output of 'npm pack'
|
|
70
|
-
*.tgz
|
|
71
|
-
|
|
72
|
-
# Yarn Integrity file
|
|
73
|
-
.yarn-integrity
|
|
74
|
-
|
|
75
|
-
# dotenv environment variable files
|
|
76
|
-
.env
|
|
77
|
-
.env.development.local
|
|
78
|
-
.env.test.local
|
|
79
|
-
.env.production.local
|
|
80
|
-
.env.local
|
|
81
|
-
|
|
82
|
-
# parcel-bundler cache (https://parceljs.org/)
|
|
83
|
-
.cache
|
|
84
|
-
.parcel-cache
|
|
85
|
-
|
|
86
|
-
# Next.js build output
|
|
87
|
-
.next
|
|
88
|
-
out
|
|
89
|
-
|
|
90
|
-
# Nuxt.js build / generate output
|
|
91
|
-
.nuxt
|
|
92
|
-
dist
|
|
93
|
-
|
|
94
|
-
# Gatsby files
|
|
95
|
-
.cache/
|
|
96
|
-
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
97
|
-
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
98
|
-
# public
|
|
99
|
-
|
|
100
|
-
# vuepress build output
|
|
101
|
-
.vuepress/dist
|
|
102
|
-
|
|
103
|
-
# vuepress v2.x temp and cache directory
|
|
104
|
-
.temp
|
|
105
|
-
.cache
|
|
106
|
-
|
|
107
|
-
# Docusaurus cache and generated files
|
|
108
|
-
.docusaurus
|
|
109
|
-
|
|
110
|
-
# Serverless directories
|
|
111
|
-
.serverless/
|
|
112
|
-
|
|
113
|
-
# FuseBox cache
|
|
114
|
-
.fusebox/
|
|
115
|
-
|
|
116
|
-
# DynamoDB Local files
|
|
117
|
-
.dynamodb/
|
|
118
|
-
|
|
119
|
-
# TernJS port file
|
|
120
|
-
.tern-port
|
|
121
|
-
|
|
122
|
-
# Stores VSCode versions used for testing VSCode extensions
|
|
123
|
-
.vscode-test
|
|
124
|
-
|
|
125
|
-
# yarn v2
|
|
126
|
-
.yarn/cache
|
|
127
|
-
.yarn/unplugged
|
|
128
|
-
.yarn/build-state.yml
|
|
129
|
-
.yarn/install-state.gz
|
|
130
|
-
.pnp.*
|
|
131
|
-
|
|
132
|
-
# IDE
|
|
133
|
-
.idea/
|
|
134
|
-
.vscode/
|
|
135
|
-
|
|
136
|
-
# Lock files
|
|
137
|
-
pnpm-lock.yaml
|
package/.prettierrc.json
DELETED
package/eslint.config.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js'
|
|
2
|
-
import eslintTs from 'typescript-eslint'
|
|
3
|
-
import eslintReact from 'eslint-plugin-react'
|
|
4
|
-
|
|
5
|
-
export default eslintTs.config(
|
|
6
|
-
{ ignores: ['dist'] },
|
|
7
|
-
js.configs.recommended,
|
|
8
|
-
eslintTs.configs.recommended,
|
|
9
|
-
eslintReact.configs.flat.recommended,
|
|
10
|
-
eslintReact.configs.flat['jsx-runtime'],
|
|
11
|
-
{
|
|
12
|
-
plugins: {
|
|
13
|
-
'@typescript-eslint': eslintTs.plugin
|
|
14
|
-
},
|
|
15
|
-
rules: {
|
|
16
|
-
// TypeScript
|
|
17
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
18
|
-
'@typescript-eslint/no-unused-vars': [
|
|
19
|
-
'warn',
|
|
20
|
-
{
|
|
21
|
-
argsIgnorePattern: '^_',
|
|
22
|
-
varsIgnorePattern: '^_',
|
|
23
|
-
caughtErrorsIgnorePattern: '^_'
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
'@typescript-eslint/ban-ts-comment': 'off',
|
|
27
|
-
'@typescript-eslint/no-this-alias': 'off',
|
|
28
|
-
'@typescript-eslint/no-var-requires': 'off',
|
|
29
|
-
'@typescript-eslint/no-unused-expressions': 'off',
|
|
30
|
-
|
|
31
|
-
// JavaScript and React rules
|
|
32
|
-
'no-useless-escape': 0,
|
|
33
|
-
'prefer-const': 2,
|
|
34
|
-
'no-unused-vars': 0
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
settings: {
|
|
40
|
-
react: {
|
|
41
|
-
version: '19'
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
)
|