Commit 6b47133ded2b21b64f952ebe79e89ceb19fddf8f

Michael Schmidt 2020-09-22T16:50:32

Match braces: Fixed JS interpolation punctuation (#2541)

diff --git a/plugins/match-braces/index.html b/plugins/match-braces/index.html
index e2cc929..9a0770f 100644
--- a/plugins/match-braces/index.html
+++ b/plugins/match-braces/index.html
@@ -44,6 +44,9 @@
 
 	<h2>JavaScript</h2>
 	<pre data-src="plugins/match-braces/prism-match-braces.js"></pre>
+	<pre class="language-js"><code>const func = (a, b) => {
+	return `${a}:${b}`;
+}</code></pre>
 
 	<h2>Lisp</h2>
 	<pre class="language-lisp"><code>(defun factorial (n)
diff --git a/plugins/match-braces/prism-match-braces.js b/plugins/match-braces/prism-match-braces.js
index 2488e34..ea278b1 100644
--- a/plugins/match-braces/prism-match-braces.js
+++ b/plugins/match-braces/prism-match-braces.js
@@ -4,26 +4,27 @@
 		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 = {
 		'(': ')',
 		'[': ']',
 		'{': '}',
 	};
 
+	// 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;
@@ -45,36 +46,32 @@
 	 * @this {HTMLElement}
 	 */
 	function hoverBrace() {
-		for (var parent = this.parentElement; parent; parent = parent.parentElement) {
-			if (NO_BRACE_HOVER_CLASS.test(parent.className)) {
-				return;
-			}
+		if (!Prism.util.isActive(this, 'brace-hover', true)) {
+			return;
 		}
 
-		[this, getPartnerBrace(this)].forEach(function (ele) {
-			ele.className = (ele.className.replace(BRACE_HOVER_CLASS, ' ') + ' brace-hover').replace(/\s+/g, ' ');
+		[this, getPartnerBrace(this)].forEach(function (e) {
+			e.classList.add('brace-hover');
 		});
 	}
 	/**
 	 * @this {HTMLElement}
 	 */
 	function leaveBrace() {
-		[this, getPartnerBrace(this)].forEach(function (ele) {
-			ele.className = ele.className.replace(BRACE_HOVER_CLASS, ' ');
+		[this, getPartnerBrace(this)].forEach(function (e) {
+			e.classList.remove('brace-hover');
 		});
 	}
 	/**
 	 * @this {HTMLElement}
 	 */
 	function clickBrace() {
-		for (var parent = this.parentElement; parent; parent = parent.parentElement) {
-			if (NO_BRACE_SELECT_CLASS.test(parent.className)) {
-				return;
-			}
+		if (!Prism.util.isActive(this, 'brace-select', true)) {
+			return;
 		}
 
-		[this, getPartnerBrace(this)].forEach(function (ele) {
-			ele.className = (ele.className.replace(BRACE_SELECTED_CLASS, ' ') + ' brace-selected').replace(/\s+/g, ' ');
+		[this, getPartnerBrace(this)].forEach(function (e) {
+			e.classList.add('brace-selected');
 		});
 	}
 
@@ -91,11 +88,8 @@
 		// 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 (Prism.util.isActive(code, 'match-braces')) {
+			toMatch.push('(', '[', '{');
 		}
 
 		if (toMatch.length == 0) {
@@ -108,8 +102,8 @@
 			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, ' ');
+				Array.prototype.slice.call(code.querySelectorAll('.brace-selected')).forEach(function (e) {
+					e.classList.remove('brace-selected');
 				});
 			});
 			Object.defineProperty(pre, '__listenerAdded', { value: true });
@@ -134,15 +128,16 @@
 				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.className += ' ' + name;
-						element.className += ' brace-open';
+						element.classList.add(name);
+						element.classList.add('brace-open');
 						openStack.push(i);
 					} else if (text === close) {
 						allBraces.push({ index: i, open: false, element: element });
-						element.className += ' ' + name;
-						element.className += ' brace-close';
+						element.classList.add(name);
+						element.classList.add('brace-close');
 						if (openStack.length) {
 							pairs.push([i, openStack.pop()]);
 						}
@@ -153,16 +148,16 @@
 			pairs.forEach(function (pair) {
 				var pairId = 'pair-' + (pairIdCounter++) + '-';
 
-				var openEle = punctuation[pair[0]];
-				var closeEle = punctuation[pair[1]];
+				var opening = punctuation[pair[0]];
+				var closing = punctuation[pair[1]];
 
-				openEle.id = pairId + 'open';
-				closeEle.id = pairId + 'close';
+				opening.id = pairId + 'open';
+				closing.id = pairId + 'close';
 
-				[openEle, closeEle].forEach(function (ele) {
-					ele.addEventListener('mouseenter', hoverBrace);
-					ele.addEventListener('mouseleave', leaveBrace);
-					ele.addEventListener('click', clickBrace);
+				[opening, closing].forEach(function (e) {
+					e.addEventListener('mouseenter', hoverBrace);
+					e.addEventListener('mouseleave', leaveBrace);
+					e.addEventListener('click', clickBrace);
 				});
 			});
 		});
@@ -171,14 +166,13 @@
 		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);
+				brace.element.classList.add('brace-level-' + (level % LEVEL_WARP + 1));
 				level++;
 			} else {
 				level = Math.max(0, level - 1);
-				brace.element.className += ' brace-level-' + (level % LEVEL_WARP + 1);
+				brace.element.classList.add('brace-level-' + (level % LEVEL_WARP + 1));
 			}
 		});
-
 	});
 
 }());
diff --git a/plugins/match-braces/prism-match-braces.min.js b/plugins/match-braces/prism-match-braces.min.js
index 4ae2953..7738439 100644
--- a/plugins/match-braces/prism-match-braces.min.js
+++ b/plugins/match-braces/prism-match-braces.min.js
@@ -1 +1 @@
-!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var c=/(?:^|\s)match-braces(?:\s|$)/,a=/(?:^|\s)brace-hover(?:\s|$)/,l=/(?:^|\s)brace-selected(?:\s|$)/,n=/(?:^|\s)no-brace-hover(?:\s|$)/,t=/(?:^|\s)no-brace-select(?:\s|$)/,u={"(":")","[":"]","{":"}"},f={"(":"brace-round","[":"brace-square","{":"brace-curly"},m=0,r=/^(pair-\d+-)(open|close)$/;Prism.hooks.add("complete",function(e){var a=e.element,n=a.parentElement;if(n&&"PRE"==n.tagName){for(var t=[],r=a;r;r=r.parentElement)if(c.test(r.className)){t.push("(","[","{");break}if(0!=t.length){n.__listenerAdded||(n.addEventListener("mousedown",function(){var e=n.querySelector("code");Array.prototype.slice.call(e.querySelectorAll(".brace-selected")).forEach(function(e){e.className=e.className.replace(l," ")})}),Object.defineProperty(n,"__listenerAdded",{value:!0}));var o=Array.prototype.slice.call(a.querySelectorAll("span.token.punctuation")),i=[];t.forEach(function(e){for(var a=u[e],n=f[e],t=[],r=[],s=0;s<o.length;s++){var c=o[s];if(0==c.childElementCount){var l=c.textContent;l===e?(i.push({index:s,open:!0,element:c}),c.className+=" "+n,c.className+=" brace-open",r.push(s)):l===a&&(i.push({index:s,open:!1,element:c}),c.className+=" "+n,c.className+=" brace-close",r.length&&t.push([s,r.pop()]))}}t.forEach(function(e){var a="pair-"+m+++"-",n=o[e[0]],t=o[e[1]];n.id=a+"open",t.id=a+"close",[n,t].forEach(function(e){e.addEventListener("mouseenter",p),e.addEventListener("mouseleave",d),e.addEventListener("click",h)})})});var s=0;i.sort(function(e,a){return e.index-a.index}),i.forEach(function(e){e.open?(e.element.className+=" brace-level-"+(s%12+1),s++):(s=Math.max(0,s-1),e.element.className+=" brace-level-"+(s%12+1))})}}})}function s(e){var a=r.exec(e.id);return document.querySelector("#"+a[1]+("open"==a[2]?"close":"open"))}function p(){for(var e=this.parentElement;e;e=e.parentElement)if(n.test(e.className))return;[this,s(this)].forEach(function(e){e.className=(e.className.replace(a," ")+" brace-hover").replace(/\s+/g," ")})}function d(){[this,s(this)].forEach(function(e){e.className=e.className.replace(a," ")})}function h(){for(var e=this.parentElement;e;e=e.parentElement)if(t.test(e.className))return;[this,s(this)].forEach(function(e){e.className=(e.className.replace(l," ")+" brace-selected").replace(/\s+/g," ")})}}();
\ No newline at end of file
+!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var d={"(":")","[":"]","{":"}"},u={"(":"brace-round","[":"brace-square","{":"brace-curly"},f={"${":"{"},h=0,n=/^(pair-\d+-)(open|close)$/;Prism.hooks.add("complete",function(e){var t=e.element,n=t.parentElement;if(n&&"PRE"==n.tagName){var c=[];if(Prism.util.isActive(t,"match-braces")&&c.push("(","[","{"),0!=c.length){n.__listenerAdded||(n.addEventListener("mousedown",function(){var e=n.querySelector("code");Array.prototype.slice.call(e.querySelectorAll(".brace-selected")).forEach(function(e){e.classList.remove("brace-selected")})}),Object.defineProperty(n,"__listenerAdded",{value:!0}));var o=Array.prototype.slice.call(t.querySelectorAll("span.token.punctuation")),l=[];c.forEach(function(e){for(var t=d[e],n=u[e],c=[],r=[],s=0;s<o.length;s++){var a=o[s];if(0==a.childElementCount){var i=a.textContent;(i=f[i]||i)===e?(l.push({index:s,open:!0,element:a}),a.classList.add(n),a.classList.add("brace-open"),r.push(s)):i===t&&(l.push({index:s,open:!1,element:a}),a.classList.add(n),a.classList.add("brace-close"),r.length&&c.push([s,r.pop()]))}}c.forEach(function(e){var t="pair-"+h+++"-",n=o[e[0]],c=o[e[1]];n.id=t+"open",c.id=t+"close",[n,c].forEach(function(e){e.addEventListener("mouseenter",p),e.addEventListener("mouseleave",v),e.addEventListener("click",m)})})});var r=0;l.sort(function(e,t){return e.index-t.index}),l.forEach(function(e){e.open?(e.element.classList.add("brace-level-"+(r%12+1)),r++):(r=Math.max(0,r-1),e.element.classList.add("brace-level-"+(r%12+1)))})}}})}function e(e){var t=n.exec(e.id);return document.querySelector("#"+t[1]+("open"==t[2]?"close":"open"))}function p(){Prism.util.isActive(this,"brace-hover",!0)&&[this,e(this)].forEach(function(e){e.classList.add("brace-hover")})}function v(){[this,e(this)].forEach(function(e){e.classList.remove("brace-hover")})}function m(){Prism.util.isActive(this,"brace-select",!0)&&[this,e(this)].forEach(function(e){e.classList.add("brace-selected")})}}();
\ No newline at end of file