Fix catalan translation
[geany-mirror.git] / scintilla / lexers / LexCoffeeScript.cxx
blob1667683218ec567d2b3c3f323be519e4d13608e8
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.
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <stdarg.h>
14 #include <assert.h>
15 #include <ctype.h>
17 #include "Platform.h"
18 #include "ILexer.h"
19 #include "Scintilla.h"
20 #include "SciLexer.h"
22 #include "WordList.h"
23 #include "LexAccessor.h"
24 #include "Accessor.h"
25 #include "StyleContext.h"
26 #include "CharacterSet.h"
27 #include "LexerModule.h"
29 #ifdef SCI_NAMESPACE
30 using namespace Scintilla;
31 #endif
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
47 // a = b+++/ptn/...
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 int pos = (int) sc.currentPos;
52 while (--pos > 0) {
53 char ch = styler[pos];
54 if (ch == '+' || ch == '-') {
55 return styler[pos - 1] == ch;
58 return false;
61 static bool followsReturnKeyword(StyleContext &sc, Accessor &styler) {
62 // Don't look at styles, so no need to flush.
63 int pos = (int) sc.currentPos;
64 int currentLine = styler.GetLine(pos);
65 int lineStartPos = styler.LineStart(currentLine);
66 while (--pos > lineStartPos) {
67 char ch = styler.SafeGetCharAt(pos);
68 if (ch != ' ' && ch != '\t') {
69 break;
72 const char *retBack = "nruter";
73 const char *s = retBack;
74 while (*s
75 && pos >= lineStartPos
76 && styler.SafeGetCharAt(pos) == *s) {
77 s++;
78 pos--;
80 return !*s;
83 static void ColouriseCoffeeScriptDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
84 Accessor &styler) {
86 WordList &keywords = *keywordlists[0];
87 WordList &keywords2 = *keywordlists[1];
88 WordList &keywords4 = *keywordlists[3];
90 CharacterSet setOKBeforeRE(CharacterSet::setNone, "([{=,:;!%^&*|?~+-");
91 CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-");
93 CharacterSet setWordStart(CharacterSet::setAlpha, "_$@", 0x80, true);
94 CharacterSet setWord(CharacterSet::setAlphaNum, "._$", 0x80, true);
96 int chPrevNonWhite = ' ';
97 int visibleChars = 0;
99 // look back to set chPrevNonWhite properly for better regex colouring
100 int endPos = startPos + length;
101 if (startPos > 0 && IsSpaceEquiv(initStyle)) {
102 unsigned int back = startPos;
103 styler.Flush();
104 while (back > 0 && IsSpaceEquiv(styler.StyleAt(--back)))
106 if (styler.StyleAt(back) == SCE_COFFEESCRIPT_OPERATOR) {
107 chPrevNonWhite = styler.SafeGetCharAt(back);
109 if (startPos != back) {
110 initStyle = styler.StyleAt(back);
111 if (IsSpaceEquiv(initStyle)) {
112 initStyle = SCE_COFFEESCRIPT_DEFAULT;
115 startPos = back;
118 StyleContext sc(startPos, endPos - startPos, initStyle, styler);
120 for (; sc.More(); sc.Forward()) {
122 if (sc.atLineStart) {
123 // Reset states to beginning of colourise so no surprises
124 // if different sets of lines lexed.
125 visibleChars = 0;
128 // Determine if the current state should terminate.
129 switch (sc.state) {
130 case SCE_COFFEESCRIPT_OPERATOR:
131 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
132 break;
133 case SCE_COFFEESCRIPT_NUMBER:
134 // We accept almost anything because of hex. and number suffixes
135 if (!setWord.Contains(sc.ch) || sc.Match('.', '.')) {
136 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
138 break;
139 case SCE_COFFEESCRIPT_IDENTIFIER:
140 if (!setWord.Contains(sc.ch) || (sc.ch == '.') || (sc.ch == '$')) {
141 char s[1000];
142 sc.GetCurrent(s, sizeof(s));
143 if (keywords.InList(s)) {
144 sc.ChangeState(SCE_COFFEESCRIPT_WORD);
145 } else if (keywords2.InList(s)) {
146 sc.ChangeState(SCE_COFFEESCRIPT_WORD2);
147 } else if (keywords4.InList(s)) {
148 sc.ChangeState(SCE_COFFEESCRIPT_GLOBALCLASS);
150 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
152 break;
153 case SCE_COFFEESCRIPT_WORD:
154 case SCE_COFFEESCRIPT_WORD2:
155 case SCE_COFFEESCRIPT_GLOBALCLASS:
156 if (!setWord.Contains(sc.ch)) {
157 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
159 break;
160 case SCE_COFFEESCRIPT_COMMENTLINE:
161 if (sc.atLineStart) {
162 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
164 break;
165 case SCE_COFFEESCRIPT_STRING:
166 if (sc.ch == '\\') {
167 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
168 sc.Forward();
170 } else if (sc.ch == '\"') {
171 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
173 break;
174 case SCE_COFFEESCRIPT_CHARACTER:
175 if (sc.ch == '\\') {
176 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
177 sc.Forward();
179 } else if (sc.ch == '\'') {
180 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
182 break;
183 case SCE_COFFEESCRIPT_REGEX:
184 if (sc.atLineStart) {
185 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
186 } else if (sc.ch == '/') {
187 sc.Forward();
188 while ((sc.ch < 0x80) && islower(sc.ch))
189 sc.Forward(); // gobble regex flags
190 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
191 } else if (sc.ch == '\\') {
192 // Gobble up the quoted character
193 if (sc.chNext == '\\' || sc.chNext == '/') {
194 sc.Forward();
197 break;
198 case SCE_COFFEESCRIPT_STRINGEOL:
199 if (sc.atLineStart) {
200 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
202 break;
203 case SCE_COFFEESCRIPT_COMMENTBLOCK:
204 if (sc.Match("###")) {
205 sc.Forward();
206 sc.Forward();
207 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
208 } else if (sc.ch == '\\') {
209 sc.Forward();
211 break;
212 case SCE_COFFEESCRIPT_VERBOSE_REGEX:
213 if (sc.Match("///")) {
214 sc.Forward();
215 sc.Forward();
216 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
217 } else if (sc.Match('#')) {
218 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT);
219 } else if (sc.ch == '\\') {
220 sc.Forward();
222 break;
223 case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT:
224 if (sc.atLineStart) {
225 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
227 break;
230 // Determine if a new state should be entered.
231 if (sc.state == SCE_COFFEESCRIPT_DEFAULT) {
232 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
233 sc.SetState(SCE_COFFEESCRIPT_NUMBER);
234 } else if (setWordStart.Contains(sc.ch)) {
235 sc.SetState(SCE_COFFEESCRIPT_IDENTIFIER);
236 } else if (sc.Match("///")) {
237 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
238 sc.Forward();
239 sc.Forward();
240 } else if (sc.ch == '/'
241 && (setOKBeforeRE.Contains(chPrevNonWhite)
242 || followsReturnKeyword(sc, styler))
243 && (!setCouldBePostOp.Contains(chPrevNonWhite)
244 || !FollowsPostfixOperator(sc, styler))) {
245 sc.SetState(SCE_COFFEESCRIPT_REGEX); // JavaScript's RegEx
246 } else if (sc.ch == '\"') {
247 sc.SetState(SCE_COFFEESCRIPT_STRING);
248 } else if (sc.ch == '\'') {
249 sc.SetState(SCE_COFFEESCRIPT_CHARACTER);
250 } else if (sc.ch == '#') {
251 if (sc.Match("###")) {
252 sc.SetState(SCE_COFFEESCRIPT_COMMENTBLOCK);
253 sc.Forward();
254 sc.Forward();
255 } else {
256 sc.SetState(SCE_COFFEESCRIPT_COMMENTLINE);
258 } else if (isoperator(static_cast<char>(sc.ch))) {
259 sc.SetState(SCE_COFFEESCRIPT_OPERATOR);
263 if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) {
264 chPrevNonWhite = sc.ch;
265 visibleChars++;
268 sc.Complete();
271 static bool IsCommentLine(int line, Accessor &styler) {
272 int pos = styler.LineStart(line);
273 int eol_pos = styler.LineStart(line + 1) - 1;
274 for (int i = pos; i < eol_pos; i++) {
275 char ch = styler[i];
276 if (ch == '#')
277 return true;
278 else if (ch != ' ' && ch != '\t')
279 return false;
281 return false;
284 static void FoldCoffeeScriptDoc(unsigned int startPos, int length, int,
285 WordList *[], Accessor &styler) {
286 // A simplified version of FoldPyDoc
287 const int maxPos = startPos + length;
288 const int maxLines = styler.GetLine(maxPos - 1); // Requested last line
289 const int docLines = styler.GetLine(styler.Length() - 1); // Available last line
291 // property fold.coffeescript.comment
292 const bool foldComment = styler.GetPropertyInt("fold.coffeescript.comment") != 0;
294 const bool foldCompact = styler.GetPropertyInt("fold.compact") != 0;
296 // Backtrack to previous non-blank line so we can determine indent level
297 // for any white space lines
298 // and so we can fix any preceding fold level (which is why we go back
299 // at least one line in all cases)
300 int spaceFlags = 0;
301 int lineCurrent = styler.GetLine(startPos);
302 int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
303 while (lineCurrent > 0) {
304 lineCurrent--;
305 indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
306 if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)
307 && !IsCommentLine(lineCurrent, styler))
308 break;
310 int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
312 // Set up initial loop state
313 int prevComment = 0;
314 if (lineCurrent >= 1)
315 prevComment = foldComment && IsCommentLine(lineCurrent - 1, styler);
317 // Process all characters to end of requested range
318 // or comment that hangs over the end of the range. Cap processing in all cases
319 // to end of document (in case of comment at end).
320 while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevComment)) {
322 // Gather info
323 int lev = indentCurrent;
324 int lineNext = lineCurrent + 1;
325 int indentNext = indentCurrent;
326 if (lineNext <= docLines) {
327 // Information about next line is only available if not at end of document
328 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
330 const int comment = foldComment && IsCommentLine(lineCurrent, styler);
331 const int comment_start = (comment && !prevComment && (lineNext <= docLines) &&
332 IsCommentLine(lineNext, styler) && (lev > SC_FOLDLEVELBASE));
333 const int comment_continue = (comment && prevComment);
334 if (!comment)
335 indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
336 if (indentNext & SC_FOLDLEVELWHITEFLAG)
337 indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;
339 if (comment_start) {
340 // Place fold point at start of a block of comments
341 lev |= SC_FOLDLEVELHEADERFLAG;
342 } else if (comment_continue) {
343 // Add level to rest of lines in the block
344 lev = lev + 1;
347 // Skip past any blank lines for next indent level info; we skip also
348 // comments (all comments, not just those starting in column 0)
349 // which effectively folds them into surrounding code rather
350 // than screwing up folding.
352 while ((lineNext < docLines) &&
353 ((indentNext & SC_FOLDLEVELWHITEFLAG) ||
354 (lineNext <= docLines && IsCommentLine(lineNext, styler)))) {
356 lineNext++;
357 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
360 const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK;
361 const int levelBeforeComments = Platform::Maximum(indentCurrentLevel,levelAfterComments);
363 // Now set all the indent levels on the lines we skipped
364 // Do this from end to start. Once we encounter one line
365 // which is indented more than the line after the end of
366 // the comment-block, use the level of the block before
368 int skipLine = lineNext;
369 int skipLevel = levelAfterComments;
371 while (--skipLine > lineCurrent) {
372 int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL);
374 if (foldCompact) {
375 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments)
376 skipLevel = levelBeforeComments;
378 int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;
380 styler.SetLevel(skipLine, skipLevel | whiteFlag);
381 } else {
382 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments &&
383 !(skipLineIndent & SC_FOLDLEVELWHITEFLAG) &&
384 !IsCommentLine(skipLine, styler))
385 skipLevel = levelBeforeComments;
387 styler.SetLevel(skipLine, skipLevel);
391 // Set fold header on non-comment line
392 if (!comment && !(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
393 if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK))
394 lev |= SC_FOLDLEVELHEADERFLAG;
397 // Keep track of block comment state of previous line
398 prevComment = comment_start || comment_continue;
400 // Set fold level for this line and move to next line
401 styler.SetLevel(lineCurrent, lev);
402 indentCurrent = indentNext;
403 lineCurrent = lineNext;
407 static const char *const csWordLists[] = {
408 "Keywords",
409 "Secondary keywords",
410 "Unused",
411 "Global classes",
415 LexerModule lmCoffeeScript(SCLEX_COFFEESCRIPT, ColouriseCoffeeScriptDoc, "coffeescript", FoldCoffeeScriptDoc, csWordLists);