/* * 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 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 *
   *     // this is comment line 1
   *     // this is comment line 2
   * 
* * @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 processedComments, @NotNull List 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 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 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 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 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 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 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 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 processedComments = new HashSet(); 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 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 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 foldElements, final @NotNull Set 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 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 foldElements, @NotNull Set 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() { @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; } }