stex.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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. }
  115. else if (ch == '}' || ch == ']') {
  116. plug = peekCommand(state);
  117. if (plug) {
  118. plug.closeBracket(ch);
  119. setState(state, beginParams);
  120. } else {
  121. return "error";
  122. }
  123. return "bracket";
  124. } else if (ch == '{' || ch == '[') {
  125. plug = plugins["DEFAULT"];
  126. plug = new plug();
  127. pushCommand(state, plug);
  128. return "bracket";
  129. }
  130. else if (/\d/.test(ch)) {
  131. source.eatWhile(/[\w.%]/);
  132. return "atom";
  133. }
  134. else {
  135. source.eatWhile(/[\w\-_]/);
  136. plug = getMostPowerful(state);
  137. if (plug.name == 'begin') {
  138. plug.argument = source.current();
  139. }
  140. return plug.styleIdentifier();
  141. }
  142. }
  143. function inMathMode(source, state, endModeSeq) {
  144. if (source.eatSpace()) {
  145. return null;
  146. }
  147. if (source.match(endModeSeq)) {
  148. setState(state, normal);
  149. return "keyword";
  150. }
  151. if (source.match(/^\\[a-zA-Z@]+/)) {
  152. return "tag";
  153. }
  154. if (source.match(/^[a-zA-Z]+/)) {
  155. return "variable-2";
  156. }
  157. // escape characters
  158. if (source.match(/^\\[$&%#{}_]/)) {
  159. return "tag";
  160. }
  161. // white space control characters
  162. if (source.match(/^\\[,;!\/]/)) {
  163. return "tag";
  164. }
  165. // special math-mode characters
  166. if (source.match(/^[\^_&]/)) {
  167. return "tag";
  168. }
  169. // non-special characters
  170. if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) {
  171. return null;
  172. }
  173. if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) {
  174. return "number";
  175. }
  176. var ch = source.next();
  177. if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") {
  178. return "bracket";
  179. }
  180. if (ch == "%") {
  181. source.skipToEnd();
  182. return "comment";
  183. }
  184. return "error";
  185. }
  186. function beginParams(source, state) {
  187. var ch = source.peek(), lastPlug;
  188. if (ch == '{' || ch == '[') {
  189. lastPlug = peekCommand(state);
  190. lastPlug.openBracket(ch);
  191. source.eat(ch);
  192. setState(state, normal);
  193. return "bracket";
  194. }
  195. if (/[ \t\r]/.test(ch)) {
  196. source.eat(ch);
  197. return null;
  198. }
  199. setState(state, normal);
  200. popCommand(state);
  201. return normal(source, state);
  202. }
  203. return {
  204. startState: function() {
  205. return {
  206. cmdState: [],
  207. f: normal
  208. };
  209. },
  210. copyState: function(s) {
  211. return {
  212. cmdState: s.cmdState.slice(),
  213. f: s.f
  214. };
  215. },
  216. token: function(stream, state) {
  217. return state.f(stream, state);
  218. },
  219. blankLine: function(state) {
  220. state.f = normal;
  221. },
  222. lineComment: "%"
  223. };
  224. });
  225. CodeMirror.defineMIME("text/x-stex", "stex");
  226. CodeMirror.defineMIME("text/x-latex", "stex");
  227. });