Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / intention / impl / ShowIntentionActionsHandler.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.CodeInsightActionHandler;
20 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
21 import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl;
22 import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass;
23 import com.intellij.codeInsight.hint.HintManagerImpl;
24 import com.intellij.codeInsight.intention.IntentionAction;
25 import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
26 import com.intellij.codeInsight.lookup.LookupEx;
27 import com.intellij.codeInsight.lookup.LookupManager;
28 import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
29 import com.intellij.codeInsight.template.impl.TemplateState;
30 import com.intellij.codeInspection.SuppressIntentionActionFromFix;
31 import com.intellij.featureStatistics.FeatureUsageTracker;
32 import com.intellij.featureStatistics.FeatureUsageTrackerImpl;
33 import com.intellij.injected.editor.EditorWindow;
34 import com.intellij.lang.injection.InjectedLanguageManager;
35 import com.intellij.openapi.application.ApplicationManager;
36 import com.intellij.openapi.command.CommandProcessor;
37 import com.intellij.openapi.diagnostic.Logger;
38 import com.intellij.openapi.editor.Editor;
39 import com.intellij.openapi.project.IndexNotReadyException;
40 import com.intellij.openapi.project.Project;
41 import com.intellij.openapi.util.Pair;
42 import com.intellij.psi.PsiCodeFragment;
43 import com.intellij.psi.PsiDocumentManager;
44 import com.intellij.psi.PsiElement;
45 import com.intellij.psi.PsiFile;
46 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
47 import com.intellij.util.IncorrectOperationException;
48 import com.intellij.util.PairProcessor;
49 import com.intellij.util.ThreeState;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52
53 /**
54  * @author mike
55  */
56 public class ShowIntentionActionsHandler implements CodeInsightActionHandler {
57   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler");
58
59   @Override
60   public void invoke(@NotNull final Project project, @NotNull Editor editor, @NotNull PsiFile file) {
61     PsiDocumentManager.getInstance(project).commitAllDocuments();
62     if (editor instanceof EditorWindow) {
63       editor = ((EditorWindow)editor).getDelegate();
64       file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file);
65     }
66
67     final LookupEx lookup = LookupManager.getActiveLookup(editor);
68     if (lookup != null) {
69       lookup.showElementActions();
70       return;
71     }
72
73     if (HintManagerImpl.getInstanceImpl().performCurrentQuestionAction()) return;
74
75     //intentions check isWritable before modification: if (!file.isWritable()) return;
76     if (file instanceof PsiCodeFragment) return;
77
78     TemplateState state = TemplateManagerImpl.getTemplateState(editor);
79     if (state != null && !state.isFinished()) {
80       return;
81     }
82
83     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(project);
84     codeAnalyzer.autoImportReferenceAtCursor(editor, file); //let autoimport complete
85
86     ShowIntentionsPass.IntentionsInfo intentions = new ShowIntentionsPass.IntentionsInfo();
87     ShowIntentionsPass.getActionsToShow(editor, file, intentions, -1);
88
89     if (!intentions.isEmpty()) {
90       IntentionHintComponent.showIntentionHint(project, file, editor, intentions, true);
91     }
92   }
93
94   @Override
95   public boolean startInWriteAction() {
96     return false;
97   }
98
99   // returns editor,file where the action is available or null if there are none
100   public static boolean availableFor(@NotNull PsiFile file, @NotNull Editor editor, @NotNull IntentionAction action) {
101     if (!file.isValid()) return false;
102
103     int offset = editor.getCaretModel().getOffset();
104     PsiElement element = file.findElementAt(offset);
105     boolean inProject = file.getManager().isInProject(file);
106     return isAvailableHere(editor, file, element, inProject, action);
107   }
108
109   private static boolean isAvailableHere(Editor editor, PsiFile psiFile, PsiElement psiElement, boolean inProject, IntentionAction action) {
110     try {
111       Project project = psiFile.getProject();
112       if (action instanceof SuppressIntentionActionFromFix) {
113         final ThreeState shouldBeAppliedToInjectionHost = ((SuppressIntentionActionFromFix)action).isShouldBeAppliedToInjectionHost();
114         if (editor instanceof EditorWindow && shouldBeAppliedToInjectionHost == ThreeState.YES) {
115           return false;
116         }
117         if (!(editor instanceof EditorWindow) && shouldBeAppliedToInjectionHost == ThreeState.NO) {
118           return false;
119         }
120       }
121       
122       if (action instanceof PsiElementBaseIntentionAction) {
123         if (!inProject || psiElement == null || !((PsiElementBaseIntentionAction)action).isAvailable(project, editor, psiElement)) return false;
124       }
125       else if (!action.isAvailable(project, editor, psiFile)) {
126         return false;
127       }
128     }
129     catch (IndexNotReadyException e) {
130       return false;
131     }
132     return true;
133   }
134
135   @Nullable
136   public static Pair<PsiFile,Editor> chooseBetweenHostAndInjected(@NotNull PsiFile hostFile, @NotNull Editor hostEditor, @NotNull PairProcessor<PsiFile, Editor> predicate) {
137     Editor editorToApply = null;
138     PsiFile fileToApply = null;
139
140     int offset = hostEditor.getCaretModel().getOffset();
141     PsiFile injectedFile = InjectedLanguageUtil.findInjectedPsiNoCommit(hostFile, offset);
142     if (injectedFile != null) {
143       Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(hostEditor, injectedFile);
144       if (predicate.process(injectedFile, injectedEditor)) {
145         editorToApply = injectedEditor;
146         fileToApply = injectedFile;
147       }
148     }
149
150     if (editorToApply == null && predicate.process(hostFile, hostEditor)) {
151       editorToApply = hostEditor;
152       fileToApply = hostFile;
153     }
154     if (editorToApply == null) return null;
155     return Pair.create(fileToApply, editorToApply);
156   }
157
158   public static boolean chooseActionAndInvoke(@NotNull PsiFile hostFile,
159                                               @NotNull final Editor hostEditor,
160                                               @NotNull final IntentionAction action,
161                                               @NotNull String text) {
162     if (!hostFile.isValid()) return false;
163     final Project project = hostFile.getProject();
164     FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.quickFix");
165     ((FeatureUsageTrackerImpl)FeatureUsageTracker.getInstance()).getFixesStats().registerInvocation();
166
167     Pair<PsiFile, Editor> pair = chooseBetweenHostAndInjected(hostFile, hostEditor, new PairProcessor<PsiFile, Editor>() {
168       @Override
169       public boolean process(PsiFile psiFile, Editor editor) {
170         return availableFor(psiFile, editor, action);
171       }
172     });
173     if (pair == null) return false;
174     final Editor editorToApply = pair.second;
175     final PsiFile fileToApply = pair.first;
176
177     Runnable runnable = new Runnable() {
178       @Override
179       public void run() {
180         try {
181           action.invoke(project, editorToApply, fileToApply);
182         }
183         catch (IncorrectOperationException e) {
184           LOG.error(e);
185         }
186         DaemonCodeAnalyzer.getInstance(project).updateVisibleHighlighters(hostEditor);
187       }
188     };
189
190     if (action.startInWriteAction()) {
191       final Runnable _runnable = runnable;
192       runnable = new Runnable() {
193         @Override
194         public void run() {
195           ApplicationManager.getApplication().runWriteAction(_runnable);
196         }
197       };
198     }
199
200     CommandProcessor.getInstance().executeCommand(project, runnable, text, null);
201     return true;
202   }
203 }