UNPKG

4.9 kBJavaScriptView Raw
1(function () {
2
3 if (typeof Prism === 'undefined' || typeof document === 'undefined') {
4 return;
5 }
6
7 function mapClassName(name) {
8 var customClass = Prism.plugins.customClass;
9 if (customClass) {
10 return customClass.apply(name, 'none');
11 } else {
12 return name;
13 }
14 }
15
16 var PARTNER = {
17 '(': ')',
18 '[': ']',
19 '{': '}',
20 };
21
22 // The names for brace types.
23 // These names have two purposes: 1) they can be used for styling and 2) they are used to pair braces. Only braces
24 // of the same type are paired.
25 var NAMES = {
26 '(': 'brace-round',
27 '[': 'brace-square',
28 '{': 'brace-curly',
29 };
30
31 // A map for brace aliases.
32 // This is useful for when some braces have a prefix/suffix as part of the punctuation token.
33 var BRACE_ALIAS_MAP = {
34 '${': '{', // JS template punctuation (e.g. `foo ${bar + 1}`)
35 };
36
37 var LEVEL_WARP = 12;
38
39 var pairIdCounter = 0;
40
41 var BRACE_ID_PATTERN = /^(pair-\d+-)(close|open)$/;
42
43 /**
44 * Returns the brace partner given one brace of a brace pair.
45 *
46 * @param {HTMLElement} brace
47 * @returns {HTMLElement}
48 */
49 function getPartnerBrace(brace) {
50 var match = BRACE_ID_PATTERN.exec(brace.id);
51 return document.querySelector('#' + match[1] + (match[2] == 'open' ? 'close' : 'open'));
52 }
53
54 /**
55 * @this {HTMLElement}
56 */
57 function hoverBrace() {
58 if (!Prism.util.isActive(this, 'brace-hover', true)) {
59 return;
60 }
61
62 [this, getPartnerBrace(this)].forEach(function (e) {
63 e.classList.add(mapClassName('brace-hover'));
64 });
65 }
66 /**
67 * @this {HTMLElement}
68 */
69 function leaveBrace() {
70 [this, getPartnerBrace(this)].forEach(function (e) {
71 e.classList.remove(mapClassName('brace-hover'));
72 });
73 }
74 /**
75 * @this {HTMLElement}
76 */
77 function clickBrace() {
78 if (!Prism.util.isActive(this, 'brace-select', true)) {
79 return;
80 }
81
82 [this, getPartnerBrace(this)].forEach(function (e) {
83 e.classList.add(mapClassName('brace-selected'));
84 });
85 }
86
87 Prism.hooks.add('complete', function (env) {
88
89 /** @type {HTMLElement} */
90 var code = env.element;
91 var pre = code.parentElement;
92
93 if (!pre || pre.tagName != 'PRE') {
94 return;
95 }
96
97 // find the braces to match
98 /** @type {string[]} */
99 var toMatch = [];
100 if (Prism.util.isActive(code, 'match-braces')) {
101 toMatch.push('(', '[', '{');
102 }
103
104 if (toMatch.length == 0) {
105 // nothing to match
106 return;
107 }
108
109 if (!pre.__listenerAdded) {
110 // code blocks might be highlighted more than once
111 pre.addEventListener('mousedown', function removeBraceSelected() {
112 // the code element might have been replaced
113 var code = pre.querySelector('code');
114 var className = mapClassName('brace-selected');
115 Array.prototype.slice.call(code.querySelectorAll('.' + className)).forEach(function (e) {
116 e.classList.remove(className);
117 });
118 });
119 Object.defineProperty(pre, '__listenerAdded', { value: true });
120 }
121
122 /** @type {HTMLSpanElement[]} */
123 var punctuation = Array.prototype.slice.call(
124 code.querySelectorAll('span.' + mapClassName('token') + '.' + mapClassName('punctuation'))
125 );
126
127 /** @type {{ index: number, open: boolean, element: HTMLElement }[]} */
128 var allBraces = [];
129
130 toMatch.forEach(function (open) {
131 var close = PARTNER[open];
132 var name = mapClassName(NAMES[open]);
133
134 /** @type {[number, number][]} */
135 var pairs = [];
136 /** @type {number[]} */
137 var openStack = [];
138
139 for (var i = 0; i < punctuation.length; i++) {
140 var element = punctuation[i];
141 if (element.childElementCount == 0) {
142 var text = element.textContent;
143 text = BRACE_ALIAS_MAP[text] || text;
144 if (text === open) {
145 allBraces.push({ index: i, open: true, element: element });
146 element.classList.add(name);
147 element.classList.add(mapClassName('brace-open'));
148 openStack.push(i);
149 } else if (text === close) {
150 allBraces.push({ index: i, open: false, element: element });
151 element.classList.add(name);
152 element.classList.add(mapClassName('brace-close'));
153 if (openStack.length) {
154 pairs.push([i, openStack.pop()]);
155 }
156 }
157 }
158 }
159
160 pairs.forEach(function (pair) {
161 var pairId = 'pair-' + (pairIdCounter++) + '-';
162
163 var opening = punctuation[pair[0]];
164 var closing = punctuation[pair[1]];
165
166 opening.id = pairId + 'open';
167 closing.id = pairId + 'close';
168
169 [opening, closing].forEach(function (e) {
170 e.addEventListener('mouseenter', hoverBrace);
171 e.addEventListener('mouseleave', leaveBrace);
172 e.addEventListener('click', clickBrace);
173 });
174 });
175 });
176
177 var level = 0;
178 allBraces.sort(function (a, b) { return a.index - b.index; });
179 allBraces.forEach(function (brace) {
180 if (brace.open) {
181 brace.element.classList.add(mapClassName('brace-level-' + (level % LEVEL_WARP + 1)));
182 level++;
183 } else {
184 level = Math.max(0, level - 1);
185 brace.element.classList.add(mapClassName('brace-level-' + (level % LEVEL_WARP + 1)));
186 }
187 });
188 });
189
190}());