react-codemirror-runmode 1.0.5 → 2.0.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/.eslintignore +1 -2
- package/.eslintrc.yml +13 -28
- package/.github/workflows/ci.yml +21 -0
- package/.prettierignore +137 -0
- package/.prettierrc.json +10 -0
- package/.travis.yml +4 -1
- package/README.md +40 -40
- package/Session.vim +260 -0
- package/dist/highlight.d.ts +4 -0
- package/dist/highlight.js +36 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/react-highlighter.d.ts +8 -0
- package/dist/react-highlighter.js +13 -0
- package/package.json +40 -38
- package/src/highlight.ts +58 -0
- package/src/index.ts +2 -0
- package/src/react-highlighter.tsx +33 -0
- package/test/__snapshots__/index.spec.ts.snap +48 -0
- package/test/index.spec.tsx +84 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +19 -0
- package/vite.config.ts +14 -0
- package/.babelrc +0 -16
- package/demo/index.html +0 -28
- package/demo/js/demo.js +0 -52
- package/demo/js/demo.min.js +0 -20567
- package/demo/js/demo.min.js.map +0 -1
- package/lib/index.js +0 -83
- package/src/index.js +0 -68
- package/test/highlight.spec.js +0 -32
- package/test/setup.js +0 -20
- package/webpack.config.demo.babel.js +0 -58
package/package.json
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-codemirror-runmode",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Syntax highlighting for react, utilizing CodeMirror's parser",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
6
13
|
"scripts": {
|
|
7
|
-
"build": "
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"prepublishOnly": "npm
|
|
14
|
+
"build": "tsc --project tsconfig.build.json",
|
|
15
|
+
"test": "vitest",
|
|
16
|
+
"lint": "eslint .",
|
|
17
|
+
"format": "prettier --write .",
|
|
18
|
+
"format:check": "prettier --check .",
|
|
19
|
+
"prepublishOnly": "npm-run-all lint format:check build && npm run test run"
|
|
13
20
|
},
|
|
14
21
|
"keywords": [
|
|
15
|
-
"remark",
|
|
16
22
|
"react",
|
|
17
23
|
"codemirror",
|
|
18
24
|
"syntax",
|
|
@@ -24,35 +30,31 @@
|
|
|
24
30
|
"type": "git",
|
|
25
31
|
"url": "https://github.com/craftzdog/react-codemirror-runmode.git"
|
|
26
32
|
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@codemirror/language": "^6.10.1",
|
|
35
|
+
"@codemirror/language-data": "^6.5.1",
|
|
36
|
+
"@lezer/common": "^1.2.1",
|
|
37
|
+
"@lezer/highlight": "^1.2.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">= 18"
|
|
41
|
+
},
|
|
27
42
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
30
|
-
"@
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
33
|
-
"@
|
|
34
|
-
"@
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"eslint-plugin-prettier": "^3.1.0",
|
|
45
|
-
"eslint-plugin-react": "^7.14.3",
|
|
46
|
-
"jsdom": "^15.1.1",
|
|
47
|
-
"mocha": "^6.2.0",
|
|
48
|
-
"prettier": "^1.18.2",
|
|
49
|
-
"prop-types": "^15.7.2",
|
|
50
|
-
"react": "^16.8.6",
|
|
51
|
-
"react-dom": "^16.8.6",
|
|
52
|
-
"react-test-renderer": "^16.8.6",
|
|
53
|
-
"remark": "^11.0.1",
|
|
54
|
-
"remark-react": "^6.0.0",
|
|
55
|
-
"webpack": "^4.37.0",
|
|
56
|
-
"webpack-cli": "^3.3.6"
|
|
43
|
+
"@codemirror/theme-one-dark": "^6.1.2",
|
|
44
|
+
"@testing-library/react": "^15.0.7",
|
|
45
|
+
"@types/node": "^20.12.11",
|
|
46
|
+
"@types/react": "^18.3.1",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
|
48
|
+
"@typescript-eslint/parser": "^7.8.0",
|
|
49
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
50
|
+
"eslint": "^8.57.0",
|
|
51
|
+
"eslint-config-prettier": "^9.1.0",
|
|
52
|
+
"jsdom": "^24.0.0",
|
|
53
|
+
"npm-run-all": "^4.1.5",
|
|
54
|
+
"prettier": "^3.2.5",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"typescript": "^5.4.5",
|
|
57
|
+
"vite": "^5.2.11",
|
|
58
|
+
"vitest": "^1.6.0"
|
|
57
59
|
}
|
|
58
60
|
}
|
package/src/highlight.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Parser } from '@lezer/common'
|
|
2
|
+
import {
|
|
3
|
+
HighlightStyle,
|
|
4
|
+
Language,
|
|
5
|
+
LanguageDescription
|
|
6
|
+
} from '@codemirror/language'
|
|
7
|
+
import { highlightTree } from '@lezer/highlight'
|
|
8
|
+
import { languages } from '@codemirror/language-data'
|
|
9
|
+
|
|
10
|
+
export async function getCodeParser(
|
|
11
|
+
languageName: string,
|
|
12
|
+
defaultLanguage?: Language
|
|
13
|
+
): Promise<Parser | null> {
|
|
14
|
+
if (languageName && languages) {
|
|
15
|
+
const found = LanguageDescription.matchLanguageName(
|
|
16
|
+
languages,
|
|
17
|
+
languageName,
|
|
18
|
+
true
|
|
19
|
+
)
|
|
20
|
+
if (found instanceof LanguageDescription) {
|
|
21
|
+
if (!found.support) await found.load()
|
|
22
|
+
return found.support ? found.support.language.parser : null
|
|
23
|
+
} else if (found) return (found as any).parser
|
|
24
|
+
}
|
|
25
|
+
return defaultLanguage ? defaultLanguage.parser : null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function highlightCode<Output>(
|
|
29
|
+
languageName: string,
|
|
30
|
+
input: string,
|
|
31
|
+
highlightStyle: HighlightStyle,
|
|
32
|
+
callback: (
|
|
33
|
+
text: string,
|
|
34
|
+
style: string | null,
|
|
35
|
+
from: number,
|
|
36
|
+
to: number
|
|
37
|
+
) => Output
|
|
38
|
+
): Promise<Output[]> {
|
|
39
|
+
const parser = await getCodeParser(languageName)
|
|
40
|
+
if (parser) {
|
|
41
|
+
const tree = parser.parse(input)
|
|
42
|
+
const output: Array<Output> = []
|
|
43
|
+
let pos = 0
|
|
44
|
+
highlightTree(tree, highlightStyle, (from, to, classes) => {
|
|
45
|
+
if (from > pos)
|
|
46
|
+
output.push(callback(input.slice(pos, from), null, pos, from))
|
|
47
|
+
output.push(callback(input.slice(from, to), classes, from, to))
|
|
48
|
+
pos = to
|
|
49
|
+
})
|
|
50
|
+
pos != tree.length &&
|
|
51
|
+
output.push(
|
|
52
|
+
callback(input.slice(pos, tree.length), null, pos, tree.length)
|
|
53
|
+
)
|
|
54
|
+
return output
|
|
55
|
+
} else {
|
|
56
|
+
return [callback(input, null, 0, input.length)]
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { memo, useEffect, useState } from 'react'
|
|
2
|
+
import { highlightCode } from './highlight'
|
|
3
|
+
import type { HighlightStyle } from '@codemirror/language'
|
|
4
|
+
|
|
5
|
+
export type HighlighterProps = {
|
|
6
|
+
lang: string
|
|
7
|
+
children: string
|
|
8
|
+
highlightStyle: HighlightStyle
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Highlighter = memo<HighlighterProps>((props: HighlighterProps) => {
|
|
12
|
+
const { lang, children: code, highlightStyle } = props
|
|
13
|
+
const [highlightedCode, setHighlightedCode] = useState<
|
|
14
|
+
React.ReactNode[] | null
|
|
15
|
+
>(null)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
highlightCode(
|
|
19
|
+
lang,
|
|
20
|
+
code,
|
|
21
|
+
highlightStyle,
|
|
22
|
+
(text: string, style: string | null, from: number) => {
|
|
23
|
+
return (
|
|
24
|
+
<span key={from} className={style || ''}>
|
|
25
|
+
{text}
|
|
26
|
+
</span>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
).then(setHighlightedCode)
|
|
30
|
+
}, [lang, code, highlightStyle])
|
|
31
|
+
|
|
32
|
+
return <>{highlightedCode || code}</>
|
|
33
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`highlightCode > highlights JavaScript code 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"from": 0,
|
|
7
|
+
"style": "ͼp",
|
|
8
|
+
"text": "const",
|
|
9
|
+
"to": 5,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"from": 5,
|
|
13
|
+
"style": null,
|
|
14
|
+
"text": " ",
|
|
15
|
+
"to": 6,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"from": 6,
|
|
19
|
+
"style": "ͼt",
|
|
20
|
+
"text": "x",
|
|
21
|
+
"to": 7,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"from": 7,
|
|
25
|
+
"style": null,
|
|
26
|
+
"text": " ",
|
|
27
|
+
"to": 8,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"from": 8,
|
|
31
|
+
"style": "ͼv",
|
|
32
|
+
"text": "=",
|
|
33
|
+
"to": 9,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"from": 9,
|
|
37
|
+
"style": null,
|
|
38
|
+
"text": " ",
|
|
39
|
+
"to": 10,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"from": 10,
|
|
43
|
+
"style": "ͼu",
|
|
44
|
+
"text": "123",
|
|
45
|
+
"to": 13,
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
`;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { Parser } from '@lezer/common'
|
|
4
|
+
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark'
|
|
5
|
+
import { getCodeParser, highlightCode, Highlighter } from '../src'
|
|
6
|
+
import { render, screen } from '@testing-library/react'
|
|
7
|
+
|
|
8
|
+
const sleep = (msec: number) =>
|
|
9
|
+
new Promise(resolve => setTimeout(resolve, msec))
|
|
10
|
+
|
|
11
|
+
describe('getCodeParser', () => {
|
|
12
|
+
it('loads a JavaScript parser', async () => {
|
|
13
|
+
const parser = await getCodeParser('js')
|
|
14
|
+
expect(parser).toBeInstanceOf(Parser)
|
|
15
|
+
})
|
|
16
|
+
it('loads a Python parser', async () => {
|
|
17
|
+
const parser = await getCodeParser('python')
|
|
18
|
+
expect(parser).toBeInstanceOf(Parser)
|
|
19
|
+
})
|
|
20
|
+
it('returns null for non-existing languages', async () => {
|
|
21
|
+
const parser = await getCodeParser('foobar123')
|
|
22
|
+
expect(parser).toBeNull()
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('highlightCode', () => {
|
|
27
|
+
it('highlights JavaScript code', async () => {
|
|
28
|
+
const highlighted = await highlightCode(
|
|
29
|
+
'js',
|
|
30
|
+
'const x = 123',
|
|
31
|
+
oneDarkHighlightStyle,
|
|
32
|
+
(text, style, from, to) => ({ text, style, from, to })
|
|
33
|
+
)
|
|
34
|
+
expect(highlighted).toHaveLength(7)
|
|
35
|
+
expect(highlighted[0]).toEqual({
|
|
36
|
+
text: 'const',
|
|
37
|
+
style: 'ͼp',
|
|
38
|
+
from: 0,
|
|
39
|
+
to: 5
|
|
40
|
+
})
|
|
41
|
+
expect(highlighted[1]).toEqual({
|
|
42
|
+
text: ' ',
|
|
43
|
+
style: null,
|
|
44
|
+
from: 5,
|
|
45
|
+
to: 6
|
|
46
|
+
})
|
|
47
|
+
expect(highlighted[2]).toEqual({
|
|
48
|
+
text: 'x',
|
|
49
|
+
style: 'ͼt',
|
|
50
|
+
from: 6,
|
|
51
|
+
to: 7
|
|
52
|
+
})
|
|
53
|
+
expect(highlighted[4]).toEqual({
|
|
54
|
+
text: '=',
|
|
55
|
+
style: 'ͼv',
|
|
56
|
+
from: 8,
|
|
57
|
+
to: 9
|
|
58
|
+
})
|
|
59
|
+
expect(highlighted[6]).toEqual({
|
|
60
|
+
text: '123',
|
|
61
|
+
style: 'ͼu',
|
|
62
|
+
from: 10,
|
|
63
|
+
to: 13
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('React Highlighter', () => {
|
|
69
|
+
it('renders highlighted code', async () => {
|
|
70
|
+
const code = 'const x = 123'
|
|
71
|
+
render(
|
|
72
|
+
<code className="mde-preview">
|
|
73
|
+
<Highlighter lang="js" highlightStyle={oneDarkHighlightStyle}>
|
|
74
|
+
{code}
|
|
75
|
+
</Highlighter>
|
|
76
|
+
</code>
|
|
77
|
+
)
|
|
78
|
+
await sleep(100)
|
|
79
|
+
const element = screen.getByText('const')
|
|
80
|
+
expect(element).toBeDefined()
|
|
81
|
+
expect(element).toHaveProperty('className', 'ͼp')
|
|
82
|
+
expect(screen.getByText('123')).toHaveProperty('className', 'ͼu')
|
|
83
|
+
})
|
|
84
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["src", "test", "vite.config.ts"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "esnext",
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"lib": ["es2022", "dom"],
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"sourceMap": false,
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"noImplicitAny": false,
|
|
15
|
+
"outDir": "dist",
|
|
16
|
+
"declaration": true,
|
|
17
|
+
"declarationDir": "dist"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="vitest" />
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
|
+
import react from '@vitejs/plugin-react'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
resolve: {
|
|
8
|
+
conditions: ['node']
|
|
9
|
+
},
|
|
10
|
+
test: {
|
|
11
|
+
globals: true,
|
|
12
|
+
environment: 'jsdom'
|
|
13
|
+
}
|
|
14
|
+
})
|
package/.babelrc
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"presets": [
|
|
3
|
-
["@babel/preset-env", {
|
|
4
|
-
"targets": { "electron": "3.0.14" },
|
|
5
|
-
"useBuiltIns": "usage",
|
|
6
|
-
"debug": false,
|
|
7
|
-
"corejs": 3
|
|
8
|
-
}],
|
|
9
|
-
"@babel/preset-react"
|
|
10
|
-
],
|
|
11
|
-
"plugins": [
|
|
12
|
-
"@babel/plugin-proposal-object-rest-spread",
|
|
13
|
-
"@babel/plugin-proposal-class-properties",
|
|
14
|
-
"add-module-exports"
|
|
15
|
-
]
|
|
16
|
-
}
|
package/demo/index.html
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<script src="../node_modules/codemirror/lib/codemirror.js" language="javascript"></script>
|
|
5
|
-
<script src="../node_modules/codemirror/addon/mode/overlay.js" language="javascript"></script>
|
|
6
|
-
<script src="../node_modules/codemirror/addon/runmode/runmode.js" language="javascript"></script>
|
|
7
|
-
<script src="../node_modules/codemirror/mode/meta.js" language="javascript"></script>
|
|
8
|
-
<script src="../node_modules/codemirror/mode/javascript/javascript.js" language="javascript"></script>
|
|
9
|
-
<script src="../node_modules/codemirror/mode/markdown/markdown.js" language="javascript"></script>
|
|
10
|
-
<script src="../node_modules/codemirror/mode/gfm/gfm.js" language="javascript"></script>
|
|
11
|
-
<script src="../node_modules/codemirror/mode/clike/clike.js" language="javascript"></script>
|
|
12
|
-
<script src="../node_modules/react/umd/react.development.js" language="javascript"></script>
|
|
13
|
-
<script src="../node_modules/react-dom/umd/react-dom.development.js" language="javascript"></script>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<h1>react-codemirror-runmode demo</h1>
|
|
17
|
-
<div id="root"></div>
|
|
18
|
-
<link rel="stylesheet" type="text/css" href="../node_modules/codemirror/lib/codemirror.css" />
|
|
19
|
-
<link rel="stylesheet" type="text/css" href="../node_modules/codemirror/theme/solarized.css" />
|
|
20
|
-
<script src="./js/demo.min.js" language="javascript"></script>
|
|
21
|
-
<style>
|
|
22
|
-
pre {
|
|
23
|
-
margin: 0;
|
|
24
|
-
padding: 1em;
|
|
25
|
-
}
|
|
26
|
-
</style>
|
|
27
|
-
</body>
|
|
28
|
-
</html>
|
package/demo/js/demo.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import 'core-js/stable'
|
|
2
|
-
import MirrorLight from '../../src/index'
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import ReactDOM from 'react-dom'
|
|
5
|
-
|
|
6
|
-
const exampleCode = `
|
|
7
|
-
* Incremental highlighted search (/, ?, #, *, g#, g*)
|
|
8
|
-
* Search/replace with confirm (:substitute, :%s)
|
|
9
|
-
* Search history
|
|
10
|
-
|
|
11
|
-
\`\`\`javascript
|
|
12
|
-
import React from 'react'
|
|
13
|
-
import PropTypes from 'prop-types'
|
|
14
|
-
|
|
15
|
-
export default class MirrorLight extends React.Component {
|
|
16
|
-
static propTypes = {
|
|
17
|
-
codeMirror: PropTypes.object.isRequired,
|
|
18
|
-
className: PropTypes.string,
|
|
19
|
-
inline: PropTypes.bool,
|
|
20
|
-
language: PropTypes.string,
|
|
21
|
-
prefix: PropTypes.string,
|
|
22
|
-
subset: PropTypes.arrayOf(PropTypes.string),
|
|
23
|
-
value: PropTypes.string.isRequired
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
render () {
|
|
27
|
-
const { inline, codeMirror, value, language, className } = this.props
|
|
28
|
-
const elements = []
|
|
29
|
-
codeMirror.runMode(value, language, (token, style) => {
|
|
30
|
-
elements.push(<span className={style}>{token}</span>)
|
|
31
|
-
})
|
|
32
|
-
const code = (
|
|
33
|
-
<code className={inline ? 'inline' : ''}>
|
|
34
|
-
{elements}
|
|
35
|
-
</code>
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
return inline ? code : <pre className={className}>{code}</pre>
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
\`\`\`
|
|
42
|
-
`
|
|
43
|
-
|
|
44
|
-
ReactDOM.render(
|
|
45
|
-
<MirrorLight
|
|
46
|
-
codeMirror={CodeMirror}
|
|
47
|
-
theme="solarized"
|
|
48
|
-
value={exampleCode}
|
|
49
|
-
language="gfm"
|
|
50
|
-
/>,
|
|
51
|
-
document.getElementById('root')
|
|
52
|
-
)
|