Commit e63058f9af2b9277bb3883ba281558f326e673cf

Vitaliy Ribachenko 2017-12-05T22:16:35

Fixes to compatibility of line number and line higlight plugins (#1194) * Fix line breaks issues in line numbers plugin used with soft wraps option * Fix line highlight plugin compatibility issues with line number plugin * Revert rename data attribute for line numbers plugin * Fix compatibility issues with line highlight and line numbers

diff --git a/plugins/line-highlight/prism-line-highlight.css b/plugins/line-highlight/prism-line-highlight.css
index 2ab49ed..6058db4 100644
--- a/plugins/line-highlight/prism-line-highlight.css
+++ b/plugins/line-highlight/prism-line-highlight.css
@@ -42,3 +42,8 @@ pre[data-line] {
 		top: auto;
 		bottom: .4em;
 	}
+
+.line-numbers .line-highlight:before,
+.line-numbers .line-highlight:after {
+	content: none;
+}
diff --git a/plugins/line-highlight/prism-line-highlight.js b/plugins/line-highlight/prism-line-highlight.js
index b55abf6..d59208c 100644
--- a/plugins/line-highlight/prism-line-highlight.js
+++ b/plugins/line-highlight/prism-line-highlight.js
@@ -36,37 +36,53 @@ var isLineHeightRounded = (function() {
 }());
 
 function highlightLines(pre, lines, classes) {
+	lines = typeof lines === 'string' ? lines : pre.getAttribute('data-line');
+	
 	var ranges = lines.replace(/\s+/g, '').split(','),
 	    offset = +pre.getAttribute('data-line-offset') || 0;
 
 	var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
 	var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
+	var hasLineNumbers = hasClass(pre, 'line-numbers');
 
-	for (var i=0, range; range = ranges[i++];) {
-		range = range.split('-');
+	for (var i=0, currentRange; currentRange = ranges[i++];) {
+		var range = currentRange.split('-');
 
 		var start = +range[0],
 		    end = +range[1] || start;
 
-		var line = document.createElement('div');
+		var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
 
-		line.textContent = Array(end - start + 2).join(' \n');
 		line.setAttribute('aria-hidden', 'true');
+		line.setAttribute('data-range', currentRange);
 		line.className = (classes || '') + ' line-highlight';
 
 		//if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers
-		if(!hasClass(pre, 'line-numbers')) {
+		if(hasLineNumbers && Prism.plugins.lineNumbers) {
+			var startNode = Prism.plugins.lineNumbers.getLine(pre, start);
+			var endNode = Prism.plugins.lineNumbers.getLine(pre, end);
+			
+			if (startNode) {
+				line.style.top = startNode.offsetTop + 'px';
+			}
+			
+			if (endNode) {
+				line.style.height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px';
+			}
+		} else {
 			line.setAttribute('data-start', start);
 
 			if(end > start) {
 				line.setAttribute('data-end', end);
 			}
-		}
+			
+			line.style.top = (start - offset - 1) * lineHeight + 'px';
 
-		line.style.top = (start - offset - 1) * lineHeight + 'px';
+			line.textContent = new Array(end - start + 2).join(' \n');
+		}
 
 		//allow this to play nicely with the line-numbers plugin
-		if(hasClass(pre, 'line-numbers')) {
+		if(hasLineNumbers) {
 			//need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
 			pre.appendChild(line);
 		} else {
@@ -133,7 +149,7 @@ Prism.hooks.add('before-sanity-check', function(env) {
 	}
 });
 
-Prism.hooks.add('complete', function(env) {
+Prism.hooks.add('complete', function completeHook(env) {
 	var pre = env.element.parentNode;
 	var lines = pre && pre.getAttribute('data-line');
 
@@ -142,11 +158,24 @@ Prism.hooks.add('complete', function(env) {
 	}
 
 	clearTimeout(fakeTimer);
-	highlightLines(pre, lines);
 
-	fakeTimer = setTimeout(applyHash, 1);
+	var hasLineNumbers = Prism.plugins.lineNumbers;
+	var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
+
+	if (hasLineNumbers && !isLineNumbersLoaded) {
+		Prism.hooks.add('line-numbers', completeHook);
+	} else {
+		highlightLines(pre, lines);
+		fakeTimer = setTimeout(applyHash, 1);
+	}
 });
 
 	window.addEventListener('hashchange', applyHash);
+	window.addEventListener('resize', function () {
+		var preElements = document.querySelectorAll('pre[data-line]');
+		Array.prototype.forEach.call(preElements, function (pre) {
+			highlightLines(pre);
+		});
+	});
 
-})();
+})();
\ No newline at end of file
diff --git a/plugins/line-numbers/prism-line-numbers.js b/plugins/line-numbers/prism-line-numbers.js
index dea8e3a..e9e684f 100644
--- a/plugins/line-numbers/prism-line-numbers.js
+++ b/plugins/line-numbers/prism-line-numbers.js
@@ -5,14 +5,20 @@
 	}
 
 	/**
-	 * Class name for <pre> which is activating the plugin
+	 * Plugin name which is used as a class name for <pre> which is activating the plugin
 	 * @type {String}
 	 */
-	var PLUGIN_CLASS = 'line-numbers';
+	var PLUGIN_NAME = 'line-numbers';
+	
+	/**
+	 * Regular expression used for determining line breaks
+	 * @type {RegExp}
+	 */
+	var NEW_LINE_EXP = /\n(?!$)/g;
 
 	/**
 	 * Resizes line numbers spans according to height of line of code
-	 * @param  {Element} element <pre> element
+	 * @param {Element} element <pre> element
 	 */
 	var _resizeElement = function (element) {
 		var codeStyles = getStyles(element);
@@ -22,7 +28,7 @@
 			var codeElement = element.querySelector('code');
 			var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
 			var lineNumberSizer = element.querySelector('.line-numbers-sizer');
-			var codeLines = element.textContent.split('\n');
+			var codeLines = codeElement.textContent.split(NEW_LINE_EXP);
 
 			if (!lineNumberSizer) {
 				lineNumberSizer = document.createElement('span');
@@ -57,7 +63,7 @@
 	};
 
 	window.addEventListener('resize', function () {
-		Array.prototype.forEach.call(document.querySelectorAll('pre.' + PLUGIN_CLASS), _resizeElement);
+		Array.prototype.forEach.call(document.querySelectorAll('pre.' + PLUGIN_NAME), _resizeElement);
 	});
 
 	Prism.hooks.add('complete', function (env) {
@@ -76,21 +82,21 @@
 			return;
 		}
 
-		if (env.element.querySelector(".line-numbers-rows")) {
+		if (env.element.querySelector('.line-numbers-rows')) {
 			// Abort if line numbers already exists
 			return;
 		}
 
 		if (clsReg.test(env.element.className)) {
-			// Remove the class "line-numbers" from the <code>
+			// Remove the class 'line-numbers' from the <code>
 			env.element.className = env.element.className.replace(clsReg, ' ');
 		}
 		if (!clsReg.test(pre.className)) {
-			// Add the class "line-numbers" to the <pre>
+			// Add the class 'line-numbers' to the <pre>
 			pre.className += ' line-numbers';
 		}
 
-		var match = env.code.match(/\n(?!$)/g);
+		var match = env.code.match(NEW_LINE_EXP);
 		var linesNum = match ? match.length + 1 : 1;
 		var lineNumbersWrapper;
 
@@ -109,6 +115,45 @@
 		env.element.appendChild(lineNumbersWrapper);
 
 		_resizeElement(pre);
+
+		Prism.hooks.run('line-numbers', env);
+	});
+
+	Prism.hooks.add('line-numbers', function (env) {
+		env.plugins = env.plugins || {};
+		env.plugins.lineNumbers = true;
 	});
+	
+	/**
+	 * Global exports
+	 */
+	Prism.plugins.lineNumbers = {
+		/**
+		 * Get node for provided line number
+		 * @param {Element} element pre element
+		 * @param {Number} number line number
+		 * @return {Element|undefined}
+		 */
+		getLine: function (element, number) {
+			if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
+				return;
+			}
+
+			var lineNumberRows = element.querySelector('.line-numbers-rows');
+			var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
+			var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);
+
+			if (number < lineNumberStart) {
+				number = lineNumberStart;
+			}
+			if (number > lineNumberEnd) {
+				number = lineNumberEnd;
+			}
+
+			var lineIndex = number - lineNumberStart;
+
+			return lineNumberRows.children[lineIndex];
+		}
+	};
 
 }());
\ No newline at end of file