scripts/CodeMirror/mode/groovy/groovy.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
14CodeMirror.defineMode("groovy", function(config) {
15 function words(str) {
16 var obj = {}, words = str.split(" ");
17 for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
18 return obj;
19 }
20 var keywords = words(
21 "abstract as assert boolean break byte case catch char class const continue def default " +
22 "do double else enum extends final finally float for goto if implements import in " +
23 "instanceof int interface long native new package private protected public return " +
24 "short static strictfp super switch synchronized threadsafe throw throws trait transient " +
25 "try void volatile while");
26 var blockKeywords = words("catch class def do else enum finally for if interface switch trait try while");
27 var standaloneKeywords = words("return break continue");
28 var atoms = words("null true false this");
29
30 var curPunc;
31 function tokenBase(stream, state) {
32 var ch = stream.next();
33 if (ch == '"' || ch == "'") {
34 return startString(ch, stream, state);
35 }
36 if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
37 curPunc = ch;
38 return null;
39 }
40 if (/\d/.test(ch)) {
41 stream.eatWhile(/[\w\.]/);
42 if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); }
43 return "number";
44 }
45 if (ch == "/") {
46 if (stream.eat("*")) {
47 state.tokenize.push(tokenComment);
48 return tokenComment(stream, state);
49 }
50 if (stream.eat("/")) {
51 stream.skipToEnd();
52 return "comment";
53 }
54 if (expectExpression(state.lastToken, false)) {
55 return startString(ch, stream, state);
56 }
57 }
58 if (ch == "-" && stream.eat(">")) {
59 curPunc = "->";
60 return null;
61 }
62 if (/[+\-*&%=<>!?|\/~]/.test(ch)) {
63 stream.eatWhile(/[+\-*&%=<>|~]/);
64 return "operator";
65 }
66 stream.eatWhile(/[\w\$_]/);
67 if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; }
68 if (state.lastToken == ".") return "property";
69 if (stream.eat(":")) { curPunc = "proplabel"; return "property"; }
70 var cur = stream.current();
71 if (atoms.propertyIsEnumerable(cur)) { return "atom"; }
72 if (keywords.propertyIsEnumerable(cur)) {
73 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
74 else if (standaloneKeywords.propertyIsEnumerable(cur)) curPunc = "standalone";
75 return "keyword";
76 }
77 return "variable";
78 }
79 tokenBase.isBase = true;
80
81 function startString(quote, stream, state) {
82 var tripleQuoted = false;
83 if (quote != "/" && stream.eat(quote)) {
84 if (stream.eat(quote)) tripleQuoted = true;
85 else return "string";
86 }
87 function t(stream, state) {
88 var escaped = false, next, end = !tripleQuoted;
89 while ((next = stream.next()) != null) {
90 if (next == quote && !escaped) {
91 if (!tripleQuoted) { break; }
92 if (stream.match(quote + quote)) { end = true; break; }
93 }
94 if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
95 state.tokenize.push(tokenBaseUntilBrace());
96 return "string";
97 }
98 escaped = !escaped && next == "\\";
99 }
100 if (end) state.tokenize.pop();
101 return "string";
102 }
103 state.tokenize.push(t);
104 return t(stream, state);
105 }
106
107 function tokenBaseUntilBrace() {
108 var depth = 1;
109 function t(stream, state) {
110 if (stream.peek() == "}") {
111 depth--;
112 if (depth == 0) {
113 state.tokenize.pop();
114 return state.tokenize[state.tokenize.length-1](stream, state);
115 }
116 } else if (stream.peek() == "{") {
117 depth++;
118 }
119 return tokenBase(stream, state);
120 }
121 t.isBase = true;
122 return t;
123 }
124
125 function tokenComment(stream, state) {
126 var maybeEnd = false, ch;
127 while (ch = stream.next()) {
128 if (ch == "/" && maybeEnd) {
129 state.tokenize.pop();
130 break;
131 }
132 maybeEnd = (ch == "*");
133 }
134 return "comment";
135 }
136
137 function expectExpression(last, newline) {
138 return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
139 last == "newstatement" || last == "keyword" || last == "proplabel" ||
140 (last == "standalone" && !newline);
141 }
142
143 function Context(indented, column, type, align, prev) {
144 this.indented = indented;
145 this.column = column;
146 this.type = type;
147 this.align = align;
148 this.prev = prev;
149 }
150 function pushContext(state, col, type) {
151 return state.context = new Context(state.indented, col, type, null, state.context);
152 }
153 function popContext(state) {
154 var t = state.context.type;
155 if (t == ")" || t == "]" || t == "}")
156 state.indented = state.context.indented;
157 return state.context = state.context.prev;
158 }
159
160 // Interface
161
162 return {
163 startState: function(basecolumn) {
164 return {
165 tokenize: [tokenBase],
166 context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
167 indented: 0,
168 startOfLine: true,
169 lastToken: null
170 };
171 },
172
173 token: function(stream, state) {
174 var ctx = state.context;
175 if (stream.sol()) {
176 if (ctx.align == null) ctx.align = false;
177 state.indented = stream.indentation();
178 state.startOfLine = true;
179 // Automatic semicolon insertion
180 if (ctx.type == "statement" && !expectExpression(state.lastToken, true)) {
181 popContext(state); ctx = state.context;
182 }
183 }
184 if (stream.eatSpace()) return null;
185 curPunc = null;
186 var style = state.tokenize[state.tokenize.length-1](stream, state);
187 if (style == "comment") return style;
188 if (ctx.align == null) ctx.align = true;
189
190 if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
191 // Handle indentation for {x -> \n ... }
192 else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
193 popContext(state);
194 state.context.align = false;
195 }
196 else if (curPunc == "{") pushContext(state, stream.column(), "}");
197 else if (curPunc == "[") pushContext(state, stream.column(), "]");
198 else if (curPunc == "(") pushContext(state, stream.column(), ")");
199 else if (curPunc == "}") {
200 while (ctx.type == "statement") ctx = popContext(state);
201 if (ctx.type == "}") ctx = popContext(state);
202 while (ctx.type == "statement") ctx = popContext(state);
203 }
204 else if (curPunc == ctx.type) popContext(state);
205 else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
206 pushContext(state, stream.column(), "statement");
207 state.startOfLine = false;
208 state.lastToken = curPunc || style;
209 return style;
210 },
211
212 indent: function(state, textAfter) {
213 if (!state.tokenize[state.tokenize.length-1].isBase) return CodeMirror.Pass;
214 var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
215 if (ctx.type == "statement" && !expectExpression(state.lastToken, true)) ctx = ctx.prev;
216 var closing = firstChar == ctx.type;
217 if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
218 else if (ctx.align) return ctx.column + (closing ? 0 : 1);
219 else return ctx.indented + (closing ? 0 : config.indentUnit);
220 },
221
222 electricChars: "{}",
223 closeBrackets: {triples: "'\""},
224 fold: "brace",
225 blockCommentStart: "/*",
226 blockCommentEnd: "*/",
227 lineComment: "//"
228 };
229});
230
231CodeMirror.defineMIME("text/x-groovy", "groovy");
232
233});