stex.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://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() {
  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["DEFAULT"] = function () {
  70. this.name = "DEFAULT";
  71. this.style = "tag";
  72. this.styleIdentifier = this.openBracket = this.closeBracket = function() {};
  73. };
  74. function setState(state, f) {
  75. state.f = f;
  76. }
  77. // called when in a normal (no environment) context
  78. function normal(source, state) {
  79. var plug;
  80. // Do we look like '\command' ? If so, attempt to apply the plugin 'command'
  81. if (source.match(/^\\[a-zA-Z@]+/)) {
  82. var cmdName = source.current().slice(1);
  83. plug = plugins[cmdName] || plugins["DEFAULT"];
  84. plug = new plug();
  85. pushCommand(state, plug);
  86. setState(state, beginParams);
  87. return plug.style;
  88. }
  89. // escape characters
  90. if (source.match(/^\\[$&%#{}_]/)) {
  91. return "tag";
  92. }
  93. // white space control characters
  94. if (source.match(/^\\[,;!\/\\]/)) {
  95. return "tag";
  96. }
  97. // find if we're starting various math modes
  98. if (source.match("\\[")) {
  99. setState(state, function(source, state){ return inMathMode(source, state, "\\]"); });
  100. return "keyword";
  101. }
  102. if (source.match("$$")) {
  103. setState(state, function(source, state){ return inMathMode(source, state, "$$"); });
  104. return "keyword";
  105. }
  106. if (source.match("$")) {
  107. setState(state, function(source, state){ return inMathMode(source, state, "$"); });
  108. return "keyword";
  109. }
  110. var ch = source.next();
  111. if (ch == "%") {
  112. source.skipToEnd();
  113. return "comment";
  114. } else if (ch == '}' || ch == ']') {
  115. plug = peekCommand(state);
  116. if (plug) {
  117. plug.closeBracket(ch);
  118. setState(state, beginParams);
  119. } else {
  120. return "error";
  121. }
  122. return "bracket";
  123. } else if (ch == '{' || ch == '[') {
  124. plug = plugins["DEFAULT"];
  125. plug = new plug();
  126. pushCommand(state, plug);
  127. return "bracket";
  128. } else if (/\d/.test(ch)) {
  129. source.eatWhile(/[\w.%]/);
  130. return "atom";
  131. } else {
  132. source.eatWhile(/[\w\-_]/);
  133. plug = getMostPowerful(state);
  134. if (plug.name == 'begin') {
  135. plug.argument = source.current();
  136. }
  137. return plug.styleIdentifier();
  138. }
  139. }
  140. function inMathMode(source, state, endModeSeq) {
  141. if (source.eatSpace()) {
  142. return null;
  143. }
  144. if (source.match(endModeSeq)) {
  145. setState(state, normal);
  146. return "keyword";
  147. }
  148. if (source.match(/^\\[a-zA-Z@]+/)) {
  149. return "tag";
  150. }
  151. if (source.match(/^[a-zA-Z]+/)) {
  152. return "variable-2";
  153. }
  154. // escape characters
  155. if (source.match(/^\\[$&%#{}_]/)) {
  156. return "tag";
  157. }
  158. // white space control characters
  159. if (source.match(/^\\[,;!\/]/)) {
  160. return "tag";
  161. }
  162. // special math-mode characters
  163. if (source.match(/^[\^_&]/)) {
  164. return "tag";
  165. }
  166. // non-special characters
  167. if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) {
  168. return null;
  169. }
  170. if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) {
  171. return "number";
  172. }
  173. var ch = source.next();
  174. if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") {
  175. return "bracket";
  176. }
  177. if (ch == "%") {
  178. source.skipToEnd();
  179. return "comment";
  180. }
  181. return "error";
  182. }
  183. function beginParams(source, state) {
  184. var ch = source.peek(), lastPlug;
  185. if (ch == '{' || ch == '[') {
  186. lastPlug = peekCommand(state);
  187. lastPlug.openBracket(ch);
  188. source.eat(ch);
  189. setState(state, normal);
  190. return "bracket";
  191. }
  192. if (/[ \t\r]/.test(ch)) {
  193. source.eat(ch);
  194. return null;
  195. }
  196. setState(state, normal);
  197. popCommand(state);
  198. return normal(source, state);
  199. }
  200. return {
  201. startState: function() {
  202. return {
  203. cmdState: [],
  204. f: normal
  205. };
  206. },
  207. copyState: function(s) {
  208. return {
  209. cmdState: s.cmdState.slice(),
  210. f: s.f
  211. };
  212. },
  213. token: function(stream, state) {
  214. return state.f(stream, state);
  215. },
  216. blankLine: function(state) {
  217. state.f = normal;
  218. state.cmdState.length = 0;
  219. },
  220. lineComment: "%"
  221. };
  222. });
  223. CodeMirror.defineMIME("text/x-stex", "stex");
  224. CodeMirror.defineMIME("text/x-latex", "stex");
  225. });