UNPKG

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