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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "enableAllProjectMcpServers": false
3
+ }
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<Output>(languageName, input, highlightStyle, callback): Promise<Output[]>`
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>`
@@ -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, defaultLanguage?: Language): Promise<Parser | null>;
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
- export async function getCodeParser(languageName, defaultLanguage) {
5
- if (languageName && languages) {
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 defaultLanguage ? defaultLanguage.parser : null;
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.2",
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.0",
35
- "@codemirror/language-data": "^6.5.1",
36
- "@lezer/common": "^1.2.3",
37
- "@lezer/highlight": "^1.2.1"
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.2",
44
- "@testing-library/react": "^16.2.0",
45
- "@types/node": "^22.13.10",
46
- "@types/react": "^19.0.12",
47
- "@vitejs/plugin-react": "^4.3.4",
48
- "eslint": "^9.22.0",
49
- "eslint-config-prettier": "^10.1.1",
50
- "eslint-plugin-react": "^7.37.4",
51
- "jsdom": "^26.0.0",
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.5.3",
54
- "react": "^19.0.0",
55
- "typescript": "^5.8.2",
56
- "typescript-eslint": "^8.27.0",
57
- "vite": "^6.2.2",
58
- "vitest": "^3.0.9"
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
- defaultLanguage?: Language
9
+ fallbackLanguage?: Language,
10
+ languages: LanguageDescription[] = builtinLanguages
9
11
  ): Promise<Parser | null> {
10
- if (languageName && languages) {
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 defaultLanguage ? defaultLanguage.parser : null
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 || ''}>
@@ -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": ["es2024", "dom"],
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": "node",
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
@@ -1,4 +1,4 @@
1
- /// <reference types="vitest" />
1
+ /// <reference types="vitest/config" />
2
2
  import { defineConfig } from 'vite'
3
3
  import react from '@vitejs/plugin-react'
4
4