From 14bdd1b060e8684c0ebe7c4e0f705bfd0277f4db Mon Sep 17 00:00:00 2001 From: "Rustam.Vishnyakov" Date: Mon, 30 Jan 2012 19:35:49 +0400 Subject: [PATCH] Custom folding classes moved to platform, other refactoring --- .../lang/folding/CustomFoldingBuilder.java | 191 ++++++++++++++++++ .../lang/folding/CustomFoldingProvider.java | 42 ++++ .../CustomFoldingConfigurable.java | 83 ++++++++ .../CustomFoldingConfiguration.java | 72 +++++++ .../CustomFoldingSettingsPanel.form | 108 ++++++++++ .../CustomFoldingSettingsPanel.java | 100 +++++++++ .../NetBeansCustomFoldingProvider.java | 45 +++++ .../VisualStudioCustomFoldingProvider.java | 40 ++++ .../src/META-INF/LangExtensionPoints.xml | 4 +- .../src/META-INF/LangExtensions.xml | 3 + 10 files changed, 687 insertions(+), 1 deletion(-) create mode 100644 platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java create mode 100644 platform/lang-api/src/com/intellij/lang/folding/CustomFoldingProvider.java create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfigurable.java create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfiguration.java create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.form create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.java create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/NetBeansCustomFoldingProvider.java create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/VisualStudioCustomFoldingProvider.java diff --git a/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java b/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java new file mode 100644 index 000000000000..e6803f563670 --- /dev/null +++ b/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java @@ -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 descriptors = new ArrayList(); + 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 descriptors, + @NotNull PsiElement root, + @NotNull Document document, + boolean quick); + + private void addCustomFoldingRegionsRecursively(@NotNull ASTNode node, List descriptors) { + Stack customFoldingNodesStack = new Stack(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: 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; + } +} diff --git a/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingProvider.java b/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingProvider.java new file mode 100644 index 000000000000..4086abd7de9a --- /dev/null +++ b/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingProvider.java @@ -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 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; + } +} diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfigurable.java b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfigurable.java new file mode 100644 index 000000000000..b35737cc6153 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfigurable.java @@ -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 Move to resources + } + + @Override + public Icon getIcon() { + return null; + } + + @Override + public String getHelpTopic() { + return null; //TODO: 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() { + } +} diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfiguration.java b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfiguration.java new file mode 100644 index 000000000000..78a88b64846c --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingConfiguration.java @@ -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 { + + 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; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.form b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.form new file mode 100644 index 000000000000..cc0911d9cfb9 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.form @@ -0,0 +1,108 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.java b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.java new file mode 100644 index 000000000000..17bd1aa1db3f --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/CustomFoldingSettingsPanel.java @@ -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(".*"); + 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); + } +} diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/NetBeansCustomFoldingProvider.java b/platform/lang-impl/src/com/intellij/lang/customFolding/NetBeansCustomFoldingProvider.java new file mode 100644 index 000000000000..feef6f0c8d07 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/NetBeansCustomFoldingProvider.java @@ -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 NetBeans folding conventions. + * @author Rustam Vishnyakov + */ +public class NetBeansCustomFoldingProvider extends CustomFoldingProvider { + @Override + public boolean isCustomRegionStart(String elementText) { + return elementText.contains("VisualStudio custom foldings. + * @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(); + } + +} diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 044b1b5cac10..6d199ff55fb9 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -109,7 +109,9 @@ + beanClass="com.intellij.lang.LanguageExtensionPoint"/> + + + + -- 2.23.3