Custom folding classes moved to platform, other refactoring

This commit is contained in:
Rustam.Vishnyakov
2012-01-31 11:58:41 +04:00
parent 4f97e2a5ee
commit 14bdd1b060
10 changed files with 687 additions and 1 deletions
@@ -0,0 +1,191 @@
package com.intellij.lang.folding;
import com.intellij.lang.ASTNode;
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.PsiFile;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* Builds custom folding regions. If custom folding is supported for a language, its FoldingBuilder must be inherited from this class.
*
* @author Rustam Vishnyakov
*/
public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements DumbAware {
private CustomFoldingProvider myDefaultProvider;
@NotNull
@Override
public final FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) {
List<FoldingDescriptor> descriptors = new ArrayList<FoldingDescriptor>();
if (CustomFoldingProvider.getAllProviders().length > 0) {
myDefaultProvider = null;
addCustomFoldingRegionsRecursively(root.getNode(), descriptors);
}
buildLanguageFoldRegions(descriptors, root, document, quick);
return descriptors.toArray(new FoldingDescriptor[descriptors.size()]);
}
@NotNull
@Override
public final FoldingDescriptor[] buildFoldRegions(@NotNull ASTNode node, @NotNull Document document) {
return buildFoldRegions(node.getPsi(), document, false);
}
/**
* Implement this method to build language folding regions besides custom folding regions.
*
* @param descriptors The list of folding descriptors to store results to.
* @param root The root node for which the folding is requested.
* @param document The document for which folding is built.
* @param quick whether the result should be provided as soon as possible without reference resolving
* and complex checks.
*/
protected abstract void buildLanguageFoldRegions(@NotNull List<FoldingDescriptor> descriptors,
@NotNull PsiElement root,
@NotNull Document document,
boolean quick);
private void addCustomFoldingRegionsRecursively(@NotNull ASTNode node, List<FoldingDescriptor> descriptors) {
Stack<ASTNode> customFoldingNodesStack = new Stack<ASTNode>(1);
for (ASTNode child = node.getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (isCustomRegionStart(child)) {
customFoldingNodesStack.push(child);
}
else if (isCustomRegionEnd(child)) {
if (!customFoldingNodesStack.isEmpty()) {
ASTNode startNode = customFoldingNodesStack.pop();
int startOffset = startNode.getTextRange().getStartOffset();
TextRange range = new TextRange(startOffset, child.getTextRange().getEndOffset());
descriptors.add(new FoldingDescriptor(node, range));
}
}
else {
addCustomFoldingRegionsRecursively(child, descriptors);
}
}
}
@Override
public final String getPlaceholderText(@NotNull ASTNode node, @NotNull TextRange range) {
if (mayContainCustomFoldings(node)) {
PsiFile file = node.getPsi().getContainingFile();
PsiElement contextElement = file.findElementAt(range.getStartOffset());
if (contextElement != null && isCustomFoldingCandidate(contextElement.getNode())) {
String elementText = contextElement.getText();
CustomFoldingProvider defaultProvider = getDefaultProvider(elementText);
if (defaultProvider != null && defaultProvider.isCustomRegionStart(elementText)) {
return defaultProvider.getPlaceholderText(elementText);
}
}
}
return getLanguagePlaceholderText(node, range);
}
protected abstract String getLanguagePlaceholderText(@NotNull ASTNode node, @NotNull TextRange range);
@Override
public final String getPlaceholderText(@NotNull ASTNode node) {
return "...";
}
@Override
public final boolean isCollapsedByDefault(@NotNull ASTNode node) {
// TODO<rv>: Modify Folding API and pass here folding range.
if (mayContainCustomFoldings(node)) {
for (ASTNode child = node.getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (isCustomRegionStart(child)) {
String childText = child.getText();
CustomFoldingProvider defaultProvider = getDefaultProvider(childText);
return defaultProvider != null && defaultProvider.isCollapsedByDefault(childText);
}
}
}
return isRegionCollapsedByDefault(node);
}
/**
* Returns the default collapsed state for the folding region related to the specified node.
*
* @param node the node for which the collapsed state is requested.
* @return true if the region is collapsed by default, false otherwise.
*/
protected abstract boolean isRegionCollapsedByDefault(@NotNull ASTNode node);
/**
* Returns true if the node corresponds to custom region start. The node must be a custom folding candidate and match custom folding
* start pattern.
*
* @param node The node which may contain custom region start.
* @return True if the node marks a custom region start.
*/
protected final boolean isCustomRegionStart(ASTNode node) {
if (isCustomFoldingCandidate(node)) {
String nodeText = node.getText();
CustomFoldingProvider defaultProvider = getDefaultProvider(nodeText);
return defaultProvider != null && defaultProvider.isCustomRegionStart(nodeText);
}
return false;
}
/**
* Returns true if the node corresponds to custom region end. The node must be a custom folding candidate and match custom folding
* end pattern.
*
* @param node The node which may contain custom region end
* @return True if the node marks a custom region end.
*/
protected final boolean isCustomRegionEnd(ASTNode node) {
if (isCustomFoldingCandidate(node)) {
String nodeText = node.getText();
CustomFoldingProvider defaultProvider = getDefaultProvider(nodeText);
return defaultProvider != null && defaultProvider.isCustomRegionEnd(nodeText);
}
return false;
}
@Nullable
private CustomFoldingProvider getDefaultProvider(String elementText) {
if (myDefaultProvider == null) {
for (CustomFoldingProvider provider : CustomFoldingProvider.getAllProviders()) {
if (provider.isCustomRegionStart(elementText) || provider.isCustomRegionEnd(elementText)) {
myDefaultProvider = provider;
}
}
}
return myDefaultProvider;
}
/**
* Checks if a node may contain custom folding tags. By default returns true for PsiComment but a language folding builder may override
* this method to allow only specific subtypes of comments (for example, line comments only).
* @param node The node to check.
* @return True if the node may contain custom folding tags.
*/
protected boolean isCustomFoldingCandidate(ASTNode node) {
return node.getPsi() instanceof PsiComment;
}
/**
* Returns true if the node may contain custom foldings in its immediate child nodes. By default any node will be checked for custom
* foldings but for performance reasons it makes sense to override this method to check only the nodes which actually may contain
* custom folding nodes (for example, group statements).
*
* @param node The node to check.
* @return True if the node may contain custom folding nodes (true by default).
*/
protected boolean mayContainCustomFoldings(ASTNode node) {
return true;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.lang.folding;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import java.util.List;
/**
* Base class and extension point for custom folding providers.
*
* @author Rustam Vishnyakov
*/
public abstract class CustomFoldingProvider {
public static final ExtensionPointName<CustomFoldingProvider> EP_NAME = ExtensionPointName.create("com.intellij.customFoldingProvider");
public static CustomFoldingProvider[] getAllProviders() {
return Extensions.getExtensions(EP_NAME);
}
public abstract boolean isCustomRegionStart(String elementText);
public abstract boolean isCustomRegionEnd(String elementText);
public abstract String getPlaceholderText(String elementText);
public boolean isCollapsedByDefault(String text) {
return false;
}
}
@@ -0,0 +1,83 @@
package com.intellij.lang.customFolding;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* @author Rustam Vishnyakov
*/
public class CustomFoldingConfigurable implements SearchableConfigurable {
private CustomFoldingConfiguration myConfiguration;
private CustomFoldingSettingsPanel mySettingsPanel;
public CustomFoldingConfigurable(Project project) {
myConfiguration = CustomFoldingConfiguration.getInstance(project);
mySettingsPanel = new CustomFoldingSettingsPanel();
}
@NotNull
@Override
public String getId() {
return getDisplayName();
}
@Override
public Runnable enableSearch(String option) {
return null;
}
@Nls
@Override
public String getDisplayName() {
return "Custom Folding"; //TODO<rv> Move to resources
}
@Override
public Icon getIcon() {
return null;
}
@Override
public String getHelpTopic() {
return null; //TODO<rv>: Define help topic
}
@Override
public JComponent createComponent() {
return mySettingsPanel.getComponent();
}
@Override
public boolean isModified() {
return myConfiguration.getState().isEnabled() != mySettingsPanel.isEnabled() ||
!myConfiguration.getState().getStartFoldingPattern().equals(mySettingsPanel.getStartPattern()) ||
!myConfiguration.getState().getEndFoldingPattern().equals(mySettingsPanel.getEndPattern()) ||
!myConfiguration.getState().getDefaultCollapsedStatePattern().equals(mySettingsPanel.getCollapsedStatePattern());
}
@Override
public void apply() throws ConfigurationException {
myConfiguration.getState().setStartFoldingPattern(mySettingsPanel.getStartPattern());
myConfiguration.getState().setEndFoldingPattern(mySettingsPanel.getEndPattern());
myConfiguration.getState().setEnabled(mySettingsPanel.isEnabled());
myConfiguration.getState().setDefaultCollapsedStatePattern(mySettingsPanel.getCollapsedStatePattern());
}
@Override
public void reset() {
mySettingsPanel.setStartPattern(myConfiguration.getState().getStartFoldingPattern());
mySettingsPanel.setEndPattern(myConfiguration.getState().getEndFoldingPattern());
mySettingsPanel.setEnabled(myConfiguration.getState().isEnabled());
mySettingsPanel.setCollapsedStatePattern(myConfiguration.getState().getDefaultCollapsedStatePattern());
}
@Override
public void disposeUIResources() {
}
}
@@ -0,0 +1,72 @@
package com.intellij.lang.customFolding;
import com.intellij.openapi.components.*;
import com.intellij.openapi.project.Project;
/**
* @author Rustam Vishnyakov
*/
@State(
name = "CustomFolding",
storages = {
@Storage( file = "$PROJECT_FILE$"),
@Storage( file = "$PROJECT_CONFIG_DIR$/customFolding.xml", scheme = StorageScheme.DIRECTORY_BASED)
})
public class CustomFoldingConfiguration implements PersistentStateComponent<CustomFoldingConfiguration.State> {
private State myState = new State();
public static CustomFoldingConfiguration getInstance(Project project) {
return ServiceManager.getService(project, CustomFoldingConfiguration.class);
}
@Override
public State getState() {
return myState;
}
@Override
public void loadState(State state) {
myState = state;
}
public static class State {
private String startFoldingPattern = "";
private String endFoldingPattern = "";
private String defaultCollapsedStatePattern = "";
private boolean isEnabled = false;
public String getDefaultCollapsedStatePattern() {
return defaultCollapsedStatePattern;
}
public void setDefaultCollapsedStatePattern(String defaultCollapsedStatePattern) {
this.defaultCollapsedStatePattern = defaultCollapsedStatePattern == null ? "" : defaultCollapsedStatePattern.trim();
}
public String getStartFoldingPattern() {
return startFoldingPattern;
}
public void setStartFoldingPattern(String startFoldingPattern) {
this.startFoldingPattern = startFoldingPattern == null ? "" : startFoldingPattern.trim();
}
public String getEndFoldingPattern() {
return endFoldingPattern;
}
public void setEndFoldingPattern(String endFoldingPattern) {
this.endFoldingPattern = endFoldingPattern == null ? "" : endFoldingPattern.trim();
}
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
}
}
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.lang.customFolding.CustomFoldingSettingsPanel">
<grid id="27dc6" binding="mySettingsPanel" layout-manager="GridLayoutManager" row-count="6" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="58b9a" class="javax.swing.JTextField" binding="myFoldingStartField">
<constraints>
<grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="1829f" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Folding start pattern:"/>
</properties>
</component>
<component id="aadcd" class="javax.swing.JLabel">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Folding end pattern:"/>
</properties>
</component>
<component id="6fcc1" class="javax.swing.JTextField" binding="myFoldingEndField">
<constraints>
<grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="70f30" class="com.intellij.ui.components.JBCheckBox" binding="myEnabledBox">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="9c33b" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Enable custom code folding"/>
</properties>
</component>
<component id="ecb20" class="javax.swing.JTextField" binding="myCollapsedStateField">
<constraints>
<grid row="3" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="530c4" class="javax.swing.JLabel">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Default collapsed state pattern:"/>
</properties>
</component>
<vspacer id="8d1b3">
<constraints>
<grid row="5" column="2" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<grid id="1f466" binding="myPredefinedPatternsPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="4" column="1" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none" title="Predefined patterns:">
<font/>
</border>
<children>
<component id="33207" class="javax.swing.JRadioButton" binding="myVisualStudioRadioButton" default-binding="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Visual Studio"/>
</properties>
</component>
<component id="bffa3" class="javax.swing.JRadioButton" binding="myNetBeansRadioButton" default-binding="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="NetBeans"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
</form>
@@ -0,0 +1,100 @@
package com.intellij.lang.customFolding;
import com.intellij.ui.components.JBCheckBox;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author Rustam Vishnyakov
*/
public class CustomFoldingSettingsPanel {
private JPanel mySettingsPanel;
private JTextField myFoldingStartField;
private JTextField myFoldingEndField;
private JBCheckBox myEnabledBox;
private JTextField myCollapsedStateField;
private JRadioButton myVisualStudioRadioButton;
private JRadioButton myNetBeansRadioButton;
private JPanel myPredefinedPatternsPanel;
public CustomFoldingSettingsPanel() {
myEnabledBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean isEnabled = myEnabledBox.isSelected();
setFieldsEnabled(isEnabled);
}
});
ButtonGroup predefinedSettingsGroup = new ButtonGroup();
predefinedSettingsGroup.add(myNetBeansRadioButton);
predefinedSettingsGroup.add(myVisualStudioRadioButton);
myVisualStudioRadioButton.setSelected(false);
myNetBeansRadioButton.setSelected(false);
myNetBeansRadioButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myFoldingStartField.setText(".*<editor-fold .*desc=\"(.*)\".*$");
myFoldingEndField.setText(".*</editor-fold>");
myCollapsedStateField.setText("defaultstate=\"collapsed\"");
}
});
myVisualStudioRadioButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myFoldingStartField.setText(".*region (.*)$");
myFoldingEndField.setText(".*endregion");
myCollapsedStateField.setText("");
}
});
}
public JComponent getComponent() {
return mySettingsPanel;
}
public String getStartPattern() {
return myFoldingStartField.getText();
}
public String getEndPattern() {
return myFoldingEndField.getText();
}
public void setStartPattern(String startPattern) {
myFoldingStartField.setText(startPattern);
}
public void setEndPattern(String endPattern) {
myFoldingEndField.setText(endPattern);
}
public void setEnabled(boolean enabled) {
myEnabledBox.setSelected(enabled);
setFieldsEnabled(enabled);
}
public boolean isEnabled() {
return myEnabledBox.isSelected();
}
public void setCollapsedStatePattern(String pattern) {
myCollapsedStateField.setText(pattern);
}
public String getCollapsedStatePattern() {
return myCollapsedStateField.getText();
}
private void setFieldsEnabled(boolean enabled) {
myFoldingStartField.setEnabled(enabled);
myFoldingEndField.setEnabled(enabled);
myCollapsedStateField.setEnabled(enabled);
myPredefinedPatternsPanel.setEnabled(enabled);
myNetBeansRadioButton.setEnabled(enabled);
myVisualStudioRadioButton.setEnabled(enabled);
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.lang.customFolding;
import com.intellij.lang.folding.CustomFoldingProvider;
/**
* Custom folding provider for <a href="http://ui.netbeans.org/docs/ui/code_folding/cf_uispec.html#menus">NetBeans folding conventions.</a>
* @author Rustam Vishnyakov
*/
public class NetBeansCustomFoldingProvider extends CustomFoldingProvider {
@Override
public boolean isCustomRegionStart(String elementText) {
return elementText.contains("<editor-fold");
}
@Override
public boolean isCustomRegionEnd(String elementText) {
return elementText.contains("</editor-fold");
}
@Override
public String getPlaceholderText(String elementText) {
String customText = elementText.replaceFirst(".*desc\\s*=\\s*\"(.*)\".*", "$1").trim();
return customText.isEmpty() ? "..." : customText;
}
@Override
public boolean isCollapsedByDefault(String text) {
return text.matches(".*defaultstate\\s*=\\s*\"collapsed\".*");
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.lang.customFolding;
import com.intellij.lang.folding.CustomFoldingProvider;
/**
* Supports <a href="http://msdn.microsoft.com/en-us/library/9a1ybwek%28v=vs.100%29.aspx">VisualStudio custom foldings.</a>
* @author Rustam Vishnyakov
*/
public class VisualStudioCustomFoldingProvider extends CustomFoldingProvider {
@Override
public boolean isCustomRegionStart(String elementText) {
return elementText.contains("region") && elementText.matches("..?\\s*region.*");
}
@Override
public boolean isCustomRegionEnd(String elementText) {
return elementText.contains("endregion");
}
@Override
public String getPlaceholderText(String elementText) {
return elementText.replaceFirst("..?\\s*region(.*)","$1").trim();
}
}
@@ -109,7 +109,9 @@
<extensionPoint name="lang.braceMatcher"
beanClass="com.intellij.lang.LanguageExtensionPoint"/>
<extensionPoint name="lang.foldingBuilder"
beanClass="com.intellij.lang.LanguageExtensionPoint"/>
beanClass="com.intellij.lang.LanguageExtensionPoint"/>
<extensionPoint name="customFoldingProvider"
interface="com.intellij.lang.folding.CustomFoldingProvider"/>
<extensionPoint name="lang.psiStructureViewFactory"
beanClass="com.intellij.lang.LanguageExtensionPoint"/>
<extensionPoint name="lang.structureViewExtension"
@@ -708,4 +708,7 @@
<projectService serviceInterface="com.intellij.openapi.module.ModulePointerManager"
serviceImplementation="com.intellij.openapi.module.impl.ModulePointerManagerImpl"/>
<customFoldingProvider implementation="com.intellij.lang.customFolding.NetBeansCustomFoldingProvider"/>
<customFoldingProvider implementation="com.intellij.lang.customFolding.VisualStudioCustomFoldingProvider"/>
</extensions>