Skip to content

Commit d5d7a5a

Browse files
LeaVerouDmitrySharabin
authored andcommitted
Simplify the Prism class
Move to separate modules: - `highlightAll()` - `highlightElement()` - `highlight()` - `stringify()`
1 parent 5a71111 commit d5d7a5a

File tree

5 files changed

+304
-245
lines changed

5 files changed

+304
-245
lines changed

src/core/classes/prism.ts

Lines changed: 13 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import { getLanguage, setLanguage } from '../../shared/dom-util';
21
import { rest, tokenize } from '../../shared/symbols';
3-
import { htmlEncode } from '../../shared/util';
2+
import { highlight } from '../highlight';
3+
import { highlightAll } from '../highlight-all';
4+
import { highlightElement } from '../highlight-element';
45
import { LinkedList } from '../linked-list';
56
import { Registry } from '../registry';
67
import { Hooks } from './hooks';
78
import { Token } from './token';
89
import type { KnownPlugins } from '../../known-plugins';
910
import type { Grammar, GrammarToken, GrammarTokens, RegExpLike } from '../../types';
11+
import type { HighlightOptions } from '../highlight';
12+
import type { HighlightAllOptions } from '../highlight-all';
13+
import type { HighlightElementOptions } from '../highlight-element';
1014
import type { LinkedListHeadNode, LinkedListMiddleNode, LinkedListTailNode } from '../linked-list';
11-
import type { HookEnv } from './hooks';
1215
import type { TokenStream } from './token';
1316

1417
/**
@@ -21,159 +24,24 @@ export default class Prism {
2124
plugins: Partial<Record<string, unknown> & KnownPlugins> = {};
2225

2326
/**
24-
* This is the most high-level function in Prism’s API.
25-
* It queries all the elements that have a `.language-xxxx` class and then calls {@link Prism#highlightElement} on
26-
* each one of them.
27-
*
28-
* The following hooks will be run:
29-
* 1. `before-highlightall`
30-
* 2. `before-all-elements-highlight`
31-
* 3. All hooks of {@link Prism#highlightElement} for each element.
27+
* See {@link highlightAll}.
3228
*/
3329
highlightAll (options: HighlightAllOptions = {}) {
34-
const { root, async, callback } = options;
35-
36-
const env: HookEnv = {
37-
callback,
38-
root: root ?? document,
39-
selector:
40-
'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
41-
};
42-
43-
this.hooks.run('before-highlightall', env);
44-
45-
env.elements = [...env.root.querySelectorAll(env.selector)];
46-
47-
this.hooks.run('before-all-elements-highlight', env);
48-
49-
for (const element of env.elements) {
50-
this.highlightElement(element, { async, callback: env.callback });
51-
}
30+
return highlightAll.call(this, options);
5231
}
5332

5433
/**
55-
* Highlights the code inside a single element.
56-
*
57-
* The following hooks will be run:
58-
* 1. `before-sanity-check`
59-
* 2. `before-highlight`
60-
* 3. All hooks of {@link Prism#highlight}. These hooks will be run by an asynchronous worker if `async` is `true`.
61-
* 4. `before-insert`
62-
* 5. `after-highlight`
63-
* 6. `complete`
64-
*
65-
* Some the above hooks will be skipped if the element doesn't contain any text or there is no grammar loaded for
66-
* the element's language.
67-
*
68-
* @param element The element containing the code.
69-
* It must have a class of `language-xxxx` to be processed, where `xxxx` is a valid language identifier.
34+
* See {@link highlightElement}
7035
*/
7136
highlightElement (element: Element, options: HighlightElementOptions = {}) {
72-
const { async, callback } = options;
73-
74-
// Find language
75-
const language = getLanguage(element);
76-
const languageId = this.components.resolveAlias(language);
77-
const grammar = this.components.getLanguage(languageId);
78-
79-
// Set language on the element, if not present
80-
setLanguage(element, language);
81-
82-
// Set language on the parent, for styling
83-
let parent = element.parentElement;
84-
if (parent && parent.nodeName.toLowerCase() === 'pre') {
85-
setLanguage(parent, language);
86-
}
87-
88-
const code = element.textContent as string;
89-
90-
const env: HookEnv = {
91-
element,
92-
language,
93-
grammar,
94-
code,
95-
};
96-
97-
const insertHighlightedCode = (highlightedCode: string) => {
98-
env.highlightedCode = highlightedCode;
99-
this.hooks.run('before-insert', env);
100-
101-
env.element.innerHTML = env.highlightedCode;
102-
103-
this.hooks.run('after-highlight', env);
104-
this.hooks.run('complete', env);
105-
callback?.(env.element);
106-
};
107-
108-
this.hooks.run('before-sanity-check', env);
109-
110-
// plugins may change/add the parent/element
111-
parent = env.element.parentElement;
112-
if (parent && parent.nodeName.toLowerCase() === 'pre' && !parent.hasAttribute('tabindex')) {
113-
parent.setAttribute('tabindex', '0');
114-
}
115-
116-
if (!env.code) {
117-
this.hooks.run('complete', env);
118-
callback?.(env.element);
119-
return;
120-
}
121-
122-
this.hooks.run('before-highlight', env);
123-
124-
if (!env.grammar) {
125-
insertHighlightedCode(htmlEncode(env.code));
126-
return;
127-
}
128-
129-
if (async) {
130-
async({
131-
language: env.language,
132-
code: env.code,
133-
grammar: env.grammar,
134-
}).then(insertHighlightedCode, error => console.log(error));
135-
}
136-
else {
137-
insertHighlightedCode(this.highlight(env.code, env.language, { grammar: env.grammar }));
138-
}
37+
return highlightElement.call(this, element, options);
13938
}
14039

14140
/**
142-
* Low-level function, only use if you know what you’re doing. It accepts a string of text as input
143-
* and the language definitions to use, and returns a string with the HTML produced.
144-
*
145-
* The following hooks will be run:
146-
* 1. `before-tokenize`
147-
* 2. `after-tokenize`
148-
* 3. `wrap`: On each {@link Token}.
149-
*
150-
* @param text A string with the code to be highlighted.
151-
* @param language The name of the language definition passed to `grammar`.
152-
* @param options An object containing the tokens to use.
153-
*
154-
* Usually a language definition like `Prism.languages.markup`.
155-
* @returns The highlighted HTML.
156-
* @example
157-
* Prism.highlight('var foo = true;', 'javascript');
41+
* See {@link highlight}
15842
*/
159-
highlight (text: string, language: string, options?: HighlightOptions): string {
160-
const languageId = this.components.resolveAlias(language);
161-
const grammar = options?.grammar ?? this.components.getLanguage(languageId);
162-
163-
const env: HookEnv = {
164-
code: text,
165-
grammar,
166-
language,
167-
};
168-
this.hooks.run('before-tokenize', env);
169-
if (!env.grammar) {
170-
throw new Error('The language "' + env.language + '" has no grammar.');
171-
}
172-
173-
env.tokens = this.tokenize(env.code, env.grammar);
174-
this.hooks.run('after-tokenize', env);
175-
176-
return stringify(env.tokens, env.language, this.hooks);
43+
highlight (text: string, language: string, options: HighlightOptions = {}): string {
44+
return highlight.call(this, text, language, options);
17745
}
17846

17947
/**
@@ -392,42 +260,6 @@ interface RematchOptions {
392260
reach: number;
393261
}
394262

395-
export interface AsyncHighlightingData {
396-
language: string;
397-
code: string;
398-
grammar: Grammar;
399-
}
400-
export type AsyncHighlighter = (data: AsyncHighlightingData) => Promise<string>;
401-
402-
export interface HighlightAllOptions {
403-
/**
404-
* The root element, whose descendants that have a `.language-xxxx` class will be highlighted.
405-
*/
406-
root?: ParentNode;
407-
async?: AsyncHighlighter;
408-
/**
409-
* An optional callback to be invoked on each element after its highlighting is done.
410-
*
411-
* @see HighlightElementOptions#callback
412-
*/
413-
callback?: (element: Element) => void;
414-
}
415-
416-
export interface HighlightElementOptions {
417-
async?: AsyncHighlighter;
418-
/**
419-
* An optional callback to be invoked after the highlighting is done.
420-
* Mostly useful when `async` is `true`, since in that case, the highlighting is done asynchronously.
421-
*
422-
* @param element The element successfully highlighted.
423-
*/
424-
callback?: (element: Element) => void;
425-
}
426-
427-
export interface HighlightOptions {
428-
grammar?: Grammar;
429-
}
430-
431263
function matchPattern (pattern: RegExp, pos: number, text: string, lookbehind: boolean) {
432264
pattern.lastIndex = pos;
433265
const match = pattern.exec(text);
@@ -440,70 +272,6 @@ function matchPattern (pattern: RegExp, pos: number, text: string, lookbehind: b
440272
return match;
441273
}
442274

443-
/**
444-
* Converts the given token or token stream to an HTML representation.
445-
*
446-
* The following hooks will be run:
447-
* 1. `wrap`: On each {@link Token}.
448-
*
449-
* @param o The token or token stream to be converted.
450-
* @param language The name of current language.
451-
* @returns The HTML representation of the token or token stream.
452-
*/
453-
function stringify (o: string | Token | TokenStream, language: string, hooks: Hooks): string {
454-
if (typeof o === 'string') {
455-
return htmlEncode(o);
456-
}
457-
if (Array.isArray(o)) {
458-
let s = '';
459-
o.forEach(e => {
460-
s += stringify(e, language, hooks);
461-
});
462-
return s;
463-
}
464-
465-
const env: HookEnv = {
466-
type: o.type,
467-
content: stringify(o.content, language, hooks),
468-
tag: 'span',
469-
classes: ['token', o.type],
470-
attributes: {},
471-
language,
472-
};
473-
474-
const aliases = o.alias;
475-
if (aliases) {
476-
if (Array.isArray(aliases)) {
477-
env.classes.push(...aliases);
478-
}
479-
else {
480-
env.classes.push(aliases);
481-
}
482-
}
483-
484-
hooks.run('wrap', env);
485-
486-
let attributes = '';
487-
for (const name in env.attributes) {
488-
attributes +=
489-
' ' + name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
490-
}
491-
492-
return (
493-
'<' +
494-
env.tag +
495-
' class="' +
496-
env.classes.join(' ') +
497-
'"' +
498-
attributes +
499-
'>' +
500-
env.content +
501-
'</' +
502-
env.tag +
503-
'>'
504-
);
505-
}
506-
507275
function toGrammarToken (pattern: GrammarToken | RegExpLike): GrammarToken {
508276
if (!pattern.pattern) {
509277
return { pattern };

src/core/highlight-all.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import singleton from './prism';
2+
import type { HookEnv } from './classes/hooks';
3+
import type { AsyncHighlighter } from './highlight-element';
4+
import type { Prism } from './prism';
5+
6+
/**
7+
* This is the most high-level function in Prism’s API.
8+
* It queries all the elements that have a `.language-xxxx` class and then calls {@link Prism#highlightElement} on
9+
* each one of them.
10+
*
11+
* The following hooks will be run:
12+
* 1. `before-highlightall`
13+
* 2. `before-all-elements-highlight`
14+
* 3. All hooks of {@link Prism#highlightElement} for each element.
15+
*/
16+
export function highlightAll (this: Prism, options: HighlightAllOptions = {}) {
17+
const prism = this ?? singleton;
18+
const { root, async, callback } = options;
19+
20+
const env: HookEnv = {
21+
callback,
22+
root: root ?? document,
23+
selector:
24+
'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
25+
};
26+
27+
prism.hooks.run('before-highlightall', env);
28+
29+
env.elements = [...env.root.querySelectorAll(env.selector)];
30+
31+
prism.hooks.run('before-all-elements-highlight', env);
32+
33+
for (const element of env.elements) {
34+
prism.highlightElement(element, { async, callback: env.callback });
35+
}
36+
}
37+
38+
export interface HighlightAllOptions {
39+
/**
40+
* The root element, whose descendants that have a `.language-xxxx` class will be highlighted.
41+
*/
42+
root?: ParentNode;
43+
async?: AsyncHighlighter;
44+
/**
45+
* An optional callback to be invoked on each element after its highlighting is done.
46+
*
47+
* @see HighlightElementOptions#callback
48+
*/
49+
callback?: (element: Element) => void;
50+
}

0 commit comments

Comments
 (0)