all repos — NoPaste @ 3e3c433f026c49e04d0efa0f0f39fc688ed32bb1

Resurrected - The PussTheCat.org fork of NoPaste

scripts/CodeMirror/mode/ruby/ruby.js (view raw)

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