` element: 84 | 85 | - `--prosemirror-highlight`: The text color of the code block 86 | - `--prosemirror-highlight-bg`: The background color of the code block 87 | 88 | You can use these variables to set the background color and text color of the code block. 89 | 90 | ```css 91 | .ProseMirror pre { 92 | color: var(--prosemirror-highlight, inherit); 93 | background-color: var(--prosemirror-highlight-bg, inherit); 94 | } 95 | ``` 96 | 97 |
80 | Highlight your ProseMirror code 81 | blocks with any syntax highlighter you like! 82 |
83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /playground/lowlight.ts: -------------------------------------------------------------------------------- 1 | import 'highlight.js/styles/default.css' 2 | 3 | import { common, createLowlight } from 'lowlight' 4 | 5 | import { createHighlightPlugin } from 'prosemirror-highlight' 6 | import { createParser } from 'prosemirror-highlight/lowlight' 7 | 8 | const lowlight = createLowlight(common) 9 | const parser = createParser(lowlight) 10 | export const lowlightPlugin = createHighlightPlugin({ parser }) 11 | -------------------------------------------------------------------------------- /playground/main.ts: -------------------------------------------------------------------------------- 1 | import lowlightCode from './lowlight?raw' 2 | import refractorCode from './refractor?raw' 3 | import { setupView } from './setup' 4 | import shikiLazyCode from './shiki-lazy?raw' 5 | import shikiCode from './shiki?raw' 6 | import sugarHighCode from './sugar-high?raw' 7 | 8 | function getOrCreateElement(id: string): HTMLElement { 9 | const container = document.getElementById('container') 10 | if (!container) { 11 | throw new Error('Container not found') 12 | } 13 | 14 | let element = document.getElementById(id) 15 | if (!element) { 16 | element = document.createElement('div') 17 | element.id = id 18 | element.classList.add('editor') 19 | element.setAttribute('spellcheck', 'false') 20 | container.appendChild(element) 21 | } 22 | return element 23 | } 24 | 25 | function main() { 26 | void setupView({ 27 | mount: getOrCreateElement('editor-shiki'), 28 | plugin: () => import('./shiki').then((mod) => mod.shikiPlugin), 29 | title: 'Shiki', 30 | code: shikiCode, 31 | }) 32 | 33 | void setupView({ 34 | mount: getOrCreateElement('editor-lowlight'), 35 | plugin: () => import('./lowlight').then((mod) => mod.lowlightPlugin), 36 | title: 'Lowlight', 37 | code: lowlightCode, 38 | }) 39 | 40 | void setupView({ 41 | mount: getOrCreateElement('editor-refractor'), 42 | plugin: () => import('./refractor').then((mod) => mod.refractorPlugin), 43 | title: 'Refractor', 44 | code: refractorCode, 45 | }) 46 | 47 | void setupView({ 48 | mount: getOrCreateElement('editor-sugar-high'), 49 | plugin: () => import('./sugar-high').then((mod) => mod.sugarHighPlugin), 50 | title: 'Sugar High', 51 | code: sugarHighCode, 52 | }) 53 | 54 | void setupView({ 55 | mount: getOrCreateElement('editor-shiki-lazy'), 56 | plugin: () => import('./shiki-lazy').then((mod) => mod.shikiLazyPlugin), 57 | title: 'Shiki (Lazy language loading)', 58 | code: shikiLazyCode, 59 | }) 60 | } 61 | 62 | main() 63 | -------------------------------------------------------------------------------- /playground/refractor.ts: -------------------------------------------------------------------------------- 1 | import { refractor } from 'refractor/all' 2 | 3 | import { createHighlightPlugin } from 'prosemirror-highlight' 4 | import { createParser } from 'prosemirror-highlight/refractor' 5 | 6 | const parser = createParser(refractor) 7 | export const refractorPlugin = createHighlightPlugin({ parser }) 8 | -------------------------------------------------------------------------------- /playground/schema.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'prosemirror-model' 2 | import { schema as basicSchema } from 'prosemirror-schema-basic' 3 | 4 | export const schema = new Schema({ 5 | nodes: basicSchema.spec.nodes.update('code_block', { 6 | content: 'text*', 7 | group: 'block', 8 | code: true, 9 | defining: true, 10 | marks: '', 11 | attrs: { 12 | language: { default: '' }, 13 | }, 14 | parseDOM: [ 15 | { 16 | tag: 'pre', 17 | preserveWhitespace: 'full', 18 | getAttrs: (node) => ({ 19 | language: (node as Element)?.getAttribute('data-language') || '', 20 | }), 21 | }, 22 | ], 23 | toDOM(node) { 24 | return [ 25 | 'pre', 26 | { 'data-language': node.attrs.language as string }, 27 | ['code', 0], 28 | ] 29 | }, 30 | }), 31 | marks: basicSchema.spec.marks, 32 | }) 33 | -------------------------------------------------------------------------------- /playground/setup.ts: -------------------------------------------------------------------------------- 1 | import { exampleSetup } from 'prosemirror-example-setup' 2 | import { DOMParser } from 'prosemirror-model' 3 | import { EditorState, type Plugin } from 'prosemirror-state' 4 | import { EditorView } from 'prosemirror-view' 5 | 6 | import { schema } from './schema' 7 | 8 | export async function setupView({ 9 | mount, 10 | plugin, 11 | title, 12 | code, 13 | }: { 14 | mount: HTMLElement 15 | plugin: () => Promise${code.trim()}
`
21 |
22 | return new EditorView(mount, {
23 | state: EditorState.create({
24 | doc: DOMParser.fromSchema(schema).parse(div),
25 | plugins: [...exampleSetup({ schema, menuBar: false }), await plugin()],
26 | }),
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/playground/shiki-lazy.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createHighlighter,
3 | type BuiltinLanguage,
4 | type Highlighter,
5 | } from 'shiki'
6 |
7 | import { createHighlightPlugin } from 'prosemirror-highlight'
8 | import { createParser, type Parser } from 'prosemirror-highlight/shiki'
9 |
10 | let highlighter: Highlighter | undefined
11 | let parser: Parser | undefined
12 |
13 | /**
14 | * Lazy load highlighter and highlighter languages.
15 | *
16 | * When the highlighter or the required language is not loaded, it returns a
17 | * promise that resolves when the highlighter or the language is loaded.
18 | * Otherwise, it returns an array of decorations.
19 | */
20 | const lazyParser: Parser = (options) => {
21 | if (!highlighter) {
22 | return createHighlighter({
23 | themes: ['github-light', 'github-dark', 'github-dark-dimmed'],
24 | langs: [],
25 | }).then((h) => {
26 | highlighter = h
27 | })
28 | }
29 |
30 | const language = options.language as BuiltinLanguage
31 | if (language && !highlighter.getLoadedLanguages().includes(language)) {
32 | return highlighter.loadLanguage(language)
33 | }
34 |
35 | if (!parser) {
36 | parser = createParser(highlighter, {
37 | themes: {
38 | light: 'github-light',
39 | dark: 'github-dark',
40 | dim: 'github-dark-dimmed',
41 | },
42 | defaultColor: 'dim',
43 | })
44 | }
45 |
46 | return parser(options)
47 | }
48 |
49 | export const shikiLazyPlugin = createHighlightPlugin({ parser: lazyParser })
50 |
--------------------------------------------------------------------------------
/playground/shiki.ts:
--------------------------------------------------------------------------------
1 | import { getSingletonHighlighter } from 'shiki'
2 |
3 | import { createHighlightPlugin } from 'prosemirror-highlight'
4 | import { createParser } from 'prosemirror-highlight/shiki'
5 |
6 | const highlighter = await getSingletonHighlighter({
7 | themes: ['github-light'],
8 | langs: ['javascript', 'typescript', 'python'],
9 | })
10 | const parser = createParser(highlighter)
11 | export const shikiPlugin = createHighlightPlugin({ parser })
12 |
--------------------------------------------------------------------------------
/playground/sugar-high.ts:
--------------------------------------------------------------------------------
1 | import { createHighlightPlugin } from 'prosemirror-highlight'
2 | import { createParser } from 'prosemirror-highlight/sugar-high'
3 |
4 | const parser = createParser()
5 | export const sugarHighPlugin = createHighlightPlugin({ parser })
6 |
--------------------------------------------------------------------------------
/src/cache.ts:
--------------------------------------------------------------------------------
1 | import type { Node as ProseMirrorNode } from 'prosemirror-model'
2 | import type { Transaction } from 'prosemirror-state'
3 | import type { Decoration } from 'prosemirror-view'
4 |
5 | /**
6 | * Represents a cache of doc positions to the node and decorations at that position
7 | */
8 | export class DecorationCache {
9 | private cache: Map
33 |
34 | console.
35 | log(
36 | 123+
37 | "456");
38 |
39 |
40 |
41 |
42 | print(
43 | "1+1",
44 | "=",2)
45 |
46 |
47 |
66 |
67 | console
68 | .
69 | log
70 | (
71 | 123
72 | +
73 | "456"
74 | )
75 | ;
76 |
77 |
78 |
79 |
80 | print
81 | (
82 | "1+1"
83 | ,
84 | "="
85 | ,
86 | 2
87 | )
88 |
89 |
90 |
109 |
110 |
111 | console
112 |
113 |
114 | .
115 |
116 |
117 | log
118 |
119 |
120 | (
121 |
122 |
123 | 123
124 |
125 |
126 | +
127 |
128 |
129 | "
130 |
131 |
132 | 456
133 |
134 |
135 | "
136 |
137 |
138 | )
139 |
140 |
141 | ;
142 |
143 |
144 |
145 |
146 |
147 |
148 | print
149 |
150 |
151 | (
152 |
153 |
154 | "
155 |
156 |
157 | 1+1
158 |
159 |
160 | "
161 |
162 |
163 | ,
164 |
165 |
166 | "
167 |
168 |
169 | =
170 |
171 |
172 | "
173 |
174 |
175 | ,
176 |
177 |
178 | 2
179 |
180 |
181 | )
182 |
183 |
184 |
185 |
216 |
217 |
218 | console.
219 |
220 |
221 | log
222 |
223 |
224 | (
225 |
226 |
227 | 123
228 |
229 |
230 | +
231 |
232 |
233 | "456"
234 |
235 |
236 | );
237 |
238 |
239 |
240 |
244 |
245 |
246 | print
247 |
248 |
249 | (
250 |
251 |
252 | "1+1"
253 |
254 |
255 | ,
256 |
257 |
258 | "="
259 |
260 |
261 | ,
262 |
263 |
264 | 2
265 |
266 |
267 | )
268 |
269 |
270 |
271 |
306 |
307 |
311 | console.
312 |
313 |
317 | log
318 |
319 |
323 | (
324 |
325 |
329 | 123
330 |
331 |
335 | +
336 |
337 |
341 | "456"
342 |
343 |
347 | );
348 |
349 |
350 |
351 |
355 |
356 |
360 | print
361 |
362 |
366 | (
367 |
368 |
372 | "1+1"
373 |
374 |
378 | ,
379 |
380 |
384 | "="
385 |
386 |
390 | ,
391 |
392 |
396 | 2
397 |
398 |
402 | )
403 |
404 |
405 |
406 |