]>
Commit | Line | Data |
---|---|---|
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); |