Files
Cloud-book/node_modules/prismjs/plugins/match-braces/prism-match-braces.js
2025-08-27 17:10:05 +08:00

185 lines
4.7 KiB
JavaScript

(function () {
if (typeof self === 'undefined' || !self.Prism || !self.document) {
return;
}
var MATCH_ALL_CLASS = /(?:^|\s)match-braces(?:\s|$)/;
var BRACE_HOVER_CLASS = /(?:^|\s)brace-hover(?:\s|$)/;
var BRACE_SELECTED_CLASS = /(?:^|\s)brace-selected(?:\s|$)/;
var NO_BRACE_HOVER_CLASS = /(?:^|\s)no-brace-hover(?:\s|$)/;
var NO_BRACE_SELECT_CLASS = /(?:^|\s)no-brace-select(?:\s|$)/;
var PARTNER = {
'(': ')',
'[': ']',
'{': '}',
};
var NAMES = {
'(': 'brace-round',
'[': 'brace-square',
'{': 'brace-curly',
};
var LEVEL_WARP = 12;
var pairIdCounter = 0;
var BRACE_ID_PATTERN = /^(pair-\d+-)(open|close)$/;
/**
* Returns the brace partner given one brace of a brace pair.
*
* @param {HTMLElement} brace
* @returns {HTMLElement}
*/
function getPartnerBrace(brace) {
var match = BRACE_ID_PATTERN.exec(brace.id);
return document.querySelector('#' + match[1] + (match[2] == 'open' ? 'close' : 'open'));
}
/**
* @this {HTMLElement}
*/
function hoverBrace() {
for (var parent = this.parentElement; parent; parent = parent.parentElement) {
if (NO_BRACE_HOVER_CLASS.test(parent.className)) {
return;
}
}
[this, getPartnerBrace(this)].forEach(function (ele) {
ele.className = (ele.className.replace(BRACE_HOVER_CLASS, ' ') + ' brace-hover').replace(/\s+/g, ' ');
});
}
/**
* @this {HTMLElement}
*/
function leaveBrace() {
[this, getPartnerBrace(this)].forEach(function (ele) {
ele.className = ele.className.replace(BRACE_HOVER_CLASS, ' ');
});
}
/**
* @this {HTMLElement}
*/
function clickBrace() {
for (var parent = this.parentElement; parent; parent = parent.parentElement) {
if (NO_BRACE_SELECT_CLASS.test(parent.className)) {
return;
}
}
[this, getPartnerBrace(this)].forEach(function (ele) {
ele.className = (ele.className.replace(BRACE_SELECTED_CLASS, ' ') + ' brace-selected').replace(/\s+/g, ' ');
});
}
Prism.hooks.add('complete', function (env) {
/** @type {HTMLElement} */
var code = env.element;
var pre = code.parentElement;
if (!pre || pre.tagName != 'PRE') {
return;
}
// find the braces to match
/** @type {string[]} */
var toMatch = [];
for (var ele = code; ele; ele = ele.parentElement) {
if (MATCH_ALL_CLASS.test(ele.className)) {
toMatch.push('(', '[', '{');
break;
}
}
if (toMatch.length == 0) {
// nothing to match
return;
}
if (!pre.__listenerAdded) {
// code blocks might be highlighted more than once
pre.addEventListener('mousedown', function removeBraceSelected() {
// the code element might have been replaced
var code = pre.querySelector('code');
Array.prototype.slice.call(code.querySelectorAll('.brace-selected')).forEach(function (element) {
element.className = element.className.replace(BRACE_SELECTED_CLASS, ' ');
});
});
Object.defineProperty(pre, '__listenerAdded', { value: true });
}
/** @type {HTMLSpanElement[]} */
var punctuation = Array.prototype.slice.call(code.querySelectorAll('span.token.punctuation'));
/** @type {{ index: number, open: boolean, element: HTMLElement }[]} */
var allBraces = [];
toMatch.forEach(function (open) {
var close = PARTNER[open];
var name = NAMES[open];
/** @type {[number, number][]} */
var pairs = [];
/** @type {number[]} */
var openStack = [];
for (var i = 0; i < punctuation.length; i++) {
var element = punctuation[i];
if (element.childElementCount == 0) {
var text = element.textContent;
if (text === open) {
allBraces.push({ index: i, open: true, element: element });
element.className += ' ' + name;
element.className += ' brace-open';
openStack.push(i);
} else if (text === close) {
allBraces.push({ index: i, open: false, element: element });
element.className += ' ' + name;
element.className += ' brace-close';
if (openStack.length) {
pairs.push([i, openStack.pop()]);
}
}
}
}
pairs.forEach(function (pair) {
var pairId = 'pair-' + (pairIdCounter++) + '-';
var openEle = punctuation[pair[0]];
var closeEle = punctuation[pair[1]];
openEle.id = pairId + 'open';
closeEle.id = pairId + 'close';
[openEle, closeEle].forEach(function (ele) {
ele.addEventListener('mouseenter', hoverBrace);
ele.addEventListener('mouseleave', leaveBrace);
ele.addEventListener('click', clickBrace);
});
});
});
var level = 0;
allBraces.sort(function (a, b) { return a.index - b.index; });
allBraces.forEach(function (brace) {
if (brace.open) {
brace.element.className += ' brace-level-' + (level % LEVEL_WARP + 1);
level++;
} else {
level = Math.max(0, level - 1);
brace.element.className += ' brace-level-' + (level % LEVEL_WARP + 1);
}
});
});
}());