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) | 
