IDEADEV-40882 add static imports when needed
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / daemon / impl / ShowIntentionsPass.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.codeInsight.daemon.impl;
18
19 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
20 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
21 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
22 import com.intellij.codeInsight.hint.HintManager;
23 import com.intellij.codeInsight.intention.AbstractIntentionAction;
24 import com.intellij.codeInsight.intention.IntentionAction;
25 import com.intellij.codeInsight.intention.IntentionManager;
26 import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
27 import com.intellij.codeInsight.intention.impl.IntentionHintComponent;
28 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
29 import com.intellij.codeInsight.lookup.LookupManager;
30 import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
31 import com.intellij.codeInsight.template.impl.TemplateState;
32 import com.intellij.ide.DataManager;
33 import com.intellij.lang.annotation.HighlightSeverity;
34 import com.intellij.lang.injection.InjectedLanguageManager;
35 import com.intellij.openapi.actionSystem.ActionManager;
36 import com.intellij.openapi.actionSystem.AnAction;
37 import com.intellij.openapi.actionSystem.AnActionEvent;
38 import com.intellij.openapi.actionSystem.Presentation;
39 import com.intellij.openapi.application.ApplicationManager;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.editor.Document;
42 import com.intellij.openapi.editor.Editor;
43 import com.intellij.openapi.editor.LogicalPosition;
44 import com.intellij.openapi.editor.markup.GutterIconRenderer;
45 import com.intellij.openapi.fileEditor.FileDocumentManager;
46 import com.intellij.openapi.progress.ProgressIndicator;
47 import com.intellij.openapi.project.IndexNotReadyException;
48 import com.intellij.openapi.project.Project;
49 import com.intellij.openapi.ui.popup.JBPopupFactory;
50 import com.intellij.psi.IntentionFilterOwner;
51 import com.intellij.psi.PsiDocumentManager;
52 import com.intellij.psi.PsiElement;
53 import com.intellij.psi.PsiFile;
54 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
55 import com.intellij.ui.awt.RelativePoint;
56 import com.intellij.util.IncorrectOperationException;
57 import com.intellij.util.containers.ContainerUtil;
58 import org.jetbrains.annotations.NotNull;
59
60 import java.awt.*;
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.Iterator;
64 import java.util.List;
65
66 public class ShowIntentionsPass extends TextEditorHighlightingPass {
67   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.ShowIntentionsPass");
68   private final Editor myEditor;
69
70   private final PsiFile myFile;
71   private final int myPassIdToShowIntentionsFor;
72   private final IntentionsInfo myIntentionsInfo = new IntentionsInfo();
73   private volatile boolean myShowBulb;
74   private volatile boolean myHasToRecreate;
75
76   public static class IntentionsInfo {
77     public final List<HighlightInfo.IntentionActionDescriptor> intentionsToShow = new ArrayList<HighlightInfo.IntentionActionDescriptor>();
78     public final List<HighlightInfo.IntentionActionDescriptor> errorFixesToShow = new ArrayList<HighlightInfo.IntentionActionDescriptor>();
79     public final List<HighlightInfo.IntentionActionDescriptor> inspectionFixesToShow = new ArrayList<HighlightInfo.IntentionActionDescriptor>();
80     public final List<HighlightInfo.IntentionActionDescriptor> guttersToShow = new ArrayList<HighlightInfo.IntentionActionDescriptor>();
81
82     public void filterActions(@NotNull IntentionFilterOwner.IntentionActionsFilter actionsFilter) {
83       filter(intentionsToShow, actionsFilter);
84       filter(errorFixesToShow, actionsFilter);
85       filter(inspectionFixesToShow, actionsFilter);
86       filter(guttersToShow, actionsFilter);
87     }
88
89     private static void filter(List<HighlightInfo.IntentionActionDescriptor> descriptors,
90                         IntentionFilterOwner.IntentionActionsFilter actionsFilter) {
91       for (Iterator<HighlightInfo.IntentionActionDescriptor> it = descriptors.iterator(); it.hasNext();) {
92           HighlightInfo.IntentionActionDescriptor actionDescriptor = it.next();
93           if (!actionsFilter.isAvailable(actionDescriptor.getAction())) it.remove();
94         }
95     }
96
97     public boolean isEmpty() {
98       return intentionsToShow.isEmpty() && errorFixesToShow.isEmpty() && inspectionFixesToShow.isEmpty() && guttersToShow.isEmpty();
99     }
100   }
101
102   ShowIntentionsPass(@NotNull Project project, @NotNull Editor editor, int passId) {
103     super(project, editor.getDocument(), false);
104     myPassIdToShowIntentionsFor = passId;
105     ApplicationManager.getApplication().assertIsDispatchThread();
106
107     myEditor = editor;
108
109     myFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
110     assert myFile != null : FileDocumentManager.getInstance().getFile(myEditor.getDocument());
111   }
112
113   public void doCollectInformation(ProgressIndicator progress) {
114     if (!myEditor.getContentComponent().hasFocus()) return;
115     TemplateState state = TemplateManagerImpl.getTemplateState(myEditor);
116     if (state == null || state.isFinished()) {
117       DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
118       getIntentionActionsToShow();
119       updateActions(codeAnalyzer);
120     }
121   }
122
123   public void doApplyInformationToEditor() {
124     ApplicationManager.getApplication().assertIsDispatchThread();
125
126     if (!myEditor.getContentComponent().hasFocus()) return;
127
128     // do not show intentions if caret is outside visible area
129     LogicalPosition caretPos = myEditor.getCaretModel().getLogicalPosition();
130     Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
131     Point xy = myEditor.logicalPositionToXY(caretPos);
132     if (!visibleArea.contains(xy)) return;
133
134     TemplateState state = TemplateManagerImpl.getTemplateState(myEditor);
135     if (myShowBulb && (state == null || state.isFinished()) && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint()) {
136       IntentionHintComponent hintComponent = IntentionHintComponent.showIntentionHint(myProject, myFile, myEditor, myIntentionsInfo, false);
137       if (myHasToRecreate) {
138         hintComponent.recreate();
139       }
140       ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject)).setLastIntentionHint(hintComponent);
141     }
142   }
143
144   private void getIntentionActionsToShow() {
145     if (LookupManager.getInstance(myProject).getActiveLookup() != null) return;
146
147     getActionsToShow(myEditor, myFile, myIntentionsInfo, myPassIdToShowIntentionsFor);
148     if (myFile instanceof IntentionFilterOwner) {
149       final IntentionFilterOwner.IntentionActionsFilter actionsFilter = ((IntentionFilterOwner)myFile).getIntentionActionsFilter();
150       if (actionsFilter == null) return;
151       if (actionsFilter != IntentionFilterOwner.IntentionActionsFilter.EVERYTHING_AVAILABLE) {
152         myIntentionsInfo.filterActions(actionsFilter);
153       }
154     }
155
156     if (myIntentionsInfo.isEmpty()) {
157       return;
158     }
159     myShowBulb = !myIntentionsInfo.guttersToShow.isEmpty();
160     if (!myShowBulb) {
161       for (HighlightInfo.IntentionActionDescriptor descriptor : ContainerUtil.concat(myIntentionsInfo.errorFixesToShow, myIntentionsInfo.inspectionFixesToShow,myIntentionsInfo.intentionsToShow)) {
162         final IntentionAction action = descriptor.getAction();
163         if (IntentionManagerSettings.getInstance().isShowLightBulb(action)) {
164           myShowBulb = true;
165           break;
166         }
167       }
168     }
169   }
170
171   private void updateActions(DaemonCodeAnalyzerImpl codeAnalyzer) {
172     IntentionHintComponent hintComponent = codeAnalyzer.getLastIntentionHint();
173     if (!myShowBulb || hintComponent == null) {
174       return;
175     }
176     Boolean result = hintComponent.updateActions(myIntentionsInfo);
177     if (result == null) {
178       // reshow all
179     }
180     else if (result == Boolean.FALSE) {
181       myHasToRecreate = true;
182     }
183     else {
184       myShowBulb = false;  // nothing to apply
185     }
186   }
187
188   public static void getActionsToShow(@NotNull final Editor editor, @NotNull final PsiFile psiFile, @NotNull IntentionsInfo intentions, int passIdToShowIntentionsFor) {
189     final PsiElement psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset());
190     LOG.assertTrue(psiElement == null || psiElement.isValid(), psiElement);
191     final boolean isInProject = psiFile.getManager().isInProject(psiFile);
192
193     int offset = editor.getCaretModel().getOffset();
194     Project project = psiFile.getProject();
195     
196     PsiElement injected = InjectedLanguageManager.getInstance(project).findInjectedElementAt(psiFile, offset);
197     PsiFile injectedFile;
198     Editor injectedEditor;
199     if (injected != null) {
200       injectedFile = injected.getContainingFile();
201       injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile);
202     }
203     else {
204       injectedFile = null;
205       injectedEditor = null;
206     }
207     
208     for (IntentionAction action : IntentionManager.getInstance().getIntentionActions()) {
209       if (injectedFile != null && isAvailableHere(injectedEditor, injectedFile, injected, isInProject, project, action) ||
210           isAvailableHere(editor, psiFile, psiElement, isInProject, project, action)
211         ) {
212         List<IntentionAction> enableDisableIntentionAction = new ArrayList<IntentionAction>();
213         enableDisableIntentionAction.add(new IntentionHintComponent.EnableDisableIntentionAction(action));
214         intentions.intentionsToShow.add(new HighlightInfo.IntentionActionDescriptor(action, enableDisableIntentionAction, null));
215       }
216     }
217
218     List<HighlightInfo.IntentionActionDescriptor> actions = QuickFixAction.getAvailableActions(editor, psiFile, passIdToShowIntentionsFor);
219     final DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project);
220     final Document document = editor.getDocument();
221     HighlightInfo infoAtCursor = ((DaemonCodeAnalyzerImpl)codeAnalyzer).findHighlightByOffset(document, offset, true);
222     if (infoAtCursor == null || infoAtCursor.getSeverity() == HighlightSeverity.ERROR) {
223       intentions.errorFixesToShow.addAll(actions);
224     }
225     else {
226       intentions.inspectionFixesToShow.addAll(actions);
227     }
228     final int line = document.getLineNumber(offset);
229     final List<HighlightInfo> infoList = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightSeverity.INFORMATION, project,
230                                                                           document.getLineStartOffset(line),
231                                                                           document.getLineEndOffset(line));
232     for (HighlightInfo info : infoList) {
233       final GutterIconRenderer renderer = info.getGutterIconRenderer();
234       if (renderer != null) {
235         final AnAction action = renderer.getClickAction();
236         if (action != null) {
237           final String text = renderer.getTooltipText();
238           if (text != null) {
239             final IntentionAction actionAdapter = new AbstractIntentionAction() {
240               public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
241                 final RelativePoint relativePoint = JBPopupFactory.getInstance().guessBestPopupLocation(editor);
242                 action.actionPerformed(
243                   new AnActionEvent(relativePoint.toMouseEvent(), DataManager.getInstance().getDataContext(), text, new Presentation(),
244                                     ActionManager.getInstance(), 0));
245               }
246
247               @NotNull
248               public String getText() {
249                 return text;
250               }
251             };
252             intentions.guttersToShow.add(new HighlightInfo.IntentionActionDescriptor(actionAdapter, Collections.<IntentionAction>emptyList(), text, renderer.getIcon()));
253           }
254         }
255       }
256     }
257   }
258
259   private static boolean isAvailableHere(Editor editor, PsiFile psiFile, PsiElement psiElement, boolean inProject, Project project,
260                                          IntentionAction action) {
261     try {
262       if (action instanceof PsiElementBaseIntentionAction) {
263         if (!inProject || !((PsiElementBaseIntentionAction)action).isAvailable(project, editor, psiElement)) return false;
264       }
265       else if (!action.isAvailable(project, editor, psiFile)) {
266         return false;
267       }
268     }
269     catch (IndexNotReadyException e) {
270       return false;
271     }
272     return true;
273   }
274 }