From 1784b175dbf0be95af5e8bc436a3689d90b98a4f Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 10 Feb 2022 18:51:13 +0000 Subject: [PATCH] Command Line: Add support for line continuation and improved colors (#3326) --- plugins/command-line/index.html | 28 +++++++++ plugins/command-line/prism-command-line.css | 12 +++- plugins/command-line/prism-command-line.js | 60 +++++++++++++------ .../command-line/prism-command-line.min.css | 2 +- .../command-line/prism-command-line.min.js | 2 +- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/plugins/command-line/index.html b/plugins/command-line/index.html index 04398bbb24..c8cd9c44be 100644 --- a/plugins/command-line/index.html +++ b/plugins/command-line/index.html @@ -49,6 +49,14 @@

How to use

Optional: To automatically present some lines as output, you can prefix those lines with any string and specify the prefix using the data-filter-output attribute on the <pre> element. For example, data-filter-output="(out)" will treat lines beginning with (out) as output and remove the prefix.

+ +

Output lines are user selectable by default, so if you select the whole content of the code block, it will select the shell commands and any output lines. This may not be desireable if you want to copy/paste just the commands and not the output. If you want to make the output not user selectable then add the following to your CSS:

+ +
.command-line span.token.output {
+	user-select: none;
+}
+ +

Optional: For multi-line commands you can specify the data-continuation-str attribute on the <pre> element. For example, data-continuation-str="\" will treat lines ending with \ as being continued on the following line. Continued lines will have a prompt as set by the attribute data-continuation-prompt or a default of >.

@@ -90,6 +98,26 @@

Windows PowerShell With Output

d-r-- 10/14/2015 5:06 PM Searches d-r-- 10/14/2015 5:06 PM Videos +

Line continuation with Output (bash)

+
echo "hello"
+(out)hello
+echo one \
+two \
+three
+(out)one two three
+(out)
+echo "goodbye"
+(out)goodbye
+ +

Line continuation with Output (PowerShell)

+
Write-Host `
+'Hello' `
+'from' `
+'PowerShell!'
+(out)Hello from PowerShell!
+Write-Host 'Goodbye from PowerShell!'
+(out)Goodbye from PowerShell!
+
diff --git a/plugins/command-line/prism-command-line.css b/plugins/command-line/prism-command-line.css index 153a87076a..984a718c1f 100644 --- a/plugins/command-line/prism-command-line.css +++ b/plugins/command-line/prism-command-line.css @@ -6,6 +6,7 @@ letter-spacing: -1px; margin-right: 1em; pointer-events: none; + text-align: right; -webkit-user-select: none; -moz-user-select: none; @@ -14,7 +15,7 @@ } .command-line-prompt > span:before { - color: #999; + opacity: 0.4; content: ' '; display: block; padding-right: 0.8em; @@ -31,3 +32,12 @@ .command-line-prompt > span[data-prompt]:before { content: attr(data-prompt); } + +.command-line-prompt > span[data-continuation-prompt]:before { + content: attr(data-continuation-prompt); +} + +.command-line span.token.output { + /* Make shell output lines a bit lighter to distinguish them from shell commands */ + opacity: 0.7; +} diff --git a/plugins/command-line/prism-command-line.js b/plugins/command-line/prism-command-line.js index c54697768f..6437775ce5 100644 --- a/plugins/command-line/prism-command-line.js +++ b/plugins/command-line/prism-command-line.js @@ -12,22 +12,16 @@ ? function (s, p) { return s.startsWith(p); } : function (s, p) { return s.indexOf(p) === 0; }; - /** - * Repeats the given string some number of times. - * - * This is just a polyfill for `String.prototype.repeat`. - * - * @param {string} str - * @param {number} times - * @returns {string} - */ - function repeat(str, times) { - var s = ''; - for (var i = 0; i < times; i++) { - s += str; + // Support for IE11 that has no endsWith() + /** @type {(str: string, suffix: string) => boolean} */ + var endsWith = ''.endsWith + ? function (str, suffix) { + return str.endsWith(suffix); } - return s; - } + : function (str, suffix) { + var len = str.length; + return str.substring(len - suffix.length, len) === suffix; + }; /** * Returns whether the given hook environment has a command line info object. @@ -79,6 +73,22 @@ } var codeLines = env.code.split('\n'); + + var continuationLineIndicies = commandLine.continuationLineIndicies = new Set(); + var lineContinuationStr = pre.getAttribute('data-continuation-str'); + + // Identify code lines that are a continuation line and thus don't need + // a prompt + if (lineContinuationStr && codeLines.length > 1) { + for (var j = 1; j < codeLines.length; j++) { + if (codeLines.hasOwnProperty(j - 1) + && endsWith(codeLines[j - 1], lineContinuationStr)) { + // Mark this line as being a continuation line + continuationLineIndicies.add(j); + } + } + } + commandLine.numberOfLines = codeLines.length; /** @type {string[]} */ var outputLines = commandLine.outputLines = []; @@ -168,15 +178,29 @@ } // Create the "rows" that will become the command-line prompts. -- cwells - var promptLines; + var promptLines = ''; var rowCount = commandLine.numberOfLines || 0; var promptText = getAttribute('data-prompt', ''); + var promptLine; if (promptText !== '') { - promptLines = repeat('', rowCount); + promptLine = ''; } else { var user = getAttribute('data-user', 'user'); var host = getAttribute('data-host', 'localhost'); - promptLines = repeat('', rowCount); + promptLine = ''; + } + + var continuationLineIndicies = commandLine.continuationLineIndicies || new Set(); + var continuationPromptText = getAttribute('data-continuation-prompt', '>'); + var continuationPromptLine = ''; + + // Assemble all the appropriate prompt/continuation lines + for (var j = 0; j < rowCount; j++) { + if (continuationLineIndicies.has(j)) { + promptLines += continuationPromptLine; + } else { + promptLines += promptLine; + } } // Create the wrapper element. -- cwells diff --git a/plugins/command-line/prism-command-line.min.css b/plugins/command-line/prism-command-line.min.css index 97b41d587e..5e5c875d7e 100644 --- a/plugins/command-line/prism-command-line.min.css +++ b/plugins/command-line/prism-command-line.min.css @@ -1 +1 @@ -.command-line-prompt{border-right:1px solid #999;display:block;float:left;font-size:100%;letter-spacing:-1px;margin-right:1em;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.command-line-prompt>span:before{color:#999;content:' ';display:block;padding-right:.8em}.command-line-prompt>span[data-user]:before{content:"[" attr(data-user) "@" attr(data-host) "] $"}.command-line-prompt>span[data-user=root]:before{content:"[" attr(data-user) "@" attr(data-host) "] #"}.command-line-prompt>span[data-prompt]:before{content:attr(data-prompt)} \ No newline at end of file +.command-line-prompt{border-right:1px solid #999;display:block;float:left;font-size:100%;letter-spacing:-1px;margin-right:1em;pointer-events:none;text-align:right;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.command-line-prompt>span:before{opacity:.4;content:' ';display:block;padding-right:.8em}.command-line-prompt>span[data-user]:before{content:"[" attr(data-user) "@" attr(data-host) "] $"}.command-line-prompt>span[data-user=root]:before{content:"[" attr(data-user) "@" attr(data-host) "] #"}.command-line-prompt>span[data-prompt]:before{content:attr(data-prompt)}.command-line-prompt>span[data-continuation-prompt]:before{content:attr(data-continuation-prompt)}.command-line span.token.output{opacity:.7} \ No newline at end of file diff --git a/plugins/command-line/prism-command-line.min.js b/plugins/command-line/prism-command-line.min.js index cfef753cba..ee6e6a47e7 100644 --- a/plugins/command-line/prism-command-line.min.js +++ b/plugins/command-line/prism-command-line.min.js @@ -1 +1 @@ -!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var p=/(?:^|\s)command-line(?:\s|$)/,d="command-line-prompt",m="".startsWith?function(e,t){return e.startsWith(t)}:function(e,t){return 0===e.indexOf(t)};Prism.hooks.add("before-highlight",function(e){var t=h(e);if(!t.complete&&e.code){var n=e.element.parentElement;if(n&&/pre/i.test(n.nodeName)&&(p.test(n.className)||p.test(e.element.className))){var a=e.element.querySelector("."+d);a&&a.remove();var s=e.code.split("\n");t.numberOfLines=s.length;var o=t.outputLines=[],r=n.getAttribute("data-output"),i=n.getAttribute("data-filter-output");if(null!==r)r.split(",").forEach(function(e){var t=e.split("-"),n=parseInt(t[0],10),a=2===t.length?parseInt(t[1],10):n;if(!isNaN(n)&&!isNaN(a)){n<1&&(n=1),a>s.length&&(a=s.length),a--;for(var r=--n;r<=a;r++)o[r]=s[r],s[r]=""}});else if(i)for(var l=0;l'+a[r]+"":n[r]=''+n[r]+"";e.highlightedCode=n.join("\n")}}),Prism.hooks.add("complete",function(e){if(function(e){return"command-line"in(e.vars=e.vars||{})}(e)){var t=h(e);if(!t.complete){var n,a=e.element.parentElement;p.test(e.element.className)&&(e.element.className=e.element.className.replace(p," ")),p.test(a.className)||(a.className+=" command-line");var r=t.numberOfLines||0,s=u("data-prompt","");if(""!==s)n=f('',r);else n=f('',r);var o=document.createElement("span");o.className=d,o.innerHTML=n;for(var i=t.outputLines||[],l=0,m=i.length;li.length&&(a=i.length),a--;for(var r=--n;r<=a;r++)l[r]=i[r],i[r]=""}});else if(u)for(var c=0;c'+a[r]+"":n[r]=''+n[r]+"";e.highlightedCode=n.join("\n")}}),Prism.hooks.add("complete",function(e){if(function(e){return"command-line"in(e.vars=e.vars||{})}(e)){var t=N(e);if(!t.complete){var n=e.element.parentElement;v.test(e.element.className)&&(e.element.className=e.element.className.replace(v," ")),v.test(n.className)||(n.className+=" command-line");var a,r="",i=t.numberOfLines||0,s=h("data-prompt","");if(""!==s)a='';else a='';for(var o=t.continuationLineIndicies||new Set,l='")+'">',m=0;m