prism-markdown.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. (function (Prism) {
  2. // Allow only one line break
  3. var inner = /(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?!\n|\r\n?))/.source;
  4. /**
  5. * This function is intended for the creation of the bold or italic pattern.
  6. *
  7. * This also adds a lookbehind group to the given pattern to ensure that the pattern is not backslash-escaped.
  8. *
  9. * _Note:_ Keep in mind that this adds a capturing group.
  10. *
  11. * @param {string} pattern
  12. * @returns {RegExp}
  13. */
  14. function createInline(pattern) {
  15. pattern = pattern.replace(/<inner>/g, function () { return inner; });
  16. return RegExp(/((?:^|[^\\])(?:\\{2})*)/.source + '(?:' + pattern + ')');
  17. }
  18. var tableCell = /(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source;
  19. var tableRow = /\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|$)/.source.replace(/__/g, function () { return tableCell; });
  20. var tableLine = /\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;
  21. Prism.languages.markdown = Prism.languages.extend('markup', {});
  22. Prism.languages.insertBefore('markdown', 'prolog', {
  23. 'blockquote': {
  24. // > ...
  25. pattern: /^>(?:[\t ]*>)*/m,
  26. alias: 'punctuation'
  27. },
  28. 'table': {
  29. pattern: RegExp('^' + tableRow + tableLine + '(?:' + tableRow + ')*', 'm'),
  30. inside: {
  31. 'table-data-rows': {
  32. pattern: RegExp('^(' + tableRow + tableLine + ')(?:' + tableRow + ')*$'),
  33. lookbehind: true,
  34. inside: {
  35. 'table-data': {
  36. pattern: RegExp(tableCell),
  37. inside: Prism.languages.markdown
  38. },
  39. 'punctuation': /\|/
  40. }
  41. },
  42. 'table-line': {
  43. pattern: RegExp('^(' + tableRow + ')' + tableLine + '$'),
  44. lookbehind: true,
  45. inside: {
  46. 'punctuation': /\||:?-{3,}:?/
  47. }
  48. },
  49. 'table-header-row': {
  50. pattern: RegExp('^' + tableRow + '$'),
  51. inside: {
  52. 'table-header': {
  53. pattern: RegExp(tableCell),
  54. alias: 'important',
  55. inside: Prism.languages.markdown
  56. },
  57. 'punctuation': /\|/
  58. }
  59. }
  60. }
  61. },
  62. 'code': [
  63. {
  64. // Prefixed by 4 spaces or 1 tab and preceded by an empty line
  65. pattern: /((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,
  66. lookbehind: true,
  67. alias: 'keyword'
  68. },
  69. {
  70. // `code`
  71. // ``code``
  72. pattern: /``.+?``|`[^`\r\n]+`/,
  73. alias: 'keyword'
  74. },
  75. {
  76. // ```optional language
  77. // code block
  78. // ```
  79. pattern: /^```[\s\S]*?^```$/m,
  80. greedy: true,
  81. inside: {
  82. 'code-block': {
  83. pattern: /^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,
  84. lookbehind: true
  85. },
  86. 'code-language': {
  87. pattern: /^(```).+/,
  88. lookbehind: true
  89. },
  90. 'punctuation': /```/
  91. }
  92. }
  93. ],
  94. 'title': [
  95. {
  96. // title 1
  97. // =======
  98. // title 2
  99. // -------
  100. pattern: /\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,
  101. alias: 'important',
  102. inside: {
  103. punctuation: /==+$|--+$/
  104. }
  105. },
  106. {
  107. // # title 1
  108. // ###### title 6
  109. pattern: /(^\s*)#+.+/m,
  110. lookbehind: true,
  111. alias: 'important',
  112. inside: {
  113. punctuation: /^#+|#+$/
  114. }
  115. }
  116. ],
  117. 'hr': {
  118. // ***
  119. // ---
  120. // * * *
  121. // -----------
  122. pattern: /(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,
  123. lookbehind: true,
  124. alias: 'punctuation'
  125. },
  126. 'list': {
  127. // * item
  128. // + item
  129. // - item
  130. // 1. item
  131. pattern: /(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,
  132. lookbehind: true,
  133. alias: 'punctuation'
  134. },
  135. 'url-reference': {
  136. // [id]: http://example.com "Optional title"
  137. // [id]: http://example.com 'Optional title'
  138. // [id]: http://example.com (Optional title)
  139. // [id]: <http://example.com> "Optional title"
  140. pattern: /!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,
  141. inside: {
  142. 'variable': {
  143. pattern: /^(!?\[)[^\]]+/,
  144. lookbehind: true
  145. },
  146. 'string': /(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,
  147. 'punctuation': /^[\[\]!:]|[<>]/
  148. },
  149. alias: 'url'
  150. },
  151. 'bold': {
  152. // **strong**
  153. // __strong__
  154. // allow one nested instance of italic text using the same delimiter
  155. pattern: createInline(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),
  156. lookbehind: true,
  157. greedy: true,
  158. inside: {
  159. 'content': {
  160. pattern: /(^..)[\s\S]+(?=..$)/,
  161. lookbehind: true,
  162. inside: {} // see below
  163. },
  164. 'punctuation': /\*\*|__/
  165. }
  166. },
  167. 'italic': {
  168. // *em*
  169. // _em_
  170. // allow one nested instance of bold text using the same delimiter
  171. pattern: createInline(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),
  172. lookbehind: true,
  173. greedy: true,
  174. inside: {
  175. 'content': {
  176. pattern: /(^.)[\s\S]+(?=.$)/,
  177. lookbehind: true,
  178. inside: {} // see below
  179. },
  180. 'punctuation': /[*_]/
  181. }
  182. },
  183. 'strike': {
  184. // ~~strike through~~
  185. // ~strike~
  186. pattern: createInline(/(~~?)(?:(?!~)<inner>)+?\2/.source),
  187. lookbehind: true,
  188. greedy: true,
  189. inside: {
  190. 'content': {
  191. pattern: /(^~~?)[\s\S]+(?=\1$)/,
  192. lookbehind: true,
  193. inside: {} // see below
  194. },
  195. 'punctuation': /~~?/
  196. }
  197. },
  198. 'url': {
  199. // [example](http://example.com "Optional title")
  200. // [example][id]
  201. // [example] [id]
  202. pattern: createInline(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[(?:(?!\])<inner>)+\])/.source),
  203. lookbehind: true,
  204. greedy: true,
  205. inside: {
  206. 'variable': {
  207. pattern: /(\[)[^\]]+(?=\]$)/,
  208. lookbehind: true
  209. },
  210. 'content': {
  211. pattern: /(^!?\[)[^\]]+(?=\])/,
  212. lookbehind: true,
  213. inside: {} // see below
  214. },
  215. 'string': {
  216. pattern: /"(?:\\.|[^"\\])*"(?=\)$)/
  217. }
  218. }
  219. }
  220. });
  221. ['url', 'bold', 'italic', 'strike'].forEach(function (token) {
  222. ['url', 'bold', 'italic', 'strike'].forEach(function (inside) {
  223. if (token !== inside) {
  224. Prism.languages.markdown[token].inside.content.inside[inside] = Prism.languages.markdown[inside];
  225. }
  226. });
  227. });
  228. Prism.hooks.add('after-tokenize', function (env) {
  229. if (env.language !== 'markdown' && env.language !== 'md') {
  230. return;
  231. }
  232. function walkTokens(tokens) {
  233. if (!tokens || typeof tokens === 'string') {
  234. return;
  235. }
  236. for (var i = 0, l = tokens.length; i < l; i++) {
  237. var token = tokens[i];
  238. if (token.type !== 'code') {
  239. walkTokens(token.content);
  240. continue;
  241. }
  242. /*
  243. * Add the correct `language-xxxx` class to this code block. Keep in mind that the `code-language` token
  244. * is optional. But the grammar is defined so that there is only one case we have to handle:
  245. *
  246. * token.content = [
  247. * <span class="punctuation">```</span>,
  248. * <span class="code-language">xxxx</span>,
  249. * '\n', // exactly one new lines (\r or \n or \r\n)
  250. * <span class="code-block">...</span>,
  251. * '\n', // exactly one new lines again
  252. * <span class="punctuation">```</span>
  253. * ];
  254. */
  255. var codeLang = token.content[1];
  256. var codeBlock = token.content[3];
  257. if (codeLang && codeBlock &&
  258. codeLang.type === 'code-language' && codeBlock.type === 'code-block' &&
  259. typeof codeLang.content === 'string') {
  260. // this might be a language that Prism does not support
  261. // do some replacements to support C++, C#, and F#
  262. var lang = codeLang.content.replace(/\b#/g, 'sharp').replace(/\b\+\+/g, 'pp')
  263. // only use the first word
  264. lang = (/[a-z][\w-]*/i.exec(lang) || [''])[0].toLowerCase();
  265. var alias = 'language-' + lang;
  266. // add alias
  267. if (!codeBlock.alias) {
  268. codeBlock.alias = [alias];
  269. } else if (typeof codeBlock.alias === 'string') {
  270. codeBlock.alias = [codeBlock.alias, alias];
  271. } else {
  272. codeBlock.alias.push(alias);
  273. }
  274. }
  275. }
  276. }
  277. walkTokens(env.tokens);
  278. });
  279. Prism.hooks.add('wrap', function (env) {
  280. if (env.type !== 'code-block') {
  281. return;
  282. }
  283. var codeLang = '';
  284. for (var i = 0, l = env.classes.length; i < l; i++) {
  285. var cls = env.classes[i];
  286. var match = /language-(.+)/.exec(cls);
  287. if (match) {
  288. codeLang = match[1];
  289. break;
  290. }
  291. }
  292. var grammar = Prism.languages[codeLang];
  293. if (!grammar) {
  294. if (codeLang && codeLang !== 'none' && Prism.plugins.autoloader) {
  295. var id = 'md-' + new Date().valueOf() + '-' + Math.floor(Math.random() * 1e16);
  296. env.attributes['id'] = id;
  297. Prism.plugins.autoloader.loadLanguages(codeLang, function () {
  298. var ele = document.getElementById(id);
  299. if (ele) {
  300. ele.innerHTML = Prism.highlight(ele.textContent, Prism.languages[codeLang], codeLang);
  301. }
  302. });
  303. }
  304. } else {
  305. // reverse Prism.util.encode
  306. var code = env.content.replace(/&lt;/g, '<').replace(/&amp;/g, '&');
  307. env.content = Prism.highlight(code, grammar, codeLang);
  308. }
  309. });
  310. Prism.languages.md = Prism.languages.markdown;
  311. }(Prism));