Improve API docs related to keybindings configuration file
[geany-mirror.git] / scintilla / lexers / LexCoffeeScript.cxx
blobf3256001633e8b74a973948ab64de940a7762a4f
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 Sci_Position pos = (Sci_Position) 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 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') {
68 break;
71 styler.Flush();
72 return styler.StyleAt(pos) == SCE_COFFEESCRIPT_WORD;
75 static void ColouriseCoffeeScriptDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[],
76 Accessor &styler) {
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 = ' ';
89 int visibleChars = 0;
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;
95 styler.Flush();
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;
107 startPos = back;
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.
117 visibleChars = 0;
120 // Determine if the current state should terminate.
121 switch (sc.state) {
122 case SCE_COFFEESCRIPT_OPERATOR:
123 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
124 break;
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);
130 break;
131 case SCE_COFFEESCRIPT_IDENTIFIER:
132 if (!setWord.Contains(sc.ch) || (sc.ch == '.') || (sc.ch == '$')) {
133 char s[1000];
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);
146 break;
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);
154 break;
155 case SCE_COFFEESCRIPT_COMMENTLINE:
156 if (sc.atLineStart) {
157 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
159 break;
160 case SCE_COFFEESCRIPT_STRING:
161 if (sc.ch == '\\') {
162 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
163 sc.Forward();
165 } else if (sc.ch == '\"') {
166 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
168 break;
169 case SCE_COFFEESCRIPT_CHARACTER:
170 if (sc.ch == '\\') {
171 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
172 sc.Forward();
174 } else if (sc.ch == '\'') {
175 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
177 break;
178 case SCE_COFFEESCRIPT_REGEX:
179 if (sc.atLineStart) {
180 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
181 } else if (sc.ch == '/') {
182 sc.Forward();
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 == '/') {
189 sc.Forward();
192 break;
193 case SCE_COFFEESCRIPT_STRINGEOL:
194 if (sc.atLineStart) {
195 sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
197 break;
198 case SCE_COFFEESCRIPT_COMMENTBLOCK:
199 if (sc.Match("###")) {
200 sc.Forward();
201 sc.Forward();
202 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
203 } else if (sc.ch == '\\') {
204 sc.Forward();
206 break;
207 case SCE_COFFEESCRIPT_VERBOSE_REGEX:
208 if (sc.Match("///")) {
209 sc.Forward();
210 sc.Forward();
211 sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
212 } else if (sc.Match('#')) {
213 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT);
214 } else if (sc.ch == '\\') {
215 sc.Forward();
217 break;
218 case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT:
219 if (sc.atLineStart) {
220 sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
222 break;
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);
233 sc.Forward();
234 sc.Forward();
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);
248 sc.Forward();
249 sc.Forward();
250 } else {
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.
256 if (sc.ch == '.') {
257 for (int i = 0; i < 2 && sc.chNext == '.'; i++, sc.Forward()) ;
262 if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) {
263 chPrevNonWhite = sc.ch;
264 visibleChars++;
267 sc.Complete();
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++) {
274 char ch = styler[i];
275 if (ch == '#')
276 return true;
277 else if (ch != ' ' && ch != '\t')
278 return false;
280 return false;
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)
299 int spaceFlags = 0;
300 Sci_Position lineCurrent = styler.GetLine(startPos);
301 int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
302 while (lineCurrent > 0) {
303 lineCurrent--;
304 indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
305 if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)
306 && !IsCommentLine(lineCurrent, styler))
307 break;
309 int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
311 // Set up initial loop state
312 int prevComment = 0;
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)) {
321 // Gather info
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);
333 if (!comment)
334 indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
335 if (indentNext & SC_FOLDLEVELWHITEFLAG)
336 indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;
338 if (comment_start) {
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
343 lev = lev + 1;
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)))) {
355 lineNext++;
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);
373 if (foldCompact) {
374 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments)
375 skipLevel = levelBeforeComments;
377 int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;
379 styler.SetLevel(skipLine, skipLevel | whiteFlag);
380 } else {
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[] = {
407 "Keywords",
408 "Secondary keywords",
409 "Unused",
410 "Global classes",
414 LexerModule lmCoffeeScript(SCLEX_COFFEESCRIPT, ColouriseCoffeeScriptDoc, "coffeescript", FoldCoffeeScriptDoc, csWordLists);