/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.formatting; import com.intellij.formatting.Alignment; import com.intellij.formatting.ChildAttributes; import com.intellij.formatting.Indent; import com.intellij.lang.ASTNode; import com.intellij.patterns.ElementPattern; import com.intellij.patterns.PatternCondition; import com.intellij.psi.JavaDocTokenType; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.JavaDocElementType; import com.intellij.psi.tree.IElementType; import com.intellij.util.ProcessingContext; import gw.plugin.ij.filetypes.GosuProgramFileProvider; import gw.plugin.ij.lang.GosuTokenSets; import gw.plugin.ij.lang.GosuTokenTypes; import gw.plugin.ij.lang.parser.GosuElementTypes; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import static com.intellij.patterns.PlatformPatterns.or; import static com.intellij.patterns.PlatformPatterns.psiElement; public class GosuIndentProcessor extends GosuElementTypes { @NotNull public static List<ASTNode> getNonEmptyASTNodes(@NotNull ASTNode... nodes) { final List<ASTNode> result = new ArrayList<>(); for (ASTNode node : nodes) { if (hasContent(node)) { result.add(node); } } return result; } private static boolean hasContent(@NotNull final ASTNode node) { return node.getText().trim().length() > 0; } @Nullable private static PatternCondition<PsiElement> hasSiblingBefore(final IElementType type) { return new PatternCondition<PsiElement>("hasSiblingBefore") { @Override public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext context) { psiElement = psiElement.getPrevSibling(); while (psiElement != null) { final ASTNode node = psiElement.getNode(); if (node != null && node.getElementType().equals(type)) { return true; } psiElement = psiElement.getPrevSibling(); } return false; } }; } @Nullable private static PatternCondition<PsiElement> hasSiblingAfter(final IElementType type) { return new PatternCondition<PsiElement>("hasSiblingAfter") { @Override public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext context) { psiElement = psiElement.getNextSibling(); while (psiElement != null) { final ASTNode node = psiElement.getNode(); if (node != null && node.getElementType().equals(type)) { return true; } psiElement = psiElement.getNextSibling(); } return false; } }; } public static final ElementPattern<PsiElement> ALL_DEFINITIONS = or( psiElement(CLASS_DEFINITION), psiElement(ANONYMOUS_CLASS_DEFINITION), psiElement(INTERFACE_DEFINITION), psiElement(ENUM_DEFINITION), psiElement(ENHANCEMENT_DEFINITION), psiElement(ANNOTATION_DEFINITION)); @NotNull public static Indent getChildIndent(@NotNull final GosuBlock parent, @Nullable final ASTNode prevNode, @NotNull final ASTNode node) { final PsiElement psi = node.getPsi(); final PsiElement prevPsi = prevNode != null ? prevNode.getPsi() : null; final PsiElement parentPsi = psi.getParent(); // Top level elements final boolean insideProgram = GosuProgramFileProvider.isProgram(psi.getContainingFile().getVirtualFile()); if (insideProgram && parentPsi instanceof PsiClass && parentPsi.getParent() instanceof PsiFile) { return Indent.getNoneIndent(); } if (parentPsi instanceof PsiFile) { return Indent.getNoneIndent(); } // UsesStatement & StatementList if (or(psiElement(ELEM_TYPE_UsesStatement), psiElement(ELEM_TYPE_StatementList)) .accepts(psi)) { return Indent.getNoneIndent(); } // Inside definitions or statement lists if (psiElement() .withParent(ALL_DEFINITIONS) .with(hasSiblingBefore(TT_OP_brace_left)) .with(hasSiblingAfter(TT_OP_brace_right)) .accepts(psi) || psiElement() .withParent(psiElement(ELEM_TYPE_StatementList)) .with(hasSiblingBefore(TT_OP_brace_left)) .with(hasSiblingAfter(TT_OP_brace_right)) .accepts(psi)) { return Indent.getNormalIndent(); } // Comments if (psiElement(JavaDocElementType.DOC_COMMENT) .accepts(psi)) { return Indent.getNoneIndent(); } if (or(psiElement(JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS), psiElement(JavaDocTokenType.DOC_COMMENT_END)) .accepts(psi)) { return Indent.getSpaceIndent(1); } // Annoations if (psiElement(ELEM_TYPE_AnnotationExpression) .accepts(psi)) { return Indent.getNoneIndent(); } // Modifiers (none indent after modifier) if (psiElement() .withElementType(ELEM_TYPE_ModifierListClause) .accepts(prevPsi) || psiElement() .withElementType(GosuTokenSets.MODIFIERS) .accepts(prevPsi)) { return Indent.getNoneIndent(); } // Braces if (or(psiElement(TT_OP_brace_left), psiElement(TT_OP_brace_right)) .accepts(psi)) { return Indent.getNoneIndent(); } // Parameters (because of alignment) if (psiElement() .withParent(psiElement(ELEM_TYPE_ParameterListClause)) .accepts(psi)) { return Indent.getNoneIndent(); } if (psiElement(ELEM_TYPE_ModifierListClause) .accepts( psi.getPrevSibling())) { return Indent.getNoneIndent(); } // * [INDENT] else if (psiElement(TT_else) .accepts(psi)) { return Indent.getNoneIndent(); } // else [INDENT] * if (psiElement(TT_else) .accepts(prevPsi)) { return Indent.getNormalIndent(); // StatementList was already processed with none indent } // if/while/for() [INDENT] * if (psiElement(TT_OP_paren_right) .accepts(prevPsi) && psiElement() .withParent(or(psiElement(ELEM_TYPE_IfStatement), psiElement(ELEM_TYPE_WhileStatement), psiElement(ELEM_TYPE_ForEachStatement))) .accepts(psi)) { return Indent.getNormalIndent(); } // [INDENT] case * if (psiElement(ELEM_TYPE_CaseClause).accepts(psi)) { return Indent.getNormalIndent(); } // ) [INDENT] ; if (or(psiElement(TT_OP_paren_right), psiElement(TT_OP_semicolon)) .accepts(psi)) { return Indent.getNoneIndent(); } if (psiElement(ELEM_TYPE_AnnotationExpression) .accepts(prevPsi)) { return Indent.getNoneIndent(); } // General case with continuation indent and none indent final boolean hasParentAndNoPrevSibling = parentPsi != null && psi.getPrevSibling() == null; return hasParentAndNoPrevSibling ? Indent.getNoneIndent() : Indent.getContinuationIndent(); } @Nullable public static Alignment getChildAlignment(GosuBlock parent, ASTNode prevNode, @NotNull ASTNode node, Alignment parentAlignment) { final PsiElement nodePsi = node.getPsi(); // Parameters if (psiElement(ELEM_TYPE_ParameterListClause).accepts(nodePsi)) { return Alignment.createChildAlignment(parentAlignment); } return null; } @Nullable public static ChildAttributes getChildAttributes(@NotNull GosuBlock parent, final int newChildIndex) { final ASTNode node = parent.getNode(); final List<ASTNode> children = getNonEmptyASTNodes(node.getChildren(null)); final PsiElement nodePsi = node.getPsi(); final int normalIndent = parent.getSettings().getIndentSize(nodePsi.getContainingFile().getFileType()); // if (true) [CURSOR] * // while(true) [CURSOR] * // for(i in 1..10) [CURSOR] * if (newChildIndex > 0) { final PsiElement prevPsi = children.get(newChildIndex - 1).getPsi(); if (or(psiElement(ELEM_TYPE_IfStatement), psiElement(ELEM_TYPE_WhileStatement), psiElement(ELEM_TYPE_ForEachStatement)).accepts(prevPsi)) { if (!psiElement() .afterLeaf(psiElement(TT_OP_paren_right)) .accepts(prevPsi.getLastChild())) { return new ChildAttributes(Indent.getSpaceIndent(2 * normalIndent), null); } } } // Other if (ALL_DEFINITIONS.accepts(nodePsi)) { final ASTNode leftBrace = node.findChildByType(GosuTokenTypes.TT_OP_brace_left); final ASTNode rightBrace = node.findChildByType(GosuTokenTypes.TT_OP_brace_right); if (leftBrace != null && rightBrace != null) { final List<ASTNode> nodeChildren = getNonEmptyASTNodes(node.getChildren(null)); final int leftBraceIndex = nodeChildren.indexOf(leftBrace); final int rightBraceIndex = nodeChildren.indexOf(rightBrace); if (leftBraceIndex < newChildIndex && newChildIndex <= rightBraceIndex) { return new ChildAttributes(Indent.getNormalIndent(), null); } } } if (or(psiElement(ELEM_TYPE_StatementList), psiElement(ELEM_TYPE_IfStatement), psiElement(ELEM_TYPE_WhileStatement), psiElement(ELEM_TYPE_ForEachStatement), psiElement(ELEM_TYPE_SwitchStatement)) .accepts(nodePsi)) { return new ChildAttributes(Indent.getNormalIndent(), null); } return new ChildAttributes(Indent.getNoneIndent(), null); } }