diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewSettings.java b/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewSettings.java index 64e374097635..1c51974af211 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewSettings.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewSettings.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -20,4 +20,6 @@ package com.intellij.ide.projectView; */ public interface ProjectViewSettings extends ViewSettings { boolean isShowExcludedFiles(); + + default boolean isUseFileNestingRules() {return true;} } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/FileNestingInProjectViewDialog.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/FileNestingInProjectViewDialog.java new file mode 100644 index 000000000000..3a4653c6fd1b --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/FileNestingInProjectViewDialog.java @@ -0,0 +1,202 @@ +/* + * Copyright 2000-2017 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.ide.projectView.impl; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.projectView.impl.ProjectViewFileNestingService.NestingRule; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.ui.DumbAwareActionButton; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.ui.table.TableView; +import com.intellij.util.Consumer; +import com.intellij.util.ui.ColumnInfo; +import com.intellij.util.ui.ElementProducer; +import com.intellij.util.ui.ListTableModel; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +public class FileNestingInProjectViewDialog extends DialogWrapper { + + private final JBCheckBox myUseNestingRulesCheckBox; + private final JPanel myRulesPanel; + private final TableView myTable; + + protected FileNestingInProjectViewDialog(@NotNull final Project project) { + super(project); + setTitle(IdeBundle.message("file.nesting.dialog.title")); + + myUseNestingRulesCheckBox = new JBCheckBox(IdeBundle.message("use.file.nesting.rules.checkbox")); + myUseNestingRulesCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + UIUtil.setEnabled(myRulesPanel, myUseNestingRulesCheckBox.isSelected(), true); + } + }); + + myTable = createTable(); + myRulesPanel = createRulesPanel(myTable); + + init(); + } + + @Override + protected String getHelpId() { + return "project.view.file.nesting.dialog"; + } + + @Override + protected JComponent createCenterPanel() { + final JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(myUseNestingRulesCheckBox, BorderLayout.NORTH); + mainPanel.add(myRulesPanel, BorderLayout.CENTER); + return mainPanel; + } + + private static JPanel createRulesPanel(@NotNull final TableView table) { + final ToolbarDecorator toolbarDecorator = + ToolbarDecorator.createDecorator(table, + new ElementProducer() { + @Override + public boolean canCreateElement() { + return true; + } + + @Override + public NestingRule createElement() { + return new NestingRule(); + } + }) + .disableUpDownActions() + .addExtraAction( + new DumbAwareActionButton(IdeBundle.message("file.nesting.use.default.ruleset"), AllIcons.Actions.Reset_to_default) { + @Override + public void actionPerformed(AnActionEvent e) { + final List rules = new ArrayList<>(); + for (NestingRule rule : ProjectViewFileNestingService.DEFAULT_NESTING_RULES) { + rules.add(new NestingRule(rule.getParentFileSuffix(), rule.getChildFileSuffix())); + } + table.getListTableModel().setItems(rules); + } + }); + return toolbarDecorator.createPanel(); + } + + private static TableView createTable() { + ListTableModel model = new ListTableModel<>( + new ColumnInfo("Parent file suffix") { + @Override + public boolean isCellEditable(NestingRule rule) { + return true; + } + + @Override + public String valueOf(NestingRule rule) { + return rule.getParentFileSuffix(); + } + + @Override + public void setValue(NestingRule rule, String value) { + rule.setParentFileSuffix(value.trim()); + } + }, + new ColumnInfo("Child file suffix") { + @Override + public boolean isCellEditable(NestingRule rule) { + return true; + } + + @Override + public String valueOf(NestingRule rule) { + return rule.getChildFileSuffix(); + } + + @Override + public void setValue(NestingRule rule, String value) { + rule.setChildFileSuffix(value); + } + } + ); + + return new TableView<>(model); + } + + + @Nullable + @Override + protected ValidationInfo doValidate() { + if (!myUseNestingRulesCheckBox.isSelected()) return null; + + List items = myTable.getListTableModel().getItems(); + for (int i = 0; i < items.size(); i++) { + final NestingRule rule = items.get(i); + final int row = i + 1; + if (rule.getParentFileSuffix().isEmpty()) { + return new ValidationInfo("Parent file suffix must not be empty (see row " + row + ")", null); + } + if (rule.getChildFileSuffix().isEmpty()) { + return new ValidationInfo("Child file suffix must not be empty (see row " + row + ")", null); + } + if (rule.getChildFileSuffix().equals(rule.getParentFileSuffix())) { + return new ValidationInfo( + "Parent and child file suffixes must not be equal ('" + rule.getParentFileSuffix() + "', see row " + row + ")", null); + } + } + + return null; + } + + @Override + protected void doOKAction() { + myTable.stopEditing(); + super.doOKAction(); + } + + public void reset(boolean useFileNestingRules) { + myUseNestingRulesCheckBox.setSelected(useFileNestingRules); + UIUtil.setEnabled(myRulesPanel, myUseNestingRulesCheckBox.isSelected(), true); + + final List rules = new ArrayList<>(); + for (NestingRule rule : ProjectViewFileNestingService.getInstance().getRules()) { + rules.add(new NestingRule(rule.getParentFileSuffix(), rule.getChildFileSuffix())); + } + myTable.getListTableModel().setItems(rules); + } + + public void apply(@NotNull final Consumer useNestingRulesOptionConsumer) { + useNestingRulesOptionConsumer.consume(myUseNestingRulesCheckBox.isSelected()); + + if (myUseNestingRulesCheckBox.isSelected()) { + final List result = new ArrayList<>(); + for (NestingRule rule : myTable.getListTableModel().getItems()) { + result.add(new NestingRule(rule.getParentFileSuffix(), rule.getChildFileSuffix())); + } + ProjectViewFileNestingService.getInstance().setRules(result); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/NestingTreeStructureProvider.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/NestingTreeStructureProvider.java index b8f92afd2be1..b686f7c03633 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/NestingTreeStructureProvider.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/NestingTreeStructureProvider.java @@ -15,15 +15,13 @@ */ package com.intellij.ide.projectView.impl; -import com.intellij.ide.projectView.ProjectViewNestingRulesProvider; -import com.intellij.ide.projectView.TreeStructureProvider; -import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.projectView.*; +import com.intellij.ide.projectView.impl.ProjectViewFileNestingService.NestingRule; import com.intellij.ide.projectView.impl.nodes.NestingTreeNode; import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; import com.intellij.ide.projectView.impl.nodes.PsiFileNode; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; @@ -48,50 +46,49 @@ import java.util.*; * {@code com.intellij.projectViewNestingRulesProvider} extensions. * * @see ProjectViewNestingRulesProvider + * @see ProjectViewFileNestingService + * @see FileNestingInProjectViewDialog */ public class NestingTreeStructureProvider implements TreeStructureProvider, DumbAware { - - private static ExtensionPointName EP_NAME = - ExtensionPointName.create("com.intellij.projectViewNestingRulesProvider"); - private static final Logger LOG = Logger.getInstance(NestingTreeStructureProvider.class); + + private int myBaseListHashCode = 0; private Set myNestingRules; @NotNull private Collection getNestingRules() { - if (myNestingRules == null) { + final List baseRules = ProjectViewFileNestingService.getInstance().getRules(); + final int baseRulesHashCode = baseRules.hashCode(); + + if (myNestingRules == null || myBaseListHashCode != baseRulesHashCode) { myNestingRules = new THashSet<>(); + myBaseListHashCode = baseRulesHashCode; final MultiMap childToParentSuffix = new MultiMap<>(); final MultiMap parentToChildSuffix = new MultiMap<>(); - final ProjectViewNestingRulesProvider.Consumer consumer = new ProjectViewNestingRulesProvider.Consumer() { - @Override - public void addNestingRule(@NotNull final String parentFileSuffix, @NotNull final String childFileSuffix) { - LOG.assertTrue(!parentFileSuffix.isEmpty() && !childFileSuffix.isEmpty(), "file suffix must not be empty"); - LOG.assertTrue(!parentFileSuffix.equals(childFileSuffix), "parent and child suffixes must be different: " + parentFileSuffix); + for (NestingRule rule : baseRules) { + final String parentFileSuffix = rule.getParentFileSuffix(); + final String childFileSuffix = rule.getChildFileSuffix(); + if (parentFileSuffix.isEmpty() || childFileSuffix.isEmpty()) continue; // shouldn't happen, checked on component loading and in UI + if (parentFileSuffix.equals(childFileSuffix)) continue; // shouldn't happen, checked on component loading and in UI - myNestingRules.add(new NestingRule(parentFileSuffix, childFileSuffix)); - childToParentSuffix.putValue(childFileSuffix, parentFileSuffix); - parentToChildSuffix.putValue(parentFileSuffix, childFileSuffix); + myNestingRules.add(rule); + childToParentSuffix.putValue(childFileSuffix, parentFileSuffix); + parentToChildSuffix.putValue(parentFileSuffix, childFileSuffix); - // for all cases like A -> B -> C we also add a rule A -> C - for (String s : parentToChildSuffix.get(childFileSuffix)) { - myNestingRules.add(new NestingRule(parentFileSuffix, s)); - parentToChildSuffix.putValue(parentFileSuffix, s); - childToParentSuffix.putValue(s, parentFileSuffix); - } - - for (String s : childToParentSuffix.get(parentFileSuffix)) { - myNestingRules.add(new NestingRule(s, childFileSuffix)); - parentToChildSuffix.putValue(s, childFileSuffix); - childToParentSuffix.putValue(childFileSuffix, s); - } + // for all cases like A -> B -> C we also add a rule A -> C + for (String s : parentToChildSuffix.get(childFileSuffix)) { + myNestingRules.add(new NestingRule(parentFileSuffix, s)); + parentToChildSuffix.putValue(parentFileSuffix, s); + childToParentSuffix.putValue(s, parentFileSuffix); } - }; - for (ProjectViewNestingRulesProvider provider : EP_NAME.getExtensions()) { - provider.addFileNestingRules(consumer); + for (String s : childToParentSuffix.get(parentFileSuffix)) { + myNestingRules.add(new NestingRule(s, childFileSuffix)); + parentToChildSuffix.putValue(s, childFileSuffix); + childToParentSuffix.putValue(childFileSuffix, s); + } } } @@ -113,6 +110,7 @@ public class NestingTreeStructureProvider implements TreeStructureProvider, Dumb public Collection modify(@NotNull final AbstractTreeNode parent, @NotNull final Collection children, final ViewSettings settings) { + if (!(settings instanceof ProjectViewSettings) || !((ProjectViewSettings)settings).isUseFileNestingRules()) return children; if (!(parent instanceof PsiDirectoryNode)) return children; final Collection rules = getNestingRules(); @@ -153,6 +151,11 @@ public class NestingTreeStructureProvider implements TreeStructureProvider, Dumb @NotNull final VirtualFile parentFile) { LOG.assertTrue(!parentFile.isDirectory()); + final AbstractProjectViewPane pane = ProjectView.getInstance(project).getProjectViewPaneById(ProjectViewPane.ID); + if (pane instanceof ProjectViewPane) { + if (!((ProjectViewPane)pane).isUseFileNestingRules()) return Collections.emptyList(); + } + final VirtualFile dir = parentFile.getParent(); if (dir == null) return Collections.emptyList(); @@ -181,8 +184,8 @@ public class NestingTreeStructureProvider implements TreeStructureProvider, Dumb final boolean matchesParent = c.first; if (matchesParent) { - final String baseName = childName.substring(0, childName.length() - rule.myParentFileSuffix.length()); - if (parentFile.getName().equals(baseName + rule.myChildFileSuffix)) { + final String baseName = childName.substring(0, childName.length() - rule.getParentFileSuffix().length()); + if (parentFile.getName().equals(baseName + rule.getChildFileSuffix())) { return Collections.emptyList(); // parentFile itself appears to be a child of childFile } } @@ -195,8 +198,8 @@ public class NestingTreeStructureProvider implements TreeStructureProvider, Dumb final boolean matchesChild = c.second; if (matchesChild) { - final String baseName = childName.substring(0, childName.length() - rule.myChildFileSuffix.length()); - if (parentFile.getName().equals(baseName + rule.myParentFileSuffix)) { + final String baseName = childName.substring(0, childName.length() - rule.getChildFileSuffix().length()); + if (parentFile.getName().equals(baseName + rule.getParentFileSuffix())) { result.add(new ChildFileInfo(child, baseName)); } } @@ -274,14 +277,14 @@ public class NestingTreeStructureProvider implements TreeStructureProvider, Dumb } if (matchesParent) { - final String baseName = fileName.substring(0, fileName.length() - rule.myParentFileSuffix.length()); + final String baseName = fileName.substring(0, fileName.length() - rule.getParentFileSuffix().length()); final Edge edge = getOrCreateEdge(baseNameAndRuleToEdge, baseName, rule); edge.from = (PsiFileNode)node; updateInfoIfEdgeComplete(parentToChildren, allChildNodes, edge); } if (matchesChild) { - final String baseName = fileName.substring(0, fileName.length() - rule.myChildFileSuffix.length()); + final String baseName = fileName.substring(0, fileName.length() - rule.getChildFileSuffix().length()); final Edge edge = getOrCreateEdge(baseNameAndRuleToEdge, baseName, rule); edge.to = (PsiFileNode)node; updateInfoIfEdgeComplete(parentToChildren, allChildNodes, edge); @@ -293,11 +296,11 @@ public class NestingTreeStructureProvider implements TreeStructureProvider, Dumb } private static Couple checkMatchingAsParentOrChild(@NotNull final NestingRule rule, @NotNull final String fileName) { - boolean matchesParent = !fileName.equals(rule.myParentFileSuffix) && fileName.endsWith(rule.myParentFileSuffix); - boolean matchesChild = !fileName.equals(rule.myChildFileSuffix) && fileName.endsWith(rule.myChildFileSuffix); + boolean matchesParent = !fileName.equals(rule.getParentFileSuffix()) && fileName.endsWith(rule.getParentFileSuffix()); + boolean matchesChild = !fileName.equals(rule.getChildFileSuffix()) && fileName.endsWith(rule.getChildFileSuffix()); if (matchesParent && matchesChild) { - if (rule.myParentFileSuffix.length() > rule.myChildFileSuffix.length()) { + if (rule.getParentFileSuffix().length() > rule.getChildFileSuffix().length()) { matchesChild = false; } else { @@ -334,32 +337,6 @@ public class NestingTreeStructureProvider implements TreeStructureProvider, Dumb } } - private static class NestingRule { - @NotNull private final String myParentFileSuffix; - @NotNull private final String myChildFileSuffix; - - public NestingRule(@NotNull String parentFileSuffix, @NotNull String childFileSuffix) { - myParentFileSuffix = parentFileSuffix; - myChildFileSuffix = childFileSuffix; - } - - @Override - public String toString() { - return myParentFileSuffix + "->" + myChildFileSuffix; - } - - @Override - public boolean equals(Object o) { - return o instanceof NestingRule && - myParentFileSuffix.equals(((NestingRule)o).myParentFileSuffix) && - myChildFileSuffix.equals(((NestingRule)o).myChildFileSuffix); - } - - @Override - public int hashCode() { - return myParentFileSuffix.hashCode() + 239 * myChildFileSuffix.hashCode(); - } - } private static class Edge { @Nullable private T from; diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewFileNestingService.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewFileNestingService.java new file mode 100644 index 000000000000..d4c7c44689d8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewFileNestingService.java @@ -0,0 +1,163 @@ +/* + * Copyright 2000-2017 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.ide.projectView.impl; + +import com.intellij.ide.projectView.ProjectViewNestingRulesProvider; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.util.containers.SortedList; +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.util.xmlb.annotations.Tag; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * Holds data used by {@link NestingTreeStructureProvider} and {@link FileNestingInProjectViewDialog}. + */ +@State( + name = "ProjectViewFileNesting", + storages = @Storage("ui.lnf.xml") +) +public class ProjectViewFileNestingService implements PersistentStateComponent { + private static final Logger LOG = Logger.getInstance(ProjectViewFileNestingService.class); + + private static final ExtensionPointName EP_NAME = + ExtensionPointName.create("com.intellij.projectViewNestingRulesProvider"); + + public static final NestingRule[] DEFAULT_NESTING_RULES = loadDefaultNestingRules(); + + private MyState myState = new MyState(); + + @NotNull + public static ProjectViewFileNestingService getInstance() { + return ServiceManager.getService(ProjectViewFileNestingService.class); + } + + @NotNull + private static NestingRule[] loadDefaultNestingRules() { + final List result = new SortedList<>(Comparator.comparing(o -> o.getParentFileSuffix())); + + final ProjectViewNestingRulesProvider.Consumer consumer = new ProjectViewNestingRulesProvider.Consumer() { + @Override + public void addNestingRule(@NotNull final String parentFileSuffix, @NotNull final String childFileSuffix) { + LOG.assertTrue(!parentFileSuffix.isEmpty() && !childFileSuffix.isEmpty(), "file suffix must not be empty"); + LOG.assertTrue(!parentFileSuffix.equals(childFileSuffix), "parent and child suffixes must be different: " + parentFileSuffix); + result.add(new NestingRule(parentFileSuffix, childFileSuffix)); + } + }; + + for (ProjectViewNestingRulesProvider provider : EP_NAME.getExtensions()) { + provider.addFileNestingRules(consumer); + } + + return result.toArray(new NestingRule[0]); + } + + @Override + public MyState getState() { + return myState; + } + + @Override + public void loadState(@NotNull final MyState state) { + myState = state; + } + + /** + * This list of rules is used for serialization and for UI. + * See also {@link NestingTreeStructureProvider}, it adjusts this list of rules to match its needs + */ + @NotNull + public List getRules() { + return myState.myRules; + } + + public void setRules(@NotNull final List rules) { + myState.myRules.clear(); + myState.myRules.addAll(rules); + } + + public static class MyState { + @Tag("nesting-rules") + @AbstractCollection(surroundWithTag = false) + public List myRules = new SortedList<>(Comparator.comparing(o -> o.getParentFileSuffix())); + + public MyState() { + myRules.addAll(Arrays.asList(DEFAULT_NESTING_RULES)); + } + } + + public static class NestingRule { + @NotNull private String myParentFileSuffix; + + @NotNull private String myChildFileSuffix; + + @SuppressWarnings("unused") // used by serializer + public NestingRule() { + this("", ""); + } + + public NestingRule(@NotNull String parentFileSuffix, @NotNull String childFileSuffix) { + myParentFileSuffix = parentFileSuffix; + myChildFileSuffix = childFileSuffix; + } + + @NotNull + @Attribute("parent-file-suffix") + public String getParentFileSuffix() { + return myParentFileSuffix; + } + + public void setParentFileSuffix(@NotNull final String parentFileSuffix) { + myParentFileSuffix = parentFileSuffix; + } + + @NotNull + @Attribute("child-file-suffix") + public String getChildFileSuffix() { + return myChildFileSuffix; + } + + public void setChildFileSuffix(@NotNull final String childFileSuffix) { + myChildFileSuffix = childFileSuffix; + } + + @Override + public String toString() { + return myParentFileSuffix + "->" + myChildFileSuffix; + } + + @Override + public boolean equals(Object o) { + return o instanceof NestingRule && + myParentFileSuffix.equals(((NestingRule)o).myParentFileSuffix) && + myChildFileSuffix.equals(((NestingRule)o).myChildFileSuffix); + } + + @Override + public int hashCode() { + return myParentFileSuffix.hashCode() + 239 * myChildFileSuffix.hashCode(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java index f467093b3973..ffaea0323a8e 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -33,6 +33,7 @@ import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.InvalidDataException; @@ -51,7 +52,10 @@ import java.awt.*; public class ProjectViewPane extends AbstractProjectViewPSIPane { @NonNls public static final String ID = "ProjectPane"; public static final String SHOW_EXCLUDED_FILES_OPTION = "show-excluded-files"; + private static final String USE_FILE_NESTING_RULES = "use-file-nesting-rules"; + private boolean myShowExcludedFiles = true; + private boolean myUseFileNestingRules = true; public ProjectViewPane(Project project) { super(project); @@ -116,6 +120,8 @@ public class ProjectViewPane extends AbstractProjectViewPSIPane { super.readExternal(element); String showExcludedOption = JDOMExternalizerUtil.readField(element, SHOW_EXCLUDED_FILES_OPTION); myShowExcludedFiles = showExcludedOption == null || Boolean.parseBoolean(showExcludedOption); + String useFileNestingRules = JDOMExternalizerUtil.readField(element, USE_FILE_NESTING_RULES); + myUseFileNestingRules = useFileNestingRules == null || Boolean.parseBoolean(useFileNestingRules); } @Override @@ -124,11 +130,19 @@ public class ProjectViewPane extends AbstractProjectViewPSIPane { if (!myShowExcludedFiles) { JDOMExternalizerUtil.writeField(element, SHOW_EXCLUDED_FILES_OPTION, String.valueOf(false)); } + if (!myUseFileNestingRules) { + JDOMExternalizerUtil.writeField(element, USE_FILE_NESTING_RULES, String.valueOf(false)); + } } @Override public void addToolbarActions(DefaultActionGroup actionGroup) { actionGroup.addAction(new ShowExcludedFilesAction()).setAsSecondary(true); + actionGroup.addAction(new ConfigureFilesNestingAction()).setAsSecondary(true); + } + + public boolean isUseFileNestingRules() { + return myUseFileNestingRules; } // should be first @@ -186,6 +200,11 @@ public class ProjectViewPane extends AbstractProjectViewPSIPane { return myShowExcludedFiles; } + @Override + public boolean isUseFileNestingRules() { + return myUseFileNestingRules; + } + @Override public boolean isToBuildChildrenInBackground(Object element) { return Registry.is("ide.projectView.ProjectViewPaneTreeStructure.BuildChildrenInBackground"); @@ -218,4 +237,27 @@ public class ProjectViewPane extends AbstractProjectViewPSIPane { presentation.setEnabledAndVisible(projectView.getCurrentProjectViewPane() == ProjectViewPane.this); } } + + private class ConfigureFilesNestingAction extends DumbAwareAction { + private ConfigureFilesNestingAction() { + super(IdeBundle.message("action.file.nesting.in.project.view")); + } + + @Override + public void update(@NotNull final AnActionEvent e) { + final ProjectView projectView = ProjectView.getInstance(myProject); + e.getPresentation().setEnabledAndVisible(projectView.getCurrentProjectViewPane() == ProjectViewPane.this); + } + + @Override + public void actionPerformed(@NotNull final AnActionEvent e) { + final FileNestingInProjectViewDialog dialog = new FileNestingInProjectViewDialog(myProject); + dialog.reset(myUseFileNestingRules); + dialog.show(); + if (dialog.isOK()) { + dialog.apply(useFileNestingRules -> myUseFileNestingRules = useFileNestingRules); + updateFromRoot(true); + } + } + } } diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties index 12053eb06097..1eaf09533c47 100644 --- a/platform/platform-resources-en/src/messages/IdeBundle.properties +++ b/platform/platform-resources-en/src/messages/IdeBundle.properties @@ -583,6 +583,10 @@ action.show.libraries.contents=Show Libraries Contents action.show.hide.library.contents=Show/Hide Library Contents action.show.excluded.files=Show Excluded Files action.show.hide.excluded.files=Show/Hide Excluded Files +action.file.nesting.in.project.view=File Nesting... +file.nesting.dialog.title=File Nesting +use.file.nesting.rules.checkbox=&Show files with the same names as nested according to the rules below +file.nesting.use.default.ruleset=Use the default ruleset action.manual.order=Manual Order action.sort.by.type=Sort by Type action.show.structure=Show Structure diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index daebc409a153..02d78afdf412 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -1028,6 +1028,8 @@ order="last"/> +