inspection toolwindow: when quick fixes are shown on popup action can't find view...
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ex / QuickFixAction.java
1 /*
2  * Copyright 2000-2009 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.codeInspection.ex;
18
19 import com.intellij.codeInsight.FileModificationService;
20 import com.intellij.codeInspection.CommonProblemDescriptor;
21 import com.intellij.codeInspection.InspectionManager;
22 import com.intellij.codeInspection.InspectionsBundle;
23 import com.intellij.codeInspection.ProblemDescriptor;
24 import com.intellij.codeInspection.reference.RefElement;
25 import com.intellij.codeInspection.reference.RefEntity;
26 import com.intellij.codeInspection.reference.RefManagerImpl;
27 import com.intellij.codeInspection.ui.InspectionResultsView;
28 import com.intellij.codeInspection.ui.InspectionTree;
29 import com.intellij.icons.AllIcons;
30 import com.intellij.ide.DataManager;
31 import com.intellij.openapi.actionSystem.*;
32 import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.application.ReadAction;
35 import com.intellij.openapi.command.CommandProcessor;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.progress.ProgressIndicator;
38 import com.intellij.openapi.progress.ProgressManager;
39 import com.intellij.openapi.project.Project;
40 import com.intellij.openapi.util.IconLoader;
41 import com.intellij.openapi.util.Ref;
42 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
43 import com.intellij.openapi.vfs.VfsUtilCore;
44 import com.intellij.openapi.vfs.VirtualFile;
45 import com.intellij.psi.PsiDocumentManager;
46 import com.intellij.psi.PsiElement;
47 import com.intellij.psi.PsiFile;
48 import com.intellij.ui.ClickListener;
49 import com.intellij.ui.IdeBorderFactory;
50 import com.intellij.util.SequentialModalProgressTask;
51 import com.intellij.util.ui.UIUtil;
52 import gnu.trove.THashSet;
53 import org.jetbrains.annotations.NotNull;
54
55 import javax.swing.*;
56 import java.awt.event.MouseEvent;
57 import java.util.*;
58
59 /**
60  * @author max
61  */
62 public class QuickFixAction extends AnAction implements CustomComponentAction {
63   private static final Logger LOG = Logger.getInstance("#" + QuickFixAction.class.getName());
64
65   public static final QuickFixAction[] EMPTY = new QuickFixAction[0];
66   protected final InspectionToolWrapper myToolWrapper;
67
68   protected static InspectionResultsView getInvoker(AnActionEvent e) {
69     return InspectionResultsView.DATA_KEY.getData(e.getDataContext());
70   }
71
72   protected QuickFixAction(String text, @NotNull InspectionToolWrapper toolWrapper) {
73     this(text, AllIcons.Actions.CreateFromUsage, null, toolWrapper);
74   }
75
76   protected QuickFixAction(String text, Icon icon, KeyStroke keyStroke, @NotNull InspectionToolWrapper toolWrapper) {
77     super(text, null, icon);
78     myToolWrapper = toolWrapper;
79     if (keyStroke != null) {
80       registerCustomShortcutSet(new CustomShortcutSet(keyStroke), null);
81     }
82   }
83
84   @Override
85   public void update(AnActionEvent e) {
86     final InspectionResultsView view = getInvoker(e);
87     if (view == null) {
88       e.getPresentation().setEnabled(false);
89       return;
90     }
91
92     e.getPresentation().setVisible(false);
93     e.getPresentation().setEnabled(false);
94
95     final InspectionTree tree = view.getTree();
96     final InspectionToolWrapper toolWrapper = tree.getSelectedToolWrapper();
97     if (!view.isSingleToolInSelection() || toolWrapper != myToolWrapper) {
98       return;
99     }
100
101     if (!isProblemDescriptorsAcceptable() && tree.getSelectedElements().length > 0 ||
102         isProblemDescriptorsAcceptable() && tree.getSelectedDescriptors().length > 0) {
103       e.getPresentation().setVisible(true);
104       e.getPresentation().setEnabled(true);
105     }
106   }
107
108   protected boolean isProblemDescriptorsAcceptable() {
109     return false;
110   }
111
112   public String getText() {
113     return getTemplatePresentation().getText();
114   }
115
116   @Override
117   public void actionPerformed(final AnActionEvent e) {
118     final InspectionResultsView view = getInvoker(e);
119     final InspectionTree tree = view.getTree();
120     try {
121       Ref<CommonProblemDescriptor[]> descriptors = Ref.create();
122       Set<VirtualFile> readOnlyFiles = new THashSet<>();
123       if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ReadAction.run(() -> {
124         final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
125         indicator.setText("Checking problem descriptors...");
126         descriptors.set(tree.getSelectedDescriptors(true, readOnlyFiles));
127       }), InspectionsBundle.message("preparing.for.apply.fix"), true, e.getProject())) {
128         return;
129       }
130       if (isProblemDescriptorsAcceptable() && descriptors.get().length > 0) {
131         doApplyFix(view.getProject(), descriptors.get(), readOnlyFiles, tree.getContext());
132       } else {
133         doApplyFix(getSelectedElements(e), view);
134       }
135     } finally {
136       view.setApplyingFix(false);
137     }
138   }
139
140
141   protected void applyFix(@NotNull Project project,
142                           @NotNull GlobalInspectionContextImpl context,
143                           @NotNull CommonProblemDescriptor[] descriptors,
144                           @NotNull Set<PsiElement> ignoredElements) {
145   }
146
147   private void doApplyFix(@NotNull final Project project,
148                           @NotNull final CommonProblemDescriptor[] descriptors,
149                           @NotNull Set<VirtualFile> readOnlyFiles,
150                           @NotNull final GlobalInspectionContextImpl context) {
151     if (!FileModificationService.getInstance().prepareVirtualFilesForWrite(project, readOnlyFiles)) return;
152     
153     final RefManagerImpl refManager = (RefManagerImpl)context.getRefManager();
154     final boolean initial = refManager.isInProcess();
155
156     refManager.inspectionReadActionFinished();
157
158     try {
159       final Set<PsiElement> ignoredElements = new HashSet<>();
160
161       final String templatePresentationText = getTemplatePresentation().getText();
162       assert templatePresentationText != null;
163       CommandProcessor.getInstance().executeCommand(project, () -> {
164         CommandProcessor.getInstance().markCurrentCommandAsGlobal(project);
165         final SequentialModalProgressTask progressTask =
166           new SequentialModalProgressTask(project, templatePresentationText, true);
167         progressTask.setMinIterationTime(200);
168         progressTask.setTask(new PerformFixesTask(project, descriptors, ignoredElements, progressTask, context));
169         ProgressManager.getInstance().run(progressTask);
170       }, templatePresentationText, null);
171
172       refreshViews(project, ignoredElements, myToolWrapper);
173     }
174     finally { //to make offline view lazy
175       if (initial) refManager.inspectionReadActionStarted();
176     }
177   }
178
179   private void doApplyFix(@NotNull final RefEntity[] refElements, @NotNull InspectionResultsView view) {
180     final RefManagerImpl refManager = (RefManagerImpl)view.getGlobalInspectionContext().getRefManager();
181
182     final boolean initial = refManager.isInProcess();
183
184     refManager.inspectionReadActionFinished();
185
186     try {
187       final boolean[] refreshNeeded = {false};
188       if (refElements.length > 0) {
189         final Project project = refElements[0].getRefManager().getProject();
190         CommandProcessor.getInstance().executeCommand(project, () -> {
191           CommandProcessor.getInstance().markCurrentCommandAsGlobal(project);
192           ApplicationManager.getApplication().runWriteAction(() -> {
193             refreshNeeded[0] = applyFix(refElements);
194           });
195         }, getTemplatePresentation().getText(), null);
196       }
197       if (refreshNeeded[0]) {
198         refreshViews(view.getProject(), refElements, myToolWrapper);
199       }
200     }
201     finally {  //to make offline view lazy
202       if (initial) refManager.inspectionReadActionStarted();
203     }
204   }
205
206   public static void removeElements(@NotNull RefEntity[] refElements, @NotNull Project project, @NotNull InspectionToolWrapper toolWrapper) {
207     refreshViews(project, refElements, toolWrapper);
208     final ArrayList<RefElement> deletedRefs = new ArrayList<>(1);
209     for (RefEntity refElement : refElements) {
210       if (!(refElement instanceof RefElement)) continue;
211       refElement.getRefManager().removeRefElement((RefElement)refElement, deletedRefs);
212     }
213   }
214
215   private static Set<VirtualFile> getReadOnlyFiles(@NotNull RefEntity[] refElements) {
216     Set<VirtualFile> readOnlyFiles = new THashSet<>();
217     for (RefEntity refElement : refElements) {
218       PsiElement psiElement = refElement instanceof RefElement ? ((RefElement)refElement).getElement() : null;
219       if (psiElement == null || psiElement.getContainingFile() == null) continue;
220       readOnlyFiles.add(psiElement.getContainingFile().getVirtualFile());
221     }
222     return readOnlyFiles;
223   }
224
225   private static RefEntity[] getSelectedElements(AnActionEvent e) {
226     final InspectionResultsView invoker = getInvoker(e);
227     if (invoker == null) return new RefElement[0];
228     List<RefEntity> selection = new ArrayList<>(Arrays.asList(invoker.getTree().getSelectedElements()));
229     PsiDocumentManager.getInstance(invoker.getProject()).commitAllDocuments();
230     Collections.sort(selection, (o1, o2) -> {
231       if (o1 instanceof RefElement && o2 instanceof RefElement) {
232         RefElement r1 = (RefElement)o1;
233         RefElement r2 = (RefElement)o2;
234         final PsiElement element1 = r1.getElement();
235         final PsiElement element2 = r2.getElement();
236         final PsiFile containingFile1 = element1.getContainingFile();
237         final PsiFile containingFile2 = element2.getContainingFile();
238         if (containingFile1 == containingFile2) {
239           int i1 = element1.getTextOffset();
240           int i2 = element2.getTextOffset();
241           if (i1 < i2) {
242             return 1;
243           }
244           if (i1 > i2){
245             return -1;
246           }
247           return 0;
248         }
249         return containingFile1.getName().compareTo(containingFile2.getName());
250       }
251       if (o1 instanceof RefElement) {
252         return 1;
253       }
254       if (o2 instanceof RefElement) {
255         return -1;
256       }
257       return o1.getName().compareTo(o2.getName());
258     });
259
260     return selection.toArray(new RefEntity[selection.size()]);
261   }
262
263   private static void refreshViews(@NotNull Project project, @NotNull Set<PsiElement> selectedElements, @NotNull InspectionToolWrapper toolWrapper) {
264     InspectionManagerEx managerEx = (InspectionManagerEx)InspectionManager.getInstance(project);
265     final Set<GlobalInspectionContextImpl> runningContexts = managerEx.getRunningContexts();
266     for (GlobalInspectionContextImpl context : runningContexts) {
267       for (PsiElement element : selectedElements) {
268         context.ignoreElement(toolWrapper.getTool(), element);
269       }
270       context.refreshViews();
271     }
272   }
273
274   private static void refreshViews(@NotNull Project project, @NotNull RefEntity[] refElements, @NotNull InspectionToolWrapper toolWrapper) {
275     final Set<PsiElement> ignoredElements = new HashSet<>();
276     for (RefEntity element : refElements) {
277       final PsiElement psiElement = element instanceof RefElement ? ((RefElement)element).getElement() : null;
278       if (psiElement != null && psiElement.isValid()) {
279         ignoredElements.add(psiElement);
280       }
281     }
282     refreshViews(project, ignoredElements, toolWrapper);
283   }
284
285   /**
286    * @return true if immediate UI update needed.
287    */
288   protected boolean applyFix(@NotNull RefEntity[] refElements) {
289     Set<VirtualFile> readOnlyFiles = getReadOnlyFiles(refElements);
290     if (!readOnlyFiles.isEmpty()) {
291       final Project project = refElements[0].getRefManager().getProject();
292       final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(
293         VfsUtilCore.toVirtualFileArray(readOnlyFiles));
294       if (operationStatus.hasReadonlyFiles()) return false;
295     }
296     return true;
297   }
298
299   @Override
300   public JComponent createCustomComponent(Presentation presentation) {
301     final JButton button = new JButton(presentation.getText());
302     Icon icon = presentation.getIcon();
303     if (icon == null) {
304       icon = AllIcons.Actions.CreateFromUsage;
305     }
306     button.setEnabled(presentation.isEnabled());
307     button.setIcon(IconLoader.getTransparentIcon(icon, 0.75f));
308     new ClickListener() {
309       @Override
310       public boolean onClick(@NotNull MouseEvent event, int clickCount) {
311         final ActionToolbar toolbar = UIUtil.getParentOfType(ActionToolbar.class, button);
312         actionPerformed(AnActionEvent.createFromAnAction(QuickFixAction.this,
313                                                          event,
314                                                          ActionPlaces.UNKNOWN,
315                                                          toolbar == null ? DataManager.getInstance().getDataContext(button) : toolbar.getToolbarDataContext()));
316         return true;
317       }
318     }.installOn(button);
319     JPanel panel = new JPanel();
320     panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
321     panel.setBorder(IdeBorderFactory.createEmptyBorder(7, 0, 8, 0));
322     panel.add(button);
323     return panel;
324   }
325
326   private class PerformFixesTask extends PerformFixesModalTask {
327     @NotNull private final GlobalInspectionContextImpl myContext;
328     @NotNull
329     private final Set<PsiElement> myIgnoredElements;
330
331     PerformFixesTask(@NotNull Project project,
332                      @NotNull CommonProblemDescriptor[] descriptors,
333                      @NotNull Set<PsiElement> ignoredElements,
334                      @NotNull SequentialModalProgressTask task,
335                      @NotNull GlobalInspectionContextImpl context) {
336       super(project, descriptors, task);
337       myContext = context;
338       myIgnoredElements = ignoredElements;
339     }
340
341     @Override
342     protected void applyFix(Project project, CommonProblemDescriptor descriptor) {
343       if (descriptor instanceof ProblemDescriptor && 
344           ((ProblemDescriptor)descriptor).getStartElement() == null &&
345           ((ProblemDescriptor)descriptor).getEndElement() == null) {
346         if (LOG.isDebugEnabled()) {
347           LOG.debug("Invalidated psi for " + descriptor);
348         }
349         return;
350       }
351
352       try {
353         QuickFixAction.this.applyFix(myProject, myContext, new CommonProblemDescriptor[]{descriptor}, myIgnoredElements);
354       }
355       catch (Throwable e) {
356         LOG.error(e);
357       }
358     }
359   }
360 }