package com.haskforce.features; import com.haskforce.HaskellParserDefinition; import com.haskforce.psi.HaskellFile; import com.haskforce.psi.HaskellTypes; import com.intellij.lang.ASTNode; import com.intellij.lang.folding.CustomFoldingBuilder; import com.intellij.lang.folding.FoldingDescriptor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.TokenType; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Set; /** * Collapses constructs into something small, such as "{- -}". */ public class HaskellFoldingBuilder extends CustomFoldingBuilder implements DumbAware { @Override protected void buildLanguageFoldRegions(@NotNull final List<FoldingDescriptor> descriptors, @NotNull PsiElement root, @NotNull Document document, boolean quick) { if (!(root instanceof HaskellFile)) return; HaskellFile file = (HaskellFile) root; final Set<PsiElement> seenComments = ContainerUtil.newHashSet(); if (!quick) { PsiTreeUtil.processElements(file, new PsiElementProcessor() { @Override public boolean execute(@NotNull PsiElement element) { if (element.getNode().getElementType().equals(HaskellTypes.COMMENT)) { addCommentFolds((PsiComment) element, seenComments, descriptors); } else if (HaskellParserDefinition.COMMENTS.contains(element.getNode().getElementType())) { TextRange range = element.getTextRange(); String placeholderText = getPlaceholderText(element.getNode()); // Only fold if we actually save space to prevent // assertions from kicking in. Means {- -} will not fold. if (placeholderText != null && range.getLength() > 1 && range.getLength() > placeholderText.length()) { descriptors.add(new FoldingDescriptor(element, range)); } } return true; } }); } } /** * Provides the text displayed on folded elements. */ @Override protected String getLanguagePlaceholderText(@NotNull ASTNode node, @NotNull TextRange range) { IElementType type = node.getElementType(); if (HaskellTypes.OPENCOM.equals(type)) return "{-"; // Need two character placeholder for hoovering to work. if (HaskellTypes.COMMENTTEXT.equals(type)) return " "; if (HaskellTypes.CLOSECOM.equals(type)) return "-}"; if (HaskellTypes.COMMENT.equals(type)) return "--"; return ".."; } /** * The default collapsed state for a folding region related to a node. */ @Override protected boolean isRegionCollapsedByDefault(@NotNull ASTNode node) { return false; } /** * Single out nodes that we custom fold. Comment is the only current * element. */ @Override protected boolean isCustomFoldingCandidate(@NotNull ASTNode node) { return HaskellTypes.COMMENT.equals(node.getElementType()); } // Cut and paste from JavaFoldingBuilderEx. private static void addCommentFolds(@NotNull PsiComment c, @NotNull Set<PsiElement> processedComments, @NotNull List<FoldingDescriptor> foldElements) { if (processedComments.contains(c) || !HaskellTypes.COMMENT.equals(c.getTokenType())) { return; } PsiElement end = null; for (PsiElement curr = c.getNextSibling(); curr != null; curr = curr.getNextSibling()) { ASTNode node = curr.getNode(); if (node == null) { break; } IElementType elementType = node.getElementType(); if (HaskellTypes.COMMENT.equals(elementType)) { end = curr; // We don't want to process, say, the second comment in case of // three subsequent comments when it's being examined during all // elements traversal. I.e. we expect to start from the first // comment and grab as many subsequent comments as possible // during the single iteration. processedComments.add(curr); continue; } if (TokenType.WHITE_SPACE.equals(elementType)) { continue; } break; } if (end != null) { TextRange rng = new TextRange(c.getTextRange().getStartOffset(), end.getTextRange().getEndOffset()); foldElements.add(new FoldingDescriptor(c, rng)); } } }