stex.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. /*
  4. * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de)
  5. * Licence: MIT
  6. */
  7. ;(function (mod) {
  8. if (typeof exports == 'object' && typeof module == 'object')
  9. // CommonJS
  10. mod(require('../../lib/codemirror'))
  11. else if (typeof define == 'function' && define.amd)
  12. // AMD
  13. define(['../../lib/codemirror'], mod)
  14. // Plain browser env
  15. else mod(CodeMirror)
  16. })(function (CodeMirror) {
  17. 'use strict'
  18. CodeMirror.defineMode('stex', function (_config, parserConfig) {
  19. 'use strict'
  20. function pushCommand(state, command) {
  21. state.cmdState.push(command)
  22. }
  23. function peekCommand(state) {
  24. if (state.cmdState.length > 0) {
  25. return state.cmdState[state.cmdState.length - 1]
  26. } else {
  27. return null
  28. }
  29. }
  30. function popCommand(state) {
  31. var plug = state.cmdState.pop()
  32. if (plug) {
  33. plug.closeBracket()
  34. }
  35. }
  36. // returns the non-default plugin closest to the end of the list
  37. function getMostPowerful(state) {
  38. var context = state.cmdState
  39. for (var i = context.length - 1; i >= 0; i--) {
  40. var plug = context[i]
  41. if (plug.name == 'DEFAULT') {
  42. continue
  43. }
  44. return plug
  45. }
  46. return {
  47. styleIdentifier: function () {
  48. return null
  49. },
  50. }
  51. }
  52. function addPluginPattern(pluginName, cmdStyle, styles) {
  53. return function () {
  54. this.name = pluginName
  55. this.bracketNo = 0
  56. this.style = cmdStyle
  57. this.styles = styles
  58. this.argument = null // \begin and \end have arguments that follow. These are stored in the plugin
  59. this.styleIdentifier = function () {
  60. return this.styles[this.bracketNo - 1] || null
  61. }
  62. this.openBracket = function () {
  63. this.bracketNo++
  64. return 'bracket'
  65. }
  66. this.closeBracket = function () {}
  67. }
  68. }
  69. var plugins = {}
  70. plugins['importmodule'] = addPluginPattern('importmodule', 'tag', ['string', 'builtin'])
  71. plugins['documentclass'] = addPluginPattern('documentclass', 'tag', ['', 'atom'])
  72. plugins['usepackage'] = addPluginPattern('usepackage', 'tag', ['atom'])
  73. plugins['begin'] = addPluginPattern('begin', 'tag', ['atom'])
  74. plugins['end'] = addPluginPattern('end', 'tag', ['atom'])
  75. plugins['label'] = addPluginPattern('label', 'tag', ['atom'])
  76. plugins['ref'] = addPluginPattern('ref', 'tag', ['atom'])
  77. plugins['eqref'] = addPluginPattern('eqref', 'tag', ['atom'])
  78. plugins['cite'] = addPluginPattern('cite', 'tag', ['atom'])
  79. plugins['bibitem'] = addPluginPattern('bibitem', 'tag', ['atom'])
  80. plugins['Bibitem'] = addPluginPattern('Bibitem', 'tag', ['atom'])
  81. plugins['RBibitem'] = addPluginPattern('RBibitem', 'tag', ['atom'])
  82. plugins['DEFAULT'] = function () {
  83. this.name = 'DEFAULT'
  84. this.style = 'tag'
  85. this.styleIdentifier = this.openBracket = this.closeBracket = function () {}
  86. }
  87. function setState(state, f) {
  88. state.f = f
  89. }
  90. // called when in a normal (no environment) context
  91. function normal(source, state) {
  92. var plug
  93. // Do we look like '\command' ? If so, attempt to apply the plugin 'command'
  94. if (source.match(/^\\[a-zA-Z@]+/)) {
  95. var cmdName = source.current().slice(1)
  96. plug = plugins.hasOwnProperty(cmdName) ? plugins[cmdName] : plugins['DEFAULT']
  97. plug = new plug()
  98. pushCommand(state, plug)
  99. setState(state, beginParams)
  100. return plug.style
  101. }
  102. // escape characters
  103. if (source.match(/^\\[$&%#{}_]/)) {
  104. return 'tag'
  105. }
  106. // white space control characters
  107. if (source.match(/^\\[,;!\/\\]/)) {
  108. return 'tag'
  109. }
  110. // find if we're starting various math modes
  111. if (source.match('\\[')) {
  112. setState(state, function (source, state) {
  113. return inMathMode(source, state, '\\]')
  114. })
  115. return 'keyword'
  116. }
  117. if (source.match('\\(')) {
  118. setState(state, function (source, state) {
  119. return inMathMode(source, state, '\\)')
  120. })
  121. return 'keyword'
  122. }
  123. if (source.match('$$')) {
  124. setState(state, function (source, state) {
  125. return inMathMode(source, state, '$$')
  126. })
  127. return 'keyword'
  128. }
  129. if (source.match('$')) {
  130. setState(state, function (source, state) {
  131. return inMathMode(source, state, '$')
  132. })
  133. return 'keyword'
  134. }
  135. var ch = source.next()
  136. if (ch == '%') {
  137. source.skipToEnd()
  138. return 'comment'
  139. } else if (ch == '}' || ch == ']') {
  140. plug = peekCommand(state)
  141. if (plug) {
  142. plug.closeBracket(ch)
  143. setState(state, beginParams)
  144. } else {
  145. return 'error'
  146. }
  147. return 'bracket'
  148. } else if (ch == '{' || ch == '[') {
  149. plug = plugins['DEFAULT']
  150. plug = new plug()
  151. pushCommand(state, plug)
  152. return 'bracket'
  153. } else if (/\d/.test(ch)) {
  154. source.eatWhile(/[\w.%]/)
  155. return 'atom'
  156. } else {
  157. source.eatWhile(/[\w\-_]/)
  158. plug = getMostPowerful(state)
  159. if (plug.name == 'begin') {
  160. plug.argument = source.current()
  161. }
  162. return plug.styleIdentifier()
  163. }
  164. }
  165. function inMathMode(source, state, endModeSeq) {
  166. if (source.eatSpace()) {
  167. return null
  168. }
  169. if (endModeSeq && source.match(endModeSeq)) {
  170. setState(state, normal)
  171. return 'keyword'
  172. }
  173. if (source.match(/^\\[a-zA-Z@]+/)) {
  174. return 'tag'
  175. }
  176. if (source.match(/^[a-zA-Z]+/)) {
  177. return 'variable-2'
  178. }
  179. // escape characters
  180. if (source.match(/^\\[$&%#{}_]/)) {
  181. return 'tag'
  182. }
  183. // white space control characters
  184. if (source.match(/^\\[,;!\/]/)) {
  185. return 'tag'
  186. }
  187. // special math-mode characters
  188. if (source.match(/^[\^_&]/)) {
  189. return 'tag'
  190. }
  191. // non-special characters
  192. if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) {
  193. return null
  194. }
  195. if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) {
  196. return 'number'
  197. }
  198. var ch = source.next()
  199. if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == '(' || ch == ')') {
  200. return 'bracket'
  201. }
  202. if (ch == '%') {
  203. source.skipToEnd()
  204. return 'comment'
  205. }
  206. return 'error'
  207. }
  208. function beginParams(source, state) {
  209. var ch = source.peek(),
  210. lastPlug
  211. if (ch == '{' || ch == '[') {
  212. lastPlug = peekCommand(state)
  213. lastPlug.openBracket(ch)
  214. source.eat(ch)
  215. setState(state, normal)
  216. return 'bracket'
  217. }
  218. if (/[ \t\r]/.test(ch)) {
  219. source.eat(ch)
  220. return null
  221. }
  222. setState(state, normal)
  223. popCommand(state)
  224. return normal(source, state)
  225. }
  226. return {
  227. startState: function () {
  228. var f = parserConfig.inMathMode
  229. ? function (source, state) {
  230. return inMathMode(source, state)
  231. }
  232. : normal
  233. return {
  234. cmdState: [],
  235. f: f,
  236. }
  237. },
  238. copyState: function (s) {
  239. return {
  240. cmdState: s.cmdState.slice(),
  241. f: s.f,
  242. }
  243. },
  244. token: function (stream, state) {
  245. return state.f(stream, state)
  246. },
  247. blankLine: function (state) {
  248. state.f = normal
  249. state.cmdState.length = 0
  250. },
  251. lineComment: '%',
  252. }
  253. })
  254. CodeMirror.defineMIME('text/x-stex', 'stex')
  255. CodeMirror.defineMIME('text/x-latex', 'stex')
  256. })