IDEADEV-40882 add static imports when needed
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / intention / impl / IntentionListStep.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.intention.impl;
18
19 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
20 import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass;
21 import com.intellij.codeInsight.hint.HintManager;
22 import com.intellij.codeInsight.intention.EmptyIntentionAction;
23 import com.intellij.codeInsight.intention.IntentionAction;
24 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
25 import com.intellij.codeInspection.ex.QuickFixWrapper;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.ui.popup.*;
30 import com.intellij.openapi.util.Comparing;
31 import com.intellij.openapi.util.Iconable;
32 import com.intellij.psi.*;
33 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
34 import com.intellij.psi.util.PsiUtilBase;
35 import gnu.trove.THashSet;
36 import gnu.trove.TObjectHashingStrategy;
37 import org.jetbrains.annotations.NotNull;
38
39 import javax.swing.*;
40 import java.util.*;
41
42 /**
43 * @author cdr
44 */
45 class IntentionListStep implements ListPopupStep<IntentionActionWithTextCaching>, SpeedSearchFilter<IntentionActionWithTextCaching> {
46   private final Set<IntentionActionWithTextCaching> myCachedIntentions = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
47   private final Set<IntentionActionWithTextCaching> myCachedErrorFixes = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
48   private final Set<IntentionActionWithTextCaching> myCachedInspectionFixes = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
49   private final Set<IntentionActionWithTextCaching> myCachedGutters = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
50   private final IntentionManagerSettings mySettings;
51   private final IntentionHintComponent myIntentionHintComponent;
52   private final Editor myEditor;
53   private final PsiFile myFile;
54   private final Project myProject;
55   private static final TObjectHashingStrategy<IntentionActionWithTextCaching> ACTION_TEXT_AND_CLASS_EQUALS = new TObjectHashingStrategy<IntentionActionWithTextCaching>() {
56     public int computeHashCode(final IntentionActionWithTextCaching object) {
57       return object.getText().hashCode();
58     }
59
60     public boolean equals(final IntentionActionWithTextCaching o1, final IntentionActionWithTextCaching o2) {
61       return o1.getAction().getClass() == o2.getAction().getClass() && o1.getText().equals(o2.getText());
62     }
63   };
64
65   IntentionListStep(IntentionHintComponent intentionHintComponent, ShowIntentionsPass.IntentionsInfo intentions, Editor editor, PsiFile file,
66                     Project project) {
67     myIntentionHintComponent = intentionHintComponent;
68     myEditor = editor;
69     myFile = file;
70     myProject = project;
71     mySettings = IntentionManagerSettings.getInstance();
72     updateActions(intentions);
73   }
74
75   //true if something changed
76   boolean updateActions(ShowIntentionsPass.IntentionsInfo intentions) {
77     boolean result = wrapActionsTo(intentions.errorFixesToShow, myCachedErrorFixes);
78     result &= wrapActionsTo(intentions.inspectionFixesToShow, myCachedInspectionFixes);
79     result &= wrapActionsTo(intentions.intentionsToShow, myCachedIntentions);
80     result &= wrapActionsTo(intentions.guttersToShow, myCachedGutters);
81     return !result;
82   }
83
84   private boolean wrapActionsTo(final List<HighlightInfo.IntentionActionDescriptor> descriptors, final Set<IntentionActionWithTextCaching> cachedActions) {
85     boolean result = true;
86     for (HighlightInfo.IntentionActionDescriptor descriptor : descriptors) {
87       IntentionAction action = descriptor.getAction();
88       IntentionActionWithTextCaching cachedAction = new IntentionActionWithTextCaching(action, descriptor.getDisplayName(), descriptor.getIcon());
89       result &= !cachedActions.add(cachedAction);
90       final int caretOffset = myEditor.getCaretModel().getOffset();
91       final int fileOffset = caretOffset > 0 && caretOffset == myFile.getTextLength() ? caretOffset - 1 : caretOffset;
92       PsiElement element;
93       if (myFile instanceof PsiCompiledElement) {
94         element = myFile;
95       }
96       else if (PsiDocumentManager.getInstance(myProject).isUncommited(myEditor.getDocument())) {
97         //???
98         FileViewProvider viewProvider = myFile.getViewProvider();
99         element = viewProvider.findElementAt(fileOffset, viewProvider.getBaseLanguage());
100       }
101       else {
102         element = InjectedLanguageUtil.findElementAtNoCommit(myFile, fileOffset);
103       }
104       final List<IntentionAction> options;
105       if (element != null && (options = descriptor.getOptions(element)) != null) {
106         for (IntentionAction option : options) {
107           boolean isErrorFix = myCachedErrorFixes.contains(new IntentionActionWithTextCaching(option, option.getText()));
108           if (isErrorFix) {
109             cachedAction.addErrorFix(option);
110           }
111           boolean isInspectionFix = myCachedInspectionFixes.contains(new IntentionActionWithTextCaching(option, option.getText()));
112           if (isInspectionFix) {
113             cachedAction.addInspectionFix(option);
114           }
115           else {
116             cachedAction.addIntention(option);
117           }
118         }
119       }
120     }
121     result &= removeInvalidActions(cachedActions);
122     return result;
123   }
124
125   private boolean removeInvalidActions(final Collection<IntentionActionWithTextCaching> cachedActions) {
126     boolean result = true;
127     Iterator<IntentionActionWithTextCaching> iterator = cachedActions.iterator();
128     while (iterator.hasNext()) {
129       IntentionActionWithTextCaching cachedAction = iterator.next();
130       IntentionAction action = cachedAction.getAction();
131       if (!myFile.isValid() || !action.isAvailable(myProject, myEditor, myFile)) {
132         iterator.remove();
133         result = false;
134       }
135     }
136     return result;
137   }
138
139   public String getTitle() {
140     return null;
141   }
142
143   public boolean isSelectable(final IntentionActionWithTextCaching action) {
144     return true;
145   }
146
147   public PopupStep onChosen(final IntentionActionWithTextCaching action, final boolean finalChoice) {
148     if (finalChoice && !(action.getAction() instanceof EmptyIntentionAction)) {
149       applyAction(action);
150       return FINAL_CHOICE;
151     }
152
153     if (hasSubstep(action)) {
154       return getSubStep(action);
155     }
156
157     return FINAL_CHOICE;
158   }
159
160   private void applyAction(final IntentionActionWithTextCaching cachedAction) {
161     ApplicationManager.getApplication().invokeLater(new Runnable() {
162       public void run() {
163         HintManager.getInstance().hideAllHints();
164         ApplicationManager.getApplication().invokeLater(new Runnable() {
165           public void run() {
166             if (myProject.isDisposed()) return;
167             PsiDocumentManager.getInstance(myProject).commitAllDocuments();
168             final PsiFile file = PsiUtilBase.getPsiFileInEditor(myEditor, myProject);
169             if (file == null) {
170               return;
171             }
172
173             ShowIntentionActionsHandler.chooseActionAndInvoke(file, myEditor, cachedAction.getAction(), cachedAction.getText());
174           }
175         });
176       }
177     });
178   }
179
180   private PopupStep getSubStep(final IntentionActionWithTextCaching action) {
181     ShowIntentionsPass.IntentionsInfo intentions = new ShowIntentionsPass.IntentionsInfo();
182     for (final IntentionAction optionIntention : action.getOptionIntentions()) {
183       intentions.intentionsToShow.add(new HighlightInfo.IntentionActionDescriptor(optionIntention, null));
184     }
185     for (final IntentionAction optionFix : action.getOptionErrorFixes()) {
186       intentions.errorFixesToShow.add(new HighlightInfo.IntentionActionDescriptor(optionFix, null));
187     }
188     for (final IntentionAction optionFix : action.getOptionInspectionFixes()) {
189       intentions.inspectionFixesToShow.add(new HighlightInfo.IntentionActionDescriptor(optionFix, null));
190     }
191
192     return new IntentionListStep(myIntentionHintComponent, intentions,myEditor, myFile, myProject){
193       public String getTitle() {
194         return action.getToolName();
195       }
196     };
197   }
198
199   public boolean hasSubstep(final IntentionActionWithTextCaching action) {
200     return action.getOptionIntentions().size() + action.getOptionErrorFixes().size() > 0;
201   }
202
203   @NotNull
204   public List<IntentionActionWithTextCaching> getValues() {
205     List<IntentionActionWithTextCaching> result = new ArrayList<IntentionActionWithTextCaching>(myCachedErrorFixes);
206     result.addAll(myCachedInspectionFixes);
207     result.addAll(myCachedIntentions);
208     result.addAll(myCachedGutters);
209     Collections.sort(result, new Comparator<IntentionActionWithTextCaching>() {
210       public int compare(final IntentionActionWithTextCaching o1, final IntentionActionWithTextCaching o2) {
211         final IntentionAction action1 = o1.getAction();
212         final IntentionAction action2 = o2.getAction();
213         if (action1 instanceof EmptyIntentionAction && !(action2 instanceof EmptyIntentionAction)) return 1;
214         if (action2 instanceof EmptyIntentionAction && !(action1 instanceof EmptyIntentionAction)) return -1;
215         int weight1 = myCachedErrorFixes.contains(o1) ? 2 : myCachedInspectionFixes.contains(o1) ? 1 : 0;
216         int weight2 = myCachedErrorFixes.contains(o2) ? 2 : myCachedInspectionFixes.contains(o2) ? 1 : 0;
217         if (weight1 != weight2) {
218           return weight2 - weight1;
219         }
220         return Comparing.compare(o1.getText(), o2.getText());
221       }
222     });
223     return result;
224   }
225
226   @NotNull
227   public String getTextFor(final IntentionActionWithTextCaching action) {
228     return action.getAction().getText();
229   }
230
231   public Icon getIconFor(final IntentionActionWithTextCaching value) {
232     if (value.getIcon() != null) {
233       return value.getIcon();
234     }
235
236     final IntentionAction action = value.getAction();
237
238     //custom icon
239     if (action instanceof QuickFixWrapper) {
240       final QuickFixWrapper quickFix = (QuickFixWrapper)action;
241       if (quickFix.getFix() instanceof Iconable) {
242         final Icon icon = ((Iconable)quickFix.getFix()).getIcon(0);
243         if (icon != null) {
244           return icon;
245         }
246       }
247     }
248
249     if (mySettings.isShowLightBulb(action)) {
250       if (myCachedErrorFixes.contains(value)) {
251         return IntentionHintComponent.ourQuickFixIcon;
252       } else if (myCachedInspectionFixes.contains(value)) {
253         return IntentionHintComponent.ourBulbIcon;
254       }
255       else {
256         return IntentionHintComponent.ourIntentionIcon;
257       }
258     }
259     else {
260       if (myCachedErrorFixes.contains(value)) {
261         return IntentionHintComponent.ourQuickFixOffIcon;
262       }
263       else {
264         return IntentionHintComponent.ourIntentionOffIcon;
265       }
266     }
267   }
268
269   public void canceled() {
270     myIntentionHintComponent.canceled(this);
271   }
272
273   public int getDefaultOptionIndex() { return 0; }
274   public ListSeparator getSeparatorAbove(final IntentionActionWithTextCaching value) {
275     List<IntentionActionWithTextCaching> values = getValues();
276     int index = values.indexOf(value);
277     if (index == 0) return null;
278     IntentionActionWithTextCaching prev = values.get(index - 1);
279
280     if (myCachedErrorFixes.contains(value) != myCachedErrorFixes.contains(prev)
281       || myCachedInspectionFixes.contains(value) != myCachedInspectionFixes.contains(prev)
282       || myCachedIntentions.contains(value) != myCachedIntentions.contains(prev)
283       || value.getAction() instanceof EmptyIntentionAction != prev.getAction() instanceof EmptyIntentionAction) {
284       return new ListSeparator();
285     }
286     return null;
287   }
288   public boolean isMnemonicsNavigationEnabled() { return false; }
289   public MnemonicNavigationFilter<IntentionActionWithTextCaching> getMnemonicNavigationFilter() { return null; }
290   public boolean isSpeedSearchEnabled() { return true; }
291   public boolean isAutoSelectionEnabled() { return false; }
292   public SpeedSearchFilter<IntentionActionWithTextCaching> getSpeedSearchFilter() { return this; }
293
294   //speed search filter
295   public boolean canBeHidden(final IntentionActionWithTextCaching value) { return true;}
296   public String getIndexedString(final IntentionActionWithTextCaching value) { return getTextFor(value);}
297 }