package com.jetbrains.lang.dart.ide.formatter;
import com.intellij.formatting.*;
import com.intellij.formatting.templateLanguages.BlockWithParent;
import com.intellij.lang.ASTNode;
import com.intellij.psi.TokenType;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.formatter.common.AbstractBlock;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.jetbrains.lang.dart.DartFileType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.jetbrains.lang.dart.DartTokenTypes.*;
import static com.jetbrains.lang.dart.DartTokenTypesSets.BLOCKS;
public class DartBlock extends AbstractBlock implements BlockWithParent {
public static final List<DartBlock> DART_EMPTY = Collections.emptyList();
private static final TokenSet STATEMENTS_WITH_OPTIONAL_BRACES = TokenSet.create(IF_STATEMENT, WHILE_STATEMENT, FOR_STATEMENT);
private static final TokenSet LAST_TOKENS_IN_SWITCH_CASE = TokenSet.create(BREAK_STATEMENT, CONTINUE_STATEMENT, RETURN_STATEMENT);
private final DartIndentProcessor myIndentProcessor;
private final DartSpacingProcessor mySpacingProcessor;
private final DartWrappingProcessor myWrappingProcessor;
private final DartAlignmentProcessor myAlignmentProcessor;
private final CodeStyleSettings mySettings;
private final DartBlockContext myContext;
private Wrap myChildWrap = null;
private final Indent myIndent;
private BlockWithParent myParent;
private List<DartBlock> mySubDartBlocks;
protected DartBlock(ASTNode node, Wrap wrap, Alignment alignment, CodeStyleSettings settings, DartBlockContext context) {
super(node, wrap, alignment);
mySettings = settings;
myContext = context;
myIndentProcessor = new DartIndentProcessor(context.getDartSettings());
mySpacingProcessor = new DartSpacingProcessor(node, context.getDartSettings());
myWrappingProcessor = new DartWrappingProcessor(node, context.getDartSettings());
myAlignmentProcessor = new DartAlignmentProcessor(node, context.getDartSettings());
myIndent = myIndentProcessor.getChildIndent(myNode, context.getMode());
}
@Override
public Indent getIndent() {
return myIndent;
}
@Override
public Spacing getSpacing(Block child1, @NotNull Block child2) {
return mySpacingProcessor.getSpacing(child1, child2);
}
@Override
protected List<Block> buildChildren() {
if (isLeaf()) {
return EMPTY;
}
final ArrayList<Block> tlChildren = new ArrayList<>();
for (ASTNode childNode = getNode().getFirstChildNode(); childNode != null; childNode = childNode.getTreeNext()) {
if (FormatterUtil.containsWhiteSpacesOnly(childNode)) continue;
final DartBlock childBlock =
new DartBlock(childNode, createChildWrap(childNode), createChildAlignment(childNode), mySettings, myContext);
childBlock.setParent(this);
tlChildren.add(childBlock);
}
return tlChildren;
}
public Wrap createChildWrap(ASTNode child) {
final IElementType childType = child.getElementType();
final Wrap wrap = myWrappingProcessor.createChildWrap(child, Wrap.createWrap(WrapType.NONE, false), myChildWrap);
if (childType == ASSIGNMENT_OPERATOR) {
myChildWrap = wrap;
}
return wrap;
}
@Nullable
protected Alignment createChildAlignment(ASTNode child) {
final IElementType type = child.getElementType();
if (type != LPAREN && !BLOCKS.contains(type)) {
return myAlignmentProcessor.createChildAlignment();
}
return null;
}
@Override
public boolean isIncomplete() {
return super.isIncomplete() || myNode.getElementType() == ARGUMENT_LIST;
}
@NotNull
@Override
public ChildAttributes getChildAttributes(final int newIndex) {
final IElementType elementType = myNode.getElementType();
final DartBlock previousBlock = newIndex == 0 ? null : getSubDartBlocks().get(newIndex - 1);
final IElementType previousType = previousBlock == null ? null : previousBlock.getNode().getElementType();
if (previousType == LBRACE || previousType == LBRACKET) {
return new ChildAttributes(Indent.getNormalIndent(), null);
}
if (previousType == RPAREN && STATEMENTS_WITH_OPTIONAL_BRACES.contains(elementType)) {
return new ChildAttributes(Indent.getNormalIndent(), null);
}
if (previousType == COLON && (elementType == SWITCH_CASE || elementType == DEFAULT_CASE)) {
return new ChildAttributes(Indent.getNormalIndent(), null);
}
if (previousType == SWITCH_CASE || previousType == DEFAULT_CASE) {
if (previousBlock != null) {
final List<DartBlock> subBlocks = previousBlock.getSubDartBlocks();
if (!subBlocks.isEmpty()) {
final DartBlock lastChildInPrevBlock = subBlocks.get(subBlocks.size() - 1);
final List<DartBlock> subSubBlocks = lastChildInPrevBlock.getSubDartBlocks();
if (isLastTokenInSwitchCase(subSubBlocks)) {
return new ChildAttributes(Indent.getNormalIndent(), null); // e.g. Enter after BREAK_STATEMENT
}
}
}
final int indentSize = mySettings.getIndentSize(DartFileType.INSTANCE) * 2;
return new ChildAttributes(Indent.getIndent(Indent.Type.SPACES, indentSize, false, false), null);
}
if (previousBlock == null) {
return new ChildAttributes(Indent.getNoneIndent(), null);
}
if (!previousBlock.isIncomplete() && newIndex < getSubDartBlocks().size() && previousType != TokenType.ERROR_ELEMENT) {
return new ChildAttributes(previousBlock.getIndent(), previousBlock.getAlignment());
}
if (myParent instanceof DartBlock && ((DartBlock)myParent).isIncomplete()) {
ASTNode child = myNode.getFirstChildNode();
if (child == null || !(child.getElementType() == OPEN_QUOTE && child.getTextLength() == 3)) {
return new ChildAttributes(Indent.getContinuationIndent(), null);
}
}
if (myParent == null && isIncomplete()) {
return new ChildAttributes(Indent.getContinuationIndent(), null);
}
return new ChildAttributes(previousBlock.getIndent(), previousBlock.getAlignment());
}
private static boolean isLastTokenInSwitchCase(@NotNull final List<DartBlock> blocks) {
int size = blocks.size();
// No blocks.
if (size == 0) {
return false;
}
// [return x;]
DartBlock lastBlock = blocks.get(size - 1);
final IElementType type = lastBlock.getNode().getElementType();
if (LAST_TOKENS_IN_SWITCH_CASE.contains(type)) {
return true;
}
// [throw expr][;]
if (type == SEMICOLON && size > 1) {
DartBlock lastBlock2 = blocks.get(size - 2);
return lastBlock2.getNode().getElementType() == THROW_EXPRESSION;
}
return false;
}
public List<DartBlock> getSubDartBlocks() {
if (mySubDartBlocks == null) {
mySubDartBlocks = new ArrayList<>();
for (Block block : getSubBlocks()) {
mySubDartBlocks.add((DartBlock)block);
}
mySubDartBlocks = !mySubDartBlocks.isEmpty() ? mySubDartBlocks : DART_EMPTY;
}
return mySubDartBlocks;
}
@Override
public boolean isLeaf() {
return false;
}
@Override
public BlockWithParent getParent() {
return myParent;
}
@Override
public void setParent(BlockWithParent newParent) {
myParent = newParent;
}
}