2 * Copyright 2000-2016 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.HighlightDisplayLevel;
20 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
21 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
22 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
23 import com.intellij.codeInsight.daemon.impl.analysis.HighlightingLevelManager;
24 import com.intellij.codeInsight.hint.HintManager;
25 import com.intellij.codeInsight.intention.IntentionAction;
26 import com.intellij.codeInsight.intention.IntentionManager;
27 import com.intellij.codeInsight.intention.impl.IntentionHintComponent;
28 import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler;
29 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
30 import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
31 import com.intellij.codeInsight.template.impl.TemplateState;
32 import com.intellij.codeInspection.*;
33 import com.intellij.codeInspection.actions.CleanupAllIntention;
34 import com.intellij.codeInspection.ex.InspectionToolWrapper;
35 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
36 import com.intellij.codeInspection.ex.QuickFixWrapper;
37 import com.intellij.concurrency.JobLauncher;
38 import com.intellij.lang.annotation.HighlightSeverity;
39 import com.intellij.openapi.application.ApplicationManager;
40 import com.intellij.openapi.diagnostic.Attachment;
41 import com.intellij.openapi.diagnostic.Logger;
42 import com.intellij.openapi.editor.Document;
43 import com.intellij.openapi.editor.Editor;
44 import com.intellij.openapi.editor.LogicalPosition;
45 import com.intellij.openapi.editor.RangeMarker;
46 import com.intellij.openapi.editor.ex.EditorEx;
47 import com.intellij.openapi.editor.ex.MarkupModelEx;
48 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
49 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
50 import com.intellij.openapi.fileEditor.FileDocumentManager;
51 import com.intellij.openapi.progress.ProgressIndicator;
52 import com.intellij.openapi.project.DumbService;
53 import com.intellij.openapi.project.Project;
54 import com.intellij.openapi.util.Pair;
55 import com.intellij.openapi.util.Segment;
56 import com.intellij.openapi.util.TextRange;
57 import com.intellij.openapi.vfs.VirtualFile;
58 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
59 import com.intellij.psi.PsiDocumentManager;
60 import com.intellij.psi.PsiElement;
61 import com.intellij.psi.PsiFile;
62 import com.intellij.psi.PsiWhiteSpace;
63 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
64 import com.intellij.util.CommonProcessors;
65 import com.intellij.util.Processor;
66 import com.intellij.util.Processors;
67 import com.intellij.util.containers.ContainerUtil;
68 import org.jetbrains.annotations.NonNls;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
73 import java.util.ArrayList;
74 import java.util.Iterator;
75 import java.util.List;
78 public class ShowIntentionsPass extends TextEditorHighlightingPass {
79 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.ShowIntentionsPass");
80 private final Editor myEditor;
82 private final PsiFile myFile;
83 private final int myPassIdToShowIntentionsFor;
84 private final IntentionsInfo myIntentionsInfo = new IntentionsInfo();
85 private volatile boolean myShowBulb;
86 private volatile boolean myHasToRecreate;
89 public static List<HighlightInfo.IntentionActionDescriptor> getAvailableFixes(@NotNull final Editor editor,
90 @NotNull final PsiFile file,
92 final int offset = ((EditorEx)editor).getExpectedCaretOffset();
93 final Project project = file.getProject();
95 List<HighlightInfo> infos = new ArrayList<>();
96 DaemonCodeAnalyzerImpl.processHighlightsNearOffset(editor.getDocument(), project, HighlightSeverity.INFORMATION, offset, true,
97 new CommonProcessors.CollectProcessor<>(infos));
98 List<HighlightInfo.IntentionActionDescriptor> result = new ArrayList<>();
99 infos.forEach(info-> addAvailableFixesForGroups(info, editor, file, result, passId, offset));
103 private static void addAvailableFixesForGroups(@NotNull HighlightInfo info,
104 @NotNull Editor editor,
105 @NotNull PsiFile file,
106 @NotNull List<HighlightInfo.IntentionActionDescriptor> outList,
109 if (info.quickFixActionMarkers == null) return;
110 if (group != -1 && group != info.getGroup()) return;
111 boolean fixRangeIsNotEmpty = !info.getFixTextRange().isEmpty();
112 Editor injectedEditor = null;
113 PsiFile injectedFile = null;
114 for (Pair<HighlightInfo.IntentionActionDescriptor, RangeMarker> pair : info.quickFixActionMarkers) {
115 HighlightInfo.IntentionActionDescriptor actionInGroup = pair.first;
116 RangeMarker range = pair.second;
117 if (!range.isValid() || fixRangeIsNotEmpty && isEmpty(range)) continue;
119 if (DumbService.isDumb(file.getProject()) && !DumbService.isDumbAware(actionInGroup.getAction())) {
123 int start = range.getStartOffset();
124 int end = range.getEndOffset();
125 final Project project = file.getProject();
126 if (start > offset || offset > end) {
131 if (info.isFromInjection()) {
132 if (injectedEditor == null) {
133 injectedFile = InjectedLanguageUtil.findInjectedPsiNoCommit(file, offset);
134 injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile);
136 editorToUse = injectedEditor;
137 fileToUse = injectedFile;
140 editorToUse = editor;
143 if (actionInGroup.getAction().isAvailable(project, editorToUse, fileToUse)) {
144 outList.add(actionInGroup);
149 private static boolean isEmpty(@NotNull Segment segment) {
150 return segment.getEndOffset() <= segment.getStartOffset();
153 public static class IntentionsInfo {
154 public final List<HighlightInfo.IntentionActionDescriptor> intentionsToShow = ContainerUtil.createLockFreeCopyOnWriteList();
155 public final List<HighlightInfo.IntentionActionDescriptor> errorFixesToShow = ContainerUtil.createLockFreeCopyOnWriteList();
156 public final List<HighlightInfo.IntentionActionDescriptor> inspectionFixesToShow = ContainerUtil.createLockFreeCopyOnWriteList();
157 public final List<HighlightInfo.IntentionActionDescriptor> guttersToShow = ContainerUtil.createLockFreeCopyOnWriteList();
158 public final List<HighlightInfo.IntentionActionDescriptor> notificationActionsToShow = ContainerUtil.createLockFreeCopyOnWriteList();
160 void filterActions(@Nullable PsiFile psiFile) {
161 IntentionActionFilter[] filters = IntentionActionFilter.EXTENSION_POINT_NAME.getExtensions();
162 filter(intentionsToShow, psiFile, filters);
163 filter(errorFixesToShow, psiFile, filters);
164 filter(inspectionFixesToShow, psiFile, filters);
165 filter(guttersToShow, psiFile, filters);
166 filter(notificationActionsToShow, psiFile, filters);
169 private static void filter(@NotNull List<HighlightInfo.IntentionActionDescriptor> descriptors,
170 @Nullable PsiFile psiFile,
171 @NotNull IntentionActionFilter[] filters) {
172 for (Iterator<HighlightInfo.IntentionActionDescriptor> it = descriptors.iterator(); it.hasNext(); ) {
173 HighlightInfo.IntentionActionDescriptor actionDescriptor = it.next();
174 for (IntentionActionFilter filter : filters) {
175 if (!filter.accept(actionDescriptor.getAction(), psiFile)) {
183 public boolean isEmpty() {
184 return intentionsToShow.isEmpty() && errorFixesToShow.isEmpty() && inspectionFixesToShow.isEmpty() && guttersToShow.isEmpty() &&
185 notificationActionsToShow.isEmpty();
190 public String toString() {
192 "Errors: " + errorFixesToShow + "; " +
193 "Inspection fixes: " + inspectionFixesToShow + "; " +
194 "Intentions: " + intentionsToShow + "; " +
195 "Gutters: " + guttersToShow +
196 "Notifications: " + notificationActionsToShow;
200 ShowIntentionsPass(@NotNull Project project, @NotNull Editor editor, int passId) {
201 super(project, editor.getDocument(), false);
202 myPassIdToShowIntentionsFor = passId;
203 ApplicationManager.getApplication().assertIsDispatchThread();
207 PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
209 myFile = documentManager.getPsiFile(myEditor.getDocument());
210 assert myFile != null : FileDocumentManager.getInstance().getFile(myEditor.getDocument());
214 public void doCollectInformation(@NotNull ProgressIndicator progress) {
215 if (!ApplicationManager.getApplication().isUnitTestMode() && !myEditor.getContentComponent().hasFocus()) return;
216 TemplateState state = TemplateManagerImpl.getTemplateState(myEditor);
217 if (state != null && !state.isFinished()) return;
218 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
219 getIntentionActionsToShow();
220 updateActions(codeAnalyzer);
224 public void doApplyInformationToEditor() {
225 ApplicationManager.getApplication().assertIsDispatchThread();
227 if (!ApplicationManager.getApplication().isUnitTestMode() && !myEditor.getContentComponent().hasFocus()) return;
229 // do not show intentions if caret is outside visible area
230 LogicalPosition caretPos = myEditor.getCaretModel().getLogicalPosition();
231 Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
232 Point xy = myEditor.logicalPositionToXY(caretPos);
233 if (!visibleArea.contains(xy)) return;
235 TemplateState state = TemplateManagerImpl.getTemplateState(myEditor);
236 if (myShowBulb && (state == null || state.isFinished()) && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(false)) {
237 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
238 codeAnalyzer.setLastIntentionHint(myProject, myFile, myEditor, myIntentionsInfo, myHasToRecreate);
242 private void getIntentionActionsToShow() {
243 getActionsToShow(myEditor, myFile, myIntentionsInfo, myPassIdToShowIntentionsFor);
245 if (myIntentionsInfo.isEmpty()) {
248 myShowBulb = !myIntentionsInfo.guttersToShow.isEmpty() || !myIntentionsInfo.notificationActionsToShow.isEmpty() ||
249 ContainerUtil.exists(ContainerUtil.concat(myIntentionsInfo.errorFixesToShow, myIntentionsInfo.inspectionFixesToShow,myIntentionsInfo.intentionsToShow),
250 descriptor -> IntentionManagerSettings.getInstance().isShowLightBulb(descriptor.getAction()));
253 private static boolean appendCleanupCode(@NotNull List<HighlightInfo.IntentionActionDescriptor> actionDescriptors, @NotNull PsiFile file) {
254 for (HighlightInfo.IntentionActionDescriptor descriptor : actionDescriptors) {
255 if (descriptor.canCleanup(file)) {
256 final ArrayList<IntentionAction> options = new ArrayList<>();
257 options.add(EditCleanupProfileIntentionAction.INSTANCE);
258 options.add(CleanupOnScopeIntention.INSTANCE);
259 actionDescriptors.add(new HighlightInfo.IntentionActionDescriptor(CleanupAllIntention.INSTANCE, options, "Code Cleanup Options"));
266 private void updateActions(@NotNull DaemonCodeAnalyzerImpl codeAnalyzer) {
267 IntentionHintComponent hintComponent = codeAnalyzer.getLastIntentionHint();
268 if (!myShowBulb || hintComponent == null || !hintComponent.isForEditor(myEditor)) {
271 IntentionHintComponent.PopupUpdateResult result = hintComponent.updateActions(myIntentionsInfo);
272 if (result == IntentionHintComponent.PopupUpdateResult.HIDE_AND_RECREATE) {
275 else if (result == IntentionHintComponent.PopupUpdateResult.CHANGED_INVISIBLE) {
276 myHasToRecreate = true;
280 public static void getActionsToShow(@NotNull final Editor hostEditor,
281 @NotNull final PsiFile hostFile,
282 @NotNull final IntentionsInfo intentions,
283 int passIdToShowIntentionsFor) {
284 final PsiElement psiElement = hostFile.findElementAt(hostEditor.getCaretModel().getOffset());
285 LOG.assertTrue(psiElement == null || psiElement.isValid(), psiElement);
287 int offset = hostEditor.getCaretModel().getOffset();
288 final Project project = hostFile.getProject();
290 List<HighlightInfo.IntentionActionDescriptor> fixes = getAvailableFixes(hostEditor, hostFile, passIdToShowIntentionsFor);
291 final DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project);
292 final Document hostDocument = hostEditor.getDocument();
293 HighlightInfo infoAtCursor = ((DaemonCodeAnalyzerImpl)codeAnalyzer).findHighlightByOffset(hostDocument, offset, true);
294 if (infoAtCursor == null) {
295 intentions.errorFixesToShow.addAll(fixes);
298 final boolean isError = infoAtCursor.getSeverity() == HighlightSeverity.ERROR;
299 for (HighlightInfo.IntentionActionDescriptor fix : fixes) {
300 if (fix.isError() && isError) {
301 intentions.errorFixesToShow.add(fix);
304 intentions.inspectionFixesToShow.add(fix);
309 for (final IntentionAction action : IntentionManager.getInstance().getAvailableIntentionActions()) {
310 Pair<PsiFile, Editor> place =
311 ShowIntentionActionsHandler.chooseBetweenHostAndInjected(hostFile, hostEditor,
312 (psiFile, editor) -> ShowIntentionActionsHandler.availableFor(psiFile, editor, action));
315 List<IntentionAction> enableDisableIntentionAction = new ArrayList<>();
316 enableDisableIntentionAction.add(new IntentionHintComponent.EnableDisableIntentionAction(action));
317 enableDisableIntentionAction.add(new IntentionHintComponent.EditIntentionSettingsAction(action));
318 HighlightInfo.IntentionActionDescriptor descriptor = new HighlightInfo.IntentionActionDescriptor(action, enableDisableIntentionAction, null);
319 if (!fixes.contains(descriptor)) {
320 intentions.intentionsToShow.add(descriptor);
325 if (HighlightingLevelManager.getInstance(project).shouldInspect(hostFile)) {
326 PsiElement intentionElement = psiElement;
327 int intentionOffset = offset;
328 if (psiElement instanceof PsiWhiteSpace && offset == psiElement.getTextRange().getStartOffset() && offset > 0) {
329 final PsiElement prev = hostFile.findElementAt(offset - 1);
330 if (prev != null && prev.isValid()) {
331 intentionElement = prev;
332 intentionOffset = offset - 1;
335 if (intentionElement != null && intentionElement.getManager().isInProject(intentionElement)) {
336 collectIntentionsFromDoNotShowLeveledInspections(project, hostFile, intentionElement, intentionOffset, intentions);
340 final int line = hostDocument.getLineNumber(offset);
341 MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(hostDocument, project, true);
342 List<RangeHighlighterEx> result = new ArrayList<>();
343 Processor<RangeHighlighterEx> processor = Processors.cancelableCollectProcessor(result);
344 model.processRangeHighlightersOverlappingWith(hostDocument.getLineStartOffset(line),
345 hostDocument.getLineEndOffset(line),
348 GutterIntentionAction.addActions(hostEditor, intentions, project, result);
350 boolean cleanup = appendCleanupCode(intentions.inspectionFixesToShow, hostFile);
352 appendCleanupCode(intentions.errorFixesToShow, hostFile);
355 EditorNotificationActions.collectDescriptorsForEditor(hostEditor, intentions.notificationActionsToShow);
357 intentions.filterActions(hostFile);
361 * Can be invoked in EDT, each inspection should be fast
363 private static void collectIntentionsFromDoNotShowLeveledInspections(@NotNull final Project project,
364 @NotNull final PsiFile hostFile,
365 PsiElement psiElement,
367 @NotNull final IntentionsInfo intentions) {
368 if (psiElement != null) {
369 if (!psiElement.isPhysical()) {
370 VirtualFile virtualFile = hostFile.getVirtualFile();
371 String text = hostFile.getText();
372 LOG.error("not physical: '" + psiElement.getText() + "' @" + offset + psiElement.getTextRange() +
373 " elem:" + psiElement + " (" + psiElement.getClass().getName() + ")" +
374 " in:" + psiElement.getContainingFile() + " host:" + hostFile + "(" + hostFile.getClass().getName() + ")",
375 new Attachment(virtualFile != null ? virtualFile.getPresentableUrl() : "null", text != null ? text : "null"));
377 if (DumbService.isDumb(project)) {
381 final List<LocalInspectionToolWrapper> intentionTools = new ArrayList<>();
382 final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile();
383 final InspectionToolWrapper[] tools = profile.getInspectionTools(hostFile);
384 for (InspectionToolWrapper toolWrapper : tools) {
385 if (toolWrapper instanceof LocalInspectionToolWrapper && !((LocalInspectionToolWrapper)toolWrapper).isUnfair()) {
386 final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName());
387 if (profile.isToolEnabled(key, hostFile) &&
388 HighlightDisplayLevel.DO_NOT_SHOW.equals(profile.getErrorLevel(key, hostFile))) {
389 intentionTools.add((LocalInspectionToolWrapper)toolWrapper);
394 if (!intentionTools.isEmpty()) {
395 final List<PsiElement> elements = new ArrayList<>();
396 PsiElement el = psiElement;
399 if (el instanceof PsiFile) break;
403 final Set<String> dialectIds = InspectionEngine.calcElementDialectIds(elements);
404 final LocalInspectionToolSession session = new LocalInspectionToolSession(hostFile, 0, hostFile.getTextLength());
405 final Processor<LocalInspectionToolWrapper> processor = toolWrapper -> {
406 final LocalInspectionTool localInspectionTool = toolWrapper.getTool();
407 final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName());
408 final String displayName = toolWrapper.getDisplayName();
409 final ProblemsHolder holder = new ProblemsHolder(InspectionManager.getInstance(project), hostFile, true) {
411 public void registerProblem(@NotNull ProblemDescriptor problemDescriptor) {
412 super.registerProblem(problemDescriptor);
413 if (problemDescriptor instanceof ProblemDescriptorBase) {
414 final TextRange range = ((ProblemDescriptorBase)problemDescriptor).getTextRange();
415 if (range != null && range.contains(offset)) {
416 final QuickFix[] fixes = problemDescriptor.getFixes();
418 for (int k = 0; k < fixes.length; k++) {
419 final IntentionAction intentionAction = QuickFixWrapper.wrap(problemDescriptor, k);
420 final HighlightInfo.IntentionActionDescriptor actionDescriptor =
421 new HighlightInfo.IntentionActionDescriptor(intentionAction, null, displayName, null,
422 key, null, HighlightSeverity.INFORMATION);
423 intentions.intentionsToShow.add(actionDescriptor);
430 InspectionEngine.createVisitorAndAcceptElements(localInspectionTool, holder, true, session, elements,
431 dialectIds, InspectionEngine.getDialectIdsSpecifiedForTool(toolWrapper));
432 localInspectionTool.inspectionFinished(session, holder);
435 JobLauncher.getInstance().invokeConcurrentlyUnderProgress(intentionTools, new DaemonProgressIndicator(), false, processor);