react-codemirror-runmode 2.2.1 → 2.4.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 ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "arrowParens": "avoid",
4
+ "semi": false,
5
+ "singleQuote": true,
6
+ "trailingComma": "none",
7
+ "sortImports": true,
8
+ "sortPackageJson": true
9
+ }
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,62 @@ 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, highlightStyle, fallbackLanguage?, languages?, callback): Promise<Output[]>`
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, defaultLanguage?): Promise<Parser | null>`
58
+ ### `getCodeParser(input, languageName, fallbackLanguage?, languages?, markdownConfig?): Promise<Parser | null>`
57
59
 
58
60
  Parameters:
59
61
 
60
- - `languageName: string` - The name of the language
61
- - `defaultLanguage?: Language` - A fallback language (Optional)
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: [/* your @lezer/markdown extensions */]
87
+ }}
88
+ >
89
+ {markdownSource}
90
+ </Highlighter>
91
+ ```
92
+
93
+ `MarkdownConfig` mirrors the `base` and `extensions` options of `markdown()`. Prefer a stable
94
+ reference for `markdownConfig` (e.g. a module-level constant) to avoid re-highlighting on every
95
+ render.
62
96
 
63
97
  ## License
64
98
 
@@ -1,6 +1,16 @@
1
- import { Parser } from '@lezer/common';
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
- export declare function getMarkdownParser(input: string, languages?: LanguageDescription[]): Promise<Parser>;
5
- export declare function getCodeParser(input: string, languageName: string, fallbackLanguage?: Language, languages?: LanguageDescription[]): Promise<Parser | null>;
6
- 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[]>;
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,7 +1,7 @@
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 { markdown } from '@codemirror/lang-markdown';
4
+ import { highlightTree } from '@lezer/highlight';
5
5
  /**
6
6
  * Extract language names from code blocks in markdown text
7
7
  */
@@ -29,17 +29,18 @@ async function preloadLanguageParsers(languageNames, languages) {
29
29
  }
30
30
  }))).filter((desc) => !!desc);
31
31
  }
32
- export async function getMarkdownParser(input, languages = builtinLanguages) {
32
+ export async function getMarkdownParser(input, languages = builtinLanguages, markdownConfig = {}) {
33
33
  const codeBlockLanguages = extractCodeBlockLanguages(input);
34
34
  const preloadedLanguages = await preloadLanguageParsers(codeBlockLanguages || [], languages);
35
35
  const langSupport = markdown({
36
+ ...markdownConfig,
36
37
  codeLanguages: preloadedLanguages
37
38
  });
38
39
  return langSupport.language.parser;
39
40
  }
40
- export async function getCodeParser(input, languageName, fallbackLanguage, languages = builtinLanguages) {
41
+ export async function getCodeParser(input, languageName, fallbackLanguage, languages = builtinLanguages, markdownConfig) {
41
42
  if (languageName === 'markdown' || languageName === 'md') {
42
- return await getMarkdownParser(input, languages);
43
+ return await getMarkdownParser(input, languages, markdownConfig);
43
44
  }
44
45
  else {
45
46
  const found = LanguageDescription.matchLanguageName(languages, languageName, true);
@@ -53,8 +54,8 @@ export async function getCodeParser(input, languageName, fallbackLanguage, langu
53
54
  }
54
55
  return fallbackLanguage ? fallbackLanguage.parser : null;
55
56
  }
56
- export async function highlightCode(languageName, input, highlighter, fallbackLanguage, languages, callback) {
57
- const parser = await getCodeParser(input, 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);
58
59
  if (parser) {
59
60
  const tree = parser.parse(input);
60
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
- theme: LezerHighlighter;
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>;
@@ -1,14 +1,15 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { memo, useEffect, useState } from 'react';
3
3
  import { highlightCode } from './highlight.js';
4
+ const WRAPPER_STYLE = { display: 'contents' };
4
5
  export const Highlighter = memo((props) => {
5
- const { lang, children: code, theme, fallbackLanguage, languages } = props;
6
+ const { lang, children: code, theme, fallbackLanguage, languages, markdownConfig } = props;
6
7
  const [highlightedCode, setHighlightedCode] = useState(null);
7
8
  useEffect(() => {
8
9
  highlightCode(lang, code, theme, fallbackLanguage, languages, (text, style, from) => {
9
10
  return (_jsx("span", { className: style || '', children: text }, from));
10
- }).then(setHighlightedCode);
11
- }, [lang, code, theme]);
12
- return _jsx(_Fragment, { children: highlightedCode || code });
11
+ }, markdownConfig).then(setHighlightedCode);
12
+ }, [lang, code, theme, markdownConfig]);
13
+ return highlightedCode ? (_jsx("span", { style: WRAPPER_STYLE, children: highlightedCode }, "highlighted")) : (_jsx("span", { style: WRAPPER_STYLE, children: code }, "pending"));
13
14
  });
14
15
  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,2 @@
1
+ export * from './highlight.js';
2
+ export * from './react-highlighter.js';
@@ -0,0 +1,2 @@
1
+ export * from './highlight.js';
2
+ export * from './react-highlighter.js';
@@ -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,62 +1,60 @@
1
1
  {
2
2
  "name": "react-codemirror-runmode",
3
- "version": "2.2.1",
3
+ "version": "2.4.0",
4
4
  "description": "Syntax highlighting for react, utilizing CodeMirror's parser",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "exports": {
8
- ".": {
9
- "import": "./dist/index.js",
10
- "types": "./dist/index.d.ts"
11
- }
12
- },
13
- "scripts": {
14
- "build": "tsc --project tsconfig.build.json",
15
- "test": "vitest",
16
- "lint": "eslint .",
17
- "typecheck": "tsc --noEmit",
18
- "format": "prettier --write .",
19
- "format:check": "prettier --check .",
20
- "prepublishOnly": "npm-run-all lint format:check build && npm run test run"
21
- },
22
5
  "keywords": [
23
- "react",
24
6
  "codemirror",
25
- "syntax",
26
- "highlight"
7
+ "highlight",
8
+ "react",
9
+ "syntax"
27
10
  ],
28
- "author": "Takuya Matsuyama <hi@craftz.dog>",
29
11
  "license": "MIT",
12
+ "author": "Takuya Matsuyama <hi@craftz.dog>",
30
13
  "repository": {
31
14
  "type": "git",
32
15
  "url": "https://github.com/craftzdog/react-codemirror-runmode.git"
33
16
  },
17
+ "type": "module",
18
+ "main": "dist/index.js",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "types": "./dist/index.d.ts"
23
+ }
24
+ },
34
25
  "dependencies": {
35
- "@codemirror/language": "^6.11.3",
26
+ "@codemirror/lang-markdown": "^6.5.1",
27
+ "@codemirror/language": "^6.12.4",
36
28
  "@codemirror/language-data": "^6.5.2",
37
- "@lezer/common": "^1.4.0",
29
+ "@lezer/common": "^1.5.2",
38
30
  "@lezer/highlight": "^1.2.3"
39
31
  },
40
- "peerDependencies": {
41
- "react": ">= 18"
42
- },
43
32
  "devDependencies": {
44
33
  "@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",
34
+ "@testing-library/react": "^16.3.2",
35
+ "@types/node": "^26.1.1",
36
+ "@types/react": "^19.2.17",
37
+ "@vitejs/plugin-react": "^6.0.4",
38
+ "jsdom": "^29.1.1",
53
39
  "npm-run-all": "^4.1.5",
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"
40
+ "oxfmt": "0.60.0",
41
+ "oxlint": "1.75.0",
42
+ "react": "^19.2.8",
43
+ "react-dom": "^19.2.8",
44
+ "typescript": "^7.0.2",
45
+ "vite": "^8.1.5",
46
+ "vitest": "^4.1.10"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">= 18"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc --project tsconfig.build.json",
53
+ "test": "vitest --run",
54
+ "bench": "vitest bench --run",
55
+ "lint": "oxlint",
56
+ "typecheck": "tsc --noEmit",
57
+ "format": "oxfmt",
58
+ "format:check": "oxfmt --check"
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,8 +1,21 @@
1
- import { Parser } from '@lezer/common'
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 { markdown } from '@codemirror/lang-markdown'
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
+ >
6
19
 
7
20
  /**
8
21
  * Extract language names from code blocks in markdown text
@@ -27,11 +40,7 @@ async function preloadLanguageParsers(
27
40
  return (
28
41
  await Promise.all(
29
42
  languageNames.map(async langName => {
30
- const found = LanguageDescription.matchLanguageName(
31
- languages,
32
- langName,
33
- true
34
- )
43
+ const found = LanguageDescription.matchLanguageName(languages, langName, true)
35
44
  if (found instanceof LanguageDescription) {
36
45
  if (!found.support) await found.load()
37
46
  if (found.support) {
@@ -45,14 +54,13 @@ async function preloadLanguageParsers(
45
54
 
46
55
  export async function getMarkdownParser(
47
56
  input: string,
48
- languages: LanguageDescription[] = builtinLanguages
57
+ languages: LanguageDescription[] = builtinLanguages,
58
+ markdownConfig: MarkdownConfig = {}
49
59
  ): Promise<Parser> {
50
60
  const codeBlockLanguages = extractCodeBlockLanguages(input)
51
- const preloadedLanguages = await preloadLanguageParsers(
52
- codeBlockLanguages || [],
53
- languages
54
- )
61
+ const preloadedLanguages = await preloadLanguageParsers(codeBlockLanguages || [], languages)
55
62
  const langSupport = markdown({
63
+ ...markdownConfig,
56
64
  codeLanguages: preloadedLanguages
57
65
  })
58
66
  return langSupport.language.parser
@@ -62,16 +70,13 @@ export async function getCodeParser(
62
70
  input: string,
63
71
  languageName: string,
64
72
  fallbackLanguage?: Language,
65
- languages: LanguageDescription[] = builtinLanguages
73
+ languages: LanguageDescription[] = builtinLanguages,
74
+ markdownConfig?: MarkdownConfig
66
75
  ): Promise<Parser | null> {
67
76
  if (languageName === 'markdown' || languageName === 'md') {
68
- return await getMarkdownParser(input, languages)
77
+ return await getMarkdownParser(input, languages, markdownConfig)
69
78
  } else {
70
- const found = LanguageDescription.matchLanguageName(
71
- languages,
72
- languageName,
73
- true
74
- )
79
+ const found = LanguageDescription.matchLanguageName(languages, languageName, true)
75
80
  if (found instanceof LanguageDescription) {
76
81
  if (!found.support) await found.load()
77
82
  return found.support ? found.support.language.parser : null
@@ -83,36 +88,30 @@ export async function getCodeParser(
83
88
  export async function highlightCode<Output>(
84
89
  languageName: string,
85
90
  input: string,
86
- highlighter: Highlighter,
91
+ highlighter: Highlighter | readonly Highlighter[],
87
92
  fallbackLanguage: Language | undefined,
88
93
  languages: LanguageDescription[] | undefined,
89
- callback: (
90
- text: string,
91
- style: string | null,
92
- from: number,
93
- to: number
94
- ) => Output
94
+ callback: (text: string, style: string | null, from: number, to: number) => Output,
95
+ markdownConfig?: MarkdownConfig
95
96
  ): Promise<Output[]> {
96
97
  const parser = await getCodeParser(
97
98
  input,
98
99
  languageName,
99
100
  fallbackLanguage,
100
- languages
101
+ languages,
102
+ markdownConfig
101
103
  )
102
104
  if (parser) {
103
105
  const tree = parser.parse(input)
104
106
  const output: Array<Output> = []
105
107
  let pos = 0
106
108
  highlightTree(tree, highlighter, (from, to, classes) => {
107
- if (from > pos)
108
- output.push(callback(input.slice(pos, from), null, pos, from))
109
+ if (from > pos) output.push(callback(input.slice(pos, from), null, pos, from))
109
110
  output.push(callback(input.slice(from, to), classes, from, to))
110
111
  pos = to
111
112
  })
112
113
  pos != tree.length &&
113
- output.push(
114
- callback(input.slice(pos, tree.length), null, pos, tree.length)
115
- )
114
+ output.push(callback(input.slice(pos, tree.length), null, pos, tree.length))
116
115
  return output
117
116
  } else {
118
117
  return [callback(input, null, 0, input.length)]