ruby.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. CodeMirror.defineMode("ruby", function(config) {
  2. function wordObj(words) {
  3. var o = {};
  4. for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true;
  5. return o;
  6. }
  7. var keywords = wordObj([
  8. "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
  9. "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or",
  10. "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
  11. "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc",
  12. "caller", "lambda", "proc", "public", "protected", "private", "require", "load",
  13. "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
  14. ]);
  15. var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then",
  16. "catch", "loop", "proc", "begin"]);
  17. var dedentWords = wordObj(["end", "until"]);
  18. var matching = {"[": "]", "{": "}", "(": ")"};
  19. var curPunc;
  20. function chain(newtok, stream, state) {
  21. state.tokenize.push(newtok);
  22. return newtok(stream, state);
  23. }
  24. function tokenBase(stream, state) {
  25. curPunc = null;
  26. if (stream.sol() && stream.match("=begin") && stream.eol()) {
  27. state.tokenize.push(readBlockComment);
  28. return "comment";
  29. }
  30. if (stream.eatSpace()) return null;
  31. var ch = stream.next(), m;
  32. if (ch == "`" || ch == "'" || ch == '"') {
  33. return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state);
  34. } else if (ch == "/" && !stream.eol() && stream.peek() != " ") {
  35. return chain(readQuoted(ch, "string-2", true), stream, state);
  36. } else if (ch == "%") {
  37. var style = "string", embed = true;
  38. if (stream.eat("s")) style = "atom";
  39. else if (stream.eat(/[WQ]/)) style = "string";
  40. else if (stream.eat(/[r]/)) style = "string-2";
  41. else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; }
  42. var delim = stream.eat(/[^\w\s]/);
  43. if (!delim) return "operator";
  44. if (matching.propertyIsEnumerable(delim)) delim = matching[delim];
  45. return chain(readQuoted(delim, style, embed, true), stream, state);
  46. } else if (ch == "#") {
  47. stream.skipToEnd();
  48. return "comment";
  49. } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) {
  50. return chain(readHereDoc(m[1]), stream, state);
  51. } else if (ch == "0") {
  52. if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/);
  53. else if (stream.eat("b")) stream.eatWhile(/[01]/);
  54. else stream.eatWhile(/[0-7]/);
  55. return "number";
  56. } else if (/\d/.test(ch)) {
  57. stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/);
  58. return "number";
  59. } else if (ch == "?") {
  60. while (stream.match(/^\\[CM]-/)) {}
  61. if (stream.eat("\\")) stream.eatWhile(/\w/);
  62. else stream.next();
  63. return "string";
  64. } else if (ch == ":") {
  65. if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state);
  66. if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state);
  67. // :> :>> :< :<< are valid symbols
  68. if (stream.eat(/[\<\>]/)) {
  69. stream.eat(/[\<\>]/);
  70. return "atom";
  71. }
  72. // :+ :- :/ :* :| :& :! are valid symbols
  73. if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) {
  74. return "atom";
  75. }
  76. // Symbols can't start by a digit
  77. if (stream.eat(/[a-zA-Z$@_]/)) {
  78. stream.eatWhile(/[\w]/);
  79. // Only one ? ! = is allowed and only as the last character
  80. stream.eat(/[\?\!\=]/);
  81. return "atom";
  82. }
  83. return "operator";
  84. } else if (ch == "@" && stream.match(/^@?[a-zA-Z_]/)) {
  85. stream.eat("@");
  86. stream.eatWhile(/[\w]/);
  87. return "variable-2";
  88. } else if (ch == "$") {
  89. if (stream.eat(/[a-zA-Z_]/)) {
  90. stream.eatWhile(/[\w]/);
  91. } else if (stream.eat(/\d/)) {
  92. stream.eat(/\d/);
  93. } else {
  94. stream.next(); // Must be a special global like $: or $!
  95. }
  96. return "variable-3";
  97. } else if (/[a-zA-Z_]/.test(ch)) {
  98. stream.eatWhile(/[\w]/);
  99. stream.eat(/[\?\!]/);
  100. if (stream.eat(":")) return "atom";
  101. return "ident";
  102. } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) {
  103. curPunc = "|";
  104. return null;
  105. } else if (/[\(\)\[\]{}\\;]/.test(ch)) {
  106. curPunc = ch;
  107. return null;
  108. } else if (ch == "-" && stream.eat(">")) {
  109. return "arrow";
  110. } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) {
  111. stream.eatWhile(/[=+\-\/*:\.^%<>~|]/);
  112. return "operator";
  113. } else {
  114. return null;
  115. }
  116. }
  117. function tokenBaseUntilBrace() {
  118. var depth = 1;
  119. return function(stream, state) {
  120. if (stream.peek() == "}") {
  121. depth--;
  122. if (depth == 0) {
  123. state.tokenize.pop();
  124. return state.tokenize[state.tokenize.length-1](stream, state);
  125. }
  126. } else if (stream.peek() == "{") {
  127. depth++;
  128. }
  129. return tokenBase(stream, state);
  130. };
  131. }
  132. function tokenBaseOnce() {
  133. var alreadyCalled = false;
  134. return function(stream, state) {
  135. if (alreadyCalled) {
  136. state.tokenize.pop();
  137. return state.tokenize[state.tokenize.length-1](stream, state);
  138. }
  139. alreadyCalled = true;
  140. return tokenBase(stream, state);
  141. };
  142. }
  143. function readQuoted(quote, style, embed, unescaped) {
  144. return function(stream, state) {
  145. var escaped = false, ch;
  146. if (state.context.type === 'read-quoted-paused') {
  147. state.context = state.context.prev;
  148. stream.eat("}");
  149. }
  150. while ((ch = stream.next()) != null) {
  151. if (ch == quote && (unescaped || !escaped)) {
  152. state.tokenize.pop();
  153. break;
  154. }
  155. if (embed && ch == "#" && !escaped) {
  156. if (stream.eat("{")) {
  157. if (quote == "}") {
  158. state.context = {prev: state.context, type: 'read-quoted-paused'};
  159. }
  160. state.tokenize.push(tokenBaseUntilBrace());
  161. break;
  162. } else if (/[@\$]/.test(stream.peek())) {
  163. state.tokenize.push(tokenBaseOnce());
  164. break;
  165. }
  166. }
  167. escaped = !escaped && ch == "\\";
  168. }
  169. return style;
  170. };
  171. }
  172. function readHereDoc(phrase) {
  173. return function(stream, state) {
  174. if (stream.match(phrase)) state.tokenize.pop();
  175. else stream.skipToEnd();
  176. return "string";
  177. };
  178. }
  179. function readBlockComment(stream, state) {
  180. if (stream.sol() && stream.match("=end") && stream.eol())
  181. state.tokenize.pop();
  182. stream.skipToEnd();
  183. return "comment";
  184. }
  185. return {
  186. startState: function() {
  187. return {tokenize: [tokenBase],
  188. indented: 0,
  189. context: {type: "top", indented: -config.indentUnit},
  190. continuedLine: false,
  191. lastTok: null,
  192. varList: false};
  193. },
  194. token: function(stream, state) {
  195. if (stream.sol()) state.indented = stream.indentation();
  196. var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype;
  197. if (style == "ident") {
  198. var word = stream.current();
  199. style = keywords.propertyIsEnumerable(stream.current()) ? "keyword"
  200. : /^[A-Z]/.test(word) ? "tag"
  201. : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def"
  202. : "variable";
  203. if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
  204. else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
  205. else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
  206. kwtype = "indent";
  207. }
  208. if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style;
  209. if (curPunc == "|") state.varList = !state.varList;
  210. if (kwtype == "indent" || /[\(\[\{]/.test(curPunc))
  211. state.context = {prev: state.context, type: curPunc || style, indented: state.indented};
  212. else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev)
  213. state.context = state.context.prev;
  214. if (stream.eol())
  215. state.continuedLine = (curPunc == "\\" || style == "operator");
  216. return style;
  217. },
  218. indent: function(state, textAfter) {
  219. if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0;
  220. var firstChar = textAfter && textAfter.charAt(0);
  221. var ct = state.context;
  222. var closing = ct.type == matching[firstChar] ||
  223. ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter);
  224. return ct.indented + (closing ? 0 : config.indentUnit) +
  225. (state.continuedLine ? config.indentUnit : 0);
  226. },
  227. electricChars: "}de", // enD and rescuE
  228. lineComment: "#"
  229. };
  230. });
  231. CodeMirror.defineMIME("text/x-ruby", "ruby");