/* * Copyright 2015-present Facebook, Inc. * * 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.facebook.buck.intellij.ideabuck.format; import static com.facebook.buck.intellij.ideabuck.lang.psi.BuckPsiUtils.hasElementType; import com.facebook.buck.intellij.ideabuck.lang.psi.BuckArrayElements; import com.facebook.buck.intellij.ideabuck.lang.psi.BuckListElements; import com.facebook.buck.intellij.ideabuck.lang.psi.BuckRuleBody; import com.facebook.buck.intellij.ideabuck.lang.psi.BuckTypes; import com.intellij.formatting.ASTBlock; import com.intellij.formatting.Alignment; import com.intellij.formatting.Block; import com.intellij.formatting.ChildAttributes; import com.intellij.formatting.Indent; import com.intellij.formatting.Spacing; import com.intellij.formatting.SpacingBuilder; import com.intellij.formatting.Wrap; import com.intellij.formatting.WrapType; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.TokenType; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.jetbrains.annotations.Nullable; /** Describes a single AST block, used for reformatting. */ public class BuckBlock implements ASTBlock { private static final TokenSet BUCK_CONTAINERS = TokenSet.create(BuckTypes.ARRAY_ELEMENTS, BuckTypes.RULE_BODY, BuckTypes.LIST_ELEMENTS); private static final TokenSet BUCK_OPEN_BRACES = TokenSet.create(BuckTypes.L_BRACKET, BuckTypes.L_PARENTHESES); private static final TokenSet BUCK_CLOSE_BRACES = TokenSet.create(BuckTypes.R_BRACKET, BuckTypes.R_PARENTHESES); private static final TokenSet BUCK_ALL_BRACES = TokenSet.orSet(BUCK_OPEN_BRACES, BUCK_CLOSE_BRACES); private final Alignment myAlignment; private final Indent myIndent; private final PsiElement myPsiElement; private final ASTNode myNode; private final Wrap myWrap; private final Wrap myChildWrap; private final CodeStyleSettings mySettings; private final SpacingBuilder mySpacingBuilder; private List<BuckBlock> mySubBlocks = null; public BuckBlock( final ASTNode node, CodeStyleSettings settings, @Nullable final Alignment alignment, final Indent indent, @Nullable final Wrap wrap) { myAlignment = alignment; myIndent = indent; myNode = node; myPsiElement = node.getPsi(); myWrap = wrap; mySettings = settings; mySpacingBuilder = BuckFormattingModelBuilder.createSpacingBuilder(settings); if (myPsiElement instanceof BuckArrayElements || myPsiElement instanceof BuckRuleBody || myPsiElement instanceof BuckListElements) { myChildWrap = Wrap.createWrap(CommonCodeStyleSettings.WRAP_ALWAYS, true); } else { myChildWrap = null; } } @Override public ASTNode getNode() { return myNode; } @Override public TextRange getTextRange() { return myNode.getTextRange(); } @Override public List<Block> getSubBlocks() { if (mySubBlocks == null) { mySubBlocks = buildSubBlocks(); } return new ArrayList<Block>(mySubBlocks); } /** Recursively build sub blocks. */ private List<BuckBlock> buildSubBlocks() { final List<BuckBlock> blocks = new ArrayList<BuckBlock>(); for (ASTNode child = myNode.getFirstChildNode(); child != null; child = child.getTreeNext()) { final IElementType childType = child.getElementType(); if (child.getTextRange().isEmpty()) { continue; } if (childType == TokenType.WHITE_SPACE) { continue; } // In most cases, glob block doesn't need reformatting. So just ignore it for now. if (childType == BuckTypes.GLOB_BLOCK) { continue; } blocks.add(buildSubBlock(child)); } return Collections.unmodifiableList(blocks); } private BuckBlock buildSubBlock(ASTNode childNode) { Indent indent = Indent.getNoneIndent(); Alignment alignment = null; Wrap wrap = null; if (hasElementType(myNode, BUCK_CONTAINERS)) { if (hasElementType(childNode, BuckTypes.COMMA)) { wrap = Wrap.createWrap(WrapType.NONE, true); } else if (!hasElementType(childNode, BUCK_ALL_BRACES)) { assert myChildWrap != null; wrap = myChildWrap; indent = Indent.getNormalIndent(); } } return new BuckBlock(childNode, mySettings, alignment, indent, wrap); } @Nullable @Override public Wrap getWrap() { return myWrap; } @Nullable @Override public Indent getIndent() { assert myIndent != null; return myIndent; } @Nullable @Override public Alignment getAlignment() { return myAlignment; } @Nullable @Override public Spacing getSpacing(@Nullable Block child1, Block child2) { return mySpacingBuilder.getSpacing(this, child1, child2); } @Override public ChildAttributes getChildAttributes(int newChildIndex) { if (hasElementType(myNode, BUCK_CONTAINERS)) { return new ChildAttributes(Indent.getNormalIndent(), null); } else if (myNode.getPsi() instanceof PsiFile) { return new ChildAttributes(Indent.getNoneIndent(), null); } else { return new ChildAttributes(null, null); } } @Override public boolean isIncomplete() { final ASTNode lastChildNode = myNode.getLastChildNode(); boolean ret = false; if (hasElementType(myNode, TokenSet.create(BuckTypes.ARRAY_ELEMENTS))) { ret = lastChildNode != null && lastChildNode.getElementType() != BuckTypes.R_BRACKET; } else if (hasElementType(myNode, TokenSet.create(BuckTypes.RULE_BODY))) { ret = lastChildNode != null && lastChildNode.getElementType() != BuckTypes.R_PARENTHESES; } else if (hasElementType(myNode, TokenSet.create(BuckTypes.LIST_ELEMENTS))) { ret = lastChildNode != null && lastChildNode.getElementType() != BuckTypes.R_PARENTHESES; } return ret; } @Override public boolean isLeaf() { return myNode.getFirstChildNode() == null; } }