react-codemirror-runmode 2.0.2 → 2.2.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/.claude/settings.local.json +3 -0
- package/README.md +5 -1
- package/dist/highlight.d.ts +3 -3
- package/dist/highlight.js +13 -6
- package/dist/react-highlighter.d.ts +3 -0
- package/dist/react-highlighter.js +2 -2
- package/package.json +22 -20
- package/src/highlight.ts +14 -5
- package/src/react-highlighter.tsx +6 -1
- package/test/index.spec.tsx +36 -0
- package/tsconfig.json +4 -3
- package/vite.config.ts +1 -1
package/README.md
CHANGED
|
@@ -39,14 +39,18 @@ Props:
|
|
|
39
39
|
- `lang`: `string` - The name of the language
|
|
40
40
|
- `theme`: [`Highlighter`](https://lezer.codemirror.net/docs/ref/#highlight.Highlighter) - The highlight style
|
|
41
41
|
- `children`: `string` - The code to highlight
|
|
42
|
+
- `fallbackLanguage`: `Language` - Optional fallback language to use if the specified language isn't found
|
|
43
|
+
- `languages`: `LanguageDescription[]` - Optional custom list of language descriptions
|
|
42
44
|
|
|
43
|
-
### `highlightCode<
|
|
45
|
+
### `highlightCode<o>(languageName, input, highlightStyle, fallbackLanguage?, languages?, callback): Promise<Output[]>`
|
|
44
46
|
|
|
45
47
|
Parameters:
|
|
46
48
|
|
|
47
49
|
- `languageName`: `string` - The name of the language
|
|
48
50
|
- `input`: `string` - The code to highlight
|
|
49
51
|
- `highlighter`: [`Highlighter`](https://lezer.codemirror.net/docs/ref/#highlight.Highlighter) - The highlight style
|
|
52
|
+
- `fallbackLanguage`: `Language` - Optional fallback language to use if the specified language isn't found
|
|
53
|
+
- `languages`: `LanguageDescription[]` - Optional custom list of language descriptions
|
|
50
54
|
- `callback`: `(text: string, style: string | null, from: number, to: number) => Output)` - A callback function that converts the parsed tokens
|
|
51
55
|
|
|
52
56
|
### `getCodeParser(languageName, defaultLanguage?): Promise<Parser | null>`
|
package/dist/highlight.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser } from '@lezer/common';
|
|
2
|
-
import { Language } from '@codemirror/language';
|
|
2
|
+
import { Language, LanguageDescription } from '@codemirror/language';
|
|
3
3
|
import { Highlighter } from '@lezer/highlight';
|
|
4
|
-
export declare function getCodeParser(languageName: string,
|
|
5
|
-
export declare function highlightCode<Output>(languageName: string, input: string, highlighter: Highlighter, callback: (text: string, style: string | null, from: number, to: number) => Output): Promise<Output[]>;
|
|
4
|
+
export declare function getCodeParser(languageName: string, fallbackLanguage?: Language, languages?: LanguageDescription[]): Promise<Parser | null>;
|
|
5
|
+
export declare function highlightCode<Output>(languageName: string, input: string, highlighter: Highlighter, fallbackLanguage: Language | undefined, languages: LanguageDescription[] | undefined, callback: (text: string, style: string | null, from: number, to: number) => Output): Promise<Output[]>;
|
package/dist/highlight.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { LanguageDescription } from '@codemirror/language';
|
|
2
2
|
import { highlightTree } from '@lezer/highlight';
|
|
3
|
-
import { languages } from '@codemirror/language-data';
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { languages as builtinLanguages } from '@codemirror/language-data';
|
|
4
|
+
import { markdown } from '@codemirror/lang-markdown';
|
|
5
|
+
export async function getCodeParser(languageName, fallbackLanguage, languages = builtinLanguages) {
|
|
6
|
+
if (languageName === 'markdown' || languageName === 'md') {
|
|
7
|
+
const mdSupport = markdown({
|
|
8
|
+
codeLanguages: builtinLanguages
|
|
9
|
+
});
|
|
10
|
+
return mdSupport.language.parser;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
6
13
|
const found = LanguageDescription.matchLanguageName(languages, languageName, true);
|
|
7
14
|
if (found instanceof LanguageDescription) {
|
|
8
15
|
if (!found.support)
|
|
@@ -12,10 +19,10 @@ export async function getCodeParser(languageName, defaultLanguage) {
|
|
|
12
19
|
else if (found)
|
|
13
20
|
return found.parser;
|
|
14
21
|
}
|
|
15
|
-
return
|
|
22
|
+
return fallbackLanguage ? fallbackLanguage.parser : null;
|
|
16
23
|
}
|
|
17
|
-
export async function highlightCode(languageName, input, highlighter, callback) {
|
|
18
|
-
const parser = await getCodeParser(languageName);
|
|
24
|
+
export async function highlightCode(languageName, input, highlighter, fallbackLanguage, languages, callback) {
|
|
25
|
+
const parser = await getCodeParser(languageName, fallbackLanguage, languages);
|
|
19
26
|
if (parser) {
|
|
20
27
|
const tree = parser.parse(input);
|
|
21
28
|
const output = [];
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Highlighter as LezerHighlighter } from '@lezer/highlight';
|
|
3
|
+
import type { Language, LanguageDescription } from '@codemirror/language';
|
|
3
4
|
export type HighlighterProps = {
|
|
4
5
|
lang: string;
|
|
5
6
|
children: string;
|
|
6
7
|
theme: LezerHighlighter;
|
|
8
|
+
fallbackLanguage?: Language;
|
|
9
|
+
languages?: LanguageDescription[];
|
|
7
10
|
};
|
|
8
11
|
export declare const Highlighter: React.NamedExoticComponent<HighlighterProps>;
|
|
@@ -2,10 +2,10 @@ 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 } = props;
|
|
5
|
+
const { lang, children: code, theme, fallbackLanguage, languages } = props;
|
|
6
6
|
const [highlightedCode, setHighlightedCode] = useState(null);
|
|
7
7
|
useEffect(() => {
|
|
8
|
-
highlightCode(lang, code, theme, (text, style, from) => {
|
|
8
|
+
highlightCode(lang, code, theme, fallbackLanguage, languages, (text, style, from) => {
|
|
9
9
|
return (_jsx("span", { className: style || '', children: text }, from));
|
|
10
10
|
}).then(setHighlightedCode);
|
|
11
11
|
}, [lang, code, theme]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-codemirror-runmode",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Syntax highlighting for react, utilizing CodeMirror's parser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"build": "tsc --project tsconfig.build.json",
|
|
15
15
|
"test": "vitest",
|
|
16
16
|
"lint": "eslint .",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
17
18
|
"format": "prettier --write .",
|
|
18
19
|
"format:check": "prettier --check .",
|
|
19
20
|
"prepublishOnly": "npm-run-all lint format:check build && npm run test run"
|
|
@@ -31,30 +32,31 @@
|
|
|
31
32
|
"url": "https://github.com/craftzdog/react-codemirror-runmode.git"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@codemirror/language": "^6.11.
|
|
35
|
-
"@codemirror/language-data": "^6.5.
|
|
36
|
-
"@lezer/common": "^1.
|
|
37
|
-
"@lezer/highlight": "^1.2.
|
|
35
|
+
"@codemirror/language": "^6.11.3",
|
|
36
|
+
"@codemirror/language-data": "^6.5.2",
|
|
37
|
+
"@lezer/common": "^1.4.0",
|
|
38
|
+
"@lezer/highlight": "^1.2.3"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"react": ">= 18"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"@codemirror/theme-one-dark": "^6.1.
|
|
44
|
-
"@testing-library/react": "^16.
|
|
45
|
-
"@types/node": "^
|
|
46
|
-
"@types/react": "^19.
|
|
47
|
-
"@vitejs/plugin-react": "^
|
|
48
|
-
"eslint": "^9.
|
|
49
|
-
"eslint-config-prettier": "^10.1.
|
|
50
|
-
"eslint-plugin-react": "^7.37.
|
|
51
|
-
"jsdom": "^
|
|
44
|
+
"@codemirror/theme-one-dark": "^6.1.3",
|
|
45
|
+
"@testing-library/react": "^16.3.0",
|
|
46
|
+
"@types/node": "^25.0.1",
|
|
47
|
+
"@types/react": "^19.2.7",
|
|
48
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
49
|
+
"eslint": "^9.39.1",
|
|
50
|
+
"eslint-config-prettier": "^10.1.8",
|
|
51
|
+
"eslint-plugin-react": "^7.37.5",
|
|
52
|
+
"jsdom": "^27.3.0",
|
|
52
53
|
"npm-run-all": "^4.1.5",
|
|
53
|
-
"prettier": "^3.
|
|
54
|
-
"react": "^19.
|
|
55
|
-
"
|
|
56
|
-
"typescript
|
|
57
|
-
"
|
|
58
|
-
"
|
|
54
|
+
"prettier": "^3.7.4",
|
|
55
|
+
"react": "^19.2.3",
|
|
56
|
+
"react-dom": "^19.2.3",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"typescript-eslint": "^8.49.0",
|
|
59
|
+
"vite": "^7.2.7",
|
|
60
|
+
"vitest": "^4.0.15"
|
|
59
61
|
}
|
|
60
62
|
}
|
package/src/highlight.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { Parser } from '@lezer/common'
|
|
2
2
|
import { Language, LanguageDescription } from '@codemirror/language'
|
|
3
3
|
import { Highlighter, highlightTree } from '@lezer/highlight'
|
|
4
|
-
import { languages } from '@codemirror/language-data'
|
|
4
|
+
import { languages as builtinLanguages } from '@codemirror/language-data'
|
|
5
|
+
import { markdown } from '@codemirror/lang-markdown'
|
|
5
6
|
|
|
6
7
|
export async function getCodeParser(
|
|
7
8
|
languageName: string,
|
|
8
|
-
|
|
9
|
+
fallbackLanguage?: Language,
|
|
10
|
+
languages: LanguageDescription[] = builtinLanguages
|
|
9
11
|
): Promise<Parser | null> {
|
|
10
|
-
if (languageName
|
|
12
|
+
if (languageName === 'markdown' || languageName === 'md') {
|
|
13
|
+
const mdSupport = markdown({
|
|
14
|
+
codeLanguages: builtinLanguages
|
|
15
|
+
})
|
|
16
|
+
return mdSupport.language.parser
|
|
17
|
+
} else {
|
|
11
18
|
const found = LanguageDescription.matchLanguageName(
|
|
12
19
|
languages,
|
|
13
20
|
languageName,
|
|
@@ -18,13 +25,15 @@ export async function getCodeParser(
|
|
|
18
25
|
return found.support ? found.support.language.parser : null
|
|
19
26
|
} else if (found) return (found as any).parser
|
|
20
27
|
}
|
|
21
|
-
return
|
|
28
|
+
return fallbackLanguage ? fallbackLanguage.parser : null
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
export async function highlightCode<Output>(
|
|
25
32
|
languageName: string,
|
|
26
33
|
input: string,
|
|
27
34
|
highlighter: Highlighter,
|
|
35
|
+
fallbackLanguage: Language | undefined,
|
|
36
|
+
languages: LanguageDescription[] | undefined,
|
|
28
37
|
callback: (
|
|
29
38
|
text: string,
|
|
30
39
|
style: string | null,
|
|
@@ -32,7 +41,7 @@ export async function highlightCode<Output>(
|
|
|
32
41
|
to: number
|
|
33
42
|
) => Output
|
|
34
43
|
): Promise<Output[]> {
|
|
35
|
-
const parser = await getCodeParser(languageName)
|
|
44
|
+
const parser = await getCodeParser(languageName, fallbackLanguage, languages)
|
|
36
45
|
if (parser) {
|
|
37
46
|
const tree = parser.parse(input)
|
|
38
47
|
const output: Array<Output> = []
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import React, { memo, useEffect, useState } from 'react'
|
|
2
2
|
import { highlightCode } from './highlight.js'
|
|
3
3
|
import type { Highlighter as LezerHighlighter } from '@lezer/highlight'
|
|
4
|
+
import type { Language, LanguageDescription } from '@codemirror/language'
|
|
4
5
|
|
|
5
6
|
export type HighlighterProps = {
|
|
6
7
|
lang: string
|
|
7
8
|
children: string
|
|
8
9
|
theme: LezerHighlighter
|
|
10
|
+
fallbackLanguage?: Language
|
|
11
|
+
languages?: LanguageDescription[]
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
export const Highlighter = memo<HighlighterProps>((props: HighlighterProps) => {
|
|
12
|
-
const { lang, children: code, theme } = props
|
|
15
|
+
const { lang, children: code, theme, fallbackLanguage, languages } = props
|
|
13
16
|
const [highlightedCode, setHighlightedCode] = useState<
|
|
14
17
|
React.ReactNode[] | null
|
|
15
18
|
>(null)
|
|
@@ -19,6 +22,8 @@ export const Highlighter = memo<HighlighterProps>((props: HighlighterProps) => {
|
|
|
19
22
|
lang,
|
|
20
23
|
code,
|
|
21
24
|
theme,
|
|
25
|
+
fallbackLanguage,
|
|
26
|
+
languages,
|
|
22
27
|
(text: string, style: string | null, from: number) => {
|
|
23
28
|
return (
|
|
24
29
|
<span key={from} className={style || ''}>
|
package/test/index.spec.tsx
CHANGED
|
@@ -29,6 +29,8 @@ describe('highlightCode', () => {
|
|
|
29
29
|
'js',
|
|
30
30
|
'const x = 123',
|
|
31
31
|
oneDarkHighlightStyle,
|
|
32
|
+
undefined,
|
|
33
|
+
undefined,
|
|
32
34
|
(text, style, from, to) => ({ text, style, from, to })
|
|
33
35
|
)
|
|
34
36
|
expect(highlighted).toHaveLength(7)
|
|
@@ -65,6 +67,40 @@ describe('highlightCode', () => {
|
|
|
65
67
|
})
|
|
66
68
|
})
|
|
67
69
|
|
|
70
|
+
describe('Highlight codeblocks', () => {
|
|
71
|
+
it('highlights codeblocks in markdown', async () => {
|
|
72
|
+
const mdCode = '```js\nconst x = 123\n```'
|
|
73
|
+
const highlighted = await highlightCode(
|
|
74
|
+
'markdown',
|
|
75
|
+
mdCode,
|
|
76
|
+
oneDarkHighlightStyle,
|
|
77
|
+
undefined,
|
|
78
|
+
undefined,
|
|
79
|
+
(text, style, from, to) => ({ text, style, from, to })
|
|
80
|
+
)
|
|
81
|
+
// Should have tokens for the markdown code block structure and the JS code inside
|
|
82
|
+
expect(highlighted.length).toBeGreaterThan(0)
|
|
83
|
+
|
|
84
|
+
// Find the code fence markers
|
|
85
|
+
const fenceStart = highlighted.find(t => t.text === '```')
|
|
86
|
+
expect(fenceStart).toBeDefined()
|
|
87
|
+
|
|
88
|
+
// Find the language info token
|
|
89
|
+
const langInfo = highlighted.find(t => t.text === 'js')
|
|
90
|
+
expect(langInfo).toBeDefined()
|
|
91
|
+
|
|
92
|
+
// Find the JS keyword 'const' inside the code block
|
|
93
|
+
const constToken = highlighted.find(t => t.text === 'const')
|
|
94
|
+
expect(constToken).toBeDefined()
|
|
95
|
+
expect(constToken?.style).not.toBeNull()
|
|
96
|
+
|
|
97
|
+
// Find the number '123'
|
|
98
|
+
const numberToken = highlighted.find(t => t.text === '123')
|
|
99
|
+
expect(numberToken).toBeDefined()
|
|
100
|
+
expect(numberToken?.style).not.toBeNull()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
68
104
|
describe('React Highlighter', () => {
|
|
69
105
|
it('renders highlighted code', async () => {
|
|
70
106
|
const code = 'const x = 123'
|
package/tsconfig.json
CHANGED
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"target": "esnext",
|
|
5
5
|
"module": "esnext",
|
|
6
|
-
"lib": ["
|
|
6
|
+
"lib": ["esnext", "dom"],
|
|
7
7
|
"jsx": "react-jsx",
|
|
8
8
|
"esModuleInterop": true,
|
|
9
9
|
"strict": true,
|
|
10
10
|
"isolatedModules": true,
|
|
11
11
|
"sourceMap": false,
|
|
12
|
-
"moduleResolution": "
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
13
|
"resolveJsonModule": true,
|
|
14
14
|
"noImplicitAny": false,
|
|
15
15
|
"outDir": "dist",
|
|
16
16
|
"declaration": true,
|
|
17
|
-
"declarationDir": "dist"
|
|
17
|
+
"declarationDir": "dist",
|
|
18
|
+
"skipLibCheck": true
|
|
18
19
|
}
|
|
19
20
|
}
|
package/vite.config.ts
CHANGED