Skip to content

Commit b9b058c

Browse files
LeaVerouDmitrySharabin
authored andcommitted
Simplify hooks
1 parent 07ae505 commit b9b058c

File tree

15 files changed

+134
-255
lines changed

15 files changed

+134
-255
lines changed

src/core/classes/hooks.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* A class for managing hooks for deep extensibility.
3+
* Inspired https://www.npmjs.com/package/blissful-hooks
4+
*/
5+
export class Hooks {
6+
private _all: Record<keyof HookEnv, HookCallback[]> = {};
7+
8+
/**
9+
* Adds the given callback to the list of callbacks for the given hook and returns a function that
10+
* removes the hook again when called.
11+
*
12+
* The callback will be invoked when the hook it is registered for is run.
13+
* Hooks are usually directly run by a highlight function but you can also run hooks yourself.
14+
*
15+
* One callback function can be registered to multiple hooks.
16+
*
17+
* A callback function must not be registered for the same hook multiple times. Doing so will cause
18+
* undefined behavior. However, registering a callback again after removing it is fine.
19+
*
20+
* @param name The name of the hook.
21+
* @param callback The callback function which is given environment variables.
22+
*/
23+
add<Name extends keyof HookEnv> (
24+
name: Name | Name[] | MultipleHooks<Name>,
25+
callback?: Name extends MultipleHooks<Name> ? never : HookCallback<Name>
26+
): () => void {
27+
if (Array.isArray(name)) {
28+
// One function, multiple hooks
29+
for (const n of name) {
30+
this.add(n, callback);
31+
}
32+
}
33+
else if (typeof name === 'object') {
34+
// Multiple hooks
35+
const hooks = name;
36+
37+
for (const name in hooks) {
38+
const callback = hooks[name];
39+
if (callback) {
40+
this.add(name as string, callback as HookCallback);
41+
}
42+
}
43+
}
44+
else {
45+
const hooks = (this._all[name] ??= []);
46+
hooks.push(callback as never);
47+
}
48+
49+
return () => {
50+
this.remove(name, callback);
51+
};
52+
}
53+
54+
remove<Name extends keyof HookEnv> (
55+
name: Name | Name[] | MultipleHooks<Name>,
56+
callback?: Name extends MultipleHooks<Name> ? never : HookCallback<Name>
57+
): void {
58+
if (Array.isArray(name)) {
59+
// Multiple hook names, same callback
60+
for (const n of name) {
61+
this.remove(n, callback);
62+
}
63+
}
64+
else if (typeof name === 'object') {
65+
// Map of hook names to callbacks
66+
for (const n in name) {
67+
this.remove(n, callback);
68+
}
69+
}
70+
else {
71+
const index = this._all[name]?.indexOf(callback as never);
72+
if (index > -1) {
73+
this._all[name].splice(index, 1);
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Runs a hook invoking all registered callbacks with the given environment variables.
80+
*
81+
* Callbacks will be invoked synchronously and in the order in which they were registered.
82+
*
83+
* @param name The name of the hook.
84+
* @param env The environment variables of the hook passed to all callbacks registered.
85+
*/
86+
run<Name extends keyof HookEnv> (name: Name, env: HookEnv[Name]): void {
87+
const callbacks = this._all[name];
88+
const context = env?.this ?? env?.context ?? env;
89+
90+
if (!callbacks || !callbacks.length) {
91+
return;
92+
}
93+
94+
for (const callback of callbacks) {
95+
callback.call(context, env);
96+
}
97+
}
98+
}
99+
100+
export interface BaseHookEnv {
101+
context?: object;
102+
}
103+
export interface HookEnv extends BaseHookEnv, Record<string, any> {}
104+
105+
export type HookCallback<T extends keyof HookEnv = string> = (env: HookEnv[T]) => void;
106+
107+
export type MultipleHooks<T extends keyof HookEnv> = { [K in T]?: HookCallback<K> };

src/core/hook-state.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/core/hooks.ts

Lines changed: 0 additions & 161 deletions
This file was deleted.

src/core/prism.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { getLanguage, setLanguage } from '../shared/dom-util';
22
import { rest, tokenize } from '../shared/symbols';
33
import { htmlEncode } from '../shared/util';
4-
import { HookState } from './hook-state';
5-
import { Hooks } from './hooks';
4+
import { Hooks } from './classes/hooks';
65
import { LinkedList } from './linked-list';
76
import { Registry } from './registry';
87
import { Token } from './classes/token';
98
import type { KnownPlugins } from '../known-plugins';
109
import type { Grammar, GrammarToken, GrammarTokens, RegExpLike } from '../types';
11-
import type { HookEnvMap } from './hooks';
10+
import type { HookEnv } from './classes/hooks';
1211
import type { LinkedListHeadNode, LinkedListMiddleNode, LinkedListTailNode } from './linked-list';
1312
import type { TokenStream } from './classes/token';
1413

@@ -36,18 +35,16 @@ export class Prism {
3635
highlightAll (options: HighlightAllOptions = {}) {
3736
const { root, async, callback } = options;
3837

39-
const env: HookEnvMap['before-highlightall'] | HookEnvMap['before-all-elements-highlight'] =
38+
const env: HookEnv =
4039
{
4140
callback,
4241
root: root ?? document,
4342
selector:
4443
'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
45-
state: new HookState(),
4644
};
4745

4846
this.hooks.run('before-highlightall', env);
4947

50-
assertEnv<'before-all-elements-highlight'>(env);
5148
env.elements = [...env.root.querySelectorAll(env.selector)];
5249

5350
this.hooks.run('before-all-elements-highlight', env);
@@ -93,16 +90,14 @@ export class Prism {
9390

9491
const code = element.textContent as string;
9592

96-
const env: HookEnvMap['before-sanity-check'] = {
93+
const env: HookEnv = {
9794
element,
9895
language,
9996
grammar,
10097
code,
101-
state: new HookState(),
10298
};
10399

104100
const insertHighlightedCode = (highlightedCode: string) => {
105-
assertEnv<'before-insert'>(env);
106101
env.highlightedCode = highlightedCode;
107102
this.hooks.run('before-insert', env);
108103

@@ -168,7 +163,7 @@ export class Prism {
168163
const languageId = this.components.resolveAlias(language);
169164
const grammar = options?.grammar ?? this.components.getLanguage(languageId);
170165

171-
const env: HookEnvMap['before-tokenize'] | HookEnvMap['after-tokenize'] = {
166+
const env: HookEnv = {
172167
code: text,
173168
grammar,
174169
language,
@@ -178,7 +173,6 @@ export class Prism {
178173
throw new Error('The language "' + env.language + '" has no grammar.');
179174
}
180175

181-
assertEnv<'after-tokenize'>(env);
182176
env.tokens = this.tokenize(env.code, env.grammar);
183177
this.hooks.run('after-tokenize', env);
184178

@@ -437,10 +431,6 @@ export interface HighlightOptions {
437431
grammar?: Grammar;
438432
}
439433

440-
function assertEnv<T extends keyof HookEnvMap> (env: unknown): asserts env is HookEnvMap[T] {
441-
/* noop */
442-
}
443-
444434
function matchPattern (pattern: RegExp, pos: number, text: string, lookbehind: boolean) {
445435
pattern.lastIndex = pos;
446436
const match = pattern.exec(text);
@@ -475,7 +465,7 @@ function stringify (o: string | Token | TokenStream, language: string, hooks: Ho
475465
return s;
476466
}
477467

478-
const env: HookEnvMap['wrap'] = {
468+
const env: HookEnv = {
479469
type: o.type,
480470
content: stringify(o.content, language, hooks),
481471
tag: 'span',

0 commit comments

Comments
 (0)