/* * Copyright 2013-2017 consulo.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package consulo.csharp.lang; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.jetbrains.annotations.NotNull; import consulo.annotations.RequiredReadAction; import consulo.csharp.lang.parser.preprocessor.EndRegionPreprocessorDirective; import consulo.csharp.lang.parser.preprocessor.PreprocessorDirective; import consulo.csharp.lang.parser.preprocessor.PreprocessorParser; import consulo.csharp.lang.parser.preprocessor.RegionPreprocessorDirective; import consulo.csharp.lang.psi.CSharpBodyWithBraces; import consulo.csharp.lang.psi.CSharpEventDeclaration; import consulo.csharp.lang.psi.CSharpPropertyDeclaration; import consulo.csharp.lang.psi.CSharpRecursiveElementVisitor; import consulo.csharp.lang.psi.CSharpStubElements; import consulo.csharp.lang.psi.CSharpTokens; import consulo.csharp.lang.psi.CSharpTokensImpl; import consulo.csharp.lang.psi.CSharpTypeDeclaration; import consulo.csharp.lang.psi.CSharpUsingListChild; import consulo.csharp.lang.psi.impl.source.CSharpBlockStatementImpl; import consulo.dotnet.psi.DotNetLikeMethodDeclaration; import com.intellij.codeInsight.folding.CodeFoldingSettings; import com.intellij.lang.ASTNode; import com.intellij.lang.folding.CustomFoldingBuilder; import com.intellij.lang.folding.FoldingDescriptor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.containers.ContainerUtil; /** * @author VISTALL * @since 30.11.13. */ public class CSharpFoldingBuilder extends CustomFoldingBuilder { @Override protected void buildLanguageFoldRegions(@NotNull final List<FoldingDescriptor> descriptors, @NotNull PsiElement root, @NotNull Document document, boolean quick) { final Deque<PsiElement> regions = new ArrayDeque<PsiElement>(); final Set<CSharpUsingListChild> processedUsingStatements = new LinkedHashSet<CSharpUsingListChild>(); root.accept(new CSharpRecursiveElementVisitor() { @Override @RequiredReadAction public void visitElement(PsiElement element) { super.visitElement(element); IElementType elementType = PsiUtilCore.getElementType(element); if(elementType == CSharpTokens.PREPROCESSOR_DIRECTIVE) { PreprocessorDirective directive = PreprocessorParser.parse(element.getText()); if(directive instanceof RegionPreprocessorDirective) { regions.addLast(element); } else if(directive instanceof EndRegionPreprocessorDirective) { PsiElement lastRegion = regions.pollLast(); if(lastRegion == null) { return; } int startOffset = lastRegion.getTextRange().getStartOffset(); String text = lastRegion.getText(); for(int i = 0; i < text.length(); i++) { if(Character.isWhitespace(text.charAt(i))) { startOffset++; } else { break; } } descriptors.add(new FoldingDescriptor(lastRegion, new TextRange(startOffset, element.getTextRange().getEndOffset()))); } } } @Override @RequiredReadAction public void visitPropertyDeclaration(CSharpPropertyDeclaration declaration) { super.visitPropertyDeclaration(declaration); addBodyWithBraces(descriptors, declaration); } @Override @RequiredReadAction public void visitEventDeclaration(CSharpEventDeclaration declaration) { super.visitEventDeclaration(declaration); addBodyWithBraces(descriptors, declaration); } @Override @RequiredReadAction public void visitUsingChild(@NotNull CSharpUsingListChild child) { super.visitUsingChild(child); if(processedUsingStatements.contains(child)) { return; } PsiElement referenceElement = child.getReferenceElement(); if(referenceElement == null) { return; } List<CSharpUsingListChild> children = new ArrayList<CSharpUsingListChild>(5); for(ASTNode node = child.getNode(); node != null; node = node.getTreeNext()) { IElementType elementType = node.getElementType(); if(elementType == TokenType.WHITE_SPACE) { CharSequence chars = node.getChars(); if(StringUtil.countNewLines(chars) > 2) { break; } } else if(elementType == CSharpTokens.PREPROCESSOR_DIRECTIVE) { break; } else if(CSharpStubElements.USING_CHILDREN.contains(elementType)) { children.add(node.getPsi(CSharpUsingListChild.class)); } } if(children.size() <= 1) { return; } PsiElement usingKeyword = child.getUsingKeywordElement(); int startOffset = usingKeyword.getTextRange().getEndOffset() + 1; int endOffset = ContainerUtil.getLastItem(children).getLastChild().getTextRange().getEndOffset(); processedUsingStatements.addAll(children); descriptors.add(new FoldingDescriptor(child, new TextRange(startOffset, endOffset))); } @Override @RequiredReadAction public void visitBlockStatement(CSharpBlockStatementImpl statement) { super.visitBlockStatement(statement); if(!(statement.getParent() instanceof DotNetLikeMethodDeclaration)) { return; } addBodyWithBraces(descriptors, statement); } @Override @RequiredReadAction public void visitComment(PsiComment comment) { PsiFile containingFile = comment.getContainingFile(); if(containingFile == null) { return; } if(containingFile.getFirstChild() == comment) { TextRange textRange = comment.getTextRange(); int startOffset = textRange.getStartOffset(); PsiElement lastComment = findLastComment(comment, comment.getTokenType()); descriptors.add(new FoldingDescriptor(comment, new TextRange(startOffset, lastComment.getTextRange().getEndOffset()))); } } }); } @Override @RequiredReadAction protected String getLanguagePlaceholderText(@NotNull ASTNode node, @NotNull TextRange range) { PsiElement psi = node.getPsi(); if(psi instanceof CSharpUsingListChild) { return "..."; } else if(psi instanceof CSharpBlockStatementImpl || psi instanceof CSharpPropertyDeclaration || psi instanceof CSharpTypeDeclaration || psi instanceof CSharpEventDeclaration) { return "{...}"; } else if(psi instanceof PsiComment) { IElementType tokenType = ((PsiComment) psi).getTokenType(); if(tokenType == CSharpTokensImpl.LINE_DOC_COMMENT) { return "/// ..."; } else if(tokenType == CSharpTokens.LINE_COMMENT) { return "// ..."; } else if(tokenType == CSharpTokens.BLOCK_COMMENT) { return "/** ... */"; } } IElementType elementType = PsiUtilCore.getElementType(psi); if(elementType == CSharpTokens.PREPROCESSOR_DIRECTIVE) { return psi.getText().trim(); } return null; } @RequiredReadAction private static PsiElement findLastComment(PsiElement element, IElementType elementType) { PsiElement target = element; PsiElement nextSibling = element.getNextSibling(); while(nextSibling != null) { if(isAcceptableComment(nextSibling, elementType)) { if(nextSibling instanceof PsiWhiteSpace) { target = nextSibling.getPrevSibling(); } else { target = nextSibling; } nextSibling = nextSibling.getNextSibling(); } else { return target; } } return element; } private static boolean isAcceptableComment(PsiElement nextSibling, IElementType elementType) { if(nextSibling == null) { return false; } return nextSibling instanceof PsiWhiteSpace || (nextSibling instanceof PsiComment && ((PsiComment) nextSibling).getTokenType() == elementType); } @RequiredReadAction private static void addBodyWithBraces(List<FoldingDescriptor> list, CSharpBodyWithBraces bodyWithBraces) { PsiElement leftBrace = bodyWithBraces.getLeftBrace(); PsiElement rightBrace = bodyWithBraces.getRightBrace(); if(leftBrace == null || rightBrace == null) { return; } list.add(new FoldingDescriptor(bodyWithBraces, new TextRange(leftBrace.getTextRange().getStartOffset(), rightBrace.getTextRange().getStartOffset() + rightBrace.getTextLength()))); } @Override protected boolean isRegionCollapsedByDefault(@NotNull ASTNode node) { PsiElement psi = node.getPsi(); if(psi instanceof CSharpUsingListChild) { return true; } else if(psi instanceof PsiComment) { return CodeFoldingSettings.getInstance().COLLAPSE_FILE_HEADER; } return false; } }