latex.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /**
  2. * @param {string} value
  3. * @returns {RegExp}
  4. * */
  5. /**
  6. * @param {RegExp | string } re
  7. * @returns {string}
  8. */
  9. function source(re) {
  10. if (!re) return null;
  11. if (typeof re === "string") return re;
  12. return re.source;
  13. }
  14. /**
  15. * Any of the passed expresssions may match
  16. *
  17. * Creates a huge this | this | that | that match
  18. * @param {(RegExp | string)[] } args
  19. * @returns {string}
  20. */
  21. function either(...args) {
  22. const joined = '(' + args.map((x) => source(x)).join("|") + ")";
  23. return joined;
  24. }
  25. /*
  26. Language: LaTeX
  27. Author: Benedikt Wilde <bwilde@posteo.de>
  28. Website: https://www.latex-project.org
  29. Category: markup
  30. */
  31. /** @type LanguageFn */
  32. function latex(hljs) {
  33. const KNOWN_CONTROL_WORDS = either(...[
  34. '(?:NeedsTeXFormat|RequirePackage|GetIdInfo)',
  35. 'Provides(?:Expl)?(?:Package|Class|File)',
  36. '(?:DeclareOption|ProcessOptions)',
  37. '(?:documentclass|usepackage|input|include)',
  38. 'makeat(?:letter|other)',
  39. 'ExplSyntax(?:On|Off)',
  40. '(?:new|renew|provide)?command',
  41. '(?:re)newenvironment',
  42. '(?:New|Renew|Provide|Declare)(?:Expandable)?DocumentCommand',
  43. '(?:New|Renew|Provide|Declare)DocumentEnvironment',
  44. '(?:(?:e|g|x)?def|let)',
  45. '(?:begin|end)',
  46. '(?:part|chapter|(?:sub){0,2}section|(?:sub)?paragraph)',
  47. 'caption',
  48. '(?:label|(?:eq|page|name)?ref|(?:paren|foot|super)?cite)',
  49. '(?:alpha|beta|[Gg]amma|[Dd]elta|(?:var)?epsilon|zeta|eta|[Tt]heta|vartheta)',
  50. '(?:iota|(?:var)?kappa|[Ll]ambda|mu|nu|[Xx]i|[Pp]i|varpi|(?:var)rho)',
  51. '(?:[Ss]igma|varsigma|tau|[Uu]psilon|[Pp]hi|varphi|chi|[Pp]si|[Oo]mega)',
  52. '(?:frac|sum|prod|lim|infty|times|sqrt|leq|geq|left|right|middle|[bB]igg?)',
  53. '(?:[lr]angle|q?quad|[lcvdi]?dots|d?dot|hat|tilde|bar)'
  54. ].map(word => word + '(?![a-zA-Z@:_])'));
  55. const L3_REGEX = new RegExp([
  56. // A function \module_function_name:signature or \__module_function_name:signature,
  57. // where both module and function_name need at least two characters and
  58. // function_name may contain single underscores.
  59. '(?:__)?[a-zA-Z]{2,}_[a-zA-Z](?:_?[a-zA-Z])+:[a-zA-Z]*',
  60. // A variable \scope_module_and_name_type or \scope__module_ane_name_type,
  61. // where scope is one of l, g or c, type needs at least two characters
  62. // and module_and_name may contain single underscores.
  63. '[lgc]__?[a-zA-Z](?:_?[a-zA-Z])*_[a-zA-Z]{2,}',
  64. // A quark \q_the_name or \q__the_name or
  65. // scan mark \s_the_name or \s__vthe_name,
  66. // where variable_name needs at least two characters and
  67. // may contain single underscores.
  68. '[qs]__?[a-zA-Z](?:_?[a-zA-Z])+',
  69. // Other LaTeX3 macro names that are not covered by the three rules above.
  70. 'use(?:_i)?:[a-zA-Z]*',
  71. '(?:else|fi|or):',
  72. '(?:if|cs|exp):w',
  73. '(?:hbox|vbox):n',
  74. '::[a-zA-Z]_unbraced',
  75. '::[a-zA-Z:]'
  76. ].map(pattern => pattern + '(?![a-zA-Z:_])').join('|'));
  77. const L2_VARIANTS = [
  78. {begin: /[a-zA-Z@]+/}, // control word
  79. {begin: /[^a-zA-Z@]?/} // control symbol
  80. ];
  81. const DOUBLE_CARET_VARIANTS = [
  82. {begin: /\^{6}[0-9a-f]{6}/},
  83. {begin: /\^{5}[0-9a-f]{5}/},
  84. {begin: /\^{4}[0-9a-f]{4}/},
  85. {begin: /\^{3}[0-9a-f]{3}/},
  86. {begin: /\^{2}[0-9a-f]{2}/},
  87. {begin: /\^{2}[\u0000-\u007f]/}
  88. ];
  89. const CONTROL_SEQUENCE = {
  90. className: 'keyword',
  91. begin: /\\/,
  92. relevance: 0,
  93. contains: [
  94. {
  95. endsParent: true,
  96. begin: KNOWN_CONTROL_WORDS
  97. },
  98. {
  99. endsParent: true,
  100. begin: L3_REGEX
  101. },
  102. {
  103. endsParent: true,
  104. variants: DOUBLE_CARET_VARIANTS
  105. },
  106. {
  107. endsParent: true,
  108. relevance: 0,
  109. variants: L2_VARIANTS
  110. }
  111. ]
  112. };
  113. const MACRO_PARAM = {
  114. className: 'params',
  115. relevance: 0,
  116. begin: /#+\d?/
  117. };
  118. const DOUBLE_CARET_CHAR = {
  119. // relevance: 1
  120. variants: DOUBLE_CARET_VARIANTS
  121. };
  122. const SPECIAL_CATCODE = {
  123. className: 'built_in',
  124. relevance: 0,
  125. begin: /[$&^_]/
  126. };
  127. const MAGIC_COMMENT = {
  128. className: 'meta',
  129. begin: '% !TeX',
  130. end: '$',
  131. relevance: 10
  132. };
  133. const COMMENT = hljs.COMMENT(
  134. '%',
  135. '$',
  136. {
  137. relevance: 0
  138. }
  139. );
  140. const EVERYTHING_BUT_VERBATIM = [
  141. CONTROL_SEQUENCE,
  142. MACRO_PARAM,
  143. DOUBLE_CARET_CHAR,
  144. SPECIAL_CATCODE,
  145. MAGIC_COMMENT,
  146. COMMENT
  147. ];
  148. const BRACE_GROUP_NO_VERBATIM = {
  149. begin: /\{/, end: /\}/,
  150. relevance: 0,
  151. contains: ['self', ...EVERYTHING_BUT_VERBATIM]
  152. };
  153. const ARGUMENT_BRACES = hljs.inherit(
  154. BRACE_GROUP_NO_VERBATIM,
  155. {
  156. relevance: 0,
  157. endsParent: true,
  158. contains: [BRACE_GROUP_NO_VERBATIM, ...EVERYTHING_BUT_VERBATIM]
  159. }
  160. );
  161. const ARGUMENT_BRACKETS = {
  162. begin: /\[/,
  163. end: /\]/,
  164. endsParent: true,
  165. relevance: 0,
  166. contains: [BRACE_GROUP_NO_VERBATIM, ...EVERYTHING_BUT_VERBATIM]
  167. };
  168. const SPACE_GOBBLER = {
  169. begin: /\s+/,
  170. relevance: 0
  171. };
  172. const ARGUMENT_M = [ARGUMENT_BRACES];
  173. const ARGUMENT_O = [ARGUMENT_BRACKETS];
  174. const ARGUMENT_AND_THEN = function(arg, starts_mode) {
  175. return {
  176. contains: [SPACE_GOBBLER],
  177. starts: {
  178. relevance: 0,
  179. contains: arg,
  180. starts: starts_mode
  181. }
  182. };
  183. };
  184. const CSNAME = function(csname, starts_mode) {
  185. return {
  186. begin: '\\\\' + csname + '(?![a-zA-Z@:_])',
  187. keywords: {$pattern: /\\[a-zA-Z]+/, keyword: '\\' + csname},
  188. relevance: 0,
  189. contains: [SPACE_GOBBLER],
  190. starts: starts_mode
  191. };
  192. };
  193. const BEGIN_ENV = function(envname, starts_mode) {
  194. return hljs.inherit(
  195. {
  196. begin: '\\\\begin(?=[ \t]*(\\r?\\n[ \t]*)?\\{' + envname + '\\})',
  197. keywords: {$pattern: /\\[a-zA-Z]+/, keyword: '\\begin'},
  198. relevance: 0,
  199. },
  200. ARGUMENT_AND_THEN(ARGUMENT_M, starts_mode)
  201. );
  202. };
  203. const VERBATIM_DELIMITED_EQUAL = (innerName = "string") => {
  204. return hljs.END_SAME_AS_BEGIN({
  205. className: innerName,
  206. begin: /(.|\r?\n)/,
  207. end: /(.|\r?\n)/,
  208. excludeBegin: true,
  209. excludeEnd: true,
  210. endsParent: true
  211. });
  212. };
  213. const VERBATIM_DELIMITED_ENV = function(envname) {
  214. return {
  215. className: 'string',
  216. end: '(?=\\\\end\\{' + envname + '\\})'
  217. };
  218. };
  219. const VERBATIM_DELIMITED_BRACES = (innerName = "string") => {
  220. return {
  221. relevance: 0,
  222. begin: /\{/,
  223. starts: {
  224. endsParent: true,
  225. contains: [
  226. {
  227. className: innerName,
  228. end: /(?=\})/,
  229. endsParent:true,
  230. contains: [
  231. {
  232. begin: /\{/,
  233. end: /\}/,
  234. relevance: 0,
  235. contains: ["self"]
  236. }
  237. ],
  238. }
  239. ]
  240. }
  241. };
  242. };
  243. const VERBATIM = [
  244. ...['verb', 'lstinline'].map(csname => CSNAME(csname, {contains: [VERBATIM_DELIMITED_EQUAL()]})),
  245. CSNAME('mint', ARGUMENT_AND_THEN(ARGUMENT_M, {contains: [VERBATIM_DELIMITED_EQUAL()]})),
  246. CSNAME('mintinline', ARGUMENT_AND_THEN(ARGUMENT_M, {contains: [VERBATIM_DELIMITED_BRACES(), VERBATIM_DELIMITED_EQUAL()]})),
  247. CSNAME('url', {contains: [VERBATIM_DELIMITED_BRACES("link"), VERBATIM_DELIMITED_BRACES("link")]}),
  248. CSNAME('hyperref', {contains: [VERBATIM_DELIMITED_BRACES("link")]}),
  249. CSNAME('href', ARGUMENT_AND_THEN(ARGUMENT_O, {contains: [VERBATIM_DELIMITED_BRACES("link")]})),
  250. ...[].concat(...['', '\\*'].map(suffix => [
  251. BEGIN_ENV('verbatim' + suffix, VERBATIM_DELIMITED_ENV('verbatim' + suffix)),
  252. BEGIN_ENV('filecontents' + suffix, ARGUMENT_AND_THEN(ARGUMENT_M, VERBATIM_DELIMITED_ENV('filecontents' + suffix))),
  253. ...['', 'B', 'L'].map(prefix =>
  254. BEGIN_ENV(prefix + 'Verbatim' + suffix, ARGUMENT_AND_THEN(ARGUMENT_O, VERBATIM_DELIMITED_ENV(prefix + 'Verbatim' + suffix)))
  255. )
  256. ])),
  257. BEGIN_ENV('minted', ARGUMENT_AND_THEN(ARGUMENT_O, ARGUMENT_AND_THEN(ARGUMENT_M, VERBATIM_DELIMITED_ENV('minted')))),
  258. ];
  259. return {
  260. name: 'LaTeX',
  261. aliases: ['tex'],
  262. contains: [
  263. ...VERBATIM,
  264. ...EVERYTHING_BUT_VERBATIM
  265. ]
  266. };
  267. }
  268. module.exports = latex;