package com.siberika.idea.pascal.editor.formatter;
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.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.TokenType;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.common.AbstractBlock;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.siberika.idea.pascal.lang.lexer.PascalLexer;
import com.siberika.idea.pascal.lang.psi.PasTypes;
import com.siberika.idea.pascal.lang.psi.PascalStructType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Author: George Bakhtadze
* Date: 02/10/2013
*/
public class PascalBlock extends AbstractBlock implements Block {
private List<Block> mySubBlocks = null;
private final SpacingBuilder mySpacingBuilder;
private final CodeStyleSettings mySettings;
private final Indent myIndent;
private static final TokenSet TOKENS_PARENT_INDENTED = TokenSet.create(PasTypes.STATEMENT, PasTypes.VAR_DECLARATION, PasTypes.CONST_DECLARATION, PasTypes.TYPE_DECLARATION,
PasTypes.CLASS_TYPE_DECL, PasTypes.RECORD_DECL, PasTypes.OBJECT_DECL, PasTypes.CLASS_HELPER_DECL, PasTypes.INTERFACE_TYPE_DECL, PasTypes.RECORD_HELPER_DECL,
PasTypes.CLASS_FIELD, PasTypes.EXPORTED_ROUTINE, PasTypes.CLASS_PROPERTY, PasTypes.CLASS_METHOD_RESOLUTION, PasTypes.USES_CLAUSE, PasTypes.RECORD_VARIANT,
PasTypes.ENUM_TYPE, PasTypes.TYPE_DECL, PasTypes.FORMAL_PARAMETER_SECTION, PasTypes.FORMAL_PARAMETER,
PasTypes.ARRAY_TYPE, PasTypes.SUB_RANGE_TYPE,
PasTypes.ARGUMENT_LIST, PasTypes.ASSIGN_PART);
private static final TokenSet TOKENS_NO_LF_AFTER_SEMI = TokenSet.create(PasTypes.FORMAL_PARAMETER_SECTION, PasTypes.EXPORTED_ROUTINE,
PasTypes.CLASS_PROPERTY_SPECIFIER, PasTypes.PROCEDURE_TYPE);
private static final TokenSet TOKENS_COMMENT = PascalLexer.COMMENTS;
private static final TokenSet TOKENS_ENTER_INDENTED =
TokenSet.create(PasTypes.VAR_SECTION, PasTypes.CONST_SECTION, PasTypes.TYPE_SECTION, PasTypes.USES_CLAUSE,
PasTypes.COMPOUND_STATEMENT, PasTypes.SUM_EXPR, PasTypes.FULLY_QUALIFIED_IDENT, PasTypes.STATEMENT);
private static final TokenSet TOKEN_COMMENT_NORMALINDENT = TokenSet.create(PasTypes.COMPOUND_STATEMENT, PasTypes.USES_CLAUSE);
private static final Map<IElementType, IElementType> TOKEN_UNINDENTED_MAP = getTokenUnindentedMap();
private static Map<IElementType, IElementType> getTokenUnindentedMap() {
Map<IElementType, IElementType> result = new HashMap<IElementType, IElementType>();
result.put(PasTypes.STATEMENT, PasTypes.COMPOUND_STATEMENT);
result.put(PasTypes.USES_CLAUSE, PasTypes.USES);
return result;
}
private static final TokenSet TOKEN_STRUCT_FILTERED = TokenSet.create();
/* private static TokenSet getTokensUsed() {
TokenSet result = TokenSet.create(PasTypes.MODULE, PasTypes.NAMESPACE_IDENT, PasTypes.SUB_IDENT, PasTypes.TYPE, PasTypes.VAR, PasTypes.CONST);
result = TokenSet.andNot(result, TOKENS_ENTER_INDENTED);
result = TokenSet.andNot(result, TOKENS_NO_LF_AFTER_SEMI);
result = TokenSet.andNot(result, TOKENS_ENTER_INDENTED);
result = TokenSet.andNot(result, PascalFormatter.TOKENS_USED);
return result;
}*/
public PascalBlock(@NotNull ASTNode node, @NotNull CodeStyleSettings settings, @Nullable Wrap wrap, Indent indent) {
super(node, wrap, null);
mySpacingBuilder = PascalFormatter.createSpacingBuilder(settings);
mySettings = settings;
myIndent = indent;
// System.out.println("block: " + block2Str(this));
}
@Override
protected List<Block> buildChildren() {
if (mySubBlocks == null) {
mySubBlocks = ContainerUtil.mapNotNull(myNode.getChildren(null), new Function<ASTNode, Block>() {
@Override
public Block fun(ASTNode node) {
if (isWhitespaceOrEmpty(node) || TOKEN_STRUCT_FILTERED.contains(node.getElementType())) {
return null;
}
return makeSubBlock(node);
}
});
}
/*mySubBlocks = new ArrayList<Block>();
for (ASTNode node : myNode.getChildren(null)) {
if (!TOKENS_USED.contains(node.getElementType()) && !isWhitespaceOrEmpty(node)) {
int minPos = node.getTextRange().getEndOffset();
int maxPos = node.getTextRange().getStartOffset();
for (ASTNode ch : node.getChildren(null)) {
minPos = minPos > ch.getTextRange().getStartOffset() ? ch.getTextRange().getStartOffset() : minPos;
maxPos = maxPos < ch.getTextRange().getEndOffset() ? ch.getTextRange().getEndOffset() : maxPos;
}
if ((minPos <= node.getTextRange().getStartOffset() && (maxPos >= node.getTextRange().getEndOffset()))) {
for (ASTNode ch : node.getChildren(null)) {
mySubBlocks.add(makeSubBlock(ch));
}
}
} else if (!isWhitespaceOrEmpty(node)) {
mySubBlocks.add(makeSubBlock(node));
}
}*/
return mySubBlocks;
}
private Block makeSubBlock(@NotNull ASTNode childNode) {
Indent indent = getBlockIndent(childNode);
/* Alignment alignment = Alignment.createAlignment(true);
if (myParent != null) {
alignment = myParent.getAlignment();
}*/
Wrap wrap = Wrap.createWrap(WrapType.NORMAL, false);
return new PascalBlock(childNode, mySettings, wrap, indent);
}
private Indent getBlockIndent(@Nullable ASTNode childNode) {
if (childNode != null) {
if (TOKENS_COMMENT.contains(childNode.getElementType())) {
// Not move at leftmost position, indent usually, not indent in already indented contexts such as statement
Indent commentIndent = Indent.getAbsoluteNoneIndent();
int curInd = getCurrentIndent(childNode);
if (curInd > 0) {
if (TOKEN_COMMENT_NORMALINDENT.contains(myNode.getElementType())) {
commentIndent = Indent.getNormalIndent(false);
} else {
commentIndent = Indent.getContinuationIndent();
}
}
return commentIndent;
}
}
if (TOKENS_PARENT_INDENTED.contains(myNode.getElementType()) && (childNode != null)) {
if (TOKEN_UNINDENTED_MAP.get(myNode.getElementType()) != childNode.getElementType()) {
// System.out.println("Parent ind: " + myNode + " . " + childNode);
return Indent.getNormalIndent();
}
}
return Indent.getNoneIndent();
}
private int getCurrentIndent(ASTNode childNode) {
ASTNode prev = childNode.getTreePrev();
if (prev instanceof PsiWhiteSpace) {
String text = prev.getText();
int pos = Math.min(text.length(), text.length() - text.lastIndexOf('\n') - 1);
return pos;
}
return 0;
}
@Override
public Indent getIndent() {
return myIndent;
}
private static boolean isWhitespaceOrEmpty(ASTNode node) {
return node.getElementType() == TokenType.WHITE_SPACE || node.getTextLength() == 0;
}
@Nullable
@Override
public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
// System.out.println("getSpacing: " + block2Str(child1) + ", " + block2Str(child2));
if ((child1 instanceof PascalBlock) && (((PascalBlock) child1).getNode().getElementType() == PasTypes.SEMI)) {
if (!TOKENS_NO_LF_AFTER_SEMI.contains(this.getNode().getElementType())) {
if (((PascalBlock) child2).getNode().getElementType() == PasTypes.COMMENT) {
return null;
}
// System.out.println("getSpacing: " + block2Str(this) + " . " + block2Str(child1) + ", " + block2Str(child2));
return Spacing.createSpacing(0, 0, 1, true, 1);
} else {
return Spacing.createSpacing(1, 1, 0, true, 1);
}
}
return mySpacingBuilder.getSpacing(this, child1, child2);
}
private String block2Str(Block r) {
if (null == r) {
return "[-]";
}
if (r instanceof PascalBlock) {
return String.format("%s, I: %s", ((PascalBlock) r).getNode().toString(), r.getIndent());
}
return String.format("[%d..%d, I: %s]", r.getTextRange().getStartOffset(), r.getTextRange().getEndOffset(), r.getIndent());
}
@Override
public boolean isLeaf() {
return myNode.getFirstChildNode() == null;
}
@Override
@NotNull
public ChildAttributes getChildAttributes(final int newChildIndex) {
return new ChildAttributes(getChildBlockIndent(myNode), null);
}
private static Indent getChildBlockIndent(ASTNode childNode) {
if ((childNode != null) && (childNode.getTreeParent() != null)) {
// System.out.println("!Enter ind: " + childNode.getTreeParent() + " . " + childNode +
// " | " + FormatterUtil.getPreviousNonWhitespaceSibling(childNode) + " | " + FormatterUtil.getPreviousNonWhitespaceLeaf(childNode));
if ((TOKENS_ENTER_INDENTED.contains(childNode.getElementType())) || (childNode.getTreeParent().getElementType() == PasTypes.STATEMENT)) {
return Indent.getNormalIndent();
}
PsiElement psi = childNode.getPsi();
if (psi instanceof PascalStructType) {
//return Indent.getIndent(Indent.Type.NORMAL, true, false); // When name will be included in struct type declarations
return Indent.getContinuationIndent(false);
}
}
return Indent.getNoneIndent();
}
}