~ubuntu-branches/ubuntu/utopic/codemirror-js/utopic

« back to all changes in this revision

Viewing changes to mode/coffeescript/coffeescript.js

  • Committer: Package Import Robot
  • Author(s): David Paleino
  • Date: 2012-04-12 12:25:28 UTC
  • Revision ID: package-import@ubuntu.com-20120412122528-8xp5a8frj4h1d3ee
Tags: upstream-2.23
ImportĀ upstreamĀ versionĀ 2.23

Show diffs side-by-side

added added

removed removed

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