package org.jetbrains.plugins.cucumber.psi.formatter;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.TokenType;
import com.intellij.psi.formatter.xml.ReadOnlyBlock;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.cucumber.psi.GherkinElementTypes;
import org.jetbrains.plugins.cucumber.psi.GherkinTable;
import org.jetbrains.plugins.cucumber.psi.GherkinTokenTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author yole
*/
public class GherkinBlock implements ASTBlock {
private final ASTNode myNode;
private final Indent myIndent;
private final TextRange myTextRange;
private final boolean myLeaf;
private List<Block> myChildren = null;
private static final TokenSet BLOCKS_TO_INDENT = TokenSet.create(GherkinElementTypes.FEATURE_HEADER,
GherkinElementTypes.SCENARIO,
GherkinElementTypes.SCENARIO_OUTLINE,
GherkinElementTypes.STEP,
GherkinElementTypes.TABLE,
GherkinElementTypes.EXAMPLES_BLOCK);
private static final TokenSet BLOCKS_TO_INDENT_CHILDREN = TokenSet.create(GherkinElementTypes.GHERKIN_FILE,
GherkinElementTypes.FEATURE,
GherkinElementTypes.SCENARIO,
GherkinElementTypes.SCENARIO_OUTLINE);
public GherkinBlock(ASTNode node) {
this(node, Indent.getAbsoluteNoneIndent());
}
public GherkinBlock(ASTNode node, Indent indent) {
this(node, indent, node.getTextRange());
}
public GherkinBlock(ASTNode node, Indent indent, final TextRange textRange) {
this(node, indent, textRange, false);
}
public GherkinBlock(ASTNode node, Indent indent, final TextRange textRange, final boolean leaf) {
myNode = node;
myIndent = indent;
myTextRange = textRange;
myLeaf = leaf;
}
public ASTNode getNode() {
return myNode;
}
@NotNull
public TextRange getTextRange() {
return myTextRange;
}
@NotNull
public List<Block> getSubBlocks() {
if (myLeaf) return Collections.emptyList();
if (myChildren == null) {
myChildren = buildChildren();
}
return myChildren;
}
private List<Block> buildChildren() {
final ASTNode[] children = myNode.getChildren(null);
if (children.length == 0) {
return Collections.emptyList();
}
List<Block> result = new ArrayList<>();
for (ASTNode child : children) {
if (child.getElementType() == TokenType.WHITE_SPACE) {
continue;
}
Indent indent = BLOCKS_TO_INDENT.contains(child.getElementType()) ? Indent.getNormalIndent() : Indent.getNoneIndent();
// skip epmty cells
if (child.getElementType() == GherkinElementTypes.TABLE_CELL) {
if (child.getChildren(null).length == 0) {
continue;
}
}
if (child.getElementType() == GherkinTokenTypes.COMMENT) {
final ASTNode commentIndentElement = child.getTreePrev();
final ASTNode parentIndentElement = myNode.getTreePrev();
if (commentIndentElement != null && (commentIndentElement.getText().contains("\n") || commentIndentElement.getTreePrev() == null)) {
final String whiteSpaceText = commentIndentElement.getText();
final int lineBreakIndex = whiteSpaceText.lastIndexOf("\n");
int parentIndent = 0;
if (parentIndentElement != null && parentIndentElement.getText().contains("\n")) {
String parentIndentText = parentIndentElement.getText();
parentIndent = parentIndentText.length() - parentIndentText.lastIndexOf("\n") - 1;
}
indent = Indent.getSpaceIndent(whiteSpaceText.length() - lineBreakIndex - 1 - parentIndent);
}
}
result.add(new GherkinBlock(child, indent));
}
return result;
}
@Nullable
public Wrap getWrap() {
return null;
}
public Indent getIndent() {
return myIndent;
}
public Alignment getAlignment() {
return null;
}
@Override
public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
if (child1 == null) {
return null;
}
ASTBlock block1 = (ASTBlock) child1;
ASTBlock block2 = (ASTBlock) child2;
final IElementType elementType1 = block1.getNode().getElementType();
final IElementType elementType2 = block2.getNode().getElementType();
if(elementType2 == GherkinElementTypes.PYSTRING) return Spacing.getReadOnlySpacing();
if (GherkinElementTypes.SCENARIOS.contains(elementType2) && elementType1 != GherkinTokenTypes.COMMENT) {
return Spacing.createSpacing(0, 0, 2, true, 2);
}
if (elementType1 == GherkinTokenTypes.PIPE &&
elementType2 == GherkinElementTypes.TABLE_CELL) {
return Spacing.createSpacing(1, 1, 0, false, 0);
}
if ((elementType1 == GherkinElementTypes.TABLE_CELL || elementType1 == GherkinTokenTypes.PIPE) &&
elementType2 == GherkinTokenTypes.PIPE) {
final ASTNode tableNode = TreeUtil.findParent(block1.getNode(), GherkinElementTypes.TABLE);
if (tableNode != null) {
int columnIndex = getTableCellColumnIndex(block1.getNode());
int maxWidth = ((GherkinTable) tableNode.getPsi()).getColumnWidth(columnIndex);
int spacingWidth = (maxWidth - block1.getNode().getText().trim().length()) + 1;
if (elementType1 == GherkinTokenTypes.PIPE) {
spacingWidth += 2;
}
return Spacing.createSpacing(spacingWidth, spacingWidth, 0, false, 0);
}
}
return null;
}
private static int getTableCellColumnIndex(ASTNode node) {
int pipeCount = 0;
while(node != null) {
if (node.getElementType() == GherkinTokenTypes.PIPE) {
pipeCount++;
}
node = node.getTreePrev();
}
return pipeCount-1;
}
@NotNull
public ChildAttributes getChildAttributes(int newChildIndex) {
Indent childIndent = BLOCKS_TO_INDENT_CHILDREN.contains(getNode().getElementType()) ? Indent.getNormalIndent() : Indent.getNoneIndent();
return new ChildAttributes(childIndent, null);
}
public boolean isIncomplete() {
if (GherkinElementTypes.SCENARIOS.contains(getNode().getElementType())) {
return true;
}
if (getNode().getElementType() == GherkinElementTypes.FEATURE) {
return getNode().getChildren(TokenSet.create(GherkinElementTypes.FEATURE_HEADER,
GherkinElementTypes.SCENARIO,
GherkinElementTypes.SCENARIO_OUTLINE)).length == 0;
}
return false;
}
public boolean isLeaf() {
return myLeaf || getSubBlocks().size() == 0;
}
}