84588369bd9a2b5d67c5eaeb77757bbbc6e00398
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / daemon / impl / ShowAutoImportPass.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.CodeInsightSettings;
21 import com.intellij.codeInsight.daemon.DaemonBundle;
22 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
23 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
24 import com.intellij.codeInsight.daemon.ReferenceImporter;
25 import com.intellij.codeInsight.intention.IntentionAction;
26 import com.intellij.codeInspection.HintAction;
27 import com.intellij.lang.annotation.HighlightSeverity;
28 import com.intellij.openapi.actionSystem.ActionManager;
29 import com.intellij.openapi.actionSystem.IdeActions;
30 import com.intellij.openapi.application.Application;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.application.TransactionGuard;
33 import com.intellij.openapi.editor.Document;
34 import com.intellij.openapi.editor.Editor;
35 import com.intellij.openapi.extensions.Extensions;
36 import com.intellij.openapi.keymap.KeymapUtil;
37 import com.intellij.openapi.progress.ProgressIndicator;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.util.Pair;
40 import com.intellij.openapi.util.TextRange;
41 import com.intellij.psi.PsiElement;
42 import com.intellij.psi.PsiFile;
43 import com.intellij.psi.PsiReference;
44 import com.intellij.util.Processor;
45 import org.jetbrains.annotations.NotNull;
46
47 import java.util.ArrayList;
48 import java.util.List;
49
50 public class ShowAutoImportPass extends TextEditorHighlightingPass {
51   private final Editor myEditor;
52
53   private final PsiFile myFile;
54
55   private final int myStartOffset;
56   private final int myEndOffset;
57
58   public ShowAutoImportPass(@NotNull Project project, @NotNull final PsiFile file, @NotNull Editor editor) {
59     super(project, editor.getDocument(), false);
60     ApplicationManager.getApplication().assertIsDispatchThread();
61
62     myEditor = editor;
63
64     TextRange range = VisibleHighlightingPassFactory.calculateVisibleRange(myEditor);
65     myStartOffset = range.getStartOffset();
66     myEndOffset = range.getEndOffset();
67
68     myFile = file;
69   }
70
71   @Override
72   public void doCollectInformation(@NotNull ProgressIndicator progress) {
73   }
74
75   @Override
76   public void doApplyInformationToEditor() {
77     TransactionGuard.submitTransaction(myProject, this::addImports);
78   }
79
80   public void addImports() {
81     if (!isValid()) return;
82
83     Application application = ApplicationManager.getApplication();
84     application.assertIsDispatchThread();
85     if (!application.isUnitTestMode() && !myEditor.getContentComponent().hasFocus()) return;
86     int caretOffset = myEditor.getCaretModel().getOffset();
87     importUnambiguousImports(caretOffset);
88     List<HighlightInfo> visibleHighlights = getVisibleHighlights(myStartOffset, myEndOffset, myProject, myEditor);
89
90     for (int i = visibleHighlights.size() - 1; i >= 0; i--) {
91       HighlightInfo info = visibleHighlights.get(i);
92       if (info.startOffset <= caretOffset && showAddImportHint(info)) return;
93     }
94
95     for (HighlightInfo visibleHighlight : visibleHighlights) {
96       if (visibleHighlight.startOffset > caretOffset && showAddImportHint(visibleHighlight)) return;
97     }
98   }
99
100   private void importUnambiguousImports(final int caretOffset) {
101     if (!DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled()) return;
102     if (!DaemonCodeAnalyzer.getInstance(myProject).isImportHintsEnabled(myFile)) return;
103     final CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
104     if (!codeInsightSettings.ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY &&
105         !codeInsightSettings.ADD_MEMBER_IMPORTS_ON_THE_FLY) return;
106
107     Document document = getDocument();
108     final List<HighlightInfo> infos = new ArrayList<HighlightInfo>();
109     DaemonCodeAnalyzerEx.processHighlights(document, myProject, null, 0, document.getTextLength(), new Processor<HighlightInfo>() {
110       @Override
111       public boolean process(HighlightInfo info) {
112         if (!info.hasHint() || info.getSeverity() != HighlightSeverity.ERROR) {
113           return true;
114         }
115         PsiReference reference = myFile.findReferenceAt(info.getActualStartOffset());
116         if (reference != null && reference.getElement().getTextRange().containsOffset(caretOffset)) return true;
117         infos.add(info);
118         return true;
119       }
120     });
121
122     ReferenceImporter[] importers = Extensions.getExtensions(ReferenceImporter.EP_NAME);
123     for (HighlightInfo info : infos) {
124       for(ReferenceImporter importer: importers) {
125         if (importer.autoImportReferenceAt(myEditor, myFile, info.getActualStartOffset())) break;
126       }
127     }
128   }
129
130   @NotNull
131   private static List<HighlightInfo> getVisibleHighlights(final int startOffset, final int endOffset, Project project, final Editor editor) {
132     final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>();
133     DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, null, startOffset, endOffset, new Processor<HighlightInfo>() {
134       @Override
135       public boolean process(HighlightInfo info) {
136         if (info.hasHint() && !editor.getFoldingModel().isOffsetCollapsed(info.startOffset)) {
137           highlights.add(info);
138         }
139         return true;
140       }
141     });
142     return highlights;
143   }
144
145   private boolean showAddImportHint(HighlightInfo info) {
146     if (!DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled()) return false;
147     if (!DaemonCodeAnalyzer.getInstance(myProject).isImportHintsEnabled(myFile)) return false;
148     PsiElement element = myFile.findElementAt(info.startOffset);
149     if (element == null || !element.isValid()) return false;
150
151     final List<Pair<HighlightInfo.IntentionActionDescriptor, TextRange>> list = info.quickFixActionRanges;
152     for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> pair : list) {
153       final IntentionAction action = pair.getFirst().getAction();
154       if (action instanceof HintAction && action.isAvailable(myProject, myEditor, myFile)) {
155         return ((HintAction)action).showHint(myEditor);
156       }
157     }
158     return false;
159   }
160
161   public static String getMessage(final boolean multiple, final String name) {
162     final String messageKey = multiple ? "import.popup.multiple" : "import.popup.text";
163     String hintText = DaemonBundle.message(messageKey, name);
164     hintText += " " + KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS));
165     return hintText;
166   }
167 }