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.

303 lines
10 KiB

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