UNPKG

5.66 kBJavaScriptView Raw
1(function () {
2
3 if (typeof self === 'undefined' || !self.Prism || !self.document) {
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 /**
16 * Repeats the given string some number of times.
17 *
18 * This is just a polyfill for `String.prototype.repeat`.
19 *
20 * @param {string} str
21 * @param {number} times
22 * @returns {string}
23 */
24 function repeat(str, times) {
25 var s = "";
26 for (var i = 0; i < times; i++) {
27 s += str;
28 }
29 return s;
30 }
31
32 /**
33 * Returns the command line info object from the given hook environment.
34 *
35 * @param {any} env
36 * @returns {CommandLineInfo}
37 *
38 * @typedef CommandLineInfo
39 * @property {boolean} [complete]
40 * @property {number} [numberOfLines]
41 * @property {string[]} [outputLines]
42 */
43 function getCommandLineInfo(env) {
44 var vars = env.vars = env.vars || {};
45 return vars['command-line'] = vars['command-line'] || {};
46 }
47
48
49 Prism.hooks.add('before-highlight', function (env) {
50 var commandLine = getCommandLineInfo(env);
51
52 if (commandLine.complete || !env.code) {
53 commandLine.complete = true;
54 return;
55 }
56
57 // Works only for <code> wrapped inside <pre> (not inline).
58 var pre = env.element.parentElement;
59 if (!pre || !/pre/i.test(pre.nodeName) || // Abort only if neither the <pre> nor the <code> have the class
60 (!CLASS_PATTERN.test(pre.className) && !CLASS_PATTERN.test(env.element.className))) {
61 commandLine.complete = true;
62 return;
63 }
64
65 // The element might be highlighted multiple times, so we just remove the previous prompt
66 var existingPrompt = env.element.querySelector('.' + PROMPT_CLASS);
67 if (existingPrompt) {
68 existingPrompt.remove();
69 }
70
71 var codeLines = env.code.split('\n');
72 commandLine.numberOfLines = codeLines.length;
73 /** @type {string[]} */
74 var outputLines = commandLine.outputLines = [];
75
76 var outputSections = pre.getAttribute('data-output');
77 var outputFilter = pre.getAttribute('data-filter-output');
78 if (outputSections !== null) { // The user specified the output lines. -- cwells
79 outputSections.split(',').forEach(function (section) {
80 var range = section.split('-');
81 var outputStart = parseInt(range[0], 10);
82 var outputEnd = range.length === 2 ? parseInt(range[1], 10) : outputStart;
83
84 if (!isNaN(outputStart) && !isNaN(outputEnd)) {
85 if (outputStart < 1) {
86 outputStart = 1;
87 }
88 if (outputEnd > codeLines.length) {
89 outputEnd = codeLines.length;
90 }
91 // Convert start and end to 0-based to simplify the arrays. -- cwells
92 outputStart--;
93 outputEnd--;
94 // Save the output line in an array and clear it in the code so it's not highlighted. -- cwells
95 for (var j = outputStart; j <= outputEnd; j++) {
96 outputLines[j] = codeLines[j];
97 codeLines[j] = '';
98 }
99 }
100 });
101 } else if (outputFilter) { // Treat lines beginning with this string as output. -- cwells
102 for (var i = 0; i < codeLines.length; i++) {
103 if (startsWith(codeLines[i], outputFilter)) { // This line is output. -- cwells
104 outputLines[i] = codeLines[i].slice(outputFilter.length);
105 codeLines[i] = '';
106 }
107 }
108 }
109
110 env.code = codeLines.join('\n');
111 });
112
113 Prism.hooks.add('before-insert', function (env) {
114 var commandLine = getCommandLineInfo(env);
115
116 if (commandLine.complete) {
117 return;
118 }
119
120 // Reinsert the output lines into the highlighted code. -- cwells
121 var codeLines = env.highlightedCode.split('\n');
122 var outputLines = commandLine.outputLines || [];
123 for (var i = 0, l = outputLines.length; i < l; i++) {
124 if (outputLines.hasOwnProperty(i)) {
125 codeLines[i] = outputLines[i];
126 }
127 }
128 env.highlightedCode = codeLines.join('\n');
129 });
130
131 Prism.hooks.add('complete', function (env) {
132 var commandLine = getCommandLineInfo(env);
133
134 if (commandLine.complete) {
135 return;
136 }
137
138 var pre = env.element.parentElement;
139 if (CLASS_PATTERN.test(env.element.className)) { // Remove the class "command-line" from the <code>
140 env.element.className = env.element.className.replace(CLASS_PATTERN, ' ');
141 }
142 if (!CLASS_PATTERN.test(pre.className)) { // Add the class "command-line" to the <pre>
143 pre.className += ' command-line';
144 }
145
146 function getAttribute(key, defaultValue) {
147 return (pre.getAttribute(key) || defaultValue).replace(/"/g, '&quot');
148 }
149
150 // Create the "rows" that will become the command-line prompts. -- cwells
151 var promptLines;
152 var rowCount = commandLine.numberOfLines || 0;
153 var promptText = getAttribute('data-prompt', '');
154 if (promptText !== '') {
155 promptLines = repeat('<span data-prompt="' + promptText + '"></span>', rowCount);
156 } else {
157 var user = getAttribute('data-user', 'user');
158 var host = getAttribute('data-host', 'localhost');
159 promptLines = repeat('<span data-user="' + user + '" data-host="' + host + '"></span>', rowCount);
160 }
161
162 // Create the wrapper element. -- cwells
163 var prompt = document.createElement('span');
164 prompt.className = PROMPT_CLASS;
165 prompt.innerHTML = promptLines;
166
167 // Remove the prompt from the output lines. -- cwells
168 var outputLines = commandLine.outputLines || [];
169 for (var i = 0, l = outputLines.length; i < l; i++) {
170 if (outputLines.hasOwnProperty(i)) {
171 var node = prompt.children[i];
172 node.removeAttribute('data-user');
173 node.removeAttribute('data-host');
174 node.removeAttribute('data-prompt');
175 }
176 }
177
178 env.element.insertBefore(prompt, env.element.firstChild);
179 commandLine.complete = true;
180 });
181
182}());