2 * Copyright 2000-2010 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.intention.impl;
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.PreferredAction;
25 import com.intellij.codeInsight.intention.impl.config.IntentionActionWrapper;
26 import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
27 import com.intellij.codeInspection.ex.QuickFixWrapper;
28 import com.intellij.openapi.application.ApplicationManager;
29 import com.intellij.openapi.editor.Editor;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.popup.*;
32 import com.intellij.openapi.util.Comparing;
33 import com.intellij.openapi.util.Iconable;
34 import com.intellij.psi.*;
35 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
36 import com.intellij.psi.util.PsiUtilBase;
37 import gnu.trove.THashSet;
38 import gnu.trove.TObjectHashingStrategy;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
48 class IntentionListStep implements ListPopupStep<IntentionActionWithTextCaching>, SpeedSearchFilter<IntentionActionWithTextCaching> {
49 private final Set<IntentionActionWithTextCaching> myCachedIntentions = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
50 private final Set<IntentionActionWithTextCaching> myCachedErrorFixes = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
51 private final Set<IntentionActionWithTextCaching> myCachedInspectionFixes = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
52 private final Set<IntentionActionWithTextCaching> myCachedGutters = new THashSet<IntentionActionWithTextCaching>(ACTION_TEXT_AND_CLASS_EQUALS);
53 private final IntentionManagerSettings mySettings;
55 private final IntentionHintComponent myIntentionHintComponent;
56 private final Editor myEditor;
57 private final PsiFile myFile;
58 private final Project myProject;
59 private static final TObjectHashingStrategy<IntentionActionWithTextCaching> ACTION_TEXT_AND_CLASS_EQUALS = new TObjectHashingStrategy<IntentionActionWithTextCaching>() {
60 public int computeHashCode(final IntentionActionWithTextCaching object) {
61 return object.getText().hashCode();
64 public boolean equals(final IntentionActionWithTextCaching o1, final IntentionActionWithTextCaching o2) {
65 return o1.getAction().getClass() == o2.getAction().getClass() && o1.getText().equals(o2.getText());
68 private Runnable myFinalRunnable;
70 IntentionListStep(@Nullable IntentionHintComponent intentionHintComponent, ShowIntentionsPass.IntentionsInfo intentions, Editor editor, PsiFile file,
72 myIntentionHintComponent = intentionHintComponent;
76 mySettings = IntentionManagerSettings.getInstance();
77 updateActions(intentions);
80 //true if something changed
81 boolean updateActions(ShowIntentionsPass.IntentionsInfo intentions) {
82 boolean result = wrapActionsTo(intentions.errorFixesToShow, myCachedErrorFixes);
83 result &= wrapActionsTo(intentions.inspectionFixesToShow, myCachedInspectionFixes);
84 result &= wrapActionsTo(intentions.intentionsToShow, myCachedIntentions);
85 result &= wrapActionsTo(intentions.guttersToShow, myCachedGutters);
89 private boolean wrapActionsTo(final List<HighlightInfo.IntentionActionDescriptor> descriptors, final Set<IntentionActionWithTextCaching> cachedActions) {
90 final int caretOffset = myEditor.getCaretModel().getOffset();
91 final int fileOffset = caretOffset > 0 && caretOffset == myFile.getTextLength() ? caretOffset - 1 : caretOffset;
93 if (myFile instanceof PsiCompiledElement) {
96 else if (PsiDocumentManager.getInstance(myProject).isUncommited(myEditor.getDocument())) {
98 FileViewProvider viewProvider = myFile.getViewProvider();
99 element = viewProvider.findElementAt(fileOffset, viewProvider.getBaseLanguage());
102 element = InjectedLanguageUtil.findElementAtNoCommit(myFile, fileOffset);
104 boolean result = removeInvalidActions(cachedActions, element);
105 for (HighlightInfo.IntentionActionDescriptor descriptor : descriptors) {
106 IntentionAction action = descriptor.getAction();
107 if (!isAvailable(action, element)) continue;
108 if (element == null) continue;
109 IntentionActionWithTextCaching cachedAction = wrapAction(element, descriptor);
110 result &= !cachedActions.add(cachedAction);
115 IntentionActionWithTextCaching wrapAction(PsiElement element, HighlightInfo.IntentionActionDescriptor descriptor) {
116 IntentionActionWithTextCaching cachedAction = new IntentionActionWithTextCaching(descriptor);
117 final List<IntentionAction> options = descriptor.getOptions(element);
118 if (options != null) {
119 for (IntentionAction option : options) {
120 if (!option.isAvailable(myProject, myEditor, element.getContainingFile())) continue;
121 IntentionActionWithTextCaching textCaching = new IntentionActionWithTextCaching(option);
122 boolean isErrorFix = myCachedErrorFixes.contains(textCaching);
124 cachedAction.addErrorFix(option);
126 boolean isInspectionFix = myCachedInspectionFixes.contains(textCaching);
127 if (isInspectionFix) {
128 cachedAction.addInspectionFix(option);
131 cachedAction.addIntention(option);
138 private boolean removeInvalidActions(final Collection<IntentionActionWithTextCaching> cachedActions, final PsiElement element) {
139 boolean result = true;
140 Iterator<IntentionActionWithTextCaching> iterator = cachedActions.iterator();
141 while (iterator.hasNext()) {
142 IntentionActionWithTextCaching cachedAction = iterator.next();
143 IntentionAction action = cachedAction.getAction();
144 if (!isAvailable(action, element)) {
152 private boolean isAvailable(IntentionAction action, PsiElement element) {
153 return ShowIntentionActionsHandler.availableFor(myFile, myEditor, action, element) != null;
156 public String getTitle() {
160 public boolean isSelectable(final IntentionActionWithTextCaching action) {
164 public PopupStep onChosen(final IntentionActionWithTextCaching action, final boolean finalChoice) {
165 if (finalChoice && !(action.getAction() instanceof EmptyIntentionAction)) {
170 if (hasSubstep(action)) {
171 return getSubStep(action, action.getToolName());
177 public Runnable getFinalRunnable() {
178 return myFinalRunnable;
181 private void applyAction(final IntentionActionWithTextCaching cachedAction) {
182 myFinalRunnable = new Runnable() {
184 HintManager.getInstance().hideAllHints();
185 ApplicationManager.getApplication().invokeLater(new Runnable() {
187 if (myProject.isDisposed()) return;
188 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
189 final PsiFile file = PsiUtilBase.getPsiFileInEditor(myEditor, myProject);
194 ShowIntentionActionsHandler.chooseActionAndInvoke(file, myEditor, cachedAction.getAction(), cachedAction.getText());
201 IntentionListStep getSubStep(final IntentionActionWithTextCaching action, final String title) {
202 ShowIntentionsPass.IntentionsInfo intentions = new ShowIntentionsPass.IntentionsInfo();
203 for (final IntentionAction optionIntention : action.getOptionIntentions()) {
204 intentions.intentionsToShow.add(new HighlightInfo.IntentionActionDescriptor(optionIntention, getIcon(optionIntention)));
206 for (final IntentionAction optionFix : action.getOptionErrorFixes()) {
207 intentions.errorFixesToShow.add(new HighlightInfo.IntentionActionDescriptor(optionFix, getIcon(optionFix)));
209 for (final IntentionAction optionFix : action.getOptionInspectionFixes()) {
210 intentions.inspectionFixesToShow.add(new HighlightInfo.IntentionActionDescriptor(optionFix, getIcon(optionFix)));
213 return new IntentionListStep(myIntentionHintComponent, intentions,myEditor, myFile, myProject){
214 public String getTitle() {
220 private static Icon getIcon(IntentionAction optionIntention) {
221 return optionIntention instanceof Iconable ? ((Iconable)optionIntention).getIcon(0) : null;
224 public boolean hasSubstep(final IntentionActionWithTextCaching action) {
225 return action.getOptionIntentions().size() + action.getOptionErrorFixes().size() > 0;
229 public List<IntentionActionWithTextCaching> getValues() {
230 List<IntentionActionWithTextCaching> result = new ArrayList<IntentionActionWithTextCaching>(myCachedErrorFixes);
231 result.addAll(myCachedInspectionFixes);
232 result.addAll(myCachedIntentions);
233 result.addAll(myCachedGutters);
234 Collections.sort(result, new Comparator<IntentionActionWithTextCaching>() {
235 public int compare(final IntentionActionWithTextCaching o1, final IntentionActionWithTextCaching o2) {
236 int weight1 = getWeight(o1);
237 int weight2 = getWeight(o2);
238 if (weight1 != weight2) {
239 return weight2 - weight1;
241 return Comparing.compare(o1.getText(), o2.getText());
247 private int getWeight(IntentionActionWithTextCaching action) {
248 if (action.getAction() instanceof PreferredAction) {
251 else if (myCachedErrorFixes.contains(action)) {
254 else if (myCachedInspectionFixes.contains(action)) {
257 else if (action.getAction() instanceof EmptyIntentionAction) {
266 public String getTextFor(final IntentionActionWithTextCaching action) {
267 return action.getAction().getText();
270 public Icon getIconFor(final IntentionActionWithTextCaching value) {
271 if (value.getIcon() != null) {
272 return value.getIcon();
275 final IntentionAction action = value.getAction();
277 Object iconable = null;
279 if (action instanceof QuickFixWrapper) {
280 iconable = ((QuickFixWrapper)action).getFix();
281 } else if (action instanceof IntentionActionWrapper) {
282 iconable = ((IntentionActionWrapper)action).getDelegate();
285 if (iconable instanceof Iconable) {
286 final Icon icon = ((Iconable)iconable).getIcon(0);
292 if (mySettings.isShowLightBulb(action)) {
293 return myCachedErrorFixes.contains(value) ? IntentionHintComponent.ourQuickFixIcon
294 : myCachedInspectionFixes.contains(value) ? IntentionHintComponent.ourBulbIcon :
295 IntentionHintComponent.ourIntentionIcon;
298 return myCachedErrorFixes.contains(value) ? IntentionHintComponent.ourQuickFixOffIcon : IntentionHintComponent.ourIntentionOffIcon;
302 public void canceled() {
303 if (myIntentionHintComponent != null) {
304 myIntentionHintComponent.canceled(this);
308 public int getDefaultOptionIndex() { return 0; }
309 public ListSeparator getSeparatorAbove(final IntentionActionWithTextCaching value) {
310 List<IntentionActionWithTextCaching> values = getValues();
311 int index = values.indexOf(value);
312 if (index == 0) return null;
313 IntentionActionWithTextCaching prev = values.get(index - 1);
315 if (getWeight(value) != getWeight(prev)) {
316 return new ListSeparator();
320 public boolean isMnemonicsNavigationEnabled() { return false; }
321 public MnemonicNavigationFilter<IntentionActionWithTextCaching> getMnemonicNavigationFilter() { return null; }
322 public boolean isSpeedSearchEnabled() { return true; }
323 public boolean isAutoSelectionEnabled() { return false; }
324 public SpeedSearchFilter<IntentionActionWithTextCaching> getSpeedSearchFilter() { return this; }
326 //speed search filter
327 public boolean canBeHidden(final IntentionActionWithTextCaching value) { return true;}
328 public String getIndexedString(final IntentionActionWithTextCaching value) { return getTextFor(value);}