void SCI_METHOD LexerSQL::Fold()

in ext/scintilla/lexers/LexSQL.cxx [630:965]


void SCI_METHOD LexerSQL::Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
	if (!options.fold)
		return;
	LexAccessor styler(pAccess);
	Sci_PositionU endPos = startPos + length;
	int visibleChars = 0;
	Sci_Position lineCurrent = styler.GetLine(startPos);
	int levelCurrent = SC_FOLDLEVELBASE;

	if (lineCurrent > 0) {
		// Backtrack to previous line in case need to fix its fold status for folding block of single-line comments (i.e. '--').
		Sci_Position lastNLPos = -1;
		// And keep going back until we find an operator ';' followed
		// by white-space and/or comments. This will improve folding.
		while (--startPos > 0) {
			char ch = styler[startPos];
			if (ch == '\n' || (ch == '\r' && styler[startPos + 1] != '\n')) {
				lastNLPos = startPos;
			} else if (ch == ';' &&
				   styler.StyleAt(startPos) == SCE_SQL_OPERATOR) {
				bool isAllClear = true;
				for (Sci_Position tempPos = startPos + 1;
				     tempPos < lastNLPos;
				     ++tempPos) {
					int tempStyle = styler.StyleAt(tempPos);
					if (!IsCommentStyle(tempStyle)
					    && tempStyle != SCE_SQL_DEFAULT) {
						isAllClear = false;
						break;
					}
				}
				if (isAllClear) {
					startPos = lastNLPos + 1;
					break;
				}
			}
		}
		lineCurrent = styler.GetLine(startPos);
		if (lineCurrent > 0)
			levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
	}
	// And because folding ends at ';', keep going until we find one
	// Otherwise if create ... view ... as is split over multiple
	// lines the folding won't always update immediately.
	Sci_PositionU docLength = styler.Length();
	for (; endPos < docLength; ++endPos) {
		if (styler.SafeGetCharAt(endPos) == ';') {
			break;
		}
	}

	int levelNext = levelCurrent;
	char chNext = styler[startPos];
	int styleNext = styler.StyleAt(startPos);
	int style = initStyle;
	bool endFound = false;
	bool isUnfoldingIgnored = false;
	// this statementFound flag avoids to fold when the statement is on only one line by ignoring ELSE or ELSIF
	// eg. "IF condition1 THEN ... ELSIF condition2 THEN ... ELSE ... END IF;"
	bool statementFound = false;
	sql_state_t sqlStatesCurrentLine = 0;
	if (!options.foldOnlyBegin) {
		sqlStatesCurrentLine = sqlStates.ForLine(lineCurrent);
	}
	for (Sci_PositionU i = startPos; i < endPos; i++) {
		char ch = chNext;
		chNext = styler.SafeGetCharAt(i + 1);
		int stylePrev = style;
		style = styleNext;
		styleNext = styler.StyleAt(i + 1);
		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
		if (atEOL || (!IsCommentStyle(style) && ch == ';')) {
			if (endFound) {
				//Maybe this is the end of "EXCEPTION" BLOCK (eg. "BEGIN ... EXCEPTION ... END;")
				sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, false);
			}
			// set endFound and isUnfoldingIgnored to false if EOL is reached or ';' is found
			endFound = false;
			isUnfoldingIgnored = false;
		}
		if ((!IsCommentStyle(style) && ch == ';')) {
			if (sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)) {
				// This is the end of "MERGE" statement.
				if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
					levelNext--;
				sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, false);
				levelNext--;
			}
			if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine))
				sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, false);
			if (sqlStates.IsIntoCreateStatement(sqlStatesCurrentLine)) {
				if (sqlStates.IsIntoCreateViewStatement(sqlStatesCurrentLine)) {
					if (sqlStates.IsIntoCreateViewAsStatement(sqlStatesCurrentLine)) {
						levelNext--;
						sqlStatesCurrentLine = sqlStates.IntoCreateViewAsStatement(sqlStatesCurrentLine, false);
					}
					sqlStatesCurrentLine = sqlStates.IntoCreateViewStatement(sqlStatesCurrentLine, false);
				}
				sqlStatesCurrentLine = sqlStates.IntoCreateStatement(sqlStatesCurrentLine, false);
			}
		}
		if (ch == ':' && chNext == '=' && !IsCommentStyle(style))
			sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);

		if (options.foldComment && IsStreamCommentStyle(style)) {
			if (!IsStreamCommentStyle(stylePrev)) {
				levelNext++;
			} else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
				// Comments don't end at end of line and the next character may be unstyled.
				levelNext--;
			}
		}
		if (options.foldComment && (style == SCE_SQL_COMMENTLINE)) {
			// MySQL needs -- comments to be followed by space or control char
			if ((ch == '-') && (chNext == '-')) {
				char chNext2 = styler.SafeGetCharAt(i + 2);
				char chNext3 = styler.SafeGetCharAt(i + 3);
				if (chNext2 == '{' || chNext3 == '{') {
					levelNext++;
				} else if (chNext2 == '}' || chNext3 == '}') {
					levelNext--;
				}
			}
		}
		// Fold block of single-line comments (i.e. '--').
		if (options.foldComment && atEOL && IsCommentLine(lineCurrent, styler)) {
			if (!IsCommentLine(lineCurrent - 1, styler) && IsCommentLine(lineCurrent + 1, styler))
				levelNext++;
			else if (IsCommentLine(lineCurrent - 1, styler) && !IsCommentLine(lineCurrent + 1, styler))
				levelNext--;
		}
		if (style == SCE_SQL_OPERATOR) {
			if (ch == '(') {
				if (levelCurrent > levelNext)
					levelCurrent--;
				levelNext++;
			} else if (ch == ')') {
				levelNext--;
			} else if ((!options.foldOnlyBegin) && ch == ';') {
				sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, false);
			}
		}
		// If new keyword (cannot trigger on elseif or nullif, does less tests)
		if (style == SCE_SQL_WORD && stylePrev != SCE_SQL_WORD) {
			const int MAX_KW_LEN = 9;	// Maximum length of folding keywords
			char s[MAX_KW_LEN + 2];
			unsigned int j = 0;
			for (; j < MAX_KW_LEN + 1; j++) {
				if (!iswordchar(styler[i + j])) {
					break;
				}
				s[j] = static_cast<char>(tolower(styler[i + j]));
			}
			if (j == MAX_KW_LEN + 1) {
				// Keyword too long, don't test it
				s[0] = '\0';
			} else {
				s[j] = '\0';
			}
			if (!options.foldOnlyBegin &&
			        strcmp(s, "select") == 0) {
				sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);
			} else if (strcmp(s, "if") == 0) {
				if (endFound) {
					endFound = false;
					if (options.foldOnlyBegin && !isUnfoldingIgnored) {
						// this end isn't for begin block, but for if block ("end if;")
						// so ignore previous "end" by increment levelNext.
						levelNext++;
					}
				} else {
					if (!options.foldOnlyBegin)
						sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
					if (levelCurrent > levelNext) {
						// doesn't include this line into the folding block
						// because doesn't hide IF (eg "END; IF")
						levelCurrent = levelNext;
					}
				}
			} else if (!options.foldOnlyBegin &&
			           strcmp(s, "then") == 0 &&
			           sqlStates.IsIntoCondition(sqlStatesCurrentLine)) {
				sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, false);
				if (!options.foldOnlyBegin) {
					if (levelCurrent > levelNext) {
						levelCurrent = levelNext;
					}
					if (!statementFound)
						levelNext++;

					statementFound = true;
				} else if (levelCurrent > levelNext) {
					// doesn't include this line into the folding block
					// because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
					levelCurrent = levelNext;
				}
			} else if (strcmp(s, "loop") == 0 ||
			           strcmp(s, "case") == 0) {
				if (endFound) {
					endFound = false;
					if (options.foldOnlyBegin && !isUnfoldingIgnored) {
						// this end isn't for begin block, but for loop block ("end loop;") or case block ("end case;")
						// so ignore previous "end" by increment levelNext.
						levelNext++;
					}
					if ((!options.foldOnlyBegin) && strcmp(s, "case") == 0) {
						sqlStatesCurrentLine = sqlStates.EndCaseBlock(sqlStatesCurrentLine);
						if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
							levelNext--; //again for the "end case;" and block when
					}
				} else if (!options.foldOnlyBegin) {
					if (strcmp(s, "case") == 0) {
						sqlStatesCurrentLine = sqlStates.BeginCaseBlock(sqlStatesCurrentLine);
						sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
					}

					if (levelCurrent > levelNext)
						levelCurrent = levelNext;

					if (!statementFound)
						levelNext++;

					statementFound = true;
				} else if (levelCurrent > levelNext) {
					// doesn't include this line into the folding block
					// because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
					levelCurrent = levelNext;
				}
			} else if ((!options.foldOnlyBegin) && (
			               // folding for ELSE and ELSIF block only if foldAtElse is set
			               // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
			               options.foldAtElse && !statementFound) && strcmp(s, "elsif") == 0) {
				sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
				levelCurrent--;
				levelNext--;
			} else if ((!options.foldOnlyBegin) && (
			               // folding for ELSE and ELSIF block only if foldAtElse is set
			               // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
			               options.foldAtElse && !statementFound) && strcmp(s, "else") == 0) {
				// prevent also ELSE is on the same line (eg. "ELSE ... END IF;")
				statementFound = true;
				if (sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) && sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
					sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
					levelNext++;
				} else {
					// we are in same case "} ELSE {" in C language
					levelCurrent--;
				}
			} else if (strcmp(s, "begin") == 0) {
				levelNext++;
				sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, false);
			} else if ((strcmp(s, "end") == 0) ||
			           // SQL Anywhere permits IF ... ELSE ... ENDIF
			           // will only be active if "endif" appears in the
			           // keyword list.
			           (strcmp(s, "endif") == 0)) {
				endFound = true;
				levelNext--;
				if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine) && !sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
					levelNext--;
				if (levelNext < SC_FOLDLEVELBASE) {
					levelNext = SC_FOLDLEVELBASE;
					isUnfoldingIgnored = true;
				}
			} else if ((!options.foldOnlyBegin) &&
			           strcmp(s, "when") == 0 &&
			           !sqlStates.IsIgnoreWhen(sqlStatesCurrentLine) &&
			           !sqlStates.IsIntoExceptionBlock(sqlStatesCurrentLine) && (
			               sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) ||
			               sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)
			               )
			           ) {
				sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);

				// Don't foldind when CASE and WHEN are on the same line (with flag statementFound) (eg. "CASE selector WHEN expression1 THEN sequence_of_statements1;\n")
				// and same way for MERGE statement.
				if (!statementFound) {
					if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
						levelCurrent--;
						levelNext--;
					}
					sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
				}
			} else if ((!options.foldOnlyBegin) && strcmp(s, "exit") == 0) {
				sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, true);
			} else if ((!options.foldOnlyBegin) && !sqlStates.IsIntoDeclareBlock(sqlStatesCurrentLine) && strcmp(s, "exception") == 0) {
				sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, true);
			} else if ((!options.foldOnlyBegin) &&
			           (strcmp(s, "declare") == 0 ||
			            strcmp(s, "function") == 0 ||
			            strcmp(s, "procedure") == 0 ||
			            strcmp(s, "package") == 0)) {
				sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, true);
			} else if ((!options.foldOnlyBegin) &&
			           strcmp(s, "merge") == 0) {
				sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, true);
				sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
				levelNext++;
				statementFound = true;
			} else if ((!options.foldOnlyBegin) &&
				   strcmp(s, "create") == 0) {
				sqlStatesCurrentLine = sqlStates.IntoCreateStatement(sqlStatesCurrentLine, true);
			} else if ((!options.foldOnlyBegin) &&
				   strcmp(s, "view") == 0 &&
				   sqlStates.IsIntoCreateStatement(sqlStatesCurrentLine)) {
				sqlStatesCurrentLine = sqlStates.IntoCreateViewStatement(sqlStatesCurrentLine, true);
			} else if ((!options.foldOnlyBegin) &&
				   strcmp(s, "as") == 0 &&
				   sqlStates.IsIntoCreateViewStatement(sqlStatesCurrentLine) &&
				   ! sqlStates.IsIntoCreateViewAsStatement(sqlStatesCurrentLine)) {
				sqlStatesCurrentLine = sqlStates.IntoCreateViewAsStatement(sqlStatesCurrentLine, true);
				levelNext++;
			}
		}
		if (atEOL) {
			int levelUse = levelCurrent;
			int lev = levelUse | levelNext << 16;
			if (visibleChars == 0 && options.foldCompact)
				lev |= SC_FOLDLEVELWHITEFLAG;
			if (levelUse < levelNext)
				lev |= SC_FOLDLEVELHEADERFLAG;
			if (lev != styler.LevelAt(lineCurrent)) {
				styler.SetLevel(lineCurrent, lev);
			}
			lineCurrent++;
			levelCurrent = levelNext;
			visibleChars = 0;
			statementFound = false;
			if (!options.foldOnlyBegin)
				sqlStates.Set(lineCurrent, sqlStatesCurrentLine);
		}
		if (!isspacechar(ch)) {
			visibleChars++;
		}
	}
}