blob: 9b6c23e79b0017d708345f6a3729dc66aace2a49 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.codeInsight.folding.impl;
import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtilBase;
import com.intellij.codeInsight.folding.JavaCodeFoldingSettings;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lang.folding.CustomFoldingBuilder;
import com.intellij.lang.folding.FoldingDescriptor;
import com.intellij.lang.folding.NamedFoldingDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.FoldingGroup;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.UnfairTextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.util.Function;
import com.intellij.util.ObjectUtils;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public abstract class JavaFoldingBuilderBase extends CustomFoldingBuilder implements DumbAware {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.folding.impl.JavaFoldingBuilder");
private static final String SMILEY = "<~>";
private static String getPlaceholderText(PsiElement element) {
if (element instanceof PsiImportList) {
return "...";
}
else if (element instanceof PsiMethod || element instanceof PsiClassInitializer || element instanceof PsiClass) {
return "{...}";
}
else if (element instanceof PsiDocComment) {
return "/**...*/";
}
else if (element instanceof PsiFile) {
return "/.../";
}
else if (element instanceof PsiAnnotation) {
return "@{...}";
}
else if (element instanceof PsiReferenceParameterList) {
return SMILEY;
}
else if (element instanceof PsiComment) {
return "//...";
}
return "...";
}
private static boolean areOnAdjacentLines(PsiElement e1, PsiElement e2, Document document) {
return document.getLineNumber(e1.getTextRange().getEndOffset()) + 1 == document.getLineNumber(e2.getTextRange().getStartOffset());
}
private static boolean isSimplePropertyAccessor(PsiMethod method) {
PsiCodeBlock body = method.getBody();
if (body == null || body.getLBrace() == null || body.getRBrace() == null) return false;
PsiStatement[] statements = body.getStatements();
if (statements.length == 0) return false;
PsiStatement statement = statements[0];
if (PropertyUtil.isSimplePropertyGetter(method)) {
if (statement instanceof PsiReturnStatement) {
return ((PsiReturnStatement)statement).getReturnValue() instanceof PsiReferenceExpression;
}
return false;
}
// builder-style setter?
if (statements.length > 1 && !(statements[1] instanceof PsiReturnStatement)) return false;
// any setter?
if (statement instanceof PsiExpressionStatement) {
PsiExpression expr = ((PsiExpressionStatement)statement).getExpression();
if (expr instanceof PsiAssignmentExpression) {
PsiExpression lhs = ((PsiAssignmentExpression)expr).getLExpression();
PsiExpression rhs = ((PsiAssignmentExpression)expr).getRExpression();
return lhs instanceof PsiReferenceExpression &&
rhs instanceof PsiReferenceExpression &&
!((PsiReferenceExpression)rhs).isQualified() &&
PropertyUtil.isSimplePropertySetter(method); // last check because it can perform long return type resolve
}
}
return false;
}
@Nullable
public TextRange getRangeToFold(PsiElement element) {
if (element instanceof SyntheticElement) {
return null;
}
if (element instanceof PsiMethod) {
PsiCodeBlock body = ((PsiMethod)element).getBody();
if (body == null) return null;
return body.getTextRange();
}
if (element instanceof PsiClassInitializer) {
return ((PsiClassInitializer)element).getBody().getTextRange();
}
if (element instanceof PsiClass) {
PsiClass aClass = (PsiClass)element;
PsiElement lBrace = aClass.getLBrace();
if (lBrace == null) return null;
PsiElement rBrace = aClass.getRBrace();
if (rBrace == null) return null;
return new TextRange(lBrace.getTextOffset(), rBrace.getTextOffset() + 1);
}
if (element instanceof PsiJavaFile) {
return getFileHeader((PsiJavaFile)element);
}
if (element instanceof PsiImportList) {
PsiImportList list = (PsiImportList)element;
PsiImportStatementBase[] statements = list.getAllImportStatements();
if (statements.length == 0) return null;
final PsiElement importKeyword = statements[0].getFirstChild();
if (importKeyword == null) return null;
int startOffset = importKeyword.getTextRange().getEndOffset() + 1;
int endOffset = statements[statements.length - 1].getTextRange().getEndOffset();
if (!hasErrorElementsNearby(element.getContainingFile(), startOffset, endOffset)) {
return new TextRange(startOffset, endOffset);
}
}
if (element instanceof PsiDocComment) {
return element.getTextRange();
}
if (element instanceof PsiAnnotation) {
int startOffset = element.getTextRange().getStartOffset();
PsiElement last = element;
while (element instanceof PsiAnnotation) {
last = element;
element = PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
}
return new TextRange(startOffset, last.getTextRange().getEndOffset());
}
return null;
}
public static boolean hasErrorElementsNearby(final PsiFile file, int startOffset, int endOffset) {
endOffset = CharArrayUtil.shiftForward(file.getViewProvider().getContents(), endOffset, " \t\n");
for (PsiElement element : CollectHighlightsUtil.getElementsInRange(file, startOffset, endOffset)) {
if (element instanceof PsiErrorElement) {
return true;
}
}
return false;
}
@Nullable
private static TextRange getFileHeader(PsiJavaFile file) {
PsiElement first = file.getFirstChild();
if (first instanceof PsiWhiteSpace) first = first.getNextSibling();
PsiElement element = first;
while (element instanceof PsiComment) {
element = element.getNextSibling();
if (element instanceof PsiWhiteSpace) {
element = element.getNextSibling();
}
else {
break;
}
}
if (element == null) return null;
if (element.getPrevSibling() instanceof PsiWhiteSpace) element = element.getPrevSibling();
if (element == null || element.equals(first)) return null;
return new UnfairTextRange(first.getTextOffset(), element.getTextOffset());
}
private void addAnnotationsToFold(PsiModifierList modifierList, List<FoldingDescriptor> foldElements, Document document) {
if (modifierList == null) return;
PsiElement[] children = modifierList.getChildren();
for (int i = 0; i < children.length; i++) {
PsiElement child = children[i];
if (child instanceof PsiAnnotation) {
addToFold(foldElements, child, document, false);
int j;
for (j = i + 1; j < children.length; j++) {
PsiElement nextChild = children[j];
if (nextChild instanceof PsiModifier) break;
}
//noinspection AssignmentToForLoopParameter
i = j;
}
}
}
/**
* We want to allow to fold subsequent single line comments like
* <pre>
* // this is comment line 1
* // this is comment line 2
* </pre>
*
* @param comment comment to check
* @param processedComments set that contains already processed elements. It is necessary because we process all elements of
* the PSI tree, hence, this method may be called for both comments from the example above. However,
* we want to create fold region during the first comment processing, put second comment to it and
* skip processing when current method is called for the second element
* @param foldElements fold descriptors holder to store newly created descriptor (if any)
*/
private static void addCommentFolds(@NotNull PsiComment comment, @NotNull Set<PsiElement> processedComments,
@NotNull List<FoldingDescriptor> foldElements)
{
if (processedComments.contains(comment) || comment.getTokenType() != JavaTokenType.END_OF_LINE_COMMENT) {
return;
}
PsiElement end = null;
for (PsiElement current = comment.getNextSibling(); current != null; current = current.getNextSibling()) {
ASTNode node = current.getNode();
if (node == null) {
break;
}
IElementType elementType = node.getElementType();
if (elementType == JavaTokenType.END_OF_LINE_COMMENT) {
end = current;
// We don't want to process, say, the second comment in case of three subsequent comments when it's being examined
// during all elements traversal. I.e. we expect to start from the first comment and grab as many subsequent
// comments as possible during the single iteration.
processedComments.add(current);
continue;
}
if (elementType == TokenType.WHITE_SPACE) {
continue;
}
break;
}
if (end != null) {
foldElements.add(
new FoldingDescriptor(comment, new TextRange(comment.getTextRange().getStartOffset(), end.getTextRange().getEndOffset()))
);
}
}
private static void addMethodGenericParametersFolding(PsiMethodCallExpression expression, List<FoldingDescriptor> foldElements, Document document, boolean quick) {
final PsiReferenceExpression methodExpression = expression.getMethodExpression();
final PsiReferenceParameterList list = methodExpression.getParameterList();
if (list == null || list.getTextLength() <= 5) {
return;
}
PsiMethodCallExpression element = expression;
while (true) {
if (!quick && !resolvesCorrectly(element.getMethodExpression())) return;
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiExpressionList) || !(parent.getParent() instanceof PsiMethodCallExpression)) break;
element = (PsiMethodCallExpression)parent.getParent();
}
addTypeParametersFolding(foldElements, document, list, 3, quick);
}
private static boolean resolvesCorrectly(PsiReferenceExpression expression) {
for (final JavaResolveResult result : expression.multiResolve(true)) {
if (!result.isValidResult()) {
return false;
}
}
return true;
}
private static void addGenericParametersFolding(PsiNewExpression expression, List<FoldingDescriptor> foldElements, Document document, boolean quick) {
final PsiElement parent = expression.getParent();
if (!(parent instanceof PsiVariable)) {
return;
}
final PsiType declType = ((PsiVariable)parent).getType();
if (!(declType instanceof PsiClassReferenceType)) {
return;
}
final PsiType[] parameters = ((PsiClassType)declType).getParameters();
if (parameters.length == 0) {
return;
}
PsiJavaCodeReferenceElement classReference = expression.getClassReference();
if (classReference == null) {
final PsiAnonymousClass anonymousClass = expression.getAnonymousClass();
if (anonymousClass != null) {
classReference = anonymousClass.getBaseClassReference();
if (quick || seemsLikeLambda(anonymousClass.getSuperClass())) {
return;
}
}
}
if (classReference != null) {
final PsiReferenceParameterList list = classReference.getParameterList();
if (list != null) {
if (quick) {
final PsiJavaCodeReferenceElement declReference = ((PsiClassReferenceType)declType).getReference();
final PsiReferenceParameterList declList = declReference.getParameterList();
if (declList == null || !list.getText().equals(declList.getText())) {
return;
}
} else {
if (!Arrays.equals(list.getTypeArguments(), parameters)) {
return;
}
}
addTypeParametersFolding(foldElements, document, list, 5, quick);
}
}
}
private static void addTypeParametersFolding(List<FoldingDescriptor> foldElements, Document document, PsiReferenceParameterList list,
final int ifLongerThan, boolean quick) {
if (!quick) {
for (final PsiType type : list.getTypeArguments()) {
if (!type.isValid()) {
return;
}
if (type instanceof PsiClassType || type instanceof PsiArrayType) {
if (PsiUtil.resolveClassInType(type) == null) {
return;
}
}
}
}
final String text = list.getText();
if (text.startsWith("<") && text.endsWith(">") && text.length() > ifLongerThan) {
final TextRange range = list.getTextRange();
addFoldRegion(foldElements, list, document, true, range);
}
}
private static boolean hasOnlyOneLambdaMethod(@NotNull PsiAnonymousClass anonymousClass, boolean checkResolve) {
PsiField[] fields = anonymousClass.getFields();
if (fields.length != 0) {
if (fields.length == 1 && HighlightUtilBase.SERIAL_VERSION_UID_FIELD_NAME.equals(fields[0].getName()) &&
fields[0].hasModifierProperty(PsiModifier.STATIC)) {
//ok
} else {
return false;
}
}
if (anonymousClass.getInitializers().length != 0) {
return false;
}
if (anonymousClass.getInnerClasses().length != 0) {
return false;
}
if (anonymousClass.getMethods().length != 1) {
return false;
}
PsiMethod method = anonymousClass.getMethods()[0];
if (method.hasModifierProperty(PsiModifier.SYNCHRONIZED)) {
return false;
}
if (checkResolve) {
PsiReferenceList throwsList = method.getThrowsList();
for (PsiClassType type : throwsList.getReferencedTypes()) {
if (type.resolve() == null) {
return false;
}
}
}
return true;
}
private String getOptionalLambdaType(PsiAnonymousClass anonymousClass, PsiNewExpression expression) {
if (shouldShowExplicitLambdaType(anonymousClass, expression)) {
final String baseClassName = ObjectUtils.assertNotNull(anonymousClass.getBaseClassType().resolve()).getName();
if (baseClassName != null) {
return "(" + baseClassName + ") ";
}
}
return "";
}
protected abstract boolean shouldShowExplicitLambdaType(PsiAnonymousClass anonymousClass, PsiNewExpression expression);
private static boolean seemsLikeLambda(@Nullable final PsiClass baseClass) {
return baseClass != null && PsiUtil.hasDefaultConstructor(baseClass, true);
}
private static boolean isImplementingLambdaMethod(PsiClass baseClass) {
if (!baseClass.hasModifierProperty(PsiModifier.ABSTRACT)) return false;
for (final PsiMethod method : baseClass.getMethods()) {
if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
return true;
}
}
try {
return !OverrideImplementExploreUtil.getMethodSignaturesToImplement(baseClass).isEmpty();
}
catch (IndexNotReadyException e) {
return false;
}
}
private boolean addToFold(List<FoldingDescriptor> list, PsiElement elementToFold, Document document, boolean allowOneLiners) {
PsiUtilCore.ensureValid(elementToFold);
TextRange range = getRangeToFold(elementToFold);
if (range == null) return false;
return addFoldRegion(list, elementToFold, document, allowOneLiners, range);
}
private static boolean addFoldRegion(final List<FoldingDescriptor> list, final PsiElement elementToFold, final Document document,
final boolean allowOneLiners,
final TextRange range) {
final TextRange fileRange = elementToFold.getContainingFile().getTextRange();
if (range.equals(fileRange)) return false;
LOG.assertTrue(range.getStartOffset() >= 0 && range.getEndOffset() <= fileRange.getEndOffset());
// PSI element text ranges may be invalid because of reparse exception (see, for example, IDEA-10617)
if (range.getStartOffset() < 0 || range.getEndOffset() > fileRange.getEndOffset()) {
return false;
}
if (!allowOneLiners) {
int startLine = document.getLineNumber(range.getStartOffset());
int endLine = document.getLineNumber(range.getEndOffset() - 1);
if (startLine < endLine && range.getLength() > 1) {
list.add(new FoldingDescriptor(elementToFold, range));
return true;
}
return false;
}
else {
if (range.getLength() > getPlaceholderText(elementToFold).length()) {
list.add(new FoldingDescriptor(elementToFold, range));
return true;
}
return false;
}
}
@Override
protected void buildLanguageFoldRegions(@NotNull List<FoldingDescriptor> descriptors,
@NotNull PsiElement root,
@NotNull Document document,
boolean quick) {
if (!(root instanceof PsiJavaFile)) {
return;
}
PsiJavaFile file = (PsiJavaFile) root;
PsiImportList importList = file.getImportList();
if (importList != null) {
PsiImportStatementBase[] statements = importList.getAllImportStatements();
if (statements.length > 1) {
final TextRange rangeToFold = getRangeToFold(importList);
if (rangeToFold != null && rangeToFold.getLength() > 1) {
descriptors.add(new FoldingDescriptor(importList, rangeToFold));
}
}
}
PsiClass[] classes = file.getClasses();
for (PsiClass aClass : classes) {
ProgressIndicatorProvider.checkCanceled();
addElementsToFold(descriptors, aClass, document, true, quick);
}
TextRange range = getFileHeader(file);
if (range != null && range.getLength() > 1 && document.getLineNumber(range.getEndOffset()) > document.getLineNumber(range.getStartOffset())) {
PsiElement anchorElementToUse = file;
PsiElement candidate = file.getFirstChild();
// We experienced the following problem situation:
// 1. There is a collapsed class-level javadoc;
// 2. User starts typing at class definition line (e.g. we had definition like 'public class Test' and user starts
// typing 'abstract' between 'public' and 'class');
// 3. Collapsed class-level javadoc automatically expanded. That happened because PSI structure became invalid (because
// class definition line at start looks like 'public class Test');
// So, our point is to preserve fold descriptor referencing javadoc PSI element.
if (candidate != null && candidate.getTextRange().equals(range)) {
ASTNode node = candidate.getNode();
if (node != null && node.getElementType() == JavaDocElementType.DOC_COMMENT) {
anchorElementToUse = candidate;
}
}
descriptors.add(new FoldingDescriptor(anchorElementToUse, range));
}
}
private void addElementsToFold(List<FoldingDescriptor> list, PsiClass aClass, Document document, boolean foldJavaDocs, boolean quick) {
if (!(aClass.getParent() instanceof PsiJavaFile) || ((PsiJavaFile)aClass.getParent()).getClasses().length > 1) {
addToFold(list, aClass, document, true);
}
PsiDocComment docComment;
if (foldJavaDocs) {
docComment = aClass.getDocComment();
if (docComment != null) {
addToFold(list, docComment, document, true);
}
}
addAnnotationsToFold(aClass.getModifierList(), list, document);
PsiElement[] children = aClass.getChildren();
Set<PsiElement> processedComments = new HashSet<PsiElement>();
for (PsiElement child : children) {
ProgressIndicatorProvider.checkCanceled();
if (child instanceof PsiMethod) {
PsiMethod method = (PsiMethod)child;
boolean oneLiner = addOneLineMethodFolding(list, method);
if (!oneLiner) {
addToFold(list, method, document, true);
}
addAnnotationsToFold(method.getModifierList(), list, document);
if (foldJavaDocs) {
docComment = method.getDocComment();
if (docComment != null) {
addToFold(list, docComment, document, true);
}
}
PsiCodeBlock body = method.getBody();
if (body != null && !oneLiner) {
addCodeBlockFolds(body, list, processedComments, document, quick);
}
}
else if (child instanceof PsiField) {
PsiField field = (PsiField)child;
if (foldJavaDocs) {
docComment = field.getDocComment();
if (docComment != null) {
addToFold(list, docComment, document, true);
}
}
addAnnotationsToFold(field.getModifierList(), list, document);
PsiExpression initializer = field.getInitializer();
if (initializer != null) {
addCodeBlockFolds(initializer, list, processedComments, document, quick);
} else if (field instanceof PsiEnumConstant) {
addCodeBlockFolds(field, list, processedComments, document, quick);
}
}
else if (child instanceof PsiClassInitializer) {
PsiClassInitializer initializer = (PsiClassInitializer)child;
addToFold(list, initializer, document, true);
addCodeBlockFolds(initializer, list, processedComments, document, quick);
}
else if (child instanceof PsiClass) {
addElementsToFold(list, (PsiClass)child, document, true, quick);
}
else if (child instanceof PsiComment && !isCustomRegionStart(child.getNode()) && !isCustomRegionEnd(child.getNode())) {
addCommentFolds((PsiComment)child, processedComments, list);
}
}
}
private boolean addOneLineMethodFolding(List<FoldingDescriptor> descriptorList, PsiMethod method) {
if (!JavaCodeFoldingSettings.getInstance().isCollapseOneLineMethods()) {
return false;
}
Document document = method.getContainingFile().getViewProvider().getDocument();
PsiCodeBlock body = method.getBody();
PsiIdentifier nameIdentifier = method.getNameIdentifier();
if (body == null || document == null || nameIdentifier == null) {
return false;
}
if (document.getLineNumber(nameIdentifier.getTextRange().getStartOffset()) !=
document.getLineNumber(method.getParameterList().getTextRange().getEndOffset())) {
return false;
}
PsiJavaToken lBrace = body.getLBrace();
PsiJavaToken rBrace = body.getRBrace();
PsiStatement[] statements = body.getStatements();
if (lBrace == null || rBrace == null || statements.length != 1) {
return false;
}
PsiStatement statement = statements[0];
if (statement.textContains('\n')) {
return false;
}
if (!areOnAdjacentLines(lBrace, statement, document) || !areOnAdjacentLines(statement, rBrace, document)) {
//the user might intend to type at an empty line
return false;
}
int leftStart = method.getParameterList().getTextRange().getEndOffset();
int bodyStart = body.getTextRange().getStartOffset();
if (bodyStart > leftStart && !StringUtil.isEmptyOrSpaces(document.getCharsSequence().subSequence(leftStart + 1, bodyStart))) {
return false;
}
int leftEnd = statement.getTextRange().getStartOffset();
int rightStart = statement.getTextRange().getEndOffset();
int rightEnd = body.getTextRange().getEndOffset();
if (leftEnd <= leftStart + 1 || rightEnd <= rightStart + 1) {
return false;
}
String leftText = " { ";
String rightText = " }";
if (!fitsRightMargin(method, document, leftStart, rightEnd, rightStart - leftEnd + leftText.length() + rightText.length())) {
return false;
}
FoldingGroup group = FoldingGroup.newGroup("one-liner");
descriptorList.add(new NamedFoldingDescriptor(lBrace, leftStart, leftEnd, group, leftText));
descriptorList.add(new NamedFoldingDescriptor(rBrace, rightStart, rightEnd, group, rightText));
return true;
}
@Override
protected String getLanguagePlaceholderText(@NotNull ASTNode node, @NotNull TextRange range) {
return getPlaceholderText(SourceTreeToPsiMap.treeElementToPsi(node));
}
@Override
protected boolean isRegionCollapsedByDefault(@NotNull ASTNode node) {
final PsiElement element = SourceTreeToPsiMap.treeElementToPsi(node);
JavaCodeFoldingSettings settings = JavaCodeFoldingSettings.getInstance();
if (element instanceof PsiNewExpression || element instanceof PsiJavaToken &&
element.getParent() instanceof PsiAnonymousClass) {
return settings.isCollapseLambdas();
}
if (element instanceof PsiJavaToken &&
element.getParent() instanceof PsiCodeBlock &&
element.getParent().getParent() instanceof PsiMethod) {
return settings.isCollapseOneLineMethods();
}
if (element instanceof PsiReferenceParameterList) {
return settings.isCollapseConstructorGenericParameters();
}
if (element instanceof PsiImportList) {
return settings.isCollapseImports();
}
else if (element instanceof PsiMethod || element instanceof PsiClassInitializer || element instanceof PsiCodeBlock) {
if (element instanceof PsiMethod && isSimplePropertyAccessor((PsiMethod)element)) {
return settings.isCollapseAccessors();
}
return settings.isCollapseMethods();
}
else if (element instanceof PsiAnonymousClass) {
return settings.isCollapseAnonymousClasses();
}
else if (element instanceof PsiClass) {
return !(element.getParent() instanceof PsiFile) && settings.isCollapseInnerClasses();
}
else if (element instanceof PsiDocComment) {
PsiElement parent = element.getParent();
if (parent instanceof PsiJavaFile) {
if (((PsiJavaFile)parent).getName().equals(PsiPackage.PACKAGE_INFO_FILE)) {
return false;
}
PsiElement firstChild = parent.getFirstChild();
if (firstChild instanceof PsiWhiteSpace) {
firstChild = firstChild.getNextSibling();
}
if (element.equals(firstChild)) {
return settings.isCollapseFileHeader();
}
}
return settings.isCollapseJavadocs();
}
else if (element instanceof PsiJavaFile) {
return settings.isCollapseFileHeader();
}
else if (element instanceof PsiAnnotation) {
return settings.isCollapseAnnotations();
}
else if (element instanceof PsiComment) {
return settings.isCollapseEndOfLineComments();
}
else if (ParameterNameFoldingManager.isLiteralExpression(element)
&& element.getParent() instanceof PsiExpressionList
&& (element.getParent().getParent() instanceof PsiCallExpression
|| element.getParent().getParent() instanceof PsiAnonymousClass)) {
return settings.isInlineParameterNamesForLiteralCallArguments();
}
else {
LOG.error("Unknown element:" + element);
return false;
}
}
private void addCodeBlockFolds(PsiElement scope, final List<FoldingDescriptor> foldElements,
final @NotNull Set<PsiElement> processedComments, final Document document, final boolean quick) {
final boolean dumb = DumbService.isDumb(scope.getProject());
scope.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitClass(PsiClass aClass) {
if (dumb || !addClosureFolding(aClass, document, foldElements, processedComments, quick)) {
addToFold(foldElements, aClass, document, true);
addElementsToFold(foldElements, aClass, document, false, quick);
}
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
if (!dumb) {
addMethodGenericParametersFolding(expression, foldElements, document, quick);
inlineLiteralArgumentsNames(expression, foldElements, quick);
}
super.visitMethodCallExpression(expression);
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
if (!dumb) {
addGenericParametersFolding(expression, foldElements, document, quick);
inlineLiteralArgumentsNames(expression, foldElements, quick);
}
super.visitNewExpression(expression);
}
@Override
public void visitComment(PsiComment comment) {
addCommentFolds(comment, processedComments, foldElements);
super.visitComment(comment);
}
});
}
private static void inlineLiteralArgumentsNames(@NotNull PsiCallExpression expression,
@NotNull List<FoldingDescriptor> foldElements,
boolean quick)
{
if (quick || !JavaCodeFoldingSettings.getInstance().isInlineParameterNamesForLiteralCallArguments()) {
return;
}
ParameterNameFoldingManager manager = new ParameterNameFoldingManager(expression);
foldElements.addAll(manager.buildDescriptors());
}
private boolean addClosureFolding(final PsiClass aClass, final Document document, final List<FoldingDescriptor> foldElements,
@NotNull Set<PsiElement> processedComments, final boolean quick) {
if (!JavaCodeFoldingSettings.getInstance().isCollapseLambdas()) {
return false;
}
boolean isClosure = false;
if (aClass instanceof PsiAnonymousClass) {
final PsiAnonymousClass anonymousClass = (PsiAnonymousClass)aClass;
final PsiElement element = anonymousClass.getParent();
if (element instanceof PsiNewExpression) {
final PsiNewExpression expression = (PsiNewExpression)element;
final PsiExpressionList argumentList = expression.getArgumentList();
if (argumentList != null && argumentList.getExpressions().length == 0) {
final PsiMethod[] methods = anonymousClass.getMethods();
PsiClass baseClass = anonymousClass.getBaseClassType().resolve();
if (hasOnlyOneLambdaMethod(anonymousClass, !quick) && seemsLikeLambda(baseClass) && !PsiUtil.isLanguageLevel8OrHigher(anonymousClass)) {
final PsiMethod method = methods[0];
final PsiCodeBlock body = method.getBody();
if (body != null) {
isClosure = true;
int rangeStart = body.getTextRange().getStartOffset();
int rangeEnd = body.getTextRange().getEndOffset();
final PsiJavaToken lbrace = body.getLBrace();
if (lbrace != null) rangeStart = lbrace.getTextRange().getEndOffset();
final PsiJavaToken rbrace = body.getRBrace();
if (rbrace != null) rangeEnd = rbrace.getTextRange().getStartOffset();
final CharSequence seq = document.getCharsSequence();
final PsiElement classRBrace = anonymousClass.getRBrace();
if (classRBrace != null && rbrace != null) {
final int methodEndLine = document.getLineNumber(rangeEnd);
final int methodEndLineStart = document.getLineStartOffset(methodEndLine);
if ("}".equals(seq.subSequence(methodEndLineStart, document.getLineEndOffset(methodEndLine)).toString().trim())) {
int classEndStart = classRBrace.getTextRange().getStartOffset();
int classEndCol = classEndStart - document.getLineStartOffset(document.getLineNumber(classEndStart));
rangeEnd = classEndCol + methodEndLineStart;
}
}
int firstLineStart = CharArrayUtil.shiftForward(seq, rangeStart, " \t");
if (firstLineStart < seq.length() - 1 && seq.charAt(firstLineStart) == '\n') firstLineStart++;
int lastLineEnd = CharArrayUtil.shiftBackward(seq, rangeEnd - 1, " \t");
if (lastLineEnd > 0 && seq.charAt(lastLineEnd) == '\n') lastLineEnd--;
if (lastLineEnd < firstLineStart) return false;
String type = quick ? "" : getOptionalLambdaType(anonymousClass, expression);
String methodName = quick || !isImplementingLambdaMethod(baseClass) ? method.getName() : "";
final String params = StringUtil.join(method.getParameterList().getParameters(), new Function<PsiParameter, String>() {
@Override
public String fun(final PsiParameter psiParameter) {
return psiParameter.getName();
}
}, ", ");
@NonNls final String lambdas = type + methodName + "(" + params + ") -> {";
final int closureStart = expression.getTextRange().getStartOffset();
final int closureEnd = expression.getTextRange().getEndOffset();
boolean oneLine = false;
String contents = seq.subSequence(firstLineStart, lastLineEnd).toString();
if (contents.indexOf('\n') < 0 &&
fitsRightMargin(aClass, document, closureStart, closureEnd, lambdas.length() + contents.length() + 5)) {
rangeStart = CharArrayUtil.shiftForward(seq, rangeStart, " \n\t");
rangeEnd = CharArrayUtil.shiftBackward(seq, rangeEnd - 1, " \n\t") + 1;
oneLine = true;
}
if (rangeStart >= rangeEnd) return false;
FoldingGroup group = FoldingGroup.newGroup("lambda");
final String prettySpace = oneLine ? " " : "";
foldElements.add(new NamedFoldingDescriptor(expression, closureStart, rangeStart, group, lambdas + prettySpace));
if (classRBrace != null && rangeEnd + 1 < closureEnd) {
foldElements.add(new NamedFoldingDescriptor(classRBrace, rangeEnd, closureEnd, group, prettySpace + "}"));
}
addCodeBlockFolds(body, foldElements, processedComments, document, quick);
}
}
}
}
}
return isClosure;
}
private boolean fitsRightMargin(PsiElement element, Document document, int foldingStart, int foldingEnd, int collapsedLength) {
final int beforeLength = foldingStart - document.getLineStartOffset(document.getLineNumber(foldingStart));
final int afterLength = document.getLineEndOffset(document.getLineNumber(foldingEnd)) - foldingEnd;
return isBelowRightMargin(element.getProject(), beforeLength + collapsedLength + afterLength);
}
protected abstract boolean isBelowRightMargin (Project project, final int lineLength);
@Override
protected boolean isCustomFoldingCandidate(ASTNode node) {
return node.getElementType() == JavaTokenType.END_OF_LINE_COMMENT;
}
@Override
protected boolean isCustomFoldingRoot(ASTNode node) {
IElementType nodeType = node.getElementType();
if (nodeType == JavaElementType.CLASS) {
ASTNode parent = node.getTreeParent();
return parent == null || parent.getElementType() != JavaElementType.CLASS;
}
return nodeType == JavaElementType.CODE_BLOCK;
}
}