You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
4.8 KiB
190 lines
4.8 KiB
(function () { |
|
|
|
if (typeof Prism === 'undefined' || typeof document === 'undefined') { |
|
return; |
|
} |
|
|
|
function mapClassName(name) { |
|
var customClass = Prism.plugins.customClass; |
|
if (customClass) { |
|
return customClass.apply(name, 'none'); |
|
} else { |
|
return name; |
|
} |
|
} |
|
|
|
var PARTNER = { |
|
'(': ')', |
|
'[': ']', |
|
'{': '}', |
|
}; |
|
|
|
// The names for brace types. |
|
// These names have two purposes: 1) they can be used for styling and 2) they are used to pair braces. Only braces |
|
// of the same type are paired. |
|
var NAMES = { |
|
'(': 'brace-round', |
|
'[': 'brace-square', |
|
'{': 'brace-curly', |
|
}; |
|
|
|
// A map for brace aliases. |
|
// This is useful for when some braces have a prefix/suffix as part of the punctuation token. |
|
var BRACE_ALIAS_MAP = { |
|
'${': '{', // JS template punctuation (e.g. `foo ${bar + 1}`) |
|
}; |
|
|
|
var LEVEL_WARP = 12; |
|
|
|
var pairIdCounter = 0; |
|
|
|
var BRACE_ID_PATTERN = /^(pair-\d+-)(close|open)$/; |
|
|
|
/** |
|
* 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() { |
|
if (!Prism.util.isActive(this, 'brace-hover', true)) { |
|
return; |
|
} |
|
|
|
[this, getPartnerBrace(this)].forEach(function (e) { |
|
e.classList.add(mapClassName('brace-hover')); |
|
}); |
|
} |
|
/** |
|
* @this {HTMLElement} |
|
*/ |
|
function leaveBrace() { |
|
[this, getPartnerBrace(this)].forEach(function (e) { |
|
e.classList.remove(mapClassName('brace-hover')); |
|
}); |
|
} |
|
/** |
|
* @this {HTMLElement} |
|
*/ |
|
function clickBrace() { |
|
if (!Prism.util.isActive(this, 'brace-select', true)) { |
|
return; |
|
} |
|
|
|
[this, getPartnerBrace(this)].forEach(function (e) { |
|
e.classList.add(mapClassName('brace-selected')); |
|
}); |
|
} |
|
|
|
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 = []; |
|
if (Prism.util.isActive(code, 'match-braces')) { |
|
toMatch.push('(', '[', '{'); |
|
} |
|
|
|
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'); |
|
var className = mapClassName('brace-selected'); |
|
Array.prototype.slice.call(code.querySelectorAll('.' + className)).forEach(function (e) { |
|
e.classList.remove(className); |
|
}); |
|
}); |
|
Object.defineProperty(pre, '__listenerAdded', { value: true }); |
|
} |
|
|
|
/** @type {HTMLSpanElement[]} */ |
|
var punctuation = Array.prototype.slice.call( |
|
code.querySelectorAll('span.' + mapClassName('token') + '.' + mapClassName('punctuation')) |
|
); |
|
|
|
/** @type {{ index: number, open: boolean, element: HTMLElement }[]} */ |
|
var allBraces = []; |
|
|
|
toMatch.forEach(function (open) { |
|
var close = PARTNER[open]; |
|
var name = mapClassName(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; |
|
text = BRACE_ALIAS_MAP[text] || text; |
|
if (text === open) { |
|
allBraces.push({ index: i, open: true, element: element }); |
|
element.classList.add(name); |
|
element.classList.add(mapClassName('brace-open')); |
|
openStack.push(i); |
|
} else if (text === close) { |
|
allBraces.push({ index: i, open: false, element: element }); |
|
element.classList.add(name); |
|
element.classList.add(mapClassName('brace-close')); |
|
if (openStack.length) { |
|
pairs.push([i, openStack.pop()]); |
|
} |
|
} |
|
} |
|
} |
|
|
|
pairs.forEach(function (pair) { |
|
var pairId = 'pair-' + (pairIdCounter++) + '-'; |
|
|
|
var opening = punctuation[pair[0]]; |
|
var closing = punctuation[pair[1]]; |
|
|
|
opening.id = pairId + 'open'; |
|
closing.id = pairId + 'close'; |
|
|
|
[opening, closing].forEach(function (e) { |
|
e.addEventListener('mouseenter', hoverBrace); |
|
e.addEventListener('mouseleave', leaveBrace); |
|
e.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.classList.add(mapClassName('brace-level-' + (level % LEVEL_WARP + 1))); |
|
level++; |
|
} else { |
|
level = Math.max(0, level - 1); |
|
brace.element.classList.add(mapClassName('brace-level-' + (level % LEVEL_WARP + 1))); |
|
} |
|
}); |
|
}); |
|
|
|
}());
|
|
|