in src/services/cssFolding.ts [20:169]
function computeFoldingRanges(document: TextDocument): FoldingRange[] {
function getStartLine(t: IToken) {
return document.positionAt(t.offset).line;
}
function getEndLine(t: IToken) {
return document.positionAt(t.offset + t.len).line;
}
function getScanner() {
switch (document.languageId) {
case 'scss':
return new SCSSScanner();
case 'less':
return new LESSScanner();
default:
return new Scanner();
}
}
function tokenToRange(t: IToken, kind?: FoldingRangeKind | string): FoldingRange | null {
const startLine = getStartLine(t);
const endLine = getEndLine(t);
if (startLine !== endLine) {
return {
startLine,
endLine,
kind
};
} else {
return null;
}
}
const ranges: FoldingRange[] = [];
const delimiterStack: Delimiter[] = [];
const scanner = getScanner();
scanner.ignoreComment = false;
scanner.setSource(document.getText());
let token = scanner.scan();
let prevToken: IToken | null = null;
while (token.type !== TokenType.EOF) {
switch (token.type) {
case TokenType.CurlyL:
case InterpolationFunction:
{
delimiterStack.push({ line: getStartLine(token), type: 'brace', isStart: true });
break;
}
case TokenType.CurlyR: {
if (delimiterStack.length !== 0) {
const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'brace');
if (!prevDelimiter) {
break;
}
let endLine = getEndLine(token);
if (prevDelimiter.type === 'brace') {
/**
* Other than the case when curly brace is not on a new line by itself, for example
* .foo {
* color: red; }
* Use endLine minus one to show ending curly brace
*/
if (prevToken && getEndLine(prevToken) !== endLine) {
endLine--;
}
if (prevDelimiter.line !== endLine) {
ranges.push({
startLine: prevDelimiter.line,
endLine,
kind: undefined
});
}
}
}
break;
}
/**
* In CSS, there is no single line comment prefixed with //
* All comments are marked as `Comment`
*/
case TokenType.Comment: {
const commentRegionMarkerToDelimiter = (marker: string): Delimiter => {
if (marker === '#region') {
return { line: getStartLine(token), type: 'comment', isStart: true };
} else {
return { line: getEndLine(token), type: 'comment', isStart: false };
}
};
const getCurrDelimiter = (token: IToken): Delimiter | null => {
const matches = token.text.match(/^\s*\/\*\s*(#region|#endregion)\b\s*(.*?)\s*\*\//);
if (matches) {
return commentRegionMarkerToDelimiter(matches[1]);
} else if (document.languageId === 'scss' || document.languageId === 'less') {
const matches = token.text.match(/^\s*\/\/\s*(#region|#endregion)\b\s*(.*?)\s*/);
if (matches) {
return commentRegionMarkerToDelimiter(matches[1]);
}
}
return null;
};
const currDelimiter = getCurrDelimiter(token);
// /* */ comment region folding
// All #region and #endregion cases
if (currDelimiter) {
if (currDelimiter.isStart) {
delimiterStack.push(currDelimiter);
} else {
const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'comment');
if (!prevDelimiter) {
break;
}
if (prevDelimiter.type === 'comment') {
if (prevDelimiter.line !== currDelimiter.line) {
ranges.push({
startLine: prevDelimiter.line,
endLine: currDelimiter.line,
kind: 'region'
});
}
}
}
}
// Multiline comment case
else {
const range = tokenToRange(token, 'comment');
if (range) {
ranges.push(range);
}
}
break;
}
}
prevToken = token;
token = scanner.scan();
}
return ranges;
}