stex.js 7.6 KB

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