2 * Copyright 2000-2013 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.daemon.impl;
19 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
20 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
21 import com.intellij.codeInsight.hint.HintManager;
22 import com.intellij.codeInsight.intention.AbstractIntentionAction;
23 import com.intellij.codeInsight.intention.IntentionAction;
24 import com.intellij.codeInsight.intention.IntentionManager;
25 import com.intellij.codeInsight.intention.impl.IntentionHintComponent;
26 import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler;
27 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
28 import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
29 import com.intellij.codeInsight.template.impl.TemplateState;
30 import com.intellij.codeInspection.actions.CleanupAllIntention;
31 import com.intellij.ide.DataManager;
32 import com.intellij.lang.annotation.HighlightSeverity;
33 import com.intellij.openapi.actionSystem.ActionManager;
34 import com.intellij.openapi.actionSystem.AnAction;
35 import com.intellij.openapi.actionSystem.AnActionEvent;
36 import com.intellij.openapi.actionSystem.Presentation;
37 import com.intellij.openapi.application.ApplicationManager;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.editor.Document;
40 import com.intellij.openapi.editor.Editor;
41 import com.intellij.openapi.editor.LogicalPosition;
42 import com.intellij.openapi.editor.RangeMarker;
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.Condition;
49 import com.intellij.openapi.util.Pair;
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.PairProcessor;
58 import com.intellij.util.Processor;
59 import com.intellij.util.containers.ContainerUtil;
60 import org.jetbrains.annotations.NonNls;
61 import org.jetbrains.annotations.NotNull;
64 import java.util.ArrayList;
65 import java.util.Collections;
66 import java.util.Iterator;
67 import java.util.List;
69 public class ShowIntentionsPass extends TextEditorHighlightingPass {
70 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.ShowIntentionsPass");
71 private final Editor myEditor;
73 private final PsiFile myFile;
74 private final int myPassIdToShowIntentionsFor;
75 private final IntentionsInfo myIntentionsInfo = new IntentionsInfo();
76 private volatile boolean myShowBulb;
77 private volatile boolean myHasToRecreate;
80 public static List<HighlightInfo.IntentionActionDescriptor> getAvailableActions(@NotNull final Editor editor, @NotNull final PsiFile file, final int passId) {
81 final int offset = editor.getCaretModel().getOffset();
82 final Project project = file.getProject();
84 final List<HighlightInfo.IntentionActionDescriptor> result = new ArrayList<HighlightInfo.IntentionActionDescriptor>();
85 DaemonCodeAnalyzerImpl.processHighlightsNearOffset(editor.getDocument(), project, HighlightSeverity.INFORMATION, offset, true, new Processor<HighlightInfo>() {
87 public boolean process(HighlightInfo info) {
88 addAvailableActionsForGroups(info, editor, file, result, passId, offset);
95 private static void addAvailableActionsForGroups(@NotNull HighlightInfo info,
96 @NotNull Editor editor,
97 @NotNull PsiFile file,
98 @NotNull List<HighlightInfo.IntentionActionDescriptor> outList,
101 if (info.quickFixActionMarkers == null) return;
102 if (group != -1 && group != info.getGroup()) return;
103 Editor injectedEditor = null;
104 PsiFile injectedFile = null;
105 for (Pair<HighlightInfo.IntentionActionDescriptor, RangeMarker> pair : info.quickFixActionMarkers) {
106 HighlightInfo.IntentionActionDescriptor actionInGroup = pair.first;
107 RangeMarker range = pair.second;
108 if (!range.isValid()) continue;
109 int start = range.getStartOffset();
110 int end = range.getEndOffset();
111 final Project project = file.getProject();
112 if (start > offset || offset > end) {
117 if (info.isFromInjection()) {
118 if (injectedEditor == null) {
119 injectedFile = InjectedLanguageUtil.findInjectedPsiNoCommit(file, offset);
120 injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile);
122 editorToUse = injectedEditor;
123 fileToUse = injectedFile;
126 editorToUse = editor;
129 if (actionInGroup.getAction().isAvailable(project, editorToUse, fileToUse)) {
130 outList.add(actionInGroup);
135 public static class IntentionsInfo {
136 public final List<HighlightInfo.IntentionActionDescriptor> intentionsToShow = ContainerUtil.createLockFreeCopyOnWriteList();
137 public final List<HighlightInfo.IntentionActionDescriptor> errorFixesToShow = ContainerUtil.createLockFreeCopyOnWriteList();
138 public final List<HighlightInfo.IntentionActionDescriptor> inspectionFixesToShow = ContainerUtil.createLockFreeCopyOnWriteList();
139 public final List<HighlightInfo.IntentionActionDescriptor> guttersToShow = ContainerUtil.createLockFreeCopyOnWriteList();
141 public void filterActions(@NotNull IntentionFilterOwner.IntentionActionsFilter actionsFilter) {
142 filter(intentionsToShow, actionsFilter);
143 filter(errorFixesToShow, actionsFilter);
144 filter(inspectionFixesToShow, actionsFilter);
145 filter(guttersToShow, actionsFilter);
148 private static void filter(@NotNull List<HighlightInfo.IntentionActionDescriptor> descriptors,
149 @NotNull IntentionFilterOwner.IntentionActionsFilter actionsFilter) {
150 for (Iterator<HighlightInfo.IntentionActionDescriptor> it = descriptors.iterator(); it.hasNext();) {
151 HighlightInfo.IntentionActionDescriptor actionDescriptor = it.next();
152 if (!actionsFilter.isAvailable(actionDescriptor.getAction())) it.remove();
156 public boolean isEmpty() {
157 return intentionsToShow.isEmpty() && errorFixesToShow.isEmpty() && inspectionFixesToShow.isEmpty() && guttersToShow.isEmpty();
162 public String toString() {
163 return "Intentions: " + intentionsToShow + "; Errors: " + errorFixesToShow + "; Inspection fixes: " + inspectionFixesToShow + "; Gutters: " + guttersToShow;
167 ShowIntentionsPass(@NotNull Project project, @NotNull Editor editor, int passId) {
168 super(project, editor.getDocument(), false);
169 myPassIdToShowIntentionsFor = passId;
170 ApplicationManager.getApplication().assertIsDispatchThread();
174 PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
176 myFile = documentManager.getPsiFile(myEditor.getDocument());
177 assert myFile != null : FileDocumentManager.getInstance().getFile(myEditor.getDocument());
181 public void doCollectInformation(@NotNull ProgressIndicator progress) {
182 if (!ApplicationManager.getApplication().isUnitTestMode() && !myEditor.getContentComponent().hasFocus()) return;
183 TemplateState state = TemplateManagerImpl.getTemplateState(myEditor);
184 if (state != null && !state.isFinished()) return;
185 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
186 getIntentionActionsToShow();
187 updateActions(codeAnalyzer);
191 public void doApplyInformationToEditor() {
192 ApplicationManager.getApplication().assertIsDispatchThread();
194 if (!ApplicationManager.getApplication().isUnitTestMode() && !myEditor.getContentComponent().hasFocus()) return;
196 // do not show intentions if caret is outside visible area
197 LogicalPosition caretPos = myEditor.getCaretModel().getLogicalPosition();
198 Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
199 Point xy = myEditor.logicalPositionToXY(caretPos);
200 if (!visibleArea.contains(xy)) return;
202 TemplateState state = TemplateManagerImpl.getTemplateState(myEditor);
203 if (myShowBulb && (state == null || state.isFinished()) && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(false)) {
204 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
205 codeAnalyzer.setLastIntentionHint(myProject, myFile, myEditor, myIntentionsInfo, myHasToRecreate);
209 private void getIntentionActionsToShow() {
210 getActionsToShow(myEditor, myFile, myIntentionsInfo, myPassIdToShowIntentionsFor);
211 if (myFile instanceof IntentionFilterOwner) {
212 final IntentionFilterOwner.IntentionActionsFilter actionsFilter = ((IntentionFilterOwner)myFile).getIntentionActionsFilter();
213 if (actionsFilter == null) return;
214 if (actionsFilter != IntentionFilterOwner.IntentionActionsFilter.EVERYTHING_AVAILABLE) {
215 myIntentionsInfo.filterActions(actionsFilter);
219 if (myIntentionsInfo.isEmpty()) {
222 myShowBulb = !myIntentionsInfo.guttersToShow.isEmpty() ||
223 ContainerUtil.exists(ContainerUtil.concat(myIntentionsInfo.errorFixesToShow, myIntentionsInfo.inspectionFixesToShow,myIntentionsInfo.intentionsToShow), new Condition<HighlightInfo.IntentionActionDescriptor>() {
225 public boolean value(HighlightInfo.IntentionActionDescriptor descriptor) {
226 return IntentionManagerSettings.getInstance().isShowLightBulb(descriptor.getAction());
231 private static boolean appendCleanupCode(final List<HighlightInfo.IntentionActionDescriptor> actionDescriptors, PsiFile file) {
232 for (HighlightInfo.IntentionActionDescriptor descriptor : actionDescriptors) {
233 if (descriptor.canCleanup(file)) {
234 final ArrayList<IntentionAction> options = new ArrayList<IntentionAction>();
235 options.add(EditCleanupProfileIntentionAction.INSTANCE);
236 options.add(CleanupOnScopeIntention.INSTANCE);
237 actionDescriptors.add(new HighlightInfo.IntentionActionDescriptor(CleanupAllIntention.INSTANCE, options, "Code Cleanup Options"));
244 private void updateActions(@NotNull DaemonCodeAnalyzerImpl codeAnalyzer) {
245 IntentionHintComponent hintComponent = codeAnalyzer.getLastIntentionHint();
246 if (!myShowBulb || hintComponent == null) {
249 Boolean result = hintComponent.updateActions(myIntentionsInfo);
250 if (result == null) {
253 else if (result == Boolean.FALSE) {
254 myHasToRecreate = true;
257 myShowBulb = false; // nothing to apply
261 public static void getActionsToShow(@NotNull final Editor hostEditor,
262 @NotNull final PsiFile hostFile,
263 @NotNull final IntentionsInfo intentions,
264 int passIdToShowIntentionsFor) {
265 final PsiElement psiElement = hostFile.findElementAt(hostEditor.getCaretModel().getOffset());
266 LOG.assertTrue(psiElement == null || psiElement.isValid(), psiElement);
268 int offset = hostEditor.getCaretModel().getOffset();
269 Project project = hostFile.getProject();
271 List<HighlightInfo.IntentionActionDescriptor> fixes = getAvailableActions(hostEditor, hostFile, passIdToShowIntentionsFor);
272 final DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project);
273 final Document hostDocument = hostEditor.getDocument();
274 HighlightInfo infoAtCursor = ((DaemonCodeAnalyzerImpl)codeAnalyzer).findHighlightByOffset(hostDocument, offset, true);
275 if (infoAtCursor == null) {
276 intentions.errorFixesToShow.addAll(fixes);
279 final boolean isError = infoAtCursor.getSeverity() == HighlightSeverity.ERROR;
280 for (HighlightInfo.IntentionActionDescriptor fix : fixes) {
281 if (fix.notError() || !isError) {
282 intentions.inspectionFixesToShow.add(fix);
285 intentions.errorFixesToShow.add(fix);
290 for (final IntentionAction action : IntentionManager.getInstance().getAvailableIntentionActions()) {
291 Pair<PsiFile, Editor> place =
292 ShowIntentionActionsHandler.chooseBetweenHostAndInjected(hostFile, hostEditor, new PairProcessor<PsiFile, Editor>() {
294 public boolean process(PsiFile psiFile, Editor editor) {
295 return ShowIntentionActionsHandler.availableFor(psiFile, editor, action);
300 List<IntentionAction> enableDisableIntentionAction = new ArrayList<IntentionAction>();
301 enableDisableIntentionAction.add(new IntentionHintComponent.EnableDisableIntentionAction(action));
302 enableDisableIntentionAction.add(new IntentionHintComponent.EditIntentionSettingsAction(action));
303 HighlightInfo.IntentionActionDescriptor descriptor = new HighlightInfo.IntentionActionDescriptor(action, enableDisableIntentionAction, null);
304 if (!fixes.contains(descriptor)) {
305 intentions.intentionsToShow.add(descriptor);
310 final int line = hostDocument.getLineNumber(offset);
311 DaemonCodeAnalyzerEx.processHighlights(hostDocument, project, null,
312 hostDocument.getLineStartOffset(line),
313 hostDocument.getLineEndOffset(line), new Processor<HighlightInfo>() {
315 public boolean process(HighlightInfo info) {
316 final GutterIconRenderer renderer = (GutterIconRenderer)info.getGutterIconRenderer();
317 if (renderer == null) {
320 final AnAction action = renderer.getClickAction();
321 if (action == null) {
324 final String text = renderer.getTooltipText();
328 final IntentionAction actionAdapter = new AbstractIntentionAction() {
330 public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
331 final RelativePoint relativePoint = JBPopupFactory.getInstance().guessBestPopupLocation(editor);
332 action.actionPerformed(
333 new AnActionEvent(relativePoint.toMouseEvent(), DataManager.getInstance().getDataContext(), text, new Presentation(),
334 ActionManager.getInstance(), 0));
339 public String getText() {
343 intentions.guttersToShow.add(
344 new HighlightInfo.IntentionActionDescriptor(actionAdapter, Collections.<IntentionAction>emptyList(), text, renderer.getIcon()));
349 boolean cleanup = appendCleanupCode(intentions.inspectionFixesToShow, hostFile);
351 appendCleanupCode(intentions.errorFixesToShow, hostFile);