]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/lexers/LexCoffeeScript.cxx
Search for font instead of creating it each time
[wxWidgets.git] / src / stc / scintilla / lexers / LexCoffeeScript.cxx
1 // Scintilla source code edit control
2 /** @file LexCoffeeScript.cxx
3 ** Lexer for CoffeeScript.
4 **/
5 // Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org>
6 // Based on the Scintilla C++ Lexer
7 // Written by Eric Promislow <ericp@activestate.com> in 2011 for the Komodo IDE
8 // The License.txt file describes the conditions under which this software may be distributed.
9
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <stdarg.h>
14 #include <assert.h>
15 #include <ctype.h>
16
17 #include "Platform.h"
18 #include "ILexer.h"
19 #include "Scintilla.h"
20 #include "SciLexer.h"
21
22 #include "WordList.h"
23 #include "LexAccessor.h"
24 #include "Accessor.h"
25 #include "StyleContext.h"
26 #include "CharacterSet.h"
27 #include "LexerModule.h"
28
29 #ifdef SCI_NAMESPACE
30 using namespace Scintilla;
31 #endif
32
33 static bool IsSpaceEquiv(int state) {
34 return (state <= SCE_C_COMMENTDOC
35 // including SCE_C_DEFAULT, SCE_C_COMMENT, SCE_C_COMMENTLINE
36 || state == SCE_C_COMMENTLINEDOC
37 || state == SCE_C_COMMENTDOCKEYWORD
38 || state == SCE_C_COMMENTDOCKEYWORDERROR
39 || state == SCE_COFFEESCRIPT_COMMENTBLOCK
40 || state == SCE_COFFEESCRIPT_VERBOSE_REGEX
41 || state == SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT
42 || state == SCE_C_WORD
43 || state == SCE_C_REGEX);
44 }
45
46 // Preconditions: sc.currentPos points to a character after '+' or '-'.
47 // The test for pos reaching 0 should be redundant,
48 // and is in only for safety measures.
49 // Limitation: this code will give the incorrect answer for code like
50 // a = b+++/ptn/...
51 // Putting a space between the '++' post-inc operator and the '+' binary op
52 // fixes this, and is highly recommended for readability anyway.
53 static bool FollowsPostfixOperator(StyleContext &sc, Accessor &styler) {
54 int pos = (int) sc.currentPos;
55 while (--pos > 0) {
56 char ch = styler[pos];
57 if (ch == '+' || ch == '-') {
58 return styler[pos - 1] == ch;
59 }
60 }
61 return false;
62 }
63
64 static bool followsReturnKeyword(StyleContext &sc, Accessor &styler) {
65 // Don't look at styles, so no need to flush.
66 int pos = (int) sc.currentPos;
67 int currentLine = styler.GetLine(pos);
68 int lineStartPos = styler.LineStart(currentLine);
69 char ch;
70 while (--pos > lineStartPos) {
71 ch = styler.SafeGetCharAt(pos);
72 if (ch != ' ' && ch != '\t') {
73 break;
74 }
75 }
76 const char *retBack = "nruter";
77 const char *s = retBack;
78 while (*s
79 && pos >= lineStartPos
80 && styler.SafeGetCharAt(pos) == *s) {
81 s++;
82 pos--;
83 }
84 return !*s;
85 }
86
87 static void ColouriseCoffeeScriptDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
88 Accessor &styler) {
89
90 WordList &keywords = *keywordlists[0];
91 WordList &keywords2 = *keywordlists[1];
92 WordList &keywords3 = *keywordlists[2];
93 WordList &keywords4 = *keywordlists[3];
94
95 // property styling.within.preprocessor
96 // For C++ code, determines whether all preprocessor code is styled in the preprocessor style (0, the default)
97 // or only from the initial # to the end of the command word(1).
98 bool stylingWithinPreprocessor = styler.GetPropertyInt("styling.within.preprocessor") != 0;
99
100 CharacterSet setOKBeforeRE(CharacterSet::setNone, "([{=,:;!%^&*|?~+-");
101 CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-");
102
103 CharacterSet setDoxygen(CharacterSet::setAlpha, "$@\\&<>#{}[]");
104
105 CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
106 CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
107
108 // property lexer.cpp.allow.dollars
109 // Set to 0 to disallow the '$' character in identifiers with the cpp lexer.
110 if (styler.GetPropertyInt("lexer.cpp.allow.dollars", 1) != 0) {
111 setWordStart.Add('$');
112 setWord.Add('$');
113 }
114
115 int chPrevNonWhite = ' ';
116 int visibleChars = 0;
117 bool lastWordWasUUID = false;
118 int styleBeforeDCKeyword = SCE_C_DEFAULT;
119 bool continuationLine = false;
120 bool isIncludePreprocessor = false;
121
122 if (initStyle == SCE_C_PREPROCESSOR) {
123 // Set continuationLine if last character of previous line is '\'
124 int lineCurrent = styler.GetLine(startPos);
125 if (lineCurrent > 0) {
126 int chBack = styler.SafeGetCharAt(startPos-1, 0);
127 int chBack2 = styler.SafeGetCharAt(startPos-2, 0);
128 int lineEndChar = '!';
129 if (chBack2 == '\r' && chBack == '\n') {
130 lineEndChar = styler.SafeGetCharAt(startPos-3, 0);
131 } else if (chBack == '\n' || chBack == '\r') {
132 lineEndChar = chBack2;
133 }
134 continuationLine = lineEndChar == '\\';
135 }
136 }
137
138 // look back to set chPrevNonWhite properly for better regex colouring
139 int endPos = startPos + length;
140 if (startPos > 0) {
141 unsigned int back = startPos;
142 styler.Flush();
143 while (back > 0 && IsSpaceEquiv(styler.StyleAt(--back)))
144 ;
145 if (styler.StyleAt(back) == SCE_C_OPERATOR) {
146 chPrevNonWhite = styler.SafeGetCharAt(back);
147 }
148 if (startPos != back) {
149 initStyle = styler.StyleAt(back);
150 }
151 startPos = back;
152 }
153
154 StyleContext sc(startPos, endPos - startPos, initStyle, styler);
155
156 for (; sc.More(); sc.Forward()) {
157
158 if (sc.atLineStart) {
159 // Reset states to begining of colourise so no surprises
160 // if different sets of lines lexed.
161 visibleChars = 0;
162 lastWordWasUUID = false;
163 isIncludePreprocessor = false;
164 }
165
166 // Handle line continuation generically.
167 if (sc.ch == '\\') {
168 if (sc.chNext == '\n' || sc.chNext == '\r') {
169 sc.Forward();
170 if (sc.ch == '\r' && sc.chNext == '\n') {
171 sc.Forward();
172 }
173 continuationLine = true;
174 continue;
175 }
176 }
177
178 // Determine if the current state should terminate.
179 switch (sc.state) {
180 case SCE_C_OPERATOR:
181 sc.SetState(SCE_C_DEFAULT);
182 break;
183 case SCE_C_NUMBER:
184 // We accept almost anything because of hex. and number suffixes
185 if (!setWord.Contains(sc.ch)) {
186 sc.SetState(SCE_C_DEFAULT);
187 }
188 break;
189 case SCE_C_IDENTIFIER:
190 if (!setWord.Contains(sc.ch) || (sc.ch == '.') || (sc.ch == '$')) {
191 char s[1000];
192 sc.GetCurrent(s, sizeof(s));
193 if (keywords.InList(s)) {
194 lastWordWasUUID = strcmp(s, "uuid") == 0;
195 sc.ChangeState(SCE_C_WORD);
196 } else if (keywords2.InList(s)) {
197 sc.ChangeState(SCE_C_WORD2);
198 } else if (keywords4.InList(s)) {
199 sc.ChangeState(SCE_C_GLOBALCLASS);
200 }
201 sc.SetState(SCE_C_DEFAULT);
202 }
203 break;
204 case SCE_C_PREPROCESSOR:
205 if (sc.atLineStart && !continuationLine) {
206 sc.SetState(SCE_C_DEFAULT);
207 } else if (stylingWithinPreprocessor) {
208 if (IsASpace(sc.ch)) {
209 sc.SetState(SCE_C_DEFAULT);
210 }
211 } else {
212 if (sc.Match('/', '*') || sc.Match('/', '/')) {
213 sc.SetState(SCE_C_DEFAULT);
214 }
215 }
216 break;
217 case SCE_C_COMMENT:
218 if (sc.Match('*', '/')) {
219 sc.Forward();
220 sc.ForwardSetState(SCE_C_DEFAULT);
221 }
222 break;
223 case SCE_C_COMMENTDOC:
224 if (sc.Match('*', '/')) {
225 sc.Forward();
226 sc.ForwardSetState(SCE_C_DEFAULT);
227 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
228 // Verify that we have the conditions to mark a comment-doc-keyword
229 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
230 styleBeforeDCKeyword = SCE_C_COMMENTDOC;
231 sc.SetState(SCE_C_COMMENTDOCKEYWORD);
232 }
233 }
234 break;
235 case SCE_C_COMMENTLINE:
236 if (sc.atLineStart) {
237 sc.SetState(SCE_C_DEFAULT);
238 }
239 break;
240 case SCE_C_COMMENTLINEDOC:
241 if (sc.atLineStart) {
242 sc.SetState(SCE_C_DEFAULT);
243 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
244 // Verify that we have the conditions to mark a comment-doc-keyword
245 if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) {
246 styleBeforeDCKeyword = SCE_C_COMMENTLINEDOC;
247 sc.SetState(SCE_C_COMMENTDOCKEYWORD);
248 }
249 }
250 break;
251 case SCE_C_COMMENTDOCKEYWORD:
252 if ((styleBeforeDCKeyword == SCE_C_COMMENTDOC) && sc.Match('*', '/')) {
253 sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR);
254 sc.Forward();
255 sc.ForwardSetState(SCE_C_DEFAULT);
256 } else if (!setDoxygen.Contains(sc.ch)) {
257 char s[100];
258 sc.GetCurrent(s, sizeof(s));
259 if (!IsASpace(sc.ch) || !keywords3.InList(s + 1)) {
260 sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR);
261 }
262 sc.SetState(styleBeforeDCKeyword);
263 }
264 break;
265 case SCE_C_STRING:
266 if (isIncludePreprocessor) {
267 if (sc.ch == '>') {
268 sc.ForwardSetState(SCE_C_DEFAULT);
269 isIncludePreprocessor = false;
270 }
271 } else if (sc.ch == '\\') {
272 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
273 sc.Forward();
274 }
275 } else if (sc.ch == '\"') {
276 sc.ForwardSetState(SCE_C_DEFAULT);
277 }
278 break;
279 case SCE_C_CHARACTER:
280 if (sc.ch == '\\') {
281 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
282 sc.Forward();
283 }
284 } else if (sc.ch == '\'') {
285 sc.ForwardSetState(SCE_C_DEFAULT);
286 }
287 break;
288 case SCE_C_REGEX:
289 if (sc.atLineStart) {
290 sc.SetState(SCE_C_DEFAULT);
291 } else if (sc.ch == '/') {
292 sc.Forward();
293 while ((sc.ch < 0x80) && islower(sc.ch))
294 sc.Forward(); // gobble regex flags
295 sc.SetState(SCE_C_DEFAULT);
296 } else if (sc.ch == '\\') {
297 // Gobble up the quoted character
298 if (sc.chNext == '\\' || sc.chNext == '/') {
299 sc.Forward();
300 }
301 }
302 break;
303 case SCE_C_STRINGEOL:
304 if (sc.atLineStart) {
305 sc.SetState(SCE_C_DEFAULT);
306 }
307 break;
308 case SCE_C_VERBATIM:
309 if (sc.ch == '\"') {
310 if (sc.chNext == '\"') {
311 sc.Forward();
312 } else {
313 sc.ForwardSetState(SCE_C_DEFAULT);
314 }
315 }
316 break;
317 case SCE_C_UUID:
318 if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ')') {
319 sc.SetState(SCE_C_DEFAULT);
320 }
321 break;
322 case SCE_COFFEESCRIPT_COMMENTBLOCK:
323 if (sc.Match("###")) {
324 sc.ChangeState(SCE_C_COMMENT);
325 sc.Forward();
326 sc.Forward();
327 sc.ForwardSetState(SCE_C_DEFAULT);
328 } else if (sc.ch == '\\') {
329 sc.Forward();
330 }
331 break;
332 case SCE_COFFEESCRIPT_VERBOSE_REGEX:
333 if (sc.Match("///")) {
334 sc.Forward();
335 sc.Forward();
336 sc.ChangeState(SCE_C_REGEX);
337 sc.ForwardSetState(SCE_C_DEFAULT);
338 } else if (sc.Match('#')) {
339 sc.ChangeState(SCE_C_REGEX);
340 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT);
341 } else if (sc.ch == '\\') {
342 sc.Forward();
343 }
344 break;
345 case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT:
346 if (sc.atLineStart) {
347 sc.ChangeState(SCE_C_COMMENT);
348 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
349 }
350 break;
351 }
352
353 // Determine if a new state should be entered.
354 if (sc.state == SCE_C_DEFAULT) {
355 if (sc.Match('@', '\"')) {
356 sc.SetState(SCE_C_VERBATIM);
357 sc.Forward();
358 } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
359 if (lastWordWasUUID) {
360 sc.SetState(SCE_C_UUID);
361 lastWordWasUUID = false;
362 } else {
363 sc.SetState(SCE_C_NUMBER);
364 }
365 } else if (setWordStart.Contains(sc.ch) || (sc.ch == '@') || (sc.ch == '$')) {
366 if (lastWordWasUUID) {
367 sc.SetState(SCE_C_UUID);
368 lastWordWasUUID = false;
369 } else {
370 sc.SetState(SCE_C_IDENTIFIER);
371 }
372 } else if (sc.Match('/', '*')) {
373 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Qt/Doxygen doc. style
374 sc.SetState(SCE_C_COMMENTDOC);
375 } else {
376 sc.SetState(SCE_C_COMMENT);
377 }
378 sc.Forward(); // Eat the * so it isn't used for the end of the comment
379 } else if (sc.Match("///")) {
380 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
381 } else if (sc.ch == '/'
382 && (setOKBeforeRE.Contains(chPrevNonWhite)
383 || followsReturnKeyword(sc, styler))
384 && (!setCouldBePostOp.Contains(chPrevNonWhite)
385 || !FollowsPostfixOperator(sc, styler))) {
386 sc.SetState(SCE_C_REGEX); // JavaScript's RegEx
387 } else if (sc.ch == '\"') {
388 sc.SetState(SCE_C_STRING);
389 isIncludePreprocessor = false; // ensure that '>' won't end the string
390 } else if (isIncludePreprocessor && sc.ch == '<') {
391 sc.SetState(SCE_C_STRING);
392 } else if (sc.ch == '\'') {
393 sc.SetState(SCE_C_CHARACTER);
394 } else if (sc.ch == '#') {
395 if (sc.Match("###")) {
396 sc.SetState(SCE_COFFEESCRIPT_COMMENTBLOCK);
397 } else {
398 sc.SetState(SCE_C_COMMENTLINE);
399 }
400 } else if (isoperator(static_cast<char>(sc.ch))) {
401 sc.SetState(SCE_C_OPERATOR);
402 }
403 }
404
405 if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) {
406 chPrevNonWhite = sc.ch;
407 visibleChars++;
408 }
409 continuationLine = false;
410 }
411 // Change temporary coffeescript states into standard C ones.
412 switch (sc.state) {
413 case SCE_COFFEESCRIPT_COMMENTBLOCK:
414 sc.ChangeState(SCE_C_COMMENT);
415 break;
416 case SCE_COFFEESCRIPT_VERBOSE_REGEX:
417 sc.ChangeState(SCE_C_REGEX);
418 break;
419 case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT:
420 sc.ChangeState(SCE_C_COMMENTLINE);
421 break;
422 }
423 sc.Complete();
424 }
425
426 static bool IsCommentLine(int line, Accessor &styler) {
427 int pos = styler.LineStart(line);
428 int eol_pos = styler.LineStart(line + 1) - 1;
429 for (int i = pos; i < eol_pos; i++) {
430 char ch = styler[i];
431 if (ch == '#')
432 return true;
433 else if (ch == '/'
434 && i < eol_pos - 1
435 && styler[i + 1] == '*')
436 return true;
437 else if (ch != ' ' && ch != '\t')
438 return false;
439 }
440 return false;
441 }
442
443 static void FoldCoffeeScriptDoc(unsigned int startPos, int length, int,
444 WordList *[], Accessor &styler) {
445 // A simplified version of FoldPyDoc
446 const int maxPos = startPos + length;
447 const int maxLines = styler.GetLine(maxPos - 1); // Requested last line
448 const int docLines = styler.GetLine(styler.Length() - 1); // Available last line
449
450 // property fold.coffeescript.comment
451 const bool foldComment = styler.GetPropertyInt("fold.coffeescript.comment") != 0;
452
453 const bool foldCompact = styler.GetPropertyInt("fold.compact") != 0;
454
455 // Backtrack to previous non-blank line so we can determine indent level
456 // for any white space lines
457 // and so we can fix any preceding fold level (which is why we go back
458 // at least one line in all cases)
459 int spaceFlags = 0;
460 int lineCurrent = styler.GetLine(startPos);
461 int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
462 while (lineCurrent > 0) {
463 lineCurrent--;
464 indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
465 if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)
466 && !IsCommentLine(lineCurrent, styler))
467 break;
468 }
469 int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
470
471 // Set up initial loop state
472 int prevComment = 0;
473 if (lineCurrent >= 1)
474 prevComment = foldComment && IsCommentLine(lineCurrent - 1, styler);
475
476 // Process all characters to end of requested range
477 // or comment that hangs over the end of the range. Cap processing in all cases
478 // to end of document (in case of comment at end).
479 while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevComment)) {
480
481 // Gather info
482 int lev = indentCurrent;
483 int lineNext = lineCurrent + 1;
484 int indentNext = indentCurrent;
485 if (lineNext <= docLines) {
486 // Information about next line is only available if not at end of document
487 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
488 }
489 const int comment = foldComment && IsCommentLine(lineCurrent, styler);
490 const int comment_start = (comment && !prevComment && (lineNext <= docLines) &&
491 IsCommentLine(lineNext, styler) && (lev > SC_FOLDLEVELBASE));
492 const int comment_continue = (comment && prevComment);
493 if (!comment)
494 indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
495 if (indentNext & SC_FOLDLEVELWHITEFLAG)
496 indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;
497
498 if (comment_start) {
499 // Place fold point at start of a block of comments
500 lev |= SC_FOLDLEVELHEADERFLAG;
501 } else if (comment_continue) {
502 // Add level to rest of lines in the block
503 lev = lev + 1;
504 }
505
506 // Skip past any blank lines for next indent level info; we skip also
507 // comments (all comments, not just those starting in column 0)
508 // which effectively folds them into surrounding code rather
509 // than screwing up folding.
510
511 while ((lineNext < docLines) &&
512 ((indentNext & SC_FOLDLEVELWHITEFLAG) ||
513 (lineNext <= docLines && IsCommentLine(lineNext, styler)))) {
514
515 lineNext++;
516 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
517 }
518
519 const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK;
520 const int levelBeforeComments = Platform::Maximum(indentCurrentLevel,levelAfterComments);
521
522 // Now set all the indent levels on the lines we skipped
523 // Do this from end to start. Once we encounter one line
524 // which is indented more than the line after the end of
525 // the comment-block, use the level of the block before
526
527 int skipLine = lineNext;
528 int skipLevel = levelAfterComments;
529
530 while (--skipLine > lineCurrent) {
531 int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL);
532
533 if (foldCompact) {
534 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments)
535 skipLevel = levelBeforeComments;
536
537 int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;
538
539 styler.SetLevel(skipLine, skipLevel | whiteFlag);
540 } else {
541 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments &&
542 !(skipLineIndent & SC_FOLDLEVELWHITEFLAG) &&
543 !IsCommentLine(skipLine, styler))
544 skipLevel = levelBeforeComments;
545
546 styler.SetLevel(skipLine, skipLevel);
547 }
548 }
549
550 // Set fold header on non-comment line
551 if (!comment && !(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
552 if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK))
553 lev |= SC_FOLDLEVELHEADERFLAG;
554 }
555
556 // Keep track of block comment state of previous line
557 prevComment = comment_start || comment_continue;
558
559 // Set fold level for this line and move to next line
560 styler.SetLevel(lineCurrent, lev);
561 indentCurrent = indentNext;
562 lineCurrent = lineNext;
563 }
564 }
565
566 static const char *const csWordLists[] = {
567 "Keywords",
568 0,
569 };
570
571 LexerModule lmCoffeeScript(SCLEX_COFFEESCRIPT, ColouriseCoffeeScriptDoc, "coffeescript", FoldCoffeeScriptDoc, csWordLists);