6d2c2b73bcad682c6139bc60b4aa5aba4bc365ee
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / actions / OptimizeImportsAction.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.actions;
18
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.ide.util.PropertiesComponent;
21 import com.intellij.lang.LanguageImportStatements;
22 import com.intellij.openapi.actionSystem.*;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
26 import com.intellij.openapi.module.Module;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.DialogWrapper;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.psi.*;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.Nullable;
35
36 import javax.swing.*;
37
38 public class OptimizeImportsAction extends AnAction {
39   private static final @NonNls String HELP_ID = "editing.manageImports";
40   private static final String NO_IMPORTS_OPTIMIZED = "Unused imports not found";
41
42
43   @Override
44   public void actionPerformed(AnActionEvent event) {
45     actionPerformedImpl(event.getDataContext());
46   }
47
48   public static void actionPerformedImpl(final DataContext dataContext) {
49     final Project project = CommonDataKeys.PROJECT.getData(dataContext);
50     if (project == null) {
51       return;
52     }
53     PsiDocumentManager.getInstance(project).commitAllDocuments();
54     final Editor editor = BaseCodeInsightAction.getInjectedEditor(project, CommonDataKeys.EDITOR.getData(dataContext));
55
56     final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
57
58     PsiFile file = null;
59     PsiDirectory dir;
60
61     if (editor != null){
62       file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
63       if (file == null) return;
64       dir = file.getContainingDirectory();
65     }
66     else if (files != null && ReformatCodeAction.containsAtLeastOneFile(files)) {
67       final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(files);
68       if (!operationStatus.hasReadonlyFiles()) {
69         new OptimizeImportsProcessor(project, ReformatCodeAction.convertToPsiFiles(files, project), null).run();
70       }
71       return;
72     }
73     else{
74       Project projectContext = PlatformDataKeys.PROJECT_CONTEXT.getData(dataContext);
75       Module moduleContext = LangDataKeys.MODULE_CONTEXT.getData(dataContext);
76
77       if (projectContext != null || moduleContext != null) {
78         final String text;
79         final boolean hasChanges;
80         if (moduleContext != null) {
81           text = CodeInsightBundle.message("process.scope.module", moduleContext.getName());
82           hasChanges = FormatChangedTextUtil.hasChanges(moduleContext);
83         }
84         else {
85           text = CodeInsightBundle.message("process.scope.project", projectContext.getPresentableUrl());
86           hasChanges = FormatChangedTextUtil.hasChanges(projectContext);
87         }
88         DialogWrapper dialog = new OptimizeImportsDialog(project, text, hasChanges);
89         if (!dialog.showAndGet()) {
90           return;
91         }
92         if (moduleContext != null) {
93           new OptimizeImportsProcessor(project, moduleContext).run();
94         }
95         else {
96           new OptimizeImportsProcessor(projectContext).run();
97         }
98         return;
99       }
100
101       PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
102       if (element == null) return;
103       if (element instanceof PsiDirectoryContainer) {
104         dir = ((PsiDirectoryContainer)element).getDirectories()[0];
105       }
106       else if (element instanceof PsiDirectory) {
107         dir = (PsiDirectory)element;
108       }
109       else{
110         file = element.getContainingFile();
111         if (file == null) return;
112         dir = file.getContainingDirectory();
113       }
114     }
115
116     boolean processDirectory = false;
117     boolean processOnlyVcsChangedFiles = false;
118     if (!ApplicationManager.getApplication().isUnitTestMode() && file == null && dir != null) {
119       String message = CodeInsightBundle.message("process.scope.directory", dir.getName());
120       OptimizeImportsDialog dialog = new OptimizeImportsDialog(project, message, FormatChangedTextUtil.hasChanges(dir));
121       dialog.show();
122       if (!dialog.isOK()) {
123         return;
124       }
125       processDirectory = true;
126       processOnlyVcsChangedFiles = dialog.isProcessOnlyVcsChangedFiles();
127     }
128
129     if (processDirectory){
130       new OptimizeImportsProcessor(project, dir, true, processOnlyVcsChangedFiles).run();
131     }
132     else{
133       final OptimizeImportsProcessor optimizer = new OptimizeImportsProcessor(project, file);
134       if (editor != null && EditorSettingsExternalizable.getInstance().getOptions().SHOW_NOTIFICATION_AFTER_OPTIMIZE_IMPORTS_ACTION) {
135         optimizer.setCollectInfo(true);
136         optimizer.setPostRunnable(new Runnable() {
137           @Override
138           public void run() {
139             LayoutCodeInfoCollector collector = optimizer.getInfoCollector();
140             if (collector != null) {
141               String info = collector.getOptimizeImportsNotification();
142               if (!editor.isDisposed() && editor.getComponent().isShowing()) {
143                 String message = info != null ? info : NO_IMPORTS_OPTIMIZED;
144                 FileInEditorProcessor.showHint(editor, StringUtil.capitalize(message));
145               }
146             }
147           }
148         });
149       }
150       optimizer.run();
151     }
152   }
153
154   @Override
155   public void update(AnActionEvent event){
156     if (!LanguageImportStatements.INSTANCE.hasAnyExtensions()) {
157       event.getPresentation().setVisible(false);
158       return;
159     }
160
161     Presentation presentation = event.getPresentation();
162     DataContext dataContext = event.getDataContext();
163     Project project = CommonDataKeys.PROJECT.getData(dataContext);
164     if (project == null){
165       presentation.setEnabled(false);
166       return;
167     }
168
169     final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
170
171     final Editor editor = BaseCodeInsightAction.getInjectedEditor(project, CommonDataKeys.EDITOR.getData(dataContext), false);
172     if (editor != null){
173       PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
174       if (file == null || !isOptimizeImportsAvailable(file)){
175         presentation.setEnabled(false);
176         return;
177       }
178     }
179     else if (files != null && ReformatCodeAction.containsAtLeastOneFile(files)) {
180       boolean anyHasOptimizeImports = false;
181       for (VirtualFile virtualFile : files) {
182         PsiFile file = PsiManager.getInstance(project).findFile(virtualFile);
183         if (file == null) {
184           presentation.setEnabled(false);
185           return;
186         }
187         if (isOptimizeImportsAvailable(file)) {
188           anyHasOptimizeImports = true;
189         }
190       }
191       if (!anyHasOptimizeImports) {
192         presentation.setEnabled(false);
193         return;
194       }
195     }
196     else if (files != null && files.length == 1) {
197       // skip. Both directories and single files are supported.
198     }
199     else if (LangDataKeys.MODULE_CONTEXT.getData(dataContext) == null &&
200              PlatformDataKeys.PROJECT_CONTEXT.getData(dataContext) == null) {
201       PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
202       if (element == null){
203         presentation.setEnabled(false);
204         return;
205       }
206
207       if (!(element instanceof PsiDirectory)){
208         PsiFile file = element.getContainingFile();
209         if (file == null || !isOptimizeImportsAvailable(file)){
210           presentation.setEnabled(false);
211           return;
212         }
213       }
214     }
215
216     presentation.setEnabled(true);
217   }
218
219   private static boolean isOptimizeImportsAvailable(final PsiFile file) {
220     return !LanguageImportStatements.INSTANCE.forFile(file).isEmpty();
221   }
222
223   private static class OptimizeImportsDialog extends DialogWrapper {
224     private final boolean myContextHasChanges;
225
226     private final String myText;
227     private JCheckBox myOnlyVcsCheckBox;
228     private final LastRunReformatCodeOptionsProvider myLastRunOptions;
229
230     OptimizeImportsDialog(Project project, String text, boolean hasChanges) {
231       super(project, false);
232       myText = text;
233       myContextHasChanges = hasChanges;
234       myLastRunOptions = new LastRunReformatCodeOptionsProvider(PropertiesComponent.getInstance());
235       setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text"));
236       setTitle(CodeInsightBundle.message("process.optimize.imports"));
237       init();
238     }
239
240     public boolean isProcessOnlyVcsChangedFiles() {
241       return myOnlyVcsCheckBox.isSelected();
242     }
243
244     @Nullable
245     @Override
246     protected JComponent createCenterPanel() {
247       JPanel panel = new JPanel();
248       BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
249       panel.setLayout(layout);
250
251       panel.add(new JLabel(myText));
252       myOnlyVcsCheckBox = new JCheckBox(CodeInsightBundle.message("process.scope.changed.files"));
253       boolean lastRunVcsChangedTextEnabled = myLastRunOptions.getLastTextRangeType() == TextRangeType.VCS_CHANGED_TEXT;
254
255       myOnlyVcsCheckBox.setEnabled(myContextHasChanges);
256       myOnlyVcsCheckBox.setSelected(myContextHasChanges && lastRunVcsChangedTextEnabled);
257
258       panel.add(myOnlyVcsCheckBox);
259       return panel;
260     }
261   }
262 }