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