UNPKG

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