1 package com.intellij.lang.folding;
3 import com.intellij.lang.ASTNode;
4 import com.intellij.openapi.editor.Document;
5 import com.intellij.openapi.project.DumbAware;
6 import com.intellij.openapi.util.TextRange;
7 import com.intellij.psi.PsiComment;
8 import com.intellij.psi.PsiElement;
9 import com.intellij.psi.PsiFile;
10 import com.intellij.util.containers.Stack;
11 import org.jetbrains.annotations.NotNull;
12 import org.jetbrains.annotations.Nullable;
14 import java.util.ArrayList;
15 import java.util.List;
18 * Builds custom folding regions. If custom folding is supported for a language, its FoldingBuilder must be inherited from this class.
20 * @author Rustam Vishnyakov
22 public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements DumbAware {
24 private CustomFoldingProvider myDefaultProvider;
28 public final FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) {
29 List<FoldingDescriptor> descriptors = new ArrayList<FoldingDescriptor>();
30 if (CustomFoldingProvider.getAllProviders().length > 0) {
31 myDefaultProvider = null;
32 addCustomFoldingRegionsRecursively(root.getNode(), descriptors);
34 buildLanguageFoldRegions(descriptors, root, document, quick);
35 return descriptors.toArray(new FoldingDescriptor[descriptors.size()]);
40 public final FoldingDescriptor[] buildFoldRegions(@NotNull ASTNode node, @NotNull Document document) {
41 return buildFoldRegions(node.getPsi(), document, false);
45 * Implement this method to build language folding regions besides custom folding regions.
47 * @param descriptors The list of folding descriptors to store results to.
48 * @param root The root node for which the folding is requested.
49 * @param document The document for which folding is built.
50 * @param quick whether the result should be provided as soon as possible without reference resolving
53 protected abstract void buildLanguageFoldRegions(@NotNull List<FoldingDescriptor> descriptors,
54 @NotNull PsiElement root,
55 @NotNull Document document,
58 private void addCustomFoldingRegionsRecursively(@NotNull ASTNode node, List<FoldingDescriptor> descriptors) {
59 Stack<ASTNode> customFoldingNodesStack = new Stack<ASTNode>(1);
60 for (ASTNode child = node.getFirstChildNode(); child != null; child = child.getTreeNext()) {
61 if (isCustomRegionStart(child)) {
62 customFoldingNodesStack.push(child);
64 else if (isCustomRegionEnd(child)) {
65 if (!customFoldingNodesStack.isEmpty()) {
66 ASTNode startNode = customFoldingNodesStack.pop();
67 int startOffset = startNode.getTextRange().getStartOffset();
68 TextRange range = new TextRange(startOffset, child.getTextRange().getEndOffset());
69 descriptors.add(new FoldingDescriptor(node, range));
73 addCustomFoldingRegionsRecursively(child, descriptors);
79 public final String getPlaceholderText(@NotNull ASTNode node, @NotNull TextRange range) {
80 if (mayContainCustomFoldings(node)) {
81 PsiFile file = node.getPsi().getContainingFile();
82 PsiElement contextElement = file.findElementAt(range.getStartOffset());
83 if (contextElement != null && isCustomFoldingCandidate(contextElement.getNode())) {
84 String elementText = contextElement.getText();
85 CustomFoldingProvider defaultProvider = getDefaultProvider(elementText);
86 if (defaultProvider != null && defaultProvider.isCustomRegionStart(elementText)) {
87 return defaultProvider.getPlaceholderText(elementText);
91 return getLanguagePlaceholderText(node, range);
94 protected abstract String getLanguagePlaceholderText(@NotNull ASTNode node, @NotNull TextRange range);
98 public final String getPlaceholderText(@NotNull ASTNode node) {
104 public final boolean isCollapsedByDefault(@NotNull ASTNode node) {
105 // TODO<rv>: Modify Folding API and pass here folding range.
106 if (mayContainCustomFoldings(node)) {
107 for (ASTNode child = node.getFirstChildNode(); child != null; child = child.getTreeNext()) {
108 if (isCustomRegionStart(child)) {
109 String childText = child.getText();
110 CustomFoldingProvider defaultProvider = getDefaultProvider(childText);
111 return defaultProvider != null && defaultProvider.isCollapsedByDefault(childText);
115 return isRegionCollapsedByDefault(node);
119 * Returns the default collapsed state for the folding region related to the specified node.
121 * @param node the node for which the collapsed state is requested.
122 * @return true if the region is collapsed by default, false otherwise.
124 protected abstract boolean isRegionCollapsedByDefault(@NotNull ASTNode node);
127 * Returns true if the node corresponds to custom region start. The node must be a custom folding candidate and match custom folding
130 * @param node The node which may contain custom region start.
131 * @return True if the node marks a custom region start.
133 protected final boolean isCustomRegionStart(ASTNode node) {
134 if (isCustomFoldingCandidate(node)) {
135 String nodeText = node.getText();
136 CustomFoldingProvider defaultProvider = getDefaultProvider(nodeText);
137 return defaultProvider != null && defaultProvider.isCustomRegionStart(nodeText);
143 * Returns true if the node corresponds to custom region end. The node must be a custom folding candidate and match custom folding
146 * @param node The node which may contain custom region end
147 * @return True if the node marks a custom region end.
149 protected final boolean isCustomRegionEnd(ASTNode node) {
150 if (isCustomFoldingCandidate(node)) {
151 String nodeText = node.getText();
152 CustomFoldingProvider defaultProvider = getDefaultProvider(nodeText);
153 return defaultProvider != null && defaultProvider.isCustomRegionEnd(nodeText);
159 private CustomFoldingProvider getDefaultProvider(String elementText) {
160 if (myDefaultProvider == null) {
161 for (CustomFoldingProvider provider : CustomFoldingProvider.getAllProviders()) {
162 if (provider.isCustomRegionStart(elementText) || provider.isCustomRegionEnd(elementText)) {
163 myDefaultProvider = provider;
167 return myDefaultProvider;
171 * Checks if a node may contain custom folding tags. By default returns true for PsiComment but a language folding builder may override
172 * this method to allow only specific subtypes of comments (for example, line comments only).
173 * @param node The node to check.
174 * @return True if the node may contain custom folding tags.
176 protected boolean isCustomFoldingCandidate(ASTNode node) {
177 return node.getPsi() instanceof PsiComment;
181 * Returns true if the node may contain custom foldings in its immediate child nodes. By default any node will be checked for custom
182 * foldings but for performance reasons it makes sense to override this method to check only the nodes which actually may contain
183 * custom folding nodes (for example, group statements).
185 * @param node The node to check.
186 * @return True if the node may contain custom folding nodes (true by default).
188 protected boolean mayContainCustomFoldings(ASTNode node) {