diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java index efc85b5805b5..fbd85a3b62a5 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java @@ -29,6 +29,7 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.file.PsiDirectoryFactory; +import com.intellij.psi.search.SearchScope; import com.intellij.testFramework.PsiTestCase; import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.containers.ContainerUtil; @@ -325,6 +326,18 @@ class MockReformatFileSettings implements LayoutCodeOptions { private boolean myProcessOnlyChangedText; private boolean myIsOK = true; + @Nullable + @Override + public SearchScope getSearchScope() { + return null; + } + + @Nullable + @Override + public String getFileTypeMask() { + return null; + } + @Override public boolean isProcessWholeFile() { return myProcessWholeFile; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java index b524697e42c1..1a682e77ca39 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java @@ -71,6 +71,7 @@ public abstract class AbstractLayoutCodeProcessor { private final boolean myProcessChangedTextOnly; protected AbstractLayoutCodeProcessor myPreviousCodeProcessor; + private List myFilters = ContainerUtil.newArrayList(); protected AbstractLayoutCodeProcessor(Project project, String commandName, String progressText, boolean processChangedTextOnly) { this(project, (Module)null, commandName, progressText, processChangedTextOnly); @@ -173,6 +174,10 @@ public abstract class AbstractLayoutCodeProcessor { : null; } + public void addFileFilter(@NotNull FileFilter filter) { + myFilters.add(filter); + } + /** * Ensures that given file is ready to reformatting and prepares it if necessary. * @@ -492,7 +497,7 @@ public abstract class AbstractLayoutCodeProcessor { if (myFileTreeIterator.hasNext()) { PsiFile file = myFileTreeIterator.next(); myFilesProcessed++; - if (file.isWritable() && canBeFormatted(file)) { + if (file.isWritable() && canBeFormatted(file) && acceptedByFilters(file)) { performFileProcessing(file); } } @@ -533,4 +538,19 @@ public abstract class AbstractLayoutCodeProcessor { myCompositeTask = compositeTask; } } + + private boolean acceptedByFilters(@NotNull PsiFile file) { + VirtualFile vFile = file.getVirtualFile(); + if (vFile == null) { + return false; + } + + for (FileFilter filter : myFilters) { + if (!filter.accept(file.getVirtualFile())) { + return false; + } + } + + return true; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/FileFilter.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/FileFilter.java new file mode 100644 index 000000000000..2d5325a2a2ea --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/FileFilter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +public interface FileFilter { + boolean accept(@NotNull VirtualFile file); + + @NotNull FileFilter ACCEPT_ALL = new FileFilter() { + @Override + public boolean accept(@NotNull VirtualFile file) { + return true; + } + }; +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java index cbf36e1f4b33..716d2f884844 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java @@ -29,6 +29,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.arrangement.Rearranger; +import com.intellij.psi.search.SearchScope; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -326,6 +327,18 @@ public class LayoutCodeDialog extends DialogWrapper implements LayoutCodeOptions return myCbArrangeEntries.isSelected(); } + @Nullable + @Override + public String getFileTypeMask() { + return null; + } + + @Nullable + @Override + public SearchScope getSearchScope() { + return null; + } + @Override public boolean isProcessOnlyChangedText() { return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutDirectoryDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutDirectoryDialog.java index e8be41b8eb05..1d6c23d1a3a6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutDirectoryDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutDirectoryDialog.java @@ -15,48 +15,34 @@ */ package com.intellij.codeInsight.actions; -import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; -import javax.swing.*; -import java.awt.*; - public class LayoutDirectoryDialog extends LayoutProjectCodeDialog implements DirectoryFormattingOptions { - private JCheckBox myIncludeSubdirs; - public LayoutDirectoryDialog(@NotNull Project project, String title, String text, boolean enableOnlyVCSChangedTextCb) { - super(project, title, text, true, enableOnlyVCSChangedTextCb); - } - - public void setEnabledIncludeSubdirsCb(boolean isEnabled) { - myIncludeSubdirs.setEnabled(isEnabled); - } - - public void setSelectedIncludeSubdirsCb(boolean isSelected) { - myIncludeSubdirs.setSelected(isSelected); + super(project, title, text, enableOnlyVCSChangedTextCb); } @Override - protected JComponent createCenterPanel() { - JPanel panel = new JPanel(new BorderLayout(0, 7)); - JComponent layoutProjectDialog = super.createCenterPanel(); + protected boolean shouldShowIncludeSubdirsCb() { + return true; + } - myIncludeSubdirs = new JCheckBox(CodeInsightBundle.message("reformat.option.include.subdirectories")); + public void setEnabledIncludeSubdirsCb(boolean isEnabled) { + myIncludeSubdirsCb.setEnabled(isEnabled); + } - panel.add(layoutProjectDialog, BorderLayout.CENTER); - panel.add(myIncludeSubdirs, BorderLayout.SOUTH); - - return panel; + public void setSelectedIncludeSubdirsCb(boolean isSelected) { + myIncludeSubdirsCb.setSelected(isSelected); } @Override public boolean isIncludeSubdirectories() { - return myIncludeSubdirs.isSelected(); + return myIncludeSubdirsCb.isSelected(); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.form b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.form new file mode 100644 index 000000000000..19f7afed74b1 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.form @@ -0,0 +1,195 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java index aa4aadc9a083..7f76f1b012a5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java @@ -17,15 +17,26 @@ package com.intellij.codeInsight.actions; import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.find.FindSettings; +import com.intellij.find.impl.FindDialog; +import com.intellij.find.impl.FindInProjectUtil; +import com.intellij.icons.AllIcons; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.ide.util.scopeChooser.ScopeChooserCombo; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Disposer; +import com.intellij.psi.search.SearchScope; +import com.intellij.ui.IdeBorderFactory; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.*; +import java.awt.event.*; +import java.util.regex.PatternSyntaxException; /** * @author max @@ -33,25 +44,35 @@ import java.awt.*; public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFilesOptions { private static @NonNls final String HELP_ID = "editing.codeReformatting"; + private final Project myProject; private final String myText; private final boolean myEnableOnlyVCSChangedTextCb; - private final boolean mySuggestOptimizeImports; - private final Project myProject; - + private JLabel myTitle; + protected JCheckBox myIncludeSubdirsCb; + + private JCheckBox myUseScopeFilteringCb; + private ScopeChooserCombo myScopeCombo; + + private JCheckBox myEnableFileNameFilterCb; + private ComboBox myFileFilter; + private JCheckBox myCbOptimizeImports; - private JCheckBox myCbOnlyVcsChangedRegions; private JCheckBox myCbRearrangeEntries; + private JCheckBox myCbOnlyVcsChangedRegions; + + private JPanel myWholePanel; + private JPanel myOptionsPanel; + private JPanel myFiltersPanel; + private JLabel myMaskWarningLabel; public LayoutProjectCodeDialog(@NotNull Project project, - String title, - String text, - boolean suggestOptimizeImports, + @NotNull String title, + @NotNull String text, boolean enableOnlyVCSChangedTextCb) { super(project, false); myText = text; - mySuggestOptimizeImports = suggestOptimizeImports; myProject = project; myEnableOnlyVCSChangedTextCb = enableOnlyVCSChangedTextCb; @@ -62,30 +83,93 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi @Override protected JComponent createCenterPanel() { - if (!mySuggestOptimizeImports) { - return new JLabel(myText); - } + myTitle.setText(myText); + myOptionsPanel.setBorder(IdeBorderFactory.createTitledBorder(CodeInsightBundle.message("reformat.directory.dialog.options"))); + myFiltersPanel.setBorder(IdeBorderFactory.createTitledBorder(CodeInsightBundle.message("reformat.directory.dialog.filters"))); - JPanel panel = new JPanel(new GridLayout(4, 1)); - myCbOptimizeImports = new JCheckBox(CodeInsightBundle.message("reformat.option.optimize.imports")); - myCbRearrangeEntries = new JCheckBox(CodeInsightBundle.message("reformat.option.rearrange.entries")); - myCbOnlyVcsChangedRegions = new JCheckBox(CodeInsightBundle.message("reformat.option.vcs.changed.region")); + myMaskWarningLabel.setIcon(AllIcons.General.Warning); + myMaskWarningLabel.setVisible(false); - panel.add(new JLabel(myText)); - panel.add(myCbOptimizeImports); - panel.add(myCbRearrangeEntries); - panel.add(myCbOnlyVcsChangedRegions); + myIncludeSubdirsCb.setVisible(shouldShowIncludeSubdirsCb()); + initFileTypeFilter(); + initScopeFilter(); + + restoreCbsStates(); + return myWholePanel; + } + + private void restoreCbsStates() { myCbOptimizeImports.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false)); myCbRearrangeEntries.setSelected(LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(myProject)); myCbOnlyVcsChangedRegions.setEnabled(myEnableOnlyVCSChangedTextCb); myCbOnlyVcsChangedRegions.setSelected( myEnableOnlyVCSChangedTextCb && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) ); - - return panel; } - + + private void initScopeFilter() { + myUseScopeFilteringCb.setSelected(false); + myScopeCombo.setEnabled(false); + myUseScopeFilteringCb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myScopeCombo.setEnabled(myUseScopeFilteringCb.isSelected()); + } + }); + } + + private void initFileTypeFilter() { + FindDialog.initFileFilter(myFileFilter, myEnableFileNameFilterCb); + myEnableFileNameFilterCb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateMaskWarning(); + } + }); + myFileFilter.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + updateMaskWarning(); + } + }); + } + + private void updateMaskWarning() { + if (myEnableFileNameFilterCb.isSelected()) { + String mask = (String)myFileFilter.getEditor().getItem(); + if (mask == null || !isMaskValid(mask)) { + showWarningAndDisableOK(); + return; + } + } + + if (myMaskWarningLabel.isVisible()) { + clearWarningAndEnableOK(); + } + } + + private void showWarningAndDisableOK() { + myMaskWarningLabel.setVisible(true); + setOKActionEnabled(false); + } + + private void clearWarningAndEnableOK() { + myMaskWarningLabel.setVisible(false); + setOKActionEnabled(true); + } + + private static boolean isMaskValid(@NotNull String mask) { + try { + FindInProjectUtil.createFileMaskRegExp(mask); + } + catch (PatternSyntaxException e) { + return false; + } + + return true; + } + @NotNull @Override protected Action[] createActions() { @@ -105,12 +189,10 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi @Override protected void doOKAction() { super.doOKAction(); - if (mySuggestOptimizeImports) { - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(isOptimizeImports())); - LayoutCodeSettingsStorage.saveRearrangeEntriesOptionFor(myProject, isRearrangeEntries()); - if (myEnableOnlyVCSChangedTextCb) { - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, Boolean.toString(myCbOnlyVcsChangedRegions.isSelected())); - } + PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(isOptimizeImports())); + LayoutCodeSettingsStorage.saveRearrangeEntriesOptionFor(myProject, isRearrangeEntries()); + if (myEnableOnlyVCSChangedTextCb) { + PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, Boolean.toString(myCbOnlyVcsChangedRegions.isSelected())); } } @@ -122,4 +204,32 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected(); } + @Nullable + public String getFileTypeMask() { + if (myEnableFileNameFilterCb.isSelected()) { + return (String)myFileFilter.getSelectedItem(); + } + + return null; + } + + protected void createUIComponents() { + myScopeCombo = new ScopeChooserCombo(myProject, false, false, FindSettings.getInstance().getDefaultScopeName()); + Disposer.register(myDisposable, myScopeCombo); + } + + @Nullable + @Override + public SearchScope getSearchScope() { + if (myUseScopeFilteringCb.isSelected()) { + return myScopeCombo.getSelectedScope(); + } + + return null; + } + + protected boolean shouldShowIncludeSubdirsCb() { + return false; + } + } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java index 5a84a20cb19e..27b89fdbf6e7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java @@ -24,10 +24,14 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; public class OptimizeImportsAction extends AnAction { private static final @NonNls String HELP_ID = "editing.manageImports"; @@ -74,8 +78,7 @@ public class OptimizeImportsAction extends AnAction { else { text = CodeInsightBundle.message("process.scope.project", projectContext.getPresentableUrl()); } - LayoutProjectCodeDialog dialog - = new LayoutProjectCodeDialog(project, CodeInsightBundle.message("process.optimize.imports"), text, false, false); + DialogWrapper dialog = new OptimizeOnModuleDialog(project, text); dialog.show(); if (!dialog.isOK()) return; if (moduleContext != null) { @@ -197,4 +200,22 @@ public class OptimizeImportsAction extends AnAction { private static boolean isOptimizeImportsAvailable(final PsiFile file) { return !LanguageImportStatements.INSTANCE.forFile(file).isEmpty(); } + + private static class OptimizeOnModuleDialog extends DialogWrapper { + private final String myText; + + OptimizeOnModuleDialog(Project project, String text) { + super(project, false); + myText = text; + setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text")); + setTitle(CodeInsightBundle.message("process.optimize.imports")); + init(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return new JLabel(myText); + } + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java index 42bfdb390ed3..50942cb06c6b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java @@ -18,6 +18,7 @@ package com.intellij.codeInsight.actions; import com.intellij.application.options.editor.EditorOptions; import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.find.impl.FindInProjectUtil; import com.intellij.formatting.FormattingModelBuilder; import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.LanguageFormatting; @@ -29,6 +30,7 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; @@ -44,17 +46,25 @@ import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.psi.*; import com.intellij.psi.codeStyle.arrangement.engine.ArrangementEngine; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import java.util.ArrayList; import java.util.Collections; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; public class ReformatCodeAction extends AnAction implements DumbAware { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.actions.ReformatCodeAction"); + private static final @NonNls String HELP_ID = "editing.codeReformatting"; protected static ReformatFilesOptions myTestOptions; @@ -230,6 +240,9 @@ public class ReformatCodeAction extends AnAction implements DumbAware { options.isProcessOnlyChangedText() ); + registerScopeFilter(processor, options.getSearchScope()); + registerFileMaskFilter(processor, options.getFileTypeMask()); + if (options.isOptimizeImports()) { processor = new OptimizeImportsProcessor(processor); } @@ -253,6 +266,8 @@ public class ReformatCodeAction extends AnAction implements DumbAware { else processor = new ReformatCodeProcessor(project, processOnlyChangedText); + registerScopeFilter(processor, selectedFlags.getSearchScope()); + registerFileMaskFilter(processor, selectedFlags.getFileTypeMask()); if (shouldOptimizeImports) { processor = new OptimizeImportsProcessor(processor); @@ -265,6 +280,51 @@ public class ReformatCodeAction extends AnAction implements DumbAware { processor.run(); } + public static void registerScopeFilter(@NotNull AbstractLayoutCodeProcessor processor, @Nullable final SearchScope scope) { + if (scope == null) { + return; + } + + processor.addFileFilter(new FileFilter() { + @Override + public boolean accept(@NotNull VirtualFile file) { + if (scope instanceof LocalSearchScope) { + return ((LocalSearchScope)scope).isInScope(file); + } + if (scope instanceof GlobalSearchScope) { + return ((GlobalSearchScope)scope).contains(file); + } + + return false; + } + }); + } + + public static void registerFileMaskFilter(@NotNull AbstractLayoutCodeProcessor processor, @Nullable String fileTypeMask) { + if (fileTypeMask == null) + return; + + final Pattern pattern = getFileTypeMaskPattern(fileTypeMask); + if (pattern != null) { + processor.addFileFilter(new FileFilter() { + @Override + public boolean accept(@NotNull VirtualFile file) { + return pattern.matcher(file.getName()).matches(); + } + }); + } + } + + @Nullable + private static Pattern getFileTypeMaskPattern(@Nullable String mask) { + try { + return FindInProjectUtil.createFileMaskRegExp(mask); + } catch (PatternSyntaxException e) { + LOG.info("Error while processing file mask: ", e); + return null; + } + } + public static void updateShowDialogSetting(LayoutCodeDialog dialog, String title) { if (dialog.isDoNotAskMe()) { Notifications.Bus.notify(new Notification("Reformat Code", title, @@ -384,7 +444,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { final boolean enableOnlyVCSChangedRegions = module != null ? FormatChangedTextUtil.hasChanges(module) : FormatChangedTextUtil.hasChanges(project); - LayoutProjectCodeDialog dialog = new LayoutProjectCodeDialog(project, CodeInsightBundle.message("process.reformat.code"), text, true, enableOnlyVCSChangedRegions); + LayoutProjectCodeDialog dialog = new LayoutProjectCodeDialog(project, CodeInsightBundle.message("process.reformat.code"), text, enableOnlyVCSChangedRegions); dialog.show(); if (!dialog.isOK()) return null; return dialog; @@ -415,6 +475,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { return LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(project); } + @TestOnly protected static void setTestOptions(ReformatFilesOptions options) { myTestOptions = options; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java index 26a158e027f4..d364857352e6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java @@ -21,7 +21,9 @@ import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.SearchScope; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -78,4 +80,16 @@ public class ReformatFilesDialog extends DialogWrapper implements ReformatFilesO static boolean isOptmizeImportsOptionOn() { return PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false); } + + @Nullable + @Override + public SearchScope getSearchScope() { + return null; + } + + @Nullable + @Override + public String getFileTypeMask() { + return null; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java index 37bf43c2c7cb..bb958e77b2fe 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java @@ -15,6 +15,9 @@ */ package com.intellij.codeInsight.actions; +import com.intellij.psi.search.SearchScope; +import org.jetbrains.annotations.Nullable; + public interface ReformatFilesOptions { boolean isOptimizeImports(); @@ -23,4 +26,10 @@ public interface ReformatFilesOptions { boolean isRearrangeEntries(); + @Nullable + String getFileTypeMask(); + + @Nullable + SearchScope getSearchScope(); + } diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index 340d1ef64efb..23931e72104b 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -3,6 +3,8 @@ error.hint.file.is.readonly=File {0} is read-only error.dialog.readonly.file.title=File Is Read-Only error.dialog.readonly.files.title=Cannot Modify Read-Only Files error.dialog.readonly.files.message={0} contains read-only file(s).\nProcess all other (writeable) files? +reformat.directory.dialog.options=Options +reformat.directory.dialog.filters=Filters process.scope.directory=Directory ''{0}'' process.scope.project=Project ''{0}'' process.scope.module=Module ''{0}'' diff --git a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java index 1b3771fe3c10..adaae703fef1 100644 --- a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java +++ b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java @@ -28,12 +28,14 @@ import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.Indent; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ThrowableRunnable; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Map; +import java.util.Set; public class MockCodeStyleManager extends CodeStyleManager { private Map myFormattedLinesForFile = new HashMap(); @@ -44,6 +46,15 @@ public class MockCodeStyleManager extends CodeStyleManager { return changedLines != null ? changedLines : new ChangedLines[0]; } + @NotNull + public Set getFormattedFiles() { + return myFormattedLinesForFile.keySet(); + } + + public void clearFormattedFiles() { + myFormattedLinesForFile = ContainerUtil.newHashMap(); + } + @Override public void reformatText(@NotNull PsiFile file, @NotNull Collection ranges) throws IncorrectOperationException { Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); diff --git a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/ReformatFilesWithFiltersTest.java b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/ReformatFilesWithFiltersTest.java new file mode 100644 index 000000000000..98ded2cdf13a --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/ReformatFilesWithFiltersTest.java @@ -0,0 +1,242 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +import com.intellij.lang.LanguageFormatting; +import com.intellij.openapi.fileTypes.PlainTextLanguage; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.search.SearchScope; +import com.intellij.testFramework.LightPlatformTestCase; +import com.intellij.testFramework.PlatformTestCase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.picocontainer.MutablePicoContainer; + +import java.io.IOException; +import java.util.Set; + +import static com.intellij.psi.search.GlobalSearchScopesCore.directoryScope; + +public class ReformatFilesWithFiltersTest extends LightPlatformTestCase { + private static final String TEMP_DIR_NAME = "dir"; + private PsiDirectory myWorkingDirectory; + + private MockCodeStyleManager myMockCodeStyleManager; + private MockPlainTextFormattingModelBuilder myMockPlainTextFormattingModelBuilder; + + private CodeStyleManager myRealCodeStyleManger; + + @Override + public void setUp() throws Exception { + PlatformTestCase.initPlatformLangPrefix(); + super.setUp(); + myWorkingDirectory = TestFileStructure.createDirectory(getProject(), getSourceRoot(), TEMP_DIR_NAME); + + myRealCodeStyleManger = CodeStyleManager.getInstance(getProject()); + myMockCodeStyleManager = new MockCodeStyleManager(); + registerCodeStyleManager(myMockCodeStyleManager); + + myMockPlainTextFormattingModelBuilder = new MockPlainTextFormattingModelBuilder(); + LanguageFormatting.INSTANCE.addExplicitExtension(PlainTextLanguage.INSTANCE, myMockPlainTextFormattingModelBuilder); + } + + @Override + public void tearDown() throws Exception { + registerCodeStyleManager(myRealCodeStyleManger); + LanguageFormatting.INSTANCE.removeExplicitExtension(PlainTextLanguage.INSTANCE, myMockPlainTextFormattingModelBuilder); + + TestFileStructure.delete(myWorkingDirectory.getVirtualFile()); + super.tearDown(); + } + + private static void registerCodeStyleManager(@NotNull CodeStyleManager manager) { + String componentKey = CodeStyleManager.class.getName(); + MutablePicoContainer container = (MutablePicoContainer)getProject().getPicoContainer(); + container.unregisterComponent(componentKey); + container.registerComponentInstance(componentKey, manager); + } + + public void testReformatWithoutMask() throws IOException { + TestFileStructure fileTree = new TestFileStructure(getModule(), myWorkingDirectory); + + PsiFile java1 = fileTree.addTestFile("Test.java", "empty content"); + PsiFile java2 = fileTree.addTestFile("Pair.java", "empty content"); + PsiFile java3 = fileTree.addTestFile("Pair2.java", "empty content"); + + PsiFile php = fileTree.addTestFile("Test.php", "empty content"); + PsiFile js = fileTree.addTestFile("Test.js", "empty content"); + + reformatDirectoryWithFileMask(myWorkingDirectory, null); + assertWasFormatted(java1, java2, java3, php, js); + } + + public void testFormatByOnlyOneMask() throws IOException { + TestFileStructure fileTree = new TestFileStructure(getModule(), myWorkingDirectory); + + PsiFile java1 = fileTree.addTestFile("Test.java", "empty content"); + PsiFile java2 = fileTree.addTestFile("Pair.java", "empty content"); + PsiFile java3 = fileTree.addTestFile("Pair2.java", "empty content"); + + PsiFile php = fileTree.addTestFile("Test.php", "empty content"); + PsiFile js = fileTree.addTestFile("Test.js", "empty content"); + + reformatDirectoryWithFileMask(myWorkingDirectory, "*.java"); + assertWasFormatted(java1, java2, java3); + assertWasNotFormatted(php, js); + + reformatDirectoryWithFileMask(myWorkingDirectory, "*.js"); + assertWasFormatted(js); + assertWasNotFormatted(java1, java2, java3, php); + + } + + public void testFormatByMultiMask() throws IOException { + TestFileStructure fileTree = new TestFileStructure(getModule(), myWorkingDirectory); + + PsiFile java1 = fileTree.addTestFile("Test.java", "empty content"); + PsiFile java2 = fileTree.addTestFile("Pair.java", "empty content"); + PsiFile java3 = fileTree.addTestFile("Pair2.java", "empty content"); + + PsiFile php1 = fileTree.addTestFile("Test.php", "empty content"); + PsiFile php2 = fileTree.addTestFile("Test2.php", "empty content"); + + PsiFile js1 = fileTree.addTestFile("Test1.js", "empty content"); + PsiFile js2 = fileTree.addTestFile("Test2.js", "empty content"); + PsiFile js3 = fileTree.addTestFile("Test3.js", "empty content"); + + PsiFile py1 = fileTree.addTestFile("Test1.py", "empty content"); + PsiFile py2 = fileTree.addTestFile("Test2.py", "empty content"); + PsiFile py3 = fileTree.addTestFile("Test3.py", "empty content"); + + reformatDirectoryWithFileMask(myWorkingDirectory, "*.js, *.java"); + assertWasFormatted(js1, js2, js3, java1, java2, java3); + assertWasNotFormatted(php1, php2, py1, py2, py3); + + reformatDirectoryWithFileMask(myWorkingDirectory, "*.php, *.js"); + assertWasFormatted(js1, js2, js3, php1, php2); + assertWasNotFormatted(java1, java2, java3, py1, py2, py3); + + reformatDirectoryWithFileMask(myWorkingDirectory, "*.js, *.php, *.java"); + assertWasFormatted(js1, js2, js3, php1, php2, java1, java2, java3); + assertWasNotFormatted(py1, py2, py3); + + reformatDirectoryWithFileMask(myWorkingDirectory, "*.js, *.php, *.java, *.py"); + assertWasFormatted(js1, js2, js3, php1, php2, java1, java2, java3, py1, py2, py3); + + reformatDirectoryWithFileMask(myWorkingDirectory, "*.jsp, *.dart"); + assertWasNotFormatted(js1, js2, js3, php1, php2, java1, java2, java3, py1, py2, py3); + } + + public void testDirectoryScope() throws IOException { + TestFileStructure fileTree = new TestFileStructure(getModule(), myWorkingDirectory); + PsiFile java1 = fileTree.addTestFile("Test1.java", "empty content"); + PsiFile php1 = fileTree.addTestFile("Pair1.php", "empty content"); + PsiFile js1 = fileTree.addTestFile("Pair1.js", "empty content"); + + PsiDirectory outer = fileTree.createDirectoryAndMakeItCurrent("toFormat"); + PsiFile java2 = fileTree.addTestFile("Test2.java", "empty content"); + PsiFile php2 = fileTree.addTestFile("Pair2.php", "empty content"); + PsiFile js2 = fileTree.addTestFile("Pair2.js", "empty content"); + + PsiDirectory inner = fileTree.createDirectoryAndMakeItCurrent("toFormat"); + PsiFile java3 = fileTree.addTestFile("Test3.java", "empty content"); + PsiFile php3 = fileTree.addTestFile("Pair3.php", "empty content"); + PsiFile js3 = fileTree.addTestFile("Pair3.js", "empty content"); + + + reformatDirectoryWithScopeFilter(myWorkingDirectory, directoryScope(outer, true)); + assertWasFormatted(java2, php2, js2, java3, php3, js3); + assertWasNotFormatted(java1, php1, js1); + + + reformatDirectoryWithScopeFilter(myWorkingDirectory, directoryScope(outer, false)); + assertWasFormatted(java2, php2, js2); + assertWasNotFormatted(java1, php1, js1, java3, php3, js3); + + + reformatDirectoryWithScopeFilter(myWorkingDirectory, directoryScope(inner, true)); + assertWasFormatted(java3, php3, js3); + assertWasNotFormatted(java1, php1, js1, java2, php2, js2); + + reformatDirectoryWithScopeFilter(myWorkingDirectory, directoryScope(myWorkingDirectory, false).union(directoryScope(inner, false))); + assertWasFormatted(java3, php3, js3, java1, php1, js1); + assertWasNotFormatted(java2, php2, js2); + } + + public void testDirectoryScopeWithMask() throws IOException { + TestFileStructure fileTree = new TestFileStructure(getModule(), myWorkingDirectory); + PsiFile java1 = fileTree.addTestFile("Test1.java", "empty content"); + PsiFile php1 = fileTree.addTestFile("Pair1.php", "empty content"); + PsiFile js1 = fileTree.addTestFile("Pair1.js", "empty content"); + + PsiDirectory outer = fileTree.createDirectoryAndMakeItCurrent("toFormat"); + PsiFile java2 = fileTree.addTestFile("Test2.java", "empty content"); + PsiFile php2 = fileTree.addTestFile("Pair2.php", "empty content"); + PsiFile js2 = fileTree.addTestFile("Pair2.js", "empty content"); + + PsiDirectory inner = fileTree.createDirectoryAndMakeItCurrent("toFormat"); + PsiFile java3 = fileTree.addTestFile("Test3.java", "empty content"); + PsiFile php3 = fileTree.addTestFile("Pair3.php", "empty content"); + PsiFile js3 = fileTree.addTestFile("Pair3.js", "empty content"); + + + reformatDirectory(myWorkingDirectory, "*.js", directoryScope(outer, true)); + assertWasFormatted(js2, js3); + assertWasNotFormatted(java1, php1, js1, java2, php2, java3, php3); + + reformatDirectory(myWorkingDirectory, "*.js", directoryScope(myWorkingDirectory, false)); + assertWasFormatted(js1); + assertWasNotFormatted(js2, js3, java1, php1, java2, php2, java3, php3); + + reformatDirectory(myWorkingDirectory, "*.java, *.php", directoryScope(myWorkingDirectory, false).union(directoryScope(inner, false))); + assertWasFormatted(java1, php1, java3, php3); + assertWasNotFormatted(java2, php2, js1, js2, js3); + } + + public void assertWasFormatted(PsiFile... files) { + final Set formattedFiles = myMockCodeStyleManager.getFormattedFiles(); + for (PsiFile file : files) { + assertTrue(file.getName() + "should be formatted", formattedFiles.contains(file)); + } + } + + public void assertWasNotFormatted(PsiFile... files) { + final Set formattedFiles = myMockCodeStyleManager.getFormattedFiles(); + for (PsiFile file : files) { + assertTrue(file.getName() + " should not be formatted", !formattedFiles.contains(file)); + } + } + + public void reformatDirectoryWithFileMask(@NotNull PsiDirectory directory, @Nullable String mask) { + reformatDirectory(directory, mask, null); + } + + public void reformatDirectoryWithScopeFilter(@NotNull PsiDirectory directory, @Nullable SearchScope scope) { + reformatDirectory(directory, null, scope); + } + + public void reformatDirectory(@NotNull PsiDirectory directory, @Nullable String mask, @Nullable SearchScope scope) { + myMockCodeStyleManager.clearFormattedFiles(); + + ReformatCodeProcessor processor = new ReformatCodeProcessor(getProject(), directory, true, false); + ReformatCodeAction.registerFileMaskFilter(processor, mask); + ReformatCodeAction.registerScopeFilter(processor, scope); + + processor.run(); + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/TestFileStructure.java b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/TestFileStructure.java index 6dbe45689332..098acd4ef5a4 100644 --- a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/TestFileStructure.java +++ b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/TestFileStructure.java @@ -67,11 +67,11 @@ public class TestFileStructure { } @NotNull - public TestFileStructure createDirectoryAndMakeItCurrent(String name) throws IOException { + public PsiDirectory createDirectoryAndMakeItCurrent(String name) throws IOException { myLevel++; myFilesForLevel.add(new ArrayList()); myCurrentLevelDirectory = createDirectory(myProject, myCurrentLevelDirectory.getVirtualFile(), name); - return this; + return myCurrentLevelDirectory; } public List getFilesAtLevel(int level) {