diff options
-rw-r--r-- | doc/ScintillaHistory.html | 1 | ||||
-rw-r--r-- | include/SciLexer.h | 1 | ||||
-rw-r--r-- | include/Scintilla.iface | 1 | ||||
-rw-r--r-- | lexers/LexCSS.cxx | 217 |
4 files changed, 196 insertions, 24 deletions
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html index 32db286fe..11a099606 100644 --- a/doc/ScintillaHistory.html +++ b/doc/ScintillaHistory.html @@ -389,6 +389,7 @@ <td>Marat Dukhan</td> <td>Stefan Weil</td> <td>Rex Conn</td> + <td>Ross McKay</td> </tr> </table> <p> diff --git a/include/SciLexer.h b/include/SciLexer.h index c60d76d29..6878c0e5a 100644 --- a/include/SciLexer.h +++ b/include/SciLexer.h @@ -677,6 +677,7 @@ #define SCE_CSS_EXTENDED_PSEUDOCLASS 20 #define SCE_CSS_EXTENDED_PSEUDOELEMENT 21 #define SCE_CSS_MEDIA 22 +#define SCE_CSS_VARIABLE 23 #define SCE_POV_DEFAULT 0 #define SCE_POV_COMMENT 1 #define SCE_POV_COMMENTLINE 2 diff --git a/include/Scintilla.iface b/include/Scintilla.iface index 7154aa36d..226e175ae 100644 --- a/include/Scintilla.iface +++ b/include/Scintilla.iface @@ -3099,6 +3099,7 @@ val SCE_CSS_EXTENDED_IDENTIFIER=19 val SCE_CSS_EXTENDED_PSEUDOCLASS=20 val SCE_CSS_EXTENDED_PSEUDOELEMENT=21 val SCE_CSS_MEDIA=22 +val SCE_CSS_VARIABLE=23 # Lexical states for SCLEX_POV lex POV=SCLEX_POV SCE_POV_ val SCE_POV_DEFAULT=0 diff --git a/lexers/LexCSS.cxx b/lexers/LexCSS.cxx index 8fbca3845..be3e688a5 100644 --- a/lexers/LexCSS.cxx +++ b/lexers/LexCSS.cxx @@ -3,10 +3,16 @@ ** Lexer for Cascading Style Sheets ** Written by Jakub Vrána ** Improved by Philippe Lhoste (CSS2) + ** Improved by Ross McKay (SCSS mode; see http://sass-lang.com/ ) **/ // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org> // The License.txt file describes the conditions under which this software may be distributed. +// TODO: handle SCSS nested properties like font: { weight: bold; size: 1em; } +// TODO: handle SCSS interpolation: #{} +// TODO: add features for Less if somebody feels like contributing; http://lesscss.org/ +// TODO: refactor this monster so that the next poor slob can read it! + #include <stdlib.h> #include <string.h> #include <stdio.h> @@ -51,6 +57,22 @@ inline bool IsCssOperator(const int ch) { return false; } +// look behind (from start of document to our start position) to determine current nesting level +inline int NestingLevelLookBehind(unsigned int startPos, Accessor &styler) { + int ch; + int nestingLevel = 0; + + for (unsigned int i = 0; i < startPos; i++) { + ch = styler.SafeGetCharAt(i); + if (ch == '{') + nestingLevel++; + else if (ch == '}') + nestingLevel--; + } + + return nestingLevel; +} + static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler) { WordList &css1Props = *keywordlists[0]; WordList &pseudoClasses = *keywordlists[1]; @@ -66,11 +88,35 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo int lastState = -1; // before operator int lastStateC = -1; // before comment int lastStateS = -1; // before single-quoted/double-quoted string + int lastStateVar = -1; // before variable (SCSS) + int lastStateVal = -1; // before value (SCSS) int op = ' '; // last operator int opPrev = ' '; // last operator + bool insideParentheses = false; // true if currently in a CSS url() or similar construct + + // property lexer.css.scss.language + // Set to 1 for Sassy CSS (.scss) + bool isScssDocument = styler.GetPropertyInt("lexer.css.scss.language") != 0; + + // TODO: implement Less support + bool isLessDocument = false; + // SCSS and Less both support single-line comments + typedef enum _CommentModes { eCommentBlock = 0, eCommentLine = 1} CommentMode; + CommentMode comment_mode = eCommentBlock; + bool hasSingleLineComments = isScssDocument || isLessDocument; + + // must keep track of nesting level in document types that support it (SCSS, Less) + bool hasNesting = false; + int nestingLevel = 0; + if (isScssDocument || isLessDocument) { + hasNesting = true; + nestingLevel = NestingLevelLookBehind(startPos, styler); + } + + // "the loop" for (; sc.More(); sc.Forward()) { - if (sc.state == SCE_CSS_COMMENT && sc.Match('*', '/')) { + if (sc.state == SCE_CSS_COMMENT && ((comment_mode == eCommentBlock && sc.Match('*', '/')) || (comment_mode == eCommentLine && sc.atLineEnd))) { if (lastStateC == -1) { // backtrack to get last state: // comments are like whitespace, so we must return to the previous state @@ -94,8 +140,13 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo if (i == 0) lastStateC = SCE_CSS_DEFAULT; } - sc.Forward(); - sc.ForwardSetState(lastStateC); + if (comment_mode == eCommentBlock) { + sc.Forward(); + sc.ForwardSetState(lastStateC); + } else /* eCommentLine */ { + sc.Forward(); + sc.SetState(lastStateC); + } } if (sc.state == SCE_CSS_COMMENT) @@ -125,7 +176,7 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo } switch (op) { case '@': - if (lastState == SCE_CSS_DEFAULT) + if (lastState == SCE_CSS_DEFAULT || hasNesting) sc.SetState(SCE_CSS_DIRECTIVE); break; case '>': @@ -144,15 +195,33 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo sc.SetState(SCE_CSS_TAG); break; case '{': - if (lastState == SCE_CSS_MEDIA) + nestingLevel++; + switch (lastState) { + case SCE_CSS_MEDIA: sc.SetState(SCE_CSS_DEFAULT); - else if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DIRECTIVE) + break; + case SCE_CSS_TAG: + case SCE_CSS_DIRECTIVE: sc.SetState(SCE_CSS_IDENTIFIER); + break; + } break; case '}': - if (lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_VALUE || lastState == SCE_CSS_IMPORTANT || - lastState == SCE_CSS_IDENTIFIER || lastState == SCE_CSS_IDENTIFIER2 || lastState == SCE_CSS_IDENTIFIER3) - sc.SetState(SCE_CSS_DEFAULT); + if (--nestingLevel < 0) + nestingLevel = 0; + switch (lastState) { + case SCE_CSS_DEFAULT: + case SCE_CSS_VALUE: + case SCE_CSS_IMPORTANT: + case SCE_CSS_IDENTIFIER: + case SCE_CSS_IDENTIFIER2: + case SCE_CSS_IDENTIFIER3: + if (hasNesting) + sc.SetState(nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT); + else + sc.SetState(SCE_CSS_DEFAULT); + break; + } break; case '(': if (lastState == SCE_CSS_PSEUDOCLASS) @@ -167,14 +236,28 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo sc.SetState(SCE_CSS_TAG); break; case ':': - if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || - lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS || - lastState == SCE_CSS_PSEUDOELEMENT || lastState == SCE_CSS_EXTENDED_PSEUDOELEMENT) + switch (lastState) { + case SCE_CSS_TAG: + case SCE_CSS_DEFAULT: + case SCE_CSS_CLASS: + case SCE_CSS_ID: + case SCE_CSS_PSEUDOCLASS: + case SCE_CSS_EXTENDED_PSEUDOCLASS: + case SCE_CSS_UNKNOWN_PSEUDOCLASS: + case SCE_CSS_PSEUDOELEMENT: + case SCE_CSS_EXTENDED_PSEUDOELEMENT: sc.SetState(SCE_CSS_PSEUDOCLASS); - else if (lastState == SCE_CSS_IDENTIFIER || lastState == SCE_CSS_IDENTIFIER2 || - lastState == SCE_CSS_IDENTIFIER3 || lastState == SCE_CSS_EXTENDED_IDENTIFIER || - lastState == SCE_CSS_UNKNOWN_IDENTIFIER) + break; + case SCE_CSS_IDENTIFIER: + case SCE_CSS_IDENTIFIER2: + case SCE_CSS_IDENTIFIER3: + case SCE_CSS_EXTENDED_IDENTIFIER: + case SCE_CSS_UNKNOWN_IDENTIFIER: + case SCE_CSS_VARIABLE: sc.SetState(SCE_CSS_VALUE); + lastStateVal = lastState; + break; + } break; case '.': if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || @@ -193,10 +276,28 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo sc.SetState(SCE_CSS_DEFAULT); break; case ';': - if (lastState == SCE_CSS_DIRECTIVE) - sc.SetState(SCE_CSS_DEFAULT); - else if (lastState == SCE_CSS_VALUE || lastState == SCE_CSS_IMPORTANT) - sc.SetState(SCE_CSS_IDENTIFIER); + switch (lastState) { + case SCE_CSS_DIRECTIVE: + if (hasNesting) { + sc.SetState(nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT); + } else { + sc.SetState(SCE_CSS_DEFAULT); + } + break; + case SCE_CSS_VALUE: + case SCE_CSS_IMPORTANT: + if (lastStateVal == SCE_CSS_VARIABLE) + sc.SetState(SCE_CSS_DEFAULT); + else + sc.SetState(SCE_CSS_IDENTIFIER); + break; + case SCE_CSS_VARIABLE: + if (lastStateVar == SCE_CSS_VALUE) + sc.SetState(SCE_CSS_IDENTIFIER); + else + sc.SetState(SCE_CSS_DEFAULT); + break; + } break; case '!': if (lastState == SCE_CSS_VALUE) @@ -205,14 +306,75 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo } } - if (IsAWordChar(sc.ch)) { - if (sc.state == SCE_CSS_DEFAULT) - sc.SetState(SCE_CSS_TAG); + if (sc.ch == '*' && sc.state == SCE_CSS_DEFAULT) { + sc.SetState(SCE_CSS_TAG); continue; } - if (sc.ch == '*' && sc.state == SCE_CSS_DEFAULT) { - sc.SetState(SCE_CSS_TAG); + // check for inside parentheses (whether part of an "operator" or not) + if (sc.ch == '(') + insideParentheses = true; + else if (sc.ch == ')') + insideParentheses = false; + + // SCSS special modes + if (isScssDocument) { + // variable name + if (sc.ch == '$') { + switch (sc.state) { + case SCE_CSS_DEFAULT: + case SCE_CSS_VALUE: + lastStateVar = sc.state; + sc.SetState(SCE_CSS_VARIABLE); + continue; + } + } + if (sc.state == SCE_CSS_VARIABLE) { + if (IsAWordChar(sc.ch)) { + // still looking at the variable name + continue; + } + if (lastStateVar == SCE_CSS_VALUE) { + // not looking at the variable name any more, and it was part of a value + sc.SetState(SCE_CSS_VALUE); + } + } + + // nested rule parent selector + if (sc.ch == '&') { + switch (sc.state) { + case SCE_CSS_DEFAULT: + case SCE_CSS_IDENTIFIER: + sc.SetState(SCE_CSS_TAG); + continue; + } + } + } + + // nesting rules that apply to SCSS and Less + if (hasNesting) { + // check for nested rule selector + if (sc.state == SCE_CSS_IDENTIFIER && (IsAWordChar(sc.ch) || sc.ch == ':' || sc.ch == '.' || sc.ch == '#')) { + // look ahead to see whether { comes before next ; and } + unsigned int endPos = startPos + length; + int ch; + + for (unsigned int i = sc.currentPos; i < endPos; i++) { + ch = styler.SafeGetCharAt(i); + if (ch == ';' || ch == '}') + break; + if (ch == '{') { + sc.SetState(SCE_CSS_DEFAULT); + continue; + } + } + } + + } + + if (IsAWordChar(sc.ch)) { + if (sc.state == SCE_CSS_DEFAULT) + sc.SetState(SCE_CSS_TAG); continue; } @@ -287,6 +449,13 @@ static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, Wo if (sc.Match('/', '*')) { lastStateC = sc.state; + comment_mode = eCommentBlock; + sc.SetState(SCE_CSS_COMMENT); + sc.Forward(); + } else if (hasSingleLineComments && sc.Match('/', '/') && !insideParentheses) { + // note that we've had to treat ([...]// as the start of a URL not a comment, e.g. url(http://example.com), url(//example.com) + lastStateC = sc.state; + comment_mode = eCommentLine; sc.SetState(SCE_CSS_COMMENT); sc.Forward(); } else if ((sc.state == SCE_CSS_VALUE || sc.state == SCE_CSS_ATTRIBUTE) |