diff --git a/plugins/file-highlight/index.html b/plugins/file-highlight/index.html index 7452bf752d..2033ef017a 100644 --- a/plugins/file-highlight/index.html +++ b/plugins/file-highlight/index.html @@ -8,6 +8,7 @@ + @@ -27,6 +28,14 @@

How to use

You don’t need to specify the language, it’s automatically determined by the file extension. If, however, the language cannot be determined from the file extension or the file extension is incorrect, you may specify a language as well (with the usual class name way).

+

Use the data-range attribute to display only a selected range of lines from the file, like so:

+ +
<pre data-src="myfile.js" data-range="1,5"></pre>
+ +

Lines start at 1, so "1,5" will display line 1 up to and including line 5. It's also possible to specify just a single line (e.g. "5" for just line 5) and open ranges (e.g. "3," for all lines starting at line 3). Negative integers can be used to specify the n-th last line, e.g. -2 for the second last line.

+ +

When data-range is used in conjunction with the Line Numbers plugin, this plugin will add the proper data-start according to the specified range. This behavior can be overridden by setting the data-start attribute manually.

+

Please note that the files are fetched with XMLHttpRequest. This means that if the file is on a different origin, fetching it will fail, unless CORS is enabled on that website.

@@ -42,6 +51,9 @@

Examples

File that doesn’t exist:


 
+	

With line numbers, and data-range="12,111":

+

+
 	

For more examples, browse around the Prism website. Most large code samples are actually files fetched with this plugin.

@@ -49,6 +61,7 @@

Examples

+ diff --git a/plugins/file-highlight/prism-file-highlight.js b/plugins/file-highlight/prism-file-highlight.js index 443dd655f6..0a87ac5403 100644 --- a/plugins/file-highlight/prism-file-highlight.js +++ b/plugins/file-highlight/prism-file-highlight.js @@ -50,6 +50,57 @@ element.className = className.replace(/\s+/g, ' ').trim(); } + /** + * Loads the given file. + * + * @param {string} src The URL or path of the source file to load. + * @param {(result: string) => void} success + * @param {(reason: string) => void} error + */ + function loadFile(src, success, error) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', src, true); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status < 400 && xhr.responseText) { + success(xhr.responseText); + } else { + if (xhr.status >= 400) { + error(FAILURE_MESSAGE(xhr.status, xhr.statusText)); + } else { + error(FAILURE_EMPTY_MESSAGE); + } + } + } + }; + xhr.send(null); + } + + /** + * Parses the given range. + * + * This returns a range with inclusive ends. + * + * @param {string | null | undefined} range + * @returns {[number, number | undefined] | undefined} + */ + function parseRange(range) { + var m = /^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(range || ''); + if (m) { + var start = Number(m[1]); + var comma = m[2]; + var end = m[3]; + + if (!comma) { + return [start, start]; + } + if (!end) { + return [start, undefined]; + } + return [start, Number(end)]; + } + return undefined; + } Prism.hooks.add('before-highlightall', function (env) { env.selector += ', ' + SELECTOR; @@ -87,31 +138,45 @@ } // load file - var xhr = new XMLHttpRequest(); - xhr.open('GET', src, true); - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status < 400 && xhr.responseText) { - // mark as loaded - pre.setAttribute(STATUS_ATTR, STATUS_LOADED); - - // highlight code - code.textContent = xhr.responseText; - Prism.highlightElement(code); - - } else { - // mark as failed - pre.setAttribute(STATUS_ATTR, STATUS_FAILED); - - if (xhr.status >= 400) { - code.textContent = FAILURE_MESSAGE(xhr.status, xhr.statusText); - } else { - code.textContent = FAILURE_EMPTY_MESSAGE; + loadFile( + src, + function (text) { + // mark as loaded + pre.setAttribute(STATUS_ATTR, STATUS_LOADED); + + // handle data-range + var range = parseRange(pre.getAttribute('data-range')); + if (range) { + var lines = text.split(/\r\n?|\n/g); + + // the range is one-based and inclusive on both ends + var start = range[0]; + var end = range[1] == null ? lines.length : range[1]; + + if (start < 0) { start += lines.length; } + start = Math.max(0, Math.min(start - 1, lines.length)); + if (end < 0) { end += lines.length; } + end = Math.max(0, Math.min(end, lines.length)); + + text = lines.slice(start, end).join('\n'); + + // add data-start for line numbers + if (!pre.hasAttribute('data-start')) { + pre.setAttribute('data-start', String(start + 1)); } } + + // highlight code + code.textContent = text; + Prism.highlightElement(code); + }, + function (error) { + // mark as failed + pre.setAttribute(STATUS_ATTR, STATUS_FAILED); + + code.textContent = error; } - }; - xhr.send(null); + ); } }); diff --git a/plugins/file-highlight/prism-file-highlight.min.js b/plugins/file-highlight/prism-file-highlight.min.js index 24de20476b..b8ec530347 100644 --- a/plugins/file-highlight/prism-file-highlight.min.js +++ b/plugins/file-highlight/prism-file-highlight.min.js @@ -1 +1 @@ -!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector);var o={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},h="data-src-status",g="loading",c="loaded",u="pre[data-src]:not(["+h+'="'+c+'"]):not(['+h+'="'+g+'"])',n=/\blang(?:uage)?-([\w-]+)\b/i;Prism.hooks.add("before-highlightall",function(e){e.selector+=", "+u}),Prism.hooks.add("before-sanity-check",function(e){var t=e.element;if(t.matches(u)){e.code="",t.setAttribute(h,g);var i=t.appendChild(document.createElement("CODE"));i.textContent="Loading…";var n=t.getAttribute("data-src"),s=e.language;if("none"===s){var a=(/\.(\w+)$/.exec(n)||[,"none"])[1];s=o[a]||a}p(i,s),p(t,s);var r=Prism.plugins.autoloader;r&&r.loadLanguages(s);var l=new XMLHttpRequest;l.open("GET",n,!0),l.onreadystatechange=function(){4==l.readyState&&(l.status<400&&l.responseText?(t.setAttribute(h,c),i.textContent=l.responseText,Prism.highlightElement(i)):(t.setAttribute(h,"failed"),400<=l.status?i.textContent=function(e,t){return"✖ Error "+e+" while fetching file: "+t}(l.status,l.statusText):i.textContent="✖ Error: File does not exist or is empty"))},l.send(null)}});var e=!(Prism.plugins.fileHighlight={highlight:function(e){for(var t,i=(e||document).querySelectorAll(u),n=0;t=i[n++];)Prism.highlightElement(t)}});Prism.fileHighlight=function(){e||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),e=!0),Prism.plugins.fileHighlight.highlight.apply(this,arguments)}}function p(e,t){var i=e.className;i=i.replace(n," ")+" language-"+t,e.className=i.replace(/\s+/g," ").trim()}}(); \ No newline at end of file +!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector);var l={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},o="data-src-status",h="loading",g="loaded",u="pre[data-src]:not(["+o+'="'+g+'"]):not(['+o+'="'+h+'"])',n=/\blang(?:uage)?-([\w-]+)\b/i;Prism.hooks.add("before-highlightall",function(t){t.selector+=", "+u}),Prism.hooks.add("before-sanity-check",function(t){var r=t.element;if(r.matches(u)){t.code="",r.setAttribute(o,h);var s=r.appendChild(document.createElement("CODE"));s.textContent="Loading…";var e=r.getAttribute("data-src"),i=t.language;if("none"===i){var n=(/\.(\w+)$/.exec(e)||[,"none"])[1];i=l[n]||n}c(s,i),c(r,i);var a=Prism.plugins.autoloader;a&&a.loadLanguages(i),function(t,e,i){var n=new XMLHttpRequest;n.open("GET",t,!0),n.onreadystatechange=function(){4==n.readyState&&(n.status<400&&n.responseText?e(n.responseText):400<=n.status?i(function(t,e){return"✖ Error "+t+" while fetching file: "+e}(n.status,n.statusText)):i("✖ Error: File does not exist or is empty"))},n.send(null)}(e,function(t){r.setAttribute(o,g);var e=function(t){var e=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(t||"");if(e){var i=Number(e[1]),n=e[2],a=e[3];return n?a?[i,Number(a)]:[i,void 0]:[i,i]}}(r.getAttribute("data-range"));if(e){var i=t.split(/\r\n?|\n/g),n=e[0],a=null==e[1]?i.length:e[1];n<0&&(n+=i.length),n=Math.max(0,Math.min(n-1,i.length)),a<0&&(a+=i.length),a=Math.max(0,Math.min(a,i.length)),t=i.slice(n,a).join("\n"),r.hasAttribute("data-start")||r.setAttribute("data-start",String(n+1))}s.textContent=t,Prism.highlightElement(s)},function(t){r.setAttribute(o,"failed"),s.textContent=t})}});var t=!(Prism.plugins.fileHighlight={highlight:function(t){for(var e,i=(t||document).querySelectorAll(u),n=0;e=i[n++];)Prism.highlightElement(e)}});Prism.fileHighlight=function(){t||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),t=!0),Prism.plugins.fileHighlight.highlight.apply(this,arguments)}}function c(t,e){var i=t.className;i=i.replace(n," ")+" language-"+e,t.className=i.replace(/\s+/g," ").trim()}}(); \ No newline at end of file diff --git a/prism.js b/prism.js index 2d790243a0..00d8cedf40 100644 --- a/prism.js +++ b/prism.js @@ -1720,6 +1720,57 @@ Prism.languages.js = Prism.languages.javascript; element.className = className.replace(/\s+/g, ' ').trim(); } + /** + * Loads the given file. + * + * @param {string} src The URL or path of the source file to load. + * @param {(result: string) => void} success + * @param {(reason: string) => void} error + */ + function loadFile(src, success, error) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', src, true); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status < 400 && xhr.responseText) { + success(xhr.responseText); + } else { + if (xhr.status >= 400) { + error(FAILURE_MESSAGE(xhr.status, xhr.statusText)); + } else { + error(FAILURE_EMPTY_MESSAGE); + } + } + } + }; + xhr.send(null); + } + + /** + * Parses the given range. + * + * This returns a range with inclusive ends. + * + * @param {string | null | undefined} range + * @returns {[number, number | undefined] | undefined} + */ + function parseRange(range) { + var m = /^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(range || ''); + if (m) { + var start = Number(m[1]); + var comma = m[2]; + var end = m[3]; + + if (!comma) { + return [start, start]; + } + if (!end) { + return [start, undefined]; + } + return [start, Number(end)]; + } + return undefined; + } Prism.hooks.add('before-highlightall', function (env) { env.selector += ', ' + SELECTOR; @@ -1757,31 +1808,45 @@ Prism.languages.js = Prism.languages.javascript; } // load file - var xhr = new XMLHttpRequest(); - xhr.open('GET', src, true); - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status < 400 && xhr.responseText) { - // mark as loaded - pre.setAttribute(STATUS_ATTR, STATUS_LOADED); - - // highlight code - code.textContent = xhr.responseText; - Prism.highlightElement(code); - - } else { - // mark as failed - pre.setAttribute(STATUS_ATTR, STATUS_FAILED); - - if (xhr.status >= 400) { - code.textContent = FAILURE_MESSAGE(xhr.status, xhr.statusText); - } else { - code.textContent = FAILURE_EMPTY_MESSAGE; + loadFile( + src, + function (text) { + // mark as loaded + pre.setAttribute(STATUS_ATTR, STATUS_LOADED); + + // handle data-range + var range = parseRange(pre.getAttribute('data-range')); + if (range) { + var lines = text.split(/\r\n?|\n/g); + + // the range is one-based and inclusive on both ends + var start = range[0]; + var end = range[1] == null ? lines.length : range[1]; + + if (start < 0) { start += lines.length; } + start = Math.max(0, Math.min(start - 1, lines.length)); + if (end < 0) { end += lines.length; } + end = Math.max(0, Math.min(end, lines.length)); + + text = lines.slice(start, end).join('\n'); + + // add data-start for line numbers + if (!pre.hasAttribute('data-start')) { + pre.setAttribute('data-start', String(start + 1)); } } + + // highlight code + code.textContent = text; + Prism.highlightElement(code); + }, + function (error) { + // mark as failed + pre.setAttribute(STATUS_ATTR, STATUS_FAILED); + + code.textContent = error; } - }; - xhr.send(null); + ); } });