/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.folding; import com.intellij.lang.ASTNode; import com.intellij.lang.folding.FoldingBuilder; import com.intellij.lang.folding.FoldingDescriptor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.TextRange; import com.intellij.patterns.ElementPattern; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.impl.source.tree.JavaDocElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import gw.plugin.ij.filetypes.GosuTemplateFileProvider; import gw.plugin.ij.lang.GosuTokenTypes; import gw.plugin.ij.lang.parser.GosuElementTypes; import gw.plugin.ij.lang.psi.api.statements.IGosuStatementList; import gw.plugin.ij.lang.psi.api.statements.IGosuUsesStatementList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import static com.intellij.patterns.PlatformPatterns.*; import static com.intellij.patterns.StandardPatterns.not; public class GosuFoldingBuilder extends GosuElementTypes implements FoldingBuilder, DumbAware { private static final String LAMBDA = "\u03BB"; @Nullable private static TextRange getBracesTextRange(@NotNull ASTNode node) { final ASTNode left = node.findChildByType(GosuTokenTypes.TT_OP_brace_left); final ASTNode right = node.findChildByType(GosuTokenTypes.TT_OP_brace_right); if (left != null && right != null) { return new TextRange(left.getStartOffset(), right.getStartOffset() + 1); } else { return null; } } @Nullable private static FoldingDescriptor getFoldingDescriptor(@NotNull PsiElement element, @NotNull TextRange range) { return range.getLength() > 1 ? new FoldingDescriptor(element, range) : null; } @NotNull @Override public FoldingDescriptor[] buildFoldRegions(@NotNull ASTNode node, @NotNull Document document) { final List<FoldingDescriptor> descriptors = new ArrayList<>(); appendDescriptors(node.getPsi(), descriptors); return descriptors.toArray(new FoldingDescriptor[descriptors.size()]); } @Nullable private static FoldingDescriptor processUsesStatementList(@NotNull PsiElement element) { if (psiElement(ELEM_TYPE_UsesStatementList).accepts(element)) { final IGosuUsesStatementList list = (IGosuUsesStatementList) element; if (list.getUsesStatements().length > 1) { final TextRange range = element.getTextRange(); return new FoldingDescriptor(element, new TextRange("uses ".length() + range.getStartOffset(), range.getEndOffset())); } } return null; } @Nullable private static FoldingDescriptor processMethodDefinition(@NotNull PsiElement element) { if (psiElement(METHOD_DEFINITION).accepts(element)) { final IGosuStatementList list = PsiTreeUtil.getChildOfType(element, IGosuStatementList.class); if (list != null) { final TextRange range = getBracesTextRange(list.getNode()); if (range != null) { return new FoldingDescriptor(element, range); } } } return null; } @Nullable private static FoldingDescriptor processNestedDefinition(@NotNull PsiElement element) { if (psiElement() .withElementType(elementType().oneOf(CLASS_DEFINITION, INTERFACE_DEFINITION, ENUM_DEFINITION, ENHANCEMENT_DEFINITION, ANNOTATION_DEFINITION)) .withParent(not(psiFile())) .accepts(element)) { final TextRange range = getBracesTextRange(element.getNode()); if (range != null) { return new FoldingDescriptor(element, range); } } return null; } @Nullable private static FoldingDescriptor processAnonymousClassDefiniition(@NotNull PsiElement element) { if (psiElement(ANONYMOUS_CLASS_DEFINITION).accepts(element)) { return getFoldingDescriptor(element, element.getTextRange()); } return null; } @Nullable private static FoldingDescriptor processBlockExpression(@NotNull PsiElement element) { if (psiElement(ELEM_TYPE_BlockExpression).accepts(element)) { return getFoldingDescriptor(element, element.getTextRange()); } return null; } @Nullable private static FoldingDescriptor processCollectionInitializer(@NotNull PsiElement element) { if (psiElement(ELEM_TYPE_CollectionInitializerExpression).accepts(element)) { return getFoldingDescriptor(element, element.getTextRange()); } return null; } @Nullable private static FoldingDescriptor processCommentMultiline(@NotNull PsiElement element) { if (or(psiElement(TT_COMMENT_MULTILINE), psiElement(JavaDocElementType.DOC_COMMENT)) .accepts(element)) { return getFoldingDescriptor(element, element.getTextRange()); } return null; } @Nullable private static FoldingDescriptor processSeveral(@NotNull ElementPattern<PsiElement> pattern, @NotNull PsiElement element) { if (pattern.accepts(element)) { if (!pattern.accepts(PsiTreeUtil.skipSiblingsBackward(element, PsiWhiteSpace.class))) { int count = 1; PsiElement prev = element; while(true) { final PsiElement next = PsiTreeUtil.skipSiblingsForward(prev, PsiWhiteSpace.class); if (!pattern.accepts(next)) { if (count > 1) { final int startOffset = element.getTextRange().getStartOffset(); final int endOffset = prev.getTextRange().getEndOffset(); return new FoldingDescriptor(element, new TextRange(startOffset, endOffset)); } else { return null; } } prev = next; count += 1; } } } return null; } @Nullable private static FoldingDescriptor processCommentLine(@NotNull PsiElement element) { return processSeveral(psiElement(TT_COMMENT_LINE), element); } @Nullable private static FoldingDescriptor processAnnotation(@NotNull PsiElement element) { return processSeveral(psiElement(ELEM_TYPE_AnnotationExpression), element); } private static void appendDescriptors(@NotNull PsiElement element, @NotNull List<FoldingDescriptor> descriptors) { if( GosuTemplateFileProvider.inTemplateFile( element )) { return; } FoldingDescriptor desc; if ((desc = processUsesStatementList(element)) != null || (desc = processMethodDefinition(element)) != null || (desc = processNestedDefinition(element)) != null || (desc = processAnonymousClassDefiniition(element)) != null || (desc = processBlockExpression(element)) != null || (desc = processCollectionInitializer(element)) != null || (desc = processAnnotation(element)) != null || (desc = processCommentMultiline(element)) != null || (desc = processCommentLine(element)) != null) { descriptors.add(desc); } PsiElement child = element.getFirstChild(); while(child != null) { appendDescriptors(child, descriptors); child = child.getNextSibling(); } } @Override public String getPlaceholderText(@NotNull ASTNode node) { final IElementType type = node.getElementType(); if (type == JavaDocElementType.DOC_COMMENT) { return "/**...*/"; } else if (type == TT_COMMENT_MULTILINE) { return "/*...*/"; } else if (type == METHOD_DEFINITION) { return "{...}"; } else if (type == CLASS_DEFINITION || type == INTERFACE_DEFINITION || type == ENUM_DEFINITION || type == ENHANCEMENT_DEFINITION || type == ANNOTATION_DEFINITION || type == ANONYMOUS_CLASS_DEFINITION || type == ELEM_TYPE_CollectionInitializerExpression) { return "{...}"; } else if (type == ELEM_TYPE_AnnotationExpression) { return "@{...}"; } else if (type == ELEM_TYPE_BlockExpression) { return LAMBDA + "..."; } return null; } @Override public boolean isCollapsedByDefault(@NotNull ASTNode node) { final IElementType type = node.getElementType(); if (type == ELEM_TYPE_UsesStatementList) { return true; } return false; } }