Instantly share code, notes, and snippets.

Embed
What would you like to do?
Prism.js line numbers plugin tweak

Notes on use

The idea here is that some of us always want line numbers but never want to add a "line-numbers" class to our HTML. This tweaked version of the plugin always adds the line numbers to any <pre> block that also has a language class that Prism will be highlighting.

To keep you from being boxed in, I've added an option to use the pre class to opt out by setting class = "no-line-numbers". If you are much more likely to want line numbers than to not want them, then this is the better way of doing things. If you think you usually don't want line numbers, then just use the plugin straight from Prism.

You should add this file after the main prism.js file on your page. Do not include the line-numbers plugin when you download Prism because you want this one to do the work and not conflict with the official one.

This may break at any time as the Prism.js teams changes the base functionality of the highlighter or the line numbers plugin.

You will also need the CSS for line numbers, which I have included here but not changed.

pre.line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre.line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
(function () {
if (typeof self === 'undefined' || !self.Prism || !self.document) {
return;
}
/**
* Class name for <pre> which is activating the plugin
* @type {String}
*/
var PLUGIN_CLASS = 'line-numbers';
/**
* Resizes line numbers spans according to height of line of code
* @param {Element} element <pre> element
*/
var _resizeElement = function (element) {
var codeStyles = getStyles(element);
var whiteSpace = codeStyles['white-space'];
if (whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line') {
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');
if (!lineNumberSizer) {
lineNumberSizer = document.createElement('span');
lineNumberSizer.className = 'line-numbers-sizer';
codeElement.appendChild(lineNumberSizer);
}
lineNumberSizer.style.display = 'block';
codeLines.forEach(function (line, lineNumber) {
lineNumberSizer.textContent = line || '\n';
var lineSize = lineNumberSizer.getBoundingClientRect().height;
lineNumbersWrapper.children[lineNumber].style.height = lineSize + 'px';
});
lineNumberSizer.textContent = '';
lineNumberSizer.style.display = 'none';
}
};
/**
* Returns style declarations for the element
* @param {Element} element
*/
var getStyles = function (element) {
if (!element) {
return null;
}
return window.getComputedStyle ? getComputedStyle(element) : (element.currentStyle || null);
};
window.addEventListener('resize', function () {
Array.prototype.forEach.call(document.querySelectorAll('pre.' + PLUGIN_CLASS), _resizeElement);
});
Prism.hooks.add('complete', function (env) {
if (!env.code) {
return;
}
// works only for <code> wrapped inside <pre> (not inline)
var pre = env.element.parentNode;
// Original regex check for class, leaving it here
// for its redundancy check
var clsReg = /\s*\bline-numbers\b\s*/;
// New regex check for opt-out class
var clsRegB = /\s*\bno-line-numbers\b\s*/;
if (env.element.querySelector(".line-numbers-rows")) {
// Abort if line numbers already exists
return;
}
// Added to facilitate opting out
if (clsRegB.test(pre.className)) {
// Respect the opt-out
return;
}
if (clsReg.test(env.element.className)) {
// 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>
pre.className += ' line-numbers';
}
var match = env.code.match(/\n(?!$)/g);
var linesNum = match ? match.length + 1 : 1;
var lineNumbersWrapper;
var lines = new Array(linesNum + 1);
lines = lines.join('<span></span>');
lineNumbersWrapper = document.createElement('span');
lineNumbersWrapper.setAttribute('aria-hidden', 'true');
lineNumbersWrapper.className = 'line-numbers-rows';
lineNumbersWrapper.innerHTML = lines;
if (pre.hasAttribute('data-start')) {
pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
}
env.element.appendChild(lineNumbersWrapper);
_resizeElement(pre);
});
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment