static void ColouriseCoffeeScriptDoc()

in ext/scintilla/lexers/LexCoffeeScript.cxx [104:337]


static void ColouriseCoffeeScriptDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[],
                            Accessor &styler) {

	WordList &keywords = *keywordlists[0];
	WordList &keywords2 = *keywordlists[1];
	WordList &keywords4 = *keywordlists[3];

	CharacterSet setOKBeforeRE(CharacterSet::setNone, "([{=,:;!%^&*|?~+-");
	CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-");

	CharacterSet setWordStart(CharacterSet::setAlpha, "_$@", 0x80, true);
	CharacterSet setWord(CharacterSet::setAlphaNum, "._$", 0x80, true);

	int chPrevNonWhite = ' ';
	int visibleChars = 0;

	// String/Regex interpolation variables, based on LexRuby.cxx.
	// In most cases a value of 2 should be ample for the code the user is
	// likely to enter. For example,
	//   "Filling the #{container} with #{liquid}..."
	// from the CoffeeScript homepage nests to a level of 2
	// If the user actually hits a 6th occurrence of '#{' in a double-quoted
	// string (including regexes), it will stay as a string.  The problem with
	// this is that quotes might flip, a 7th '#{' will look like a comment,
	// and code-folding might be wrong.
#define INNER_STRINGS_MAX_COUNT 5
	// These vars track our instances of "...#{,,,'..#{,,,}...',,,}..."
	int inner_string_types[INNER_STRINGS_MAX_COUNT];
	// Track # braces when we push a new #{ thing
	int inner_expn_brace_counts[INNER_STRINGS_MAX_COUNT];
	int inner_string_count = 0;
	int brace_counts = 0;   // Number of #{ ... } things within an expression
	for (int i = 0; i < INNER_STRINGS_MAX_COUNT; i++) {
		inner_string_types[i] = 0;
		inner_expn_brace_counts[i] = 0;
	}

	// look back to set chPrevNonWhite properly for better regex colouring
	Sci_Position endPos = startPos + length;
        if (startPos > 0 && IsSpaceEquiv(initStyle)) {
		Sci_PositionU back = startPos;
		styler.Flush();
		while (back > 0 && IsSpaceEquiv(styler.StyleAt(--back)))
			;
		if (styler.StyleAt(back) == SCE_COFFEESCRIPT_OPERATOR) {
			chPrevNonWhite = styler.SafeGetCharAt(back);
		}
		if (startPos != back) {
			initStyle = styler.StyleAt(back);
			if (IsSpaceEquiv(initStyle)) {
				initStyle = SCE_COFFEESCRIPT_DEFAULT;
			}
		}
		startPos = back;
	}

	StyleContext sc(startPos, endPos - startPos, initStyle, styler);

	for (; sc.More();) {

		if (sc.atLineStart) {
			// Reset states to beginning of colourise so no surprises
			// if different sets of lines lexed.
			visibleChars = 0;
		}

		// Determine if the current state should terminate.
		switch (sc.state) {
			case SCE_COFFEESCRIPT_OPERATOR:
				sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				break;
			case SCE_COFFEESCRIPT_NUMBER:
				// We accept almost anything because of hex. and number suffixes
				if (!setWord.Contains(sc.ch) || sc.Match('.', '.')) {
					sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				}
				break;
			case SCE_COFFEESCRIPT_IDENTIFIER:
				if (!setWord.Contains(sc.ch) || (sc.ch == '.') || (sc.ch == '$')) {
					char s[1000];
					sc.GetCurrent(s, sizeof(s));
					if (keywords.InList(s)) {
						sc.ChangeState(SCE_COFFEESCRIPT_WORD);
					} else if (keywords2.InList(s)) {
						sc.ChangeState(SCE_COFFEESCRIPT_WORD2);
					} else if (keywords4.InList(s)) {
						sc.ChangeState(SCE_COFFEESCRIPT_GLOBALCLASS);
					} else if (sc.LengthCurrent() > 0 && s[0] == '@') {
						sc.ChangeState(SCE_COFFEESCRIPT_INSTANCEPROPERTY);
					}
					sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				}
				break;
			case SCE_COFFEESCRIPT_WORD:
			case SCE_COFFEESCRIPT_WORD2:
			case SCE_COFFEESCRIPT_GLOBALCLASS:
			case SCE_COFFEESCRIPT_INSTANCEPROPERTY:
				if (!setWord.Contains(sc.ch)) {
					sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				}
				break;
			case SCE_COFFEESCRIPT_COMMENTLINE:
				if (sc.atLineStart) {
					sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				}
				break;
			case SCE_COFFEESCRIPT_STRING:
				if (sc.ch == '\\') {
					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
						sc.Forward();
					}
				} else if (sc.ch == '\"') {
					sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
				} else if (sc.ch == '#' && sc.chNext == '{' && inner_string_count < INNER_STRINGS_MAX_COUNT) {
					// process interpolated code #{ ... }
					enterInnerExpression(inner_string_types,
					                     inner_expn_brace_counts,
					                     inner_string_count,
					                     sc.state,
					                     brace_counts);
					sc.SetState(SCE_COFFEESCRIPT_OPERATOR);
					sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
				}
				break;
			case SCE_COFFEESCRIPT_CHARACTER:
				if (sc.ch == '\\') {
					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
						sc.Forward();
					}
				} else if (sc.ch == '\'') {
					sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
				}
				break;
			case SCE_COFFEESCRIPT_REGEX:
				if (sc.atLineStart) {
					sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				} else if (sc.ch == '/') {
					sc.Forward();
					while ((sc.ch < 0x80) && islower(sc.ch))
						sc.Forward();    // gobble regex flags
					sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				} else if (sc.ch == '\\') {
					// Gobble up the quoted character
					if (sc.chNext == '\\' || sc.chNext == '/') {
						sc.Forward();
					}
				}
				break;
			case SCE_COFFEESCRIPT_STRINGEOL:
				if (sc.atLineStart) {
					sc.SetState(SCE_COFFEESCRIPT_DEFAULT);
				}
				break;
			case SCE_COFFEESCRIPT_COMMENTBLOCK:
				if (sc.Match("###")) {
					sc.Forward();
					sc.Forward();
					sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
				} else if (sc.ch == '\\') {
					sc.Forward();
				}
				break;
			case SCE_COFFEESCRIPT_VERBOSE_REGEX:
				if (sc.Match("///")) {
					sc.Forward();
					sc.Forward();
					sc.ForwardSetState(SCE_COFFEESCRIPT_DEFAULT);
				} else if (sc.Match('#')) {
					sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT);
				} else if (sc.ch == '\\') {
					sc.Forward();
				}
				break;
			case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT:
				if (sc.atLineStart) {
					sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
				}
				break;
		}

		// Determine if a new state should be entered.
		if (sc.state == SCE_COFFEESCRIPT_DEFAULT) {
			if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
				sc.SetState(SCE_COFFEESCRIPT_NUMBER);
			} else if (setWordStart.Contains(sc.ch)) {
				sc.SetState(SCE_COFFEESCRIPT_IDENTIFIER);
			} else if (sc.Match("///")) {
				sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX);
				sc.Forward();
				sc.Forward();
			} else if (sc.ch == '/'
				   && (setOKBeforeRE.Contains(chPrevNonWhite)
				       || followsKeyword(sc, styler))
				   && (!setCouldBePostOp.Contains(chPrevNonWhite)
				       || !FollowsPostfixOperator(sc, styler))) {
				sc.SetState(SCE_COFFEESCRIPT_REGEX);	// JavaScript's RegEx
			} else if (sc.ch == '\"') {
				sc.SetState(SCE_COFFEESCRIPT_STRING);
			} else if (sc.ch == '\'') {
				sc.SetState(SCE_COFFEESCRIPT_CHARACTER);
			} else if (sc.ch == '#') {
				if (sc.Match("###")) {
					sc.SetState(SCE_COFFEESCRIPT_COMMENTBLOCK);
					sc.Forward();
					sc.Forward();
				} else {
					sc.SetState(SCE_COFFEESCRIPT_COMMENTLINE);
				}
			} else if (isoperator(static_cast<char>(sc.ch))) {
				sc.SetState(SCE_COFFEESCRIPT_OPERATOR);
				// Handle '..' and '...' operators correctly.
				if (sc.ch == '.') {
					for (int i = 0; i < 2 && sc.chNext == '.'; i++, sc.Forward()) ;
				} else if (sc.ch == '{') {
					++brace_counts;
				} else if (sc.ch == '}' && --brace_counts <= 0 && inner_string_count > 0) {
					// Return to previous state before #{ ... }
					sc.ForwardSetState(exitInnerExpression(inner_string_types,
					                                       inner_expn_brace_counts,
					                                       inner_string_count,
					                                       brace_counts));
					continue; // skip sc.Forward() at loop end
				}
			}
		}

		if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) {
			chPrevNonWhite = sc.ch;
			visibleChars++;
		}
		sc.Forward();
	}
	sc.Complete();
}