aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornyamatongwe <unknown>2011-11-29 11:52:13 +1100
committernyamatongwe <unknown>2011-11-29 11:52:13 +1100
commit1e5e7dc3c7d21f21293fcd6612e0e1520d22594b (patch)
tree4352e261226d7d272e6926af9f7eda673f00f014
parentf43d2b475dd1985ac9f420b35fefea74f3fc7fe5 (diff)
downloadscintilla-mirror-1e5e7dc3c7d21f21293fcd6612e0e1520d22594b.tar.gz
Feature #3436256. Lexer for CoffeeScript from Eric Promislow
of ActiveState.
-rw-r--r--include/SciLexer.h27
-rw-r--r--include/Scintilla.iface29
-rw-r--r--lexers/LexCoffeeScript.cxx572
-rw-r--r--src/Catalogue.cxx1
-rw-r--r--win32/scintilla.mak3
-rw-r--r--win32/scintilla_vc6.mak3
6 files changed, 635 insertions, 0 deletions
diff --git a/include/SciLexer.h b/include/SciLexer.h
index 255e091b7..48ac30f7a 100644
--- a/include/SciLexer.h
+++ b/include/SciLexer.h
@@ -114,6 +114,7 @@
#define SCLEX_TXT2TAGS 99
#define SCLEX_A68K 100
#define SCLEX_MODULA 101
+#define SCLEX_COFFEESCRIPT 102
#define SCLEX_AUTOMATIC 1000
#define SCE_P_DEFAULT 0
#define SCE_P_COMMENTLINE 1
@@ -1477,6 +1478,32 @@
#define SCE_MODULA_PRGKEY 15
#define SCE_MODULA_OPERATOR 16
#define SCE_MODULA_BADSTR 17
+#define SCE_COFFEESCRIPT_DEFAULT 0
+#define SCE_COFFEESCRIPT_COMMENT 1
+#define SCE_COFFEESCRIPT_COMMENTLINE 2
+#define SCE_COFFEESCRIPT_COMMENTDOC 3
+#define SCE_COFFEESCRIPT_NUMBER 4
+#define SCE_COFFEESCRIPT_WORD 5
+#define SCE_COFFEESCRIPT_STRING 6
+#define SCE_COFFEESCRIPT_CHARACTER 7
+#define SCE_COFFEESCRIPT_UUID 8
+#define SCE_COFFEESCRIPT_PREPROCESSOR 9
+#define SCE_COFFEESCRIPT_OPERATOR 10
+#define SCE_COFFEESCRIPT_IDENTIFIER 11
+#define SCE_COFFEESCRIPT_STRINGEOL 12
+#define SCE_COFFEESCRIPT_VERBATIM 13
+#define SCE_COFFEESCRIPT_REGEX 14
+#define SCE_COFFEESCRIPT_COMMENTLINEDOC 15
+#define SCE_COFFEESCRIPT_WORD2 16
+#define SCE_COFFEESCRIPT_COMMENTDOCKEYWORD 17
+#define SCE_COFFEESCRIPT_COMMENTDOCKEYWORDERROR 18
+#define SCE_COFFEESCRIPT_GLOBALCLASS 19
+#define SCE_COFFEESCRIPT_STRINGRAW 20
+#define SCE_COFFEESCRIPT_TRIPLEVERBATIM 21
+#define SCE_COFFEESCRIPT_HASHQUOTEDSTRING 22
+#define SCE_COFFEESCRIPT_COMMENTBLOCK 22
+#define SCE_COFFEESCRIPT_VERBOSE_REGEX 23
+#define SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT 24
/* --Autogenerated -- end of section automatically generated from Scintilla.iface */
#endif
diff --git a/include/Scintilla.iface b/include/Scintilla.iface
index d2cfc2e40..3d19fc261 100644
--- a/include/Scintilla.iface
+++ b/include/Scintilla.iface
@@ -2453,6 +2453,7 @@ val SCLEX_MARKDOWN=98
val SCLEX_TXT2TAGS=99
val SCLEX_A68K=100
val SCLEX_MODULA=101
+val SCLEX_COFFEESCRIPT=102
# When a lexer specifies its language as SCLEX_AUTOMATIC it receives a
# value assigned in sequence from SCLEX_AUTOMATIC+1.
@@ -4001,6 +4002,34 @@ val SCE_MODULA_PRAGMA=14
val SCE_MODULA_PRGKEY=15
val SCE_MODULA_OPERATOR=16
val SCE_MODULA_BADSTR=17
+# Lexical states for SCLEX_COFFEESCRIPT
+lex CoffeeScript=SCLEX_COFFEESCRIPT SCE_COFFEESCRIPT_
+val SCE_COFFEESCRIPT_DEFAULT=0
+val SCE_COFFEESCRIPT_COMMENT=1
+val SCE_COFFEESCRIPT_COMMENTLINE=2
+val SCE_COFFEESCRIPT_COMMENTDOC=3
+val SCE_COFFEESCRIPT_NUMBER=4
+val SCE_COFFEESCRIPT_WORD=5
+val SCE_COFFEESCRIPT_STRING=6
+val SCE_COFFEESCRIPT_CHARACTER=7
+val SCE_COFFEESCRIPT_UUID=8
+val SCE_COFFEESCRIPT_PREPROCESSOR=9
+val SCE_COFFEESCRIPT_OPERATOR=10
+val SCE_COFFEESCRIPT_IDENTIFIER=11
+val SCE_COFFEESCRIPT_STRINGEOL=12
+val SCE_COFFEESCRIPT_VERBATIM=13
+val SCE_COFFEESCRIPT_REGEX=14
+val SCE_COFFEESCRIPT_COMMENTLINEDOC=15
+val SCE_COFFEESCRIPT_WORD2=16
+val SCE_COFFEESCRIPT_COMMENTDOCKEYWORD=17
+val SCE_COFFEESCRIPT_COMMENTDOCKEYWORDERROR=18
+val SCE_COFFEESCRIPT_GLOBALCLASS=19
+val SCE_COFFEESCRIPT_STRINGRAW=20
+val SCE_COFFEESCRIPT_TRIPLEVERBATIM=21
+val SCE_COFFEESCRIPT_HASHQUOTEDSTRING=22
+val SCE_COFFEESCRIPT_COMMENTBLOCK=22
+val SCE_COFFEESCRIPT_VERBOSE_REGEX=23
+val SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT=24
# Events
diff --git a/lexers/LexCoffeeScript.cxx b/lexers/LexCoffeeScript.cxx
new file mode 100644
index 000000000..590cef5e1
--- /dev/null
+++ b/lexers/LexCoffeeScript.cxx
@@ -0,0 +1,572 @@
+// Scintilla source code edit control
+/** @file LexCoffeeScript.cxx
+ ** Lexer for CoffeeScript.
+ **/
+// Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org>
+// Based on the Scintilla C++ Lexer
+// Written by Eric Promislow <ericp@activestate.com> in 2011 for the Komodo IDE
+// The License.txt file describes the conditions under which this software may be distributed.
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "Platform.h"
+#include "ILexer.h"
+#include "Scintilla.h"
+#include "SciLexer.h"
+
+#include "WordList.h"
+#include "LexAccessor.h"
+#include "Accessor.h"
+#include "StyleContext.h"
+#include "CharacterSet.h"
+#include "LexerModule.h"
+
+#ifdef SCI_NAMESPACE
+using namespace Scintilla;
+#endif
+
+static bool IsSpaceEquiv(int state) {
+ return (state <= SCE_C_COMMENTDOC
+ // including SCE_C_DEFAULT, SCE_C_COMMENT, SCE_C_COMMENTLINE
+ || state == SCE_C_COMMENTLINEDOC
+ || state == SCE_C_COMMENTDOCKEYWORD
+ || state == SCE_C_COMMENTDOCKEYWORDERROR
+ || state == SCE_COFFEESCRIPT_COMMENTBLOCK
+ || state == SCE_COFFEESCRIPT_VERBOSE_REGEX
+ || state == SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT
+ || state == SCE_C_WORD
+ || state == SCE_C_REGEX);
+}
+
+// Preconditions: sc.currentPos points to a character after '+' or '-'.
+// The test for pos reaching 0 should be redundant,
+// and is in only for safety measures.
+// Limitation: this code will give the incorrect answer for code like
+// a = b+++/ptn/...
+// Putting a space between the '++' post-inc operator and the '+' binary op
+// fixes this, and is highly recommended for readability anyway.
+static bool FollowsPostfixOperator(StyleContext &sc, Accessor &styler) {
+ int pos = (int) sc.currentPos;
+ while (--pos > 0) {
+ char ch = styler[pos];
+ if (ch == '+' || ch == '-') {
+ return styler[pos - 1] == ch;
+ }
+ }
+ return false;
+}
+
+static bool followsReturnKeyword(StyleContext &sc, Accessor &styler) {
+ // Don't look at styles, so no need to flush.
+ int pos = (int) sc.currentPos;
+ int currentLine = styler.GetLine(pos);
+ int lineStartPos = styler.LineStart(currentLine);
+ char ch;
+ while (--pos > lineStartPos) {
+ ch = styler.SafeGetCharAt(pos);
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ }
+ const char *retBack = "nruter";
+ const char *s = retBack;
+ while (*s
+ && pos >= lineStartPos
+ && styler.SafeGetCharAt(pos) == *s) {
+ s++;
+ pos--;
+ }
+ return !*s;
+}
+
+static void ColouriseCoffeeScriptDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
+ Accessor &styler) {
+
+ WordList &keywords = *keywordlists[0];
+ WordList &keywords2 = *keywordlists[1];
+ WordList &keywords3 = *keywordlists[2];
+ WordList &keywords4 = *keywordlists[3];
+
+ // property styling.within.preprocessor
+ // For C++ code, determines whether all preprocessor code is styled in the preprocessor style (0, the default)
+ // or only from the initial # to the end of the command word(1).
+ bool stylingWithinPreprocessor = styler.GetPropertyInt("styling.within.preprocessor") != 0;
+
+ CharacterSet setOKBeforeRE(CharacterSet::setNone, "([{=,:;!%^&*|?~+-");
+ CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-");
+
+ CharacterSet setDoxygen(CharacterSet::setAlpha, "$@\\&<>#{}[]");
+
+ CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
+ CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
+
+ // property lexer.cpp.allow.dollars
+ // Set to 0 to disallow the '$' character in identifiers with the cpp lexer.
+ if (styler.GetPropertyInt("lexer.cpp.allow.dollars", 1) != 0) {
+ setWordStart.Add('$');
+ setWord.Add('$');
+ }
+
+ int chPrevNonWhite = ' ';
+ int visibleChars = 0;
+ bool lastWordWasUUID = false;
+ int styleBeforeDCKeyword = SCE_C_DEFAULT;
+ bool continuationLine = false;
+ bool isIncludePreprocessor = false;
+
+ if (initStyle == SCE_C_PREPROCESSOR) {
+ // Set continuationLine if last character of previous line is '\'
+ int lineCurrent = styler.GetLine(startPos);
+ if (lineCurrent > 0) {
+ int chBack = styler.SafeGetCharAt(startPos-1, 0);
+ int chBack2 = styler.SafeGetCharAt(startPos-2, 0);
+ int lineEndChar = '!';
+ if (chBack2 == '\r' && chBack == '\n') {
+ lineEndChar = styler.SafeGetCharAt(startPos-3, 0);
+ } else if (chBack == '\n' || chBack == '\r') {
+ lineEndChar = chBack2;
+ }
+ continuationLine = lineEndChar == '\\';
+ }
+ }
+
+ // look back to set chPrevNonWhite properly for better regex colouring
+ int endPos = startPos + length;
+ if (startPos > 0) {
+ unsigned int back = startPos;
+ styler.Flush();
+ while (back > 0 && IsSpaceEquiv(styler.StyleAt(--back)))
+ ;
+ if (styler.StyleAt(back) == SCE_C_OPERATOR) {
+ chPrevNonWhite = styler.SafeGetCharAt(back);
+ }
+ if (startPos != back) {
+ initStyle = styler.StyleAt(back);
+ }
+ startPos = back;
+ }
+
+ StyleContext sc(startPos, endPos - startPos, initStyle, styler);
+
+ for (; sc.More(); sc.Forward()) {
+
+ if (sc.atLineStart) {
+ // Reset states to begining of colourise so no surprises
+ // if different sets of lines lexed.
+ visibleChars = 0;
+ lastWordWasUUID = false;
+ isIncludePreprocessor = false;
+ }
+
+ // Handle line continuation generically.
+ if (sc.ch == '\\') {
+ if (sc.chNext == '\n' || sc.chNext == '\r') {
+ sc.Forward();
+ if (sc.ch == '\r' && sc.chNext == '\n') {
+ sc.Forward();
+ }
+ continuationLine = true;
+ continue;
+ }
+ }
+
+ // Determine if the current state should terminate.
+ switch (sc.state) {
+ case SCE_C_OPERATOR:
+ sc.SetState(SCE_C_DEFAULT);
+ break;
+ case SCE_C_NUMBER:
+ // We accept almost anything because of hex. and number suffixes
+ if (!setWord.Contains(sc.ch)) {
+ sc.SetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_C_IDENTIFIER:
+ if (!setWord.Contains(sc.ch) || (sc.ch == '.') || (sc.ch == '$')) {
+ char s[1000];
+ sc.GetCurrent(s, sizeof(s));
+ if (keywords.InList(s)) {
+ lastWordWasUUID = strcmp(s, "uuid") == 0;
+ sc.ChangeState(SCE_C_WORD);
+ } else if (keywords2.InList(s)) {
+ sc.ChangeState(SCE_C_WORD2);
+ } else if (keywords4.InList(s)) {
+ sc.ChangeState(SCE_C_GLOBALCLASS);
+ }
+ sc.SetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_C_PREPROCESSOR:
+ if (sc.atLineStart && !continuationLine) {
+ sc.SetState(SCE_C_DEFAULT);
+ } else if (stylingWithinPreprocessor) {
+ if (IsASpace(sc.ch)) {
+ sc.SetState(SCE_C_DEFAULT);
+ }
+ } else {
+ if (sc.Match('/', '*') || sc.Match('/', '/')) {
+ sc.SetState(SCE_C_DEFAULT);
+ }
+ }
+ break;
+ case SCE_C_COMMENT:
+ if (sc.Match('*', '/')) {
+ sc.Forward();
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_C_COMMENTDOC:
+ if (sc.Match('*', '/')) {
+ sc.Forward();
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
+ // Verify that we have the conditions to mark a comment-doc-keyword
+ if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
+ styleBeforeDCKeyword = SCE_C_COMMENTDOC;
+ sc.SetState(SCE_C_COMMENTDOCKEYWORD);
+ }
+ }
+ break;
+ case SCE_C_COMMENTLINE:
+ if (sc.atLineStart) {
+ sc.SetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_C_COMMENTLINEDOC:
+ if (sc.atLineStart) {
+ sc.SetState(SCE_C_DEFAULT);
+ } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
+ // Verify that we have the conditions to mark a comment-doc-keyword
+ if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) {
+ styleBeforeDCKeyword = SCE_C_COMMENTLINEDOC;
+ sc.SetState(SCE_C_COMMENTDOCKEYWORD);
+ }
+ }
+ break;
+ case SCE_C_COMMENTDOCKEYWORD:
+ if ((styleBeforeDCKeyword == SCE_C_COMMENTDOC) && sc.Match('*', '/')) {
+ sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR);
+ sc.Forward();
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ } else if (!setDoxygen.Contains(sc.ch)) {
+ char s[100];
+ sc.GetCurrent(s, sizeof(s));
+ if (!IsASpace(sc.ch) || !keywords3.InList(s + 1)) {
+ sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR);
+ }
+ sc.SetState(styleBeforeDCKeyword);
+ }
+ break;
+ case SCE_C_STRING:
+ if (isIncludePreprocessor) {
+ if (sc.ch == '>') {
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ isIncludePreprocessor = false;
+ }
+ } else if (sc.ch == '\\') {
+ if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
+ sc.Forward();
+ }
+ } else if (sc.ch == '\"') {
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_C_CHARACTER:
+ if (sc.ch == '\\') {
+ if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
+ sc.Forward();
+ }
+ } else if (sc.ch == '\'') {
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_C_REGEX:
+ if (sc.atLineStart) {
+ sc.SetState(SCE_C_DEFAULT);
+ } else if (sc.ch == '/') {
+ sc.Forward();
+ while ((sc.ch < 0x80) && islower(sc.ch))
+ sc.Forward(); // gobble regex flags
+ sc.SetState(SCE_C_DEFAULT);
+ } else if (sc.ch == '\\') {
+ // Gobble up the quoted character
+ if (sc.chNext == '\\' || sc.chNext == '/') {
+ sc.Forward();
+ }
+ }
+ break;
+ case SCE_C_STRINGEOL:
+ if (sc.atLineStart) {
+ sc.SetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_C_VERBATIM:
+ if (sc.ch == '\"') {
+ if (sc.chNext == '\"') {
+ sc.Forward();
+ } else {
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ }
+ }
+ break;
+ case SCE_C_UUID:
+ if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ')') {
+ sc.SetState(SCE_C_DEFAULT);
+ }
+ break;
+ case SCE_COFFEESCRIPT_COMMENTBLOCK:
+ if (sc.Match("###")) {
+ sc.ChangeState(SCE_C_COMMENT);
+ sc.Forward();
+ sc.Forward();
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ } else if (sc.ch == '\\') {
+ sc.Forward();
+ }
+ break;
+ case SCE_COFFEESCRIPT_VERBOSE_REGEX:
+ if (sc.Match("///")) {
+ sc.Forward();
+ sc.Forward();
+ sc.ChangeState(SCE_C_REGEX);
+ sc.ForwardSetState(SCE_C_DEFAULT);
+ } else if (sc.Match('#')) {
+ sc.ChangeState(SCE_C_REGEX);
+ sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT);
+ } else if (sc.ch == '\\') {
+ sc.Forward();
+ }
+ break;
+ case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT:
+ if (sc.atLineStart) {
+ sc.ChangeState(SCE_C_COMMENT);
+ sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
+ }
+ break;
+ }
+
+ // Determine if a new state should be entered.
+ if (sc.state == SCE_C_DEFAULT) {
+ if (sc.Match('@', '\"')) {
+ sc.SetState(SCE_C_VERBATIM);
+ sc.Forward();
+ } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
+ if (lastWordWasUUID) {
+ sc.SetState(SCE_C_UUID);
+ lastWordWasUUID = false;
+ } else {
+ sc.SetState(SCE_C_NUMBER);
+ }
+ } else if (setWordStart.Contains(sc.ch) || (sc.ch == '@') || (sc.ch == '$')) {
+ if (lastWordWasUUID) {
+ sc.SetState(SCE_C_UUID);
+ lastWordWasUUID = false;
+ } else {
+ sc.SetState(SCE_C_IDENTIFIER);
+ }
+ } else if (sc.Match('/', '*')) {
+ if (sc.Match("/**") || sc.Match("/*!")) { // Support of Qt/Doxygen doc. style
+ sc.SetState(SCE_C_COMMENTDOC);
+ } else {
+ sc.SetState(SCE_C_COMMENT);
+ }
+ sc.Forward(); // Eat the * so it isn't used for the end of the comment
+ } else if (sc.Match("///")) {
+ sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
+ } else if (sc.ch == '/'
+ && (setOKBeforeRE.Contains(chPrevNonWhite)
+ || followsReturnKeyword(sc, styler))
+ && (!setCouldBePostOp.Contains(chPrevNonWhite)
+ || !FollowsPostfixOperator(sc, styler))) {
+ sc.SetState(SCE_C_REGEX); // JavaScript's RegEx
+ } else if (sc.ch == '\"') {
+ sc.SetState(SCE_C_STRING);
+ isIncludePreprocessor = false; // ensure that '>' won't end the string
+ } else if (isIncludePreprocessor && sc.ch == '<') {
+ sc.SetState(SCE_C_STRING);
+ } else if (sc.ch == '\'') {
+ sc.SetState(SCE_C_CHARACTER);
+ } else if (sc.ch == '#') {
+ if (sc.Match("###")) {
+ sc.SetState(SCE_COFFEESCRIPT_COMMENTBLOCK);
+ } else {
+ sc.SetState(SCE_C_COMMENTLINE);
+ }
+ } else if (isoperator(static_cast<char>(sc.ch))) {
+ sc.SetState(SCE_C_OPERATOR);
+ }
+ }
+
+ if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) {
+ chPrevNonWhite = sc.ch;
+ visibleChars++;
+ }
+ continuationLine = false;
+ }
+ // Change temporary coffeescript states into standard C ones.
+ switch (sc.state) {
+ case SCE_COFFEESCRIPT_COMMENTBLOCK:
+ sc.ChangeState(SCE_C_COMMENT);
+ break;
+ case SCE_COFFEESCRIPT_VERBOSE_REGEX:
+ sc.ChangeState(SCE_C_REGEX);
+ break;
+ case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT:
+ sc.ChangeState(SCE_C_COMMENTLINE);
+ break;
+ }
+ sc.Complete();
+}
+
+static bool IsCommentLine(int line, Accessor &styler) {
+ int pos = styler.LineStart(line);
+ int eol_pos = styler.LineStart(line + 1) - 1;
+ for (int i = pos; i < eol_pos; i++) {
+ char ch = styler[i];
+ if (ch == '#')
+ return true;
+ else if (ch == '/'
+ && i < eol_pos - 1
+ && styler[i + 1] == '*')
+ return true;
+ else if (ch != ' ' && ch != '\t')
+ return false;
+ }
+ return false;
+}
+
+static void FoldCoffeeScriptDoc(unsigned int startPos, int length, int,
+ WordList *[], Accessor &styler) {
+ // A simplified version of FoldPyDoc
+ const int maxPos = startPos + length;
+ const int maxLines = styler.GetLine(maxPos - 1); // Requested last line
+ const int docLines = styler.GetLine(styler.Length() - 1); // Available last line
+
+ // property fold.coffeescript.comment
+ const bool foldComment = styler.GetPropertyInt("fold.coffeescript.comment") != 0;
+
+ const bool foldCompact = styler.GetPropertyInt("fold.compact") != 0;
+
+ // Backtrack to previous non-blank line so we can determine indent level
+ // for any white space lines
+ // and so we can fix any preceding fold level (which is why we go back
+ // at least one line in all cases)
+ int spaceFlags = 0;
+ int lineCurrent = styler.GetLine(startPos);
+ int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
+ while (lineCurrent > 0) {
+ lineCurrent--;
+ indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
+ if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)
+ && !IsCommentLine(lineCurrent, styler))
+ break;
+ }
+ int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
+
+ // Set up initial loop state
+ startPos = styler.LineStart(lineCurrent);
+ int prevComment = 0;
+ if (lineCurrent >= 1)
+ prevComment = foldComment && IsCommentLine(lineCurrent - 1, styler);
+
+ // Process all characters to end of requested range
+ // or comment that hangs over the end of the range. Cap processing in all cases
+ // to end of document (in case of comment at end).
+ while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevComment)) {
+
+ // Gather info
+ int lev = indentCurrent;
+ int lineNext = lineCurrent + 1;
+ int indentNext = indentCurrent;
+ if (lineNext <= docLines) {
+ // Information about next line is only available if not at end of document
+ indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
+ }
+ const int comment = foldComment && IsCommentLine(lineCurrent, styler);
+ const int comment_start = (comment && !prevComment && (lineNext <= docLines) &&
+ IsCommentLine(lineNext, styler) && (lev > SC_FOLDLEVELBASE));
+ const int comment_continue = (comment && prevComment);
+ if (!comment)
+ indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
+ if (indentNext & SC_FOLDLEVELWHITEFLAG)
+ indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;
+
+ if (comment_start) {
+ // Place fold point at start of a block of comments
+ lev |= SC_FOLDLEVELHEADERFLAG;
+ } else if (comment_continue) {
+ // Add level to rest of lines in the block
+ lev = lev + 1;
+ }
+
+ // Skip past any blank lines for next indent level info; we skip also
+ // comments (all comments, not just those starting in column 0)
+ // which effectively folds them into surrounding code rather
+ // than screwing up folding.
+
+ while ((lineNext < docLines) &&
+ ((indentNext & SC_FOLDLEVELWHITEFLAG) ||
+ (lineNext <= docLines && IsCommentLine(lineNext, styler)))) {
+
+ lineNext++;
+ indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
+ }
+
+ const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK;
+ const int levelBeforeComments = Platform::Maximum(indentCurrentLevel,levelAfterComments);
+
+ // Now set all the indent levels on the lines we skipped
+ // Do this from end to start. Once we encounter one line
+ // which is indented more than the line after the end of
+ // the comment-block, use the level of the block before
+
+ int skipLine = lineNext;
+ int skipLevel = levelAfterComments;
+
+ while (--skipLine > lineCurrent) {
+ int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL);
+
+ if (foldCompact) {
+ if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments)
+ skipLevel = levelBeforeComments;
+
+ int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;
+
+ styler.SetLevel(skipLine, skipLevel | whiteFlag);
+ } else {
+ if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments &&
+ !(skipLineIndent & SC_FOLDLEVELWHITEFLAG) &&
+ !IsCommentLine(skipLine, styler))
+ skipLevel = levelBeforeComments;
+
+ styler.SetLevel(skipLine, skipLevel);
+ }
+ }
+
+ // Set fold header on non-comment line
+ if (!comment && !(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
+ if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK))
+ lev |= SC_FOLDLEVELHEADERFLAG;
+ }
+
+ // Keep track of block comment state of previous line
+ prevComment = comment_start || comment_continue;
+
+ // Set fold level for this line and move to next line
+ styler.SetLevel(lineCurrent, lev);
+ indentCurrent = indentNext;
+ lineCurrent = lineNext;
+ }
+}
+
+static const char *const csWordLists[] = {
+ "Keywords",
+ 0,
+};
+
+LexerModule lmCoffeeScript(SCLEX_COFFEESCRIPT, ColouriseCoffeeScriptDoc, "coffeescript", FoldCoffeeScriptDoc, csWordLists);
diff --git a/src/Catalogue.cxx b/src/Catalogue.cxx
index 00785d549..0e8ea9611 100644
--- a/src/Catalogue.cxx
+++ b/src/Catalogue.cxx
@@ -100,6 +100,7 @@ int Scintilla_LinkLexers() {
LINK_LEXER(lmClwNoCase);
LINK_LEXER(lmCmake);
LINK_LEXER(lmCOBOL);
+ LINK_LEXER(lmCoffeeScript);
LINK_LEXER(lmConf);
LINK_LEXER(lmCPP);
LINK_LEXER(lmCPPNoCase);
diff --git a/win32/scintilla.mak b/win32/scintilla.mak
index 8265c2968..9e7841aa3 100644
--- a/win32/scintilla.mak
+++ b/win32/scintilla.mak
@@ -100,6 +100,7 @@ LEXOBJS=\
$(DIR_O)\LexCLW.obj \
$(DIR_O)\LexCmake.obj \
$(DIR_O)\LexCOBOL.obj \
+ $(DIR_O)\LexCoffeeScript.obj \
$(DIR_O)\LexConf.obj \
$(DIR_O)\LexCPP.obj \
$(DIR_O)\LexCrontab.obj \
@@ -314,6 +315,8 @@ $(DIR_O)\LexCmake.obj: ..\lexers\LexCmake.cxx $(LEX_HEADERS)
$(DIR_O)\LexCOBOL.obj: ..\lexers\LexCOBOL.cxx $(LEX_HEADERS)
+$(DIR_O)\LexCoffeeScript.obj: ..\lexers\LexCoffeeScript.cxx $(LEX_HEADERS)
+
$(DIR_O)\LexConf.obj: ..\lexers\LexConf.cxx $(LEX_HEADERS)
$(DIR_O)\LexCPP.obj: ..\lexers\LexCPP.cxx $(LEX_HEADERS)
diff --git a/win32/scintilla_vc6.mak b/win32/scintilla_vc6.mak
index 237edf605..efa7c7e5f 100644
--- a/win32/scintilla_vc6.mak
+++ b/win32/scintilla_vc6.mak
@@ -101,6 +101,7 @@ LEXOBJS=\
$(DIR_O)\LexCLW.obj \
$(DIR_O)\LexCmake.obj \
$(DIR_O)\LexCOBOL.obj \
+ $(DIR_O)\LexCoffeeScript.obj \
$(DIR_O)\LexConf.obj \
$(DIR_O)\LexCPP.obj \
$(DIR_O)\LexCrontab.obj \
@@ -312,6 +313,8 @@ $(DIR_O)\LexCmake.obj: ..\lexers\LexCmake.cxx $(LEX_HEADERS)
$(DIR_O)\LexCOBOL.obj: ..\lexers\LexCOBOL.cxx $(LEX_HEADERS)
+$(DIR_O)\LexCoffeeScript.obj: ..\lexers\LexCoffeeScript.cxx $(LEX_HEADERS)
+
$(DIR_O)\LexConf.obj: ..\lexers\LexConf.cxx $(LEX_HEADERS)
$(DIR_O)\LexCPP.obj: ..\lexers\LexCPP.cxx $(LEX_HEADERS)