prism-command-line.js 5.5 KB

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