You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

369 lines
9.9 KiB

3 years ago
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://codemirror.net/LICENSE
  3. /**
  4. * Link to the project's GitHub page:
  5. * https://github.com/pickhardt/coffeescript-codemirror-mode
  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("coffeescript", function(conf, parserConf) {
  17. var ERRORCLASS = "error";
  18. function wordRegexp(words) {
  19. return new RegExp("^((" + words.join(")|(") + "))\\b");
  20. }
  21. var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
  22. var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
  23. var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
  24. var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/;
  25. var wordOperators = wordRegexp(["and", "or", "not",
  26. "is", "isnt", "in",
  27. "instanceof", "typeof"]);
  28. var indentKeywords = ["for", "while", "loop", "if", "unless", "else",
  29. "switch", "try", "catch", "finally", "class"];
  30. var commonKeywords = ["break", "by", "continue", "debugger", "delete",
  31. "do", "in", "of", "new", "return", "then",
  32. "this", "@", "throw", "when", "until", "extends"];
  33. var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
  34. indentKeywords = wordRegexp(indentKeywords);
  35. var stringPrefixes = /^('{3}|\"{3}|['\"])/;
  36. var regexPrefixes = /^(\/{3}|\/)/;
  37. var commonConstants = ["Infinity", "NaN", "undefined", "null", "true", "false", "on", "off", "yes", "no"];
  38. var constants = wordRegexp(commonConstants);
  39. // Tokenizers
  40. function tokenBase(stream, state) {
  41. // Handle scope changes
  42. if (stream.sol()) {
  43. if (state.scope.align === null) state.scope.align = false;
  44. var scopeOffset = state.scope.offset;
  45. if (stream.eatSpace()) {
  46. var lineOffset = stream.indentation();
  47. if (lineOffset > scopeOffset && state.scope.type == "coffee") {
  48. return "indent";
  49. } else if (lineOffset < scopeOffset) {
  50. return "dedent";
  51. }
  52. return null;
  53. } else {
  54. if (scopeOffset > 0) {
  55. dedent(stream, state);
  56. }
  57. }
  58. }
  59. if (stream.eatSpace()) {
  60. return null;
  61. }
  62. var ch = stream.peek();
  63. // Handle docco title comment (single line)
  64. if (stream.match("####")) {
  65. stream.skipToEnd();
  66. return "comment";
  67. }
  68. // Handle multi line comments
  69. if (stream.match("###")) {
  70. state.tokenize = longComment;
  71. return state.tokenize(stream, state);
  72. }
  73. // Single line comment
  74. if (ch === "#") {
  75. stream.skipToEnd();
  76. return "comment";
  77. }
  78. // Handle number literals
  79. if (stream.match(/^-?[0-9\.]/, false)) {
  80. var floatLiteral = false;
  81. // Floats
  82. if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) {
  83. floatLiteral = true;
  84. }
  85. if (stream.match(/^-?\d+\.\d*/)) {
  86. floatLiteral = true;
  87. }
  88. if (stream.match(/^-?\.\d+/)) {
  89. floatLiteral = true;
  90. }
  91. if (floatLiteral) {
  92. // prevent from getting extra . on 1..
  93. if (stream.peek() == "."){
  94. stream.backUp(1);
  95. }
  96. return "number";
  97. }
  98. // Integers
  99. var intLiteral = false;
  100. // Hex
  101. if (stream.match(/^-?0x[0-9a-f]+/i)) {
  102. intLiteral = true;
  103. }
  104. // Decimal
  105. if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) {
  106. intLiteral = true;
  107. }
  108. // Zero by itself with no other piece of number.
  109. if (stream.match(/^-?0(?![\dx])/i)) {
  110. intLiteral = true;
  111. }
  112. if (intLiteral) {
  113. return "number";
  114. }
  115. }
  116. // Handle strings
  117. if (stream.match(stringPrefixes)) {
  118. state.tokenize = tokenFactory(stream.current(), false, "string");
  119. return state.tokenize(stream, state);
  120. }
  121. // Handle regex literals
  122. if (stream.match(regexPrefixes)) {
  123. if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division
  124. state.tokenize = tokenFactory(stream.current(), true, "string-2");
  125. return state.tokenize(stream, state);
  126. } else {
  127. stream.backUp(1);
  128. }
  129. }
  130. // Handle operators and delimiters
  131. if (stream.match(operators) || stream.match(wordOperators)) {
  132. return "operator";
  133. }
  134. if (stream.match(delimiters)) {
  135. return "punctuation";
  136. }
  137. if (stream.match(constants)) {
  138. return "atom";
  139. }
  140. if (stream.match(keywords)) {
  141. return "keyword";
  142. }
  143. if (stream.match(identifiers)) {
  144. return "variable";
  145. }
  146. if (stream.match(properties)) {
  147. return "property";
  148. }
  149. // Handle non-detected items
  150. stream.next();
  151. return ERRORCLASS;
  152. }
  153. function tokenFactory(delimiter, singleline, outclass) {
  154. return function(stream, state) {
  155. while (!stream.eol()) {
  156. stream.eatWhile(/[^'"\/\\]/);
  157. if (stream.eat("\\")) {
  158. stream.next();
  159. if (singleline && stream.eol()) {
  160. return outclass;
  161. }
  162. } else if (stream.match(delimiter)) {
  163. state.tokenize = tokenBase;
  164. return outclass;
  165. } else {
  166. stream.eat(/['"\/]/);
  167. }
  168. }
  169. if (singleline) {
  170. if (parserConf.singleLineStringErrors) {
  171. outclass = ERRORCLASS;
  172. } else {
  173. state.tokenize = tokenBase;
  174. }
  175. }
  176. return outclass;
  177. };
  178. }
  179. function longComment(stream, state) {
  180. while (!stream.eol()) {
  181. stream.eatWhile(/[^#]/);
  182. if (stream.match("###")) {
  183. state.tokenize = tokenBase;
  184. break;
  185. }
  186. stream.eatWhile("#");
  187. }
  188. return "comment";
  189. }
  190. function indent(stream, state, type) {
  191. type = type || "coffee";
  192. var offset = 0, align = false, alignOffset = null;
  193. for (var scope = state.scope; scope; scope = scope.prev) {
  194. if (scope.type === "coffee" || scope.type == "}") {
  195. offset = scope.offset + conf.indentUnit;
  196. break;
  197. }
  198. }
  199. if (type !== "coffee") {
  200. align = null;
  201. alignOffset = stream.column() + stream.current().length;
  202. } else if (state.scope.align) {
  203. state.scope.align = false;
  204. }
  205. state.scope = {
  206. offset: offset,
  207. type: type,
  208. prev: state.scope,
  209. align: align,
  210. alignOffset: alignOffset
  211. };
  212. }
  213. function dedent(stream, state) {
  214. if (!state.scope.prev) return;
  215. if (state.scope.type === "coffee") {
  216. var _indent = stream.indentation();
  217. var matched = false;
  218. for (var scope = state.scope; scope; scope = scope.prev) {
  219. if (_indent === scope.offset) {
  220. matched = true;
  221. break;
  222. }
  223. }
  224. if (!matched) {
  225. return true;
  226. }
  227. while (state.scope.prev && state.scope.offset !== _indent) {
  228. state.scope = state.scope.prev;
  229. }
  230. return false;
  231. } else {
  232. state.scope = state.scope.prev;
  233. return false;
  234. }
  235. }
  236. function tokenLexer(stream, state) {
  237. var style = state.tokenize(stream, state);
  238. var current = stream.current();
  239. // Handle "." connected identifiers
  240. if (current === ".") {
  241. style = state.tokenize(stream, state);
  242. current = stream.current();
  243. if (/^\.[\w$]+$/.test(current)) {
  244. return "variable";
  245. } else {
  246. return ERRORCLASS;
  247. }
  248. }
  249. // Handle scope changes.
  250. if (current === "return") {
  251. state.dedent = true;
  252. }
  253. if (((current === "->" || current === "=>") &&
  254. !state.lambda &&
  255. !stream.peek())
  256. || style === "indent") {
  257. indent(stream, state);
  258. }
  259. var delimiter_index = "[({".indexOf(current);
  260. if (delimiter_index !== -1) {
  261. indent(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
  262. }
  263. if (indentKeywords.exec(current)){
  264. indent(stream, state);
  265. }
  266. if (current == "then"){
  267. dedent(stream, state);
  268. }
  269. if (style === "dedent") {
  270. if (dedent(stream, state)) {
  271. return ERRORCLASS;
  272. }
  273. }
  274. delimiter_index = "])}".indexOf(current);
  275. if (delimiter_index !== -1) {
  276. while (state.scope.type == "coffee" && state.scope.prev)
  277. state.scope = state.scope.prev;
  278. if (state.scope.type == current)
  279. state.scope = state.scope.prev;
  280. }
  281. if (state.dedent && stream.eol()) {
  282. if (state.scope.type == "coffee" && state.scope.prev)
  283. state.scope = state.scope.prev;
  284. state.dedent = false;
  285. }
  286. return style;
  287. }
  288. var external = {
  289. startState: function(basecolumn) {
  290. return {
  291. tokenize: tokenBase,
  292. scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
  293. lastToken: null,
  294. lambda: false,
  295. dedent: 0
  296. };
  297. },
  298. token: function(stream, state) {
  299. var fillAlign = state.scope.align === null && state.scope;
  300. if (fillAlign && stream.sol()) fillAlign.align = false;
  301. var style = tokenLexer(stream, state);
  302. if (fillAlign && style && style != "comment") fillAlign.align = true;
  303. state.lastToken = {style:style, content: stream.current()};
  304. if (stream.eol() && stream.lambda) {
  305. state.lambda = false;
  306. }
  307. return style;
  308. },
  309. indent: function(state, text) {
  310. if (state.tokenize != tokenBase) return 0;
  311. var scope = state.scope;
  312. var closer = text && "])}".indexOf(text.charAt(0)) > -1;
  313. if (closer) while (scope.type == "coffee" && scope.prev) scope = scope.prev;
  314. var closes = closer && scope.type === text.charAt(0);
  315. if (scope.align)
  316. return scope.alignOffset - (closes ? 1 : 0);
  317. else
  318. return (closes ? scope.prev : scope).offset;
  319. },
  320. lineComment: "#",
  321. fold: "indent"
  322. };
  323. return external;
  324. });
  325. CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
  326. });