package com.siberika.idea.pascal.lang;
import com.intellij.lang.ASTNode;
import com.intellij.lang.folding.FoldingBuilderEx;
import com.intellij.lang.folding.FoldingDescriptor;
import com.intellij.lang.folding.NamedFoldingDescriptor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.siberika.idea.pascal.lang.psi.PasCaseStatement;
import com.siberika.idea.pascal.lang.psi.PasClassHelperDecl;
import com.siberika.idea.pascal.lang.psi.PasClassTypeDecl;
import com.siberika.idea.pascal.lang.psi.PasClassTypeTypeDecl;
import com.siberika.idea.pascal.lang.psi.PasCompoundStatement;
import com.siberika.idea.pascal.lang.psi.PasConstSection;
import com.siberika.idea.pascal.lang.psi.PasEnumType;
import com.siberika.idea.pascal.lang.psi.PasHandler;
import com.siberika.idea.pascal.lang.psi.PasInterfaceTypeDecl;
import com.siberika.idea.pascal.lang.psi.PasNamedIdent;
import com.siberika.idea.pascal.lang.psi.PasObjectDecl;
import com.siberika.idea.pascal.lang.psi.PasRecordDecl;
import com.siberika.idea.pascal.lang.psi.PasRecordHelperDecl;
import com.siberika.idea.pascal.lang.psi.PasRepeatStatement;
import com.siberika.idea.pascal.lang.psi.PasTypeDeclaration;
import com.siberika.idea.pascal.lang.psi.PasTypeSection;
import com.siberika.idea.pascal.lang.psi.PasTypes;
import com.siberika.idea.pascal.lang.psi.PasUnitFinalization;
import com.siberika.idea.pascal.lang.psi.PasUnitImplementation;
import com.siberika.idea.pascal.lang.psi.PasUnitInitialization;
import com.siberika.idea.pascal.lang.psi.PasUnitInterface;
import com.siberika.idea.pascal.lang.psi.PasUsesClause;
import com.siberika.idea.pascal.lang.psi.PasVarSection;
import com.siberika.idea.pascal.lang.psi.PascalPsiElement;
import com.siberika.idea.pascal.lang.psi.PascalQualifiedIdent;
import com.siberika.idea.pascal.lang.psi.impl.PasRoutineImplDeclImpl;
import com.siberika.idea.pascal.util.PsiUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Author: George Bakhtadze
* Date: 24/03/2013
*/
public class PascalFoldingBuilder extends FoldingBuilderEx {
private static final TokenSet TOKENS_COLLAPSED = TokenSet.create(PasTypes.COMMENT, PasTypes.USES_CLAUSE);
@NotNull
@Override
public FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) {
final List<FoldingDescriptor> descriptors = new ArrayList<FoldingDescriptor>();
foldCommon(root, descriptors);
foldCase(root, descriptors);
foldUses(root, descriptors);
foldEnums(root, descriptors);
foldRoutines(root, descriptors);
if (!quick) {
foldComments(root, descriptors);
}
return descriptors.toArray(new FoldingDescriptor[descriptors.size()]);
}
private void foldRoutines(PsiElement root, List<FoldingDescriptor> descriptors) {
@SuppressWarnings("unchecked")
Collection<PasRoutineImplDeclImpl> routineList = PsiUtil.findChildrenOfAnyType(root, PasRoutineImplDeclImpl.class);
for (PasRoutineImplDeclImpl routine : routineList) {
int foldStart = getStartOffset(routine);
TextRange range = getRange(foldStart, routine.getTextRange().getEndOffset());
if (range.getLength() > 1) {
descriptors.add(new NamedFoldingDescriptor(routine.getNode(), range, null,
" " + PsiUtil.normalizeRoutineName(routine) + ";"));
}
}
}
private void foldCommon(PsiElement root, List<FoldingDescriptor> descriptors) {
@SuppressWarnings("unchecked")
Collection<PascalPsiElement> blocks = PsiUtil.findChildrenOfAnyType(root,
PasUnitInterface.class, PasUnitImplementation.class, PasUnitInitialization.class, PasUnitFinalization.class,
PasVarSection.class, PasTypeSection.class, PasConstSection.class,
PasClassTypeTypeDecl.class, PasClassHelperDecl.class, PasClassTypeDecl.class,
PasInterfaceTypeDecl.class, PasObjectDecl.class, PasRecordHelperDecl.class, PasRecordDecl.class,
PasCompoundStatement.class, PasHandler.class, PasRepeatStatement.class);
for (final PsiElement block : blocks) {
int foldStart = getStartOffset(block);
TextRange range = getRange(foldStart, block.getTextRange().getEndOffset());
if (range.getLength() > 1) {
descriptors.add(new FoldingDescriptor(block.getNode(), range, null));
}
}
}
private int getStartOffset(PsiElement block) {
return block.getFirstChild() != null ? block.getFirstChild().getTextRange().getEndOffset() : block.getTextRange().getStartOffset();
}
private TextRange getRange(int start, int end) {
return new TextRange(start, end);
}
private void foldCase(PsiElement root, List<FoldingDescriptor> descriptors) {
Collection<PasCaseStatement> caseStatements = PsiTreeUtil.findChildrenOfType(root, PasCaseStatement.class);
for (final PasCaseStatement caseStatement : caseStatements) {
PsiElement caseItem = PsiUtil.getNextSibling(caseStatement.getFirstChild());
if (caseItem != null) {
caseItem = PsiUtil.getNextSibling(caseItem);
}
int foldStart = caseItem != null ? caseItem.getTextRange().getStartOffset() : caseStatement.getTextRange().getStartOffset();
TextRange range = getRange(foldStart, caseStatement.getTextRange().getEndOffset());
if (range.getLength() > 0) {
descriptors.add(new FoldingDescriptor(caseStatement.getNode(), range, null));
}
}
}
private void foldUses(PsiElement root, List<FoldingDescriptor> descriptors) {
@SuppressWarnings("unchecked")
Collection<PasUsesClause> usesList = PsiUtil.findChildrenOfAnyType(root, PasUsesClause.class);
for (final PasUsesClause uses : usesList) {
int foldStart = getStartOffset(uses);
TextRange range = getRange(foldStart, uses.getTextRange().getEndOffset());
if (range.getLength() > 1) {
descriptors.add(new FoldingDescriptor(uses.getNode(), range, null) {
@Nullable
@Override
public String getPlaceholderText() {
StringBuilder sb = new StringBuilder(" ");
boolean first = true;
for (PascalQualifiedIdent ident : uses.getNamespaceIdentList()) {
if (!first) {
sb.append(", ").append(ident.getName());
} else {
sb.append(ident.getName());
first = false;
}
}
sb.append(";");
return sb.toString();
}
});
}
}
}
private void foldEnums(PsiElement root, List<FoldingDescriptor> descriptors) {
@SuppressWarnings("unchecked")
Collection<PasEnumType> enums = PsiUtil.findChildrenOfAnyType(root, PasEnumType.class);
for (final PasEnumType enumType : enums) {
final PasTypeDeclaration decl = PsiTreeUtil.getParentOfType(enumType, PasTypeDeclaration.class);
if (decl != null) {
TextRange range = getRange(decl.getGenericTypeIdent().getTextRange().getEndOffset(), decl.getTextRange().getEndOffset());
if (range.getLength() > 0) {
descriptors.add(new FoldingDescriptor(decl.getNode(), range, null) {
@Nullable
@Override
public String getPlaceholderText() {
StringBuilder sb = new StringBuilder(" = (");
boolean first = true;
for (PasNamedIdent ident : enumType.getNamedIdentList()) {
if (!first) {
sb.append(", ").append(ident.getName());
} else {
sb.append(ident.getName());
first = false;
}
}
sb.append(");");
return sb.toString();
}
});
}
}
}
}
private void foldComments(PsiElement root, List<FoldingDescriptor> descriptors) {
final Collection<PsiComment> comments = PsiTreeUtil.findChildrenOfType(root, PsiComment.class);
TextRange commentRange = null;
PsiComment lastComment = null;
for (final PsiComment comment : comments) {
if ((null == lastComment) || (commentRange.getEndOffset() < comment.getTextRange().getStartOffset())) {
lastComment = comment;
final String endSymbol = getEndSymbol(lastComment);
commentRange = comment.getTextRange();
// Merge sibling comments
PsiElement sibling = PsiUtil.getNextSibling(comment);
while (sibling instanceof PsiComment) {
commentRange = commentRange.union(sibling.getTextRange());
sibling = PsiUtil.getNextSibling(sibling);
}
int lfPos = lastComment.getText().indexOf('\n') + lastComment.getTextOffset();
if (lfPos < lastComment.getTextOffset()) {
lfPos = lastComment.getTextRange().getEndOffset();
}
if (lfPos < commentRange.getEndOffset()) {
descriptors.add(new FoldingDescriptor(lastComment.getNode(), getRange(lfPos, commentRange.getEndOffset()), null) {
@Nullable
@Override
public String getPlaceholderText() {
return "..." + endSymbol;
}
});
}
}
}
}
private String getEndSymbol(PsiComment comment) {
if (StringUtils.isNotEmpty(comment.getText())) {
if (comment.getText().startsWith("{")) {
return "}";
} else if (comment.getText().startsWith("(*")) {
return "*)";
}
}
return "";
}
@Nullable
@Override
public String getPlaceholderText(@NotNull ASTNode node) {
return "...";
}
@Override
public boolean isCollapsedByDefault(@NotNull ASTNode node) {
return TOKENS_COLLAPSED.contains(node.getElementType());
}
}