1 // Scintilla source code edit control
2 /** @file LexCoffeeScript.cxx
3 ** Lexer for CoffeeScript.
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.
19 #include "Scintilla.h"
23 #include "LexAccessor.h"
25 #include "StyleContext.h"
26 #include "CharacterSet.h"
27 #include "LexerModule.h"
30 using namespace Scintilla
;
33 static bool IsSpaceEquiv(int state
) {
34 return (state
== SCE_COFFEESCRIPT_DEFAULT
35 || state
== SCE_COFFEESCRIPT_COMMENTLINE
36 || state
== SCE_COFFEESCRIPT_COMMENTBLOCK
37 || state
== SCE_COFFEESCRIPT_VERBOSE_REGEX
38 || state
== SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT
39 || state
== SCE_COFFEESCRIPT_WORD
40 || state
== SCE_COFFEESCRIPT_REGEX
);
43 // Preconditions: sc.currentPos points to a character after '+' or '-'.
44 // The test for pos reaching 0 should be redundant,
45 // and is in only for safety measures.
46 // Limitation: this code will give the incorrect answer for code like
48 // Putting a space between the '++' post-inc operator and the '+' binary op
49 // fixes this, and is highly recommended for readability anyway.
50 static bool FollowsPostfixOperator(StyleContext
&sc
, Accessor
&styler
) {
51 Sci_Position pos
= (Sci_Position
) sc
.currentPos
;
53 char ch
= styler
[pos
];
54 if (ch
== '+' || ch
== '-') {
55 return styler
[pos
- 1] == ch
;
61 static bool followsKeyword(StyleContext
&sc
, Accessor
&styler
) {
62 Sci_Position pos
= (Sci_Position
) sc
.currentPos
;
63 Sci_Position currentLine
= styler
.GetLine(pos
);
64 Sci_Position lineStartPos
= styler
.LineStart(currentLine
);
65 while (--pos
> lineStartPos
) {
66 char ch
= styler
.SafeGetCharAt(pos
);
67 if (ch
!= ' ' && ch
!= '\t') {
72 return styler
.StyleAt(pos
) == SCE_COFFEESCRIPT_WORD
;
75 static void ColouriseCoffeeScriptDoc(Sci_PositionU startPos
, Sci_Position length
, int initStyle
, WordList
*keywordlists
[],
78 WordList
&keywords
= *keywordlists
[0];
79 WordList
&keywords2
= *keywordlists
[1];
80 WordList
&keywords4
= *keywordlists
[3];
82 CharacterSet
setOKBeforeRE(CharacterSet::setNone
, "([{=,:;!%^&*|?~+-");
83 CharacterSet
setCouldBePostOp(CharacterSet::setNone
, "+-");
85 CharacterSet
setWordStart(CharacterSet::setAlpha
, "_$@", 0x80, true);
86 CharacterSet
setWord(CharacterSet::setAlphaNum
, "._$", 0x80, true);
88 int chPrevNonWhite
= ' ';
91 // look back to set chPrevNonWhite properly for better regex colouring
92 Sci_Position endPos
= startPos
+ length
;
93 if (startPos
> 0 && IsSpaceEquiv(initStyle
)) {
94 Sci_PositionU back
= startPos
;
96 while (back
> 0 && IsSpaceEquiv(styler
.StyleAt(--back
)))
98 if (styler
.StyleAt(back
) == SCE_COFFEESCRIPT_OPERATOR
) {
99 chPrevNonWhite
= styler
.SafeGetCharAt(back
);
101 if (startPos
!= back
) {
102 initStyle
= styler
.StyleAt(back
);
103 if (IsSpaceEquiv(initStyle
)) {
104 initStyle
= SCE_COFFEESCRIPT_DEFAULT
;
110 StyleContext
sc(startPos
, endPos
- startPos
, initStyle
, styler
);
112 for (; sc
.More(); sc
.Forward()) {
114 if (sc
.atLineStart
) {
115 // Reset states to beginning of colourise so no surprises
116 // if different sets of lines lexed.
120 // Determine if the current state should terminate.
122 case SCE_COFFEESCRIPT_OPERATOR
:
123 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
125 case SCE_COFFEESCRIPT_NUMBER
:
126 // We accept almost anything because of hex. and number suffixes
127 if (!setWord
.Contains(sc
.ch
) || sc
.Match('.', '.')) {
128 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
131 case SCE_COFFEESCRIPT_IDENTIFIER
:
132 if (!setWord
.Contains(sc
.ch
) || (sc
.ch
== '.') || (sc
.ch
== '$')) {
134 sc
.GetCurrent(s
, sizeof(s
));
135 if (keywords
.InList(s
)) {
136 sc
.ChangeState(SCE_COFFEESCRIPT_WORD
);
137 } else if (keywords2
.InList(s
)) {
138 sc
.ChangeState(SCE_COFFEESCRIPT_WORD2
);
139 } else if (keywords4
.InList(s
)) {
140 sc
.ChangeState(SCE_COFFEESCRIPT_GLOBALCLASS
);
141 } else if (sc
.LengthCurrent() > 0 && s
[0] == '@') {
142 sc
.ChangeState(SCE_COFFEESCRIPT_INSTANCEPROPERTY
);
144 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
147 case SCE_COFFEESCRIPT_WORD
:
148 case SCE_COFFEESCRIPT_WORD2
:
149 case SCE_COFFEESCRIPT_GLOBALCLASS
:
150 case SCE_COFFEESCRIPT_INSTANCEPROPERTY
:
151 if (!setWord
.Contains(sc
.ch
)) {
152 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
155 case SCE_COFFEESCRIPT_COMMENTLINE
:
156 if (sc
.atLineStart
) {
157 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
160 case SCE_COFFEESCRIPT_STRING
:
162 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
165 } else if (sc
.ch
== '\"') {
166 sc
.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT
);
169 case SCE_COFFEESCRIPT_CHARACTER
:
171 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
174 } else if (sc
.ch
== '\'') {
175 sc
.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT
);
178 case SCE_COFFEESCRIPT_REGEX
:
179 if (sc
.atLineStart
) {
180 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
181 } else if (sc
.ch
== '/') {
183 while ((sc
.ch
< 0x80) && islower(sc
.ch
))
184 sc
.Forward(); // gobble regex flags
185 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
186 } else if (sc
.ch
== '\\') {
187 // Gobble up the quoted character
188 if (sc
.chNext
== '\\' || sc
.chNext
== '/') {
193 case SCE_COFFEESCRIPT_STRINGEOL
:
194 if (sc
.atLineStart
) {
195 sc
.SetState(SCE_COFFEESCRIPT_DEFAULT
);
198 case SCE_COFFEESCRIPT_COMMENTBLOCK
:
199 if (sc
.Match("###")) {
202 sc
.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT
);
203 } else if (sc
.ch
== '\\') {
207 case SCE_COFFEESCRIPT_VERBOSE_REGEX
:
208 if (sc
.Match("///")) {
211 sc
.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT
);
212 } else if (sc
.Match('#')) {
213 sc
.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT
);
214 } else if (sc
.ch
== '\\') {
218 case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT
:
219 if (sc
.atLineStart
) {
220 sc
.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX
);
225 // Determine if a new state should be entered.
226 if (sc
.state
== SCE_COFFEESCRIPT_DEFAULT
) {
227 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
228 sc
.SetState(SCE_COFFEESCRIPT_NUMBER
);
229 } else if (setWordStart
.Contains(sc
.ch
)) {
230 sc
.SetState(SCE_COFFEESCRIPT_IDENTIFIER
);
231 } else if (sc
.Match("///")) {
232 sc
.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX
);
235 } else if (sc
.ch
== '/'
236 && (setOKBeforeRE
.Contains(chPrevNonWhite
)
237 || followsKeyword(sc
, styler
))
238 && (!setCouldBePostOp
.Contains(chPrevNonWhite
)
239 || !FollowsPostfixOperator(sc
, styler
))) {
240 sc
.SetState(SCE_COFFEESCRIPT_REGEX
); // JavaScript's RegEx
241 } else if (sc
.ch
== '\"') {
242 sc
.SetState(SCE_COFFEESCRIPT_STRING
);
243 } else if (sc
.ch
== '\'') {
244 sc
.SetState(SCE_COFFEESCRIPT_CHARACTER
);
245 } else if (sc
.ch
== '#') {
246 if (sc
.Match("###")) {
247 sc
.SetState(SCE_COFFEESCRIPT_COMMENTBLOCK
);
251 sc
.SetState(SCE_COFFEESCRIPT_COMMENTLINE
);
253 } else if (isoperator(static_cast<char>(sc
.ch
))) {
254 sc
.SetState(SCE_COFFEESCRIPT_OPERATOR
);
255 // Handle '..' and '...' operators correctly.
257 for (int i
= 0; i
< 2 && sc
.chNext
== '.'; i
++, sc
.Forward()) ;
262 if (!IsASpace(sc
.ch
) && !IsSpaceEquiv(sc
.state
)) {
263 chPrevNonWhite
= sc
.ch
;
270 static bool IsCommentLine(Sci_Position line
, Accessor
&styler
) {
271 Sci_Position pos
= styler
.LineStart(line
);
272 Sci_Position eol_pos
= styler
.LineStart(line
+ 1) - 1;
273 for (Sci_Position i
= pos
; i
< eol_pos
; i
++) {
277 else if (ch
!= ' ' && ch
!= '\t')
283 static void FoldCoffeeScriptDoc(Sci_PositionU startPos
, Sci_Position length
, int,
284 WordList
*[], Accessor
&styler
) {
285 // A simplified version of FoldPyDoc
286 const Sci_Position maxPos
= startPos
+ length
;
287 const Sci_Position maxLines
= styler
.GetLine(maxPos
- 1); // Requested last line
288 const Sci_Position docLines
= styler
.GetLine(styler
.Length() - 1); // Available last line
290 // property fold.coffeescript.comment
291 const bool foldComment
= styler
.GetPropertyInt("fold.coffeescript.comment") != 0;
293 const bool foldCompact
= styler
.GetPropertyInt("fold.compact") != 0;
295 // Backtrack to previous non-blank line so we can determine indent level
296 // for any white space lines
297 // and so we can fix any preceding fold level (which is why we go back
298 // at least one line in all cases)
300 Sci_Position lineCurrent
= styler
.GetLine(startPos
);
301 int indentCurrent
= styler
.IndentAmount(lineCurrent
, &spaceFlags
, NULL
);
302 while (lineCurrent
> 0) {
304 indentCurrent
= styler
.IndentAmount(lineCurrent
, &spaceFlags
, NULL
);
305 if (!(indentCurrent
& SC_FOLDLEVELWHITEFLAG
)
306 && !IsCommentLine(lineCurrent
, styler
))
309 int indentCurrentLevel
= indentCurrent
& SC_FOLDLEVELNUMBERMASK
;
311 // Set up initial loop state
313 if (lineCurrent
>= 1)
314 prevComment
= foldComment
&& IsCommentLine(lineCurrent
- 1, styler
);
316 // Process all characters to end of requested range
317 // or comment that hangs over the end of the range. Cap processing in all cases
318 // to end of document (in case of comment at end).
319 while ((lineCurrent
<= docLines
) && ((lineCurrent
<= maxLines
) || prevComment
)) {
322 int lev
= indentCurrent
;
323 Sci_Position lineNext
= lineCurrent
+ 1;
324 int indentNext
= indentCurrent
;
325 if (lineNext
<= docLines
) {
326 // Information about next line is only available if not at end of document
327 indentNext
= styler
.IndentAmount(lineNext
, &spaceFlags
, NULL
);
329 const int comment
= foldComment
&& IsCommentLine(lineCurrent
, styler
);
330 const int comment_start
= (comment
&& !prevComment
&& (lineNext
<= docLines
) &&
331 IsCommentLine(lineNext
, styler
) && (lev
> SC_FOLDLEVELBASE
));
332 const int comment_continue
= (comment
&& prevComment
);
334 indentCurrentLevel
= indentCurrent
& SC_FOLDLEVELNUMBERMASK
;
335 if (indentNext
& SC_FOLDLEVELWHITEFLAG
)
336 indentNext
= SC_FOLDLEVELWHITEFLAG
| indentCurrentLevel
;
339 // Place fold point at start of a block of comments
340 lev
|= SC_FOLDLEVELHEADERFLAG
;
341 } else if (comment_continue
) {
342 // Add level to rest of lines in the block
346 // Skip past any blank lines for next indent level info; we skip also
347 // comments (all comments, not just those starting in column 0)
348 // which effectively folds them into surrounding code rather
349 // than screwing up folding.
351 while ((lineNext
< docLines
) &&
352 ((indentNext
& SC_FOLDLEVELWHITEFLAG
) ||
353 (lineNext
<= docLines
&& IsCommentLine(lineNext
, styler
)))) {
356 indentNext
= styler
.IndentAmount(lineNext
, &spaceFlags
, NULL
);
359 const int levelAfterComments
= indentNext
& SC_FOLDLEVELNUMBERMASK
;
360 const int levelBeforeComments
= Platform::Maximum(indentCurrentLevel
,levelAfterComments
);
362 // Now set all the indent levels on the lines we skipped
363 // Do this from end to start. Once we encounter one line
364 // which is indented more than the line after the end of
365 // the comment-block, use the level of the block before
367 Sci_Position skipLine
= lineNext
;
368 int skipLevel
= levelAfterComments
;
370 while (--skipLine
> lineCurrent
) {
371 int skipLineIndent
= styler
.IndentAmount(skipLine
, &spaceFlags
, NULL
);
374 if ((skipLineIndent
& SC_FOLDLEVELNUMBERMASK
) > levelAfterComments
)
375 skipLevel
= levelBeforeComments
;
377 int whiteFlag
= skipLineIndent
& SC_FOLDLEVELWHITEFLAG
;
379 styler
.SetLevel(skipLine
, skipLevel
| whiteFlag
);
381 if ((skipLineIndent
& SC_FOLDLEVELNUMBERMASK
) > levelAfterComments
&&
382 !(skipLineIndent
& SC_FOLDLEVELWHITEFLAG
) &&
383 !IsCommentLine(skipLine
, styler
))
384 skipLevel
= levelBeforeComments
;
386 styler
.SetLevel(skipLine
, skipLevel
);
390 // Set fold header on non-comment line
391 if (!comment
&& !(indentCurrent
& SC_FOLDLEVELWHITEFLAG
)) {
392 if ((indentCurrent
& SC_FOLDLEVELNUMBERMASK
) < (indentNext
& SC_FOLDLEVELNUMBERMASK
))
393 lev
|= SC_FOLDLEVELHEADERFLAG
;
396 // Keep track of block comment state of previous line
397 prevComment
= comment_start
|| comment_continue
;
399 // Set fold level for this line and move to next line
400 styler
.SetLevel(lineCurrent
, lev
);
401 indentCurrent
= indentNext
;
402 lineCurrent
= lineNext
;
406 static const char *const csWordLists
[] = {
408 "Secondary keywords",
414 LexerModule
lmCoffeeScript(SCLEX_COFFEESCRIPT
, ColouriseCoffeeScriptDoc
, "coffeescript", FoldCoffeeScriptDoc
, csWordLists
);