markdown.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*
  2. Language: Markdown
  3. Requires: xml.js
  4. Author: John Crepezzi <john.crepezzi@gmail.com>
  5. Website: https://daringfireball.net/projects/markdown/
  6. Category: common, markup
  7. */
  8. function markdown(hljs) {
  9. const regex = hljs.regex;
  10. const INLINE_HTML = {
  11. begin: /<\/?[A-Za-z_]/,
  12. end: '>',
  13. subLanguage: 'xml',
  14. relevance: 0
  15. };
  16. const HORIZONTAL_RULE = {
  17. begin: '^[-\\*]{3,}',
  18. end: '$'
  19. };
  20. const CODE = {
  21. className: 'code',
  22. variants: [
  23. // TODO: fix to allow these to work with sublanguage also
  24. { begin: '(`{3,})[^`](.|\\n)*?\\1`*[ ]*' },
  25. { begin: '(~{3,})[^~](.|\\n)*?\\1~*[ ]*' },
  26. // needed to allow markdown as a sublanguage to work
  27. {
  28. begin: '```',
  29. end: '```+[ ]*$'
  30. },
  31. {
  32. begin: '~~~',
  33. end: '~~~+[ ]*$'
  34. },
  35. { begin: '`.+?`' },
  36. {
  37. begin: '(?=^( {4}|\\t))',
  38. // use contains to gobble up multiple lines to allow the block to be whatever size
  39. // but only have a single open/close tag vs one per line
  40. contains: [
  41. {
  42. begin: '^( {4}|\\t)',
  43. end: '(\\n)$'
  44. }
  45. ],
  46. relevance: 0
  47. }
  48. ]
  49. };
  50. const LIST = {
  51. className: 'bullet',
  52. begin: '^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)',
  53. end: '\\s+',
  54. excludeEnd: true
  55. };
  56. const LINK_REFERENCE = {
  57. begin: /^\[[^\n]+\]:/,
  58. returnBegin: true,
  59. contains: [
  60. {
  61. className: 'symbol',
  62. begin: /\[/,
  63. end: /\]/,
  64. excludeBegin: true,
  65. excludeEnd: true
  66. },
  67. {
  68. className: 'link',
  69. begin: /:\s*/,
  70. end: /$/,
  71. excludeBegin: true
  72. }
  73. ]
  74. };
  75. const URL_SCHEME = /[A-Za-z][A-Za-z0-9+.-]*/;
  76. const LINK = {
  77. variants: [
  78. // too much like nested array access in so many languages
  79. // to have any real relevance
  80. {
  81. begin: /\[.+?\]\[.*?\]/,
  82. relevance: 0
  83. },
  84. // popular internet URLs
  85. {
  86. begin: /\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,
  87. relevance: 2
  88. },
  89. {
  90. begin: regex.concat(/\[.+?\]\(/, URL_SCHEME, /:\/\/.*?\)/),
  91. relevance: 2
  92. },
  93. // relative urls
  94. {
  95. begin: /\[.+?\]\([./?&#].*?\)/,
  96. relevance: 1
  97. },
  98. // whatever else, lower relevance (might not be a link at all)
  99. {
  100. begin: /\[.*?\]\(.*?\)/,
  101. relevance: 0
  102. }
  103. ],
  104. returnBegin: true,
  105. contains: [
  106. {
  107. // empty strings for alt or link text
  108. match: /\[(?=\])/ },
  109. {
  110. className: 'string',
  111. relevance: 0,
  112. begin: '\\[',
  113. end: '\\]',
  114. excludeBegin: true,
  115. returnEnd: true
  116. },
  117. {
  118. className: 'link',
  119. relevance: 0,
  120. begin: '\\]\\(',
  121. end: '\\)',
  122. excludeBegin: true,
  123. excludeEnd: true
  124. },
  125. {
  126. className: 'symbol',
  127. relevance: 0,
  128. begin: '\\]\\[',
  129. end: '\\]',
  130. excludeBegin: true,
  131. excludeEnd: true
  132. }
  133. ]
  134. };
  135. const BOLD = {
  136. className: 'strong',
  137. contains: [], // defined later
  138. variants: [
  139. {
  140. begin: /_{2}(?!\s)/,
  141. end: /_{2}/
  142. },
  143. {
  144. begin: /\*{2}(?!\s)/,
  145. end: /\*{2}/
  146. }
  147. ]
  148. };
  149. const ITALIC = {
  150. className: 'emphasis',
  151. contains: [], // defined later
  152. variants: [
  153. {
  154. begin: /\*(?![*\s])/,
  155. end: /\*/
  156. },
  157. {
  158. begin: /_(?![_\s])/,
  159. end: /_/,
  160. relevance: 0
  161. }
  162. ]
  163. };
  164. // 3 level deep nesting is not allowed because it would create confusion
  165. // in cases like `***testing***` because where we don't know if the last
  166. // `***` is starting a new bold/italic or finishing the last one
  167. const BOLD_WITHOUT_ITALIC = hljs.inherit(BOLD, { contains: [] });
  168. const ITALIC_WITHOUT_BOLD = hljs.inherit(ITALIC, { contains: [] });
  169. BOLD.contains.push(ITALIC_WITHOUT_BOLD);
  170. ITALIC.contains.push(BOLD_WITHOUT_ITALIC);
  171. let CONTAINABLE = [
  172. INLINE_HTML,
  173. LINK
  174. ];
  175. [
  176. BOLD,
  177. ITALIC,
  178. BOLD_WITHOUT_ITALIC,
  179. ITALIC_WITHOUT_BOLD
  180. ].forEach(m => {
  181. m.contains = m.contains.concat(CONTAINABLE);
  182. });
  183. CONTAINABLE = CONTAINABLE.concat(BOLD, ITALIC);
  184. const HEADER = {
  185. className: 'section',
  186. variants: [
  187. {
  188. begin: '^#{1,6}',
  189. end: '$',
  190. contains: CONTAINABLE
  191. },
  192. {
  193. begin: '(?=^.+?\\n[=-]{2,}$)',
  194. contains: [
  195. { begin: '^[=-]*$' },
  196. {
  197. begin: '^',
  198. end: "\\n",
  199. contains: CONTAINABLE
  200. }
  201. ]
  202. }
  203. ]
  204. };
  205. const BLOCKQUOTE = {
  206. className: 'quote',
  207. begin: '^>\\s+',
  208. contains: CONTAINABLE,
  209. end: '$'
  210. };
  211. return {
  212. name: 'Markdown',
  213. aliases: [
  214. 'md',
  215. 'mkdown',
  216. 'mkd'
  217. ],
  218. contains: [
  219. HEADER,
  220. INLINE_HTML,
  221. LIST,
  222. BOLD,
  223. ITALIC,
  224. BLOCKQUOTE,
  225. CODE,
  226. HORIZONTAL_RULE,
  227. LINK,
  228. LINK_REFERENCE
  229. ]
  230. };
  231. }
  232. module.exports = markdown;