UNPKG

7.91 kBJavaScriptView Raw
1(function () {
2
3 if (typeof Prism === 'undefined' || typeof document === 'undefined') {
4 return;
5 }
6
7 var CLASS_PATTERN = /(?:^|\s)command-line(?:\s|$)/;
8 var PROMPT_CLASS = 'command-line-prompt';
9
10 /** @type {(str: string, prefix: string) => boolean} */
11 var startsWith = ''.startsWith
12 ? function (s, p) { return s.startsWith(p); }
13 : function (s, p) { return s.indexOf(p) === 0; };
14
15 // Support for IE11 that has no endsWith()
16 /** @type {(str: string, suffix: string) => boolean} */
17 var endsWith = ''.endsWith
18 ? function (str, suffix) {
19 return str.endsWith(suffix);
20 }
21 : function (str, suffix) {
22 var len = str.length;
23 return str.substring(len - suffix.length, len) === suffix;
24 };
25
26 /**
27 * Returns whether the given hook environment has a command line info object.
28 *
29 * @param {any} env
30 * @returns {boolean}
31 */
32 function hasCommandLineInfo(env) {
33 var vars = env.vars = env.vars || {};
34 return 'command-line' in vars;
35 }
36 /**
37 * Returns the command line info object from the given hook environment.
38 *
39 * @param {any} env
40 * @returns {CommandLineInfo}
41 *
42 * @typedef CommandLineInfo
43 * @property {boolean} [complete]
44 * @property {number} [numberOfLines]
45 * @property {string[]} [outputLines]
46 */
47 function getCommandLineInfo(env) {
48 var vars = env.vars = env.vars || {};
49 return vars['command-line'] = vars['command-line'] || {};
50 }
51
52
53 Prism.hooks.add('before-highlight', function (env) {
54 var commandLine = getCommandLineInfo(env);
55
56 if (commandLine.complete || !env.code) {
57 commandLine.complete = true;
58 return;
59 }
60
61 // Works only for <code> wrapped inside <pre> (not inline).
62 var pre = env.element.parentElement;
63 if (!pre || !/pre/i.test(pre.nodeName) || // Abort only if neither the <pre> nor the <code> have the class
64 (!CLASS_PATTERN.test(pre.className) && !CLASS_PATTERN.test(env.element.className))) {
65 commandLine.complete = true;
66 return;
67 }
68
69 // The element might be highlighted multiple times, so we just remove the previous prompt
70 var existingPrompt = env.element.querySelector('.' + PROMPT_CLASS);
71 if (existingPrompt) {
72 existingPrompt.remove();
73 }
74
75 var codeLines = env.code.split('\n');
76
77 commandLine.numberOfLines = codeLines.length;
78 /** @type {string[]} */
79 var outputLines = commandLine.outputLines = [];
80
81 var outputSections = pre.getAttribute('data-output');
82 var outputFilter = pre.getAttribute('data-filter-output');
83 if (outputSections !== null) { // The user specified the output lines. -- cwells
84 outputSections.split(',').forEach(function (section) {
85 var range = section.split('-');
86 var outputStart = parseInt(range[0], 10);
87 var outputEnd = range.length === 2 ? parseInt(range[1], 10) : outputStart;
88
89 if (!isNaN(outputStart) && !isNaN(outputEnd)) {
90 if (outputStart < 1) {
91 outputStart = 1;
92 }
93 if (outputEnd > codeLines.length) {
94 outputEnd = codeLines.length;
95 }
96 // Convert start and end to 0-based to simplify the arrays. -- cwells
97 outputStart--;
98 outputEnd--;
99 // Save the output line in an array and clear it in the code so it's not highlighted. -- cwells
100 for (var j = outputStart; j <= outputEnd; j++) {
101 outputLines[j] = codeLines[j];
102 codeLines[j] = '';
103 }
104 }
105 });
106 } else if (outputFilter) { // Treat lines beginning with this string as output. -- cwells
107 for (var i = 0; i < codeLines.length; i++) {
108 if (startsWith(codeLines[i], outputFilter)) { // This line is output. -- cwells
109 outputLines[i] = codeLines[i].slice(outputFilter.length);
110 codeLines[i] = '';
111 }
112 }
113 }
114
115 var continuationLineIndicies = commandLine.continuationLineIndicies = new Set();
116 var lineContinuationStr = pre.getAttribute('data-continuation-str');
117 var continuationFilter = pre.getAttribute('data-filter-continuation');
118
119 // Identify code lines where the command has continued onto subsequent
120 // lines and thus need a different prompt. Need to do this after the output
121 // lines have been removed to ensure we don't pick up a continuation string
122 // in an output line.
123 for (var j = 0; j < codeLines.length; j++) {
124 var line = codeLines[j];
125 if (!line) {
126 continue;
127 }
128
129 // Record the next line as a continuation if this one ends in a continuation str.
130 if (lineContinuationStr && endsWith(line, lineContinuationStr)) {
131 continuationLineIndicies.add(j + 1);
132 }
133 // Record this line as a continuation if marked with a continuation prefix
134 // (that we will remove).
135 if (j > 0 && continuationFilter && startsWith(line, continuationFilter)) {
136 codeLines[j] = line.slice(continuationFilter.length);
137 continuationLineIndicies.add(j);
138 }
139 }
140
141 env.code = codeLines.join('\n');
142 });
143
144 Prism.hooks.add('before-insert', function (env) {
145 var commandLine = getCommandLineInfo(env);
146
147 if (commandLine.complete) {
148 return;
149 }
150
151 // Reinsert the output lines into the highlighted code. -- cwells
152 var codeLines = env.highlightedCode.split('\n');
153 var outputLines = commandLine.outputLines || [];
154 for (var i = 0, l = codeLines.length; i < l; i++) {
155 // Add spans to allow distinction of input/output text for styling
156 if (outputLines.hasOwnProperty(i)) {
157 // outputLines were removed from codeLines so missed out on escaping
158 // of markup so do it here.
159 codeLines[i] = '<span class="token output">'
160 + Prism.util.encode(outputLines[i]) + '</span>';
161 } else {
162 codeLines[i] = '<span class="token command">'
163 + codeLines[i] + '</span>';
164 }
165 }
166 env.highlightedCode = codeLines.join('\n');
167 });
168
169 Prism.hooks.add('complete', function (env) {
170 if (!hasCommandLineInfo(env)) {
171 // the previous hooks never ran
172 return;
173 }
174
175 var commandLine = getCommandLineInfo(env);
176
177 if (commandLine.complete) {
178 return;
179 }
180
181 var pre = env.element.parentElement;
182 if (CLASS_PATTERN.test(env.element.className)) { // Remove the class "command-line" from the <code>
183 env.element.className = env.element.className.replace(CLASS_PATTERN, ' ');
184 }
185 if (!CLASS_PATTERN.test(pre.className)) { // Add the class "command-line" to the <pre>
186 pre.className += ' command-line';
187 }
188
189 function getAttribute(key, defaultValue) {
190 return (pre.getAttribute(key) || defaultValue).replace(/"/g, '&quot');
191 }
192
193 // Create the "rows" that will become the command-line prompts. -- cwells
194 var promptLines = '';
195 var rowCount = commandLine.numberOfLines || 0;
196 var promptText = getAttribute('data-prompt', '');
197 var promptLine;
198 if (promptText !== '') {
199 promptLine = '<span data-prompt="' + promptText + '"></span>';
200 } else {
201 var user = getAttribute('data-user', 'user');
202 var host = getAttribute('data-host', 'localhost');
203 promptLine = '<span data-user="' + user + '" data-host="' + host + '"></span>';
204 }
205
206 var continuationLineIndicies = commandLine.continuationLineIndicies || new Set();
207 var continuationPromptText = getAttribute('data-continuation-prompt', '>');
208 var continuationPromptLine = '<span data-continuation-prompt="' + continuationPromptText + '"></span>';
209
210 // Assemble all the appropriate prompt/continuation lines
211 for (var j = 0; j < rowCount; j++) {
212 if (continuationLineIndicies.has(j)) {
213 promptLines += continuationPromptLine;
214 } else {
215 promptLines += promptLine;
216 }
217 }
218
219 // Create the wrapper element. -- cwells
220 var prompt = document.createElement('span');
221 prompt.className = PROMPT_CLASS;
222 prompt.innerHTML = promptLines;
223
224 // Remove the prompt from the output lines. -- cwells
225 var outputLines = commandLine.outputLines || [];
226 for (var i = 0, l = outputLines.length; i < l; i++) {
227 if (outputLines.hasOwnProperty(i)) {
228 var node = prompt.children[i];
229 node.removeAttribute('data-user');
230 node.removeAttribute('data-host');
231 node.removeAttribute('data-prompt');
232 }
233 }
234
235 env.element.insertBefore(prompt, env.element.firstChild);
236 commandLine.complete = true;
237 });
238
239}());