Merge branch 'master' of git.labs.intellij.net:idea/community
[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.HighlightInfo;
23 import com.intellij.codeInsight.daemon.impl.LocalInspectionsPass;
24 import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass;
25 import com.intellij.codeInsight.hint.HintManagerImpl;
26 import com.intellij.codeInsight.intention.IntentionAction;
27 import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
28 import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
29 import com.intellij.codeInsight.template.impl.TemplateState;
30 import com.intellij.idea.ActionsBundle;
31 import com.intellij.lang.annotation.HighlightSeverity;
32 import com.intellij.lang.injection.InjectedLanguageManager;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.command.CommandProcessor;
35 import com.intellij.openapi.diagnostic.Logger;
36 import com.intellij.openapi.editor.Document;
37 import com.intellij.openapi.editor.Editor;
38 import com.intellij.openapi.fileEditor.FileDocumentManager;
39 import com.intellij.openapi.progress.ProgressIndicator;
40 import com.intellij.openapi.progress.ProgressManager;
41 import com.intellij.openapi.progress.Task;
42 import com.intellij.openapi.project.IndexNotReadyException;
43 import com.intellij.openapi.project.Project;
44 import com.intellij.openapi.util.Pair;
45 import com.intellij.openapi.util.TextRange;
46 import com.intellij.openapi.vfs.VirtualFile;
47 import com.intellij.psi.PsiCodeFragment;
48 import com.intellij.psi.PsiDocumentManager;
49 import com.intellij.psi.PsiElement;
50 import com.intellij.psi.PsiFile;
51 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
52 import com.intellij.util.IncorrectOperationException;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 import javax.swing.*;
57
58 /**
59  * @author mike
60  */
61 public class ShowIntentionActionsHandler implements CodeInsightActionHandler {
62   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler");
63
64   public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
65     PsiDocumentManager.getInstance(project).commitAllDocuments();
66
67     if (HintManagerImpl.getInstanceImpl().performCurrentQuestionAction()) return;
68
69     //intentions check isWritable before modification: if (!file.isWritable()) return;
70     if (file instanceof PsiCodeFragment) return;
71
72     TemplateState state = TemplateManagerImpl.getTemplateState(editor);
73     if (state != null && !state.isFinished()) return;
74
75     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(project);
76     codeAnalyzer.autoImportReferenceAtCursor(editor, file); //let autoimport complete
77
78     ShowIntentionsPass.IntentionsInfo intentions = new ShowIntentionsPass.IntentionsInfo();
79     ShowIntentionsPass.getActionsToShow(editor, file, intentions, -1);
80     
81     if (!codeAnalyzer.isAllAnalysisFinished(file)) {
82       runPassesAndShowIntentions(project, editor, file, intentions);
83     }
84     else if (!intentions.isEmpty()) {
85       IntentionHintComponent.showIntentionHint(project, file, editor, intentions, true);
86     }
87   }
88
89   private static void runPassesAndShowIntentions(final Project project, final Editor editor, final PsiFile file, final ShowIntentionsPass.IntentionsInfo intentions) {
90     intentions.errorFixesToShow.clear();
91     intentions.inspectionFixesToShow.clear();
92     final VirtualFile virtualFile = file.getVirtualFile();
93     if (virtualFile == null) return;
94     final Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
95     final int offset = editor.getCaretModel().getOffset();
96     final PsiElement element = file.findElementAt(offset);
97     if (element == null) return;
98     final Task.Backgroundable task = new Task.Backgroundable(project, ActionsBundle.message("action.ShowIntentionActions.text"), true) {
99       public void run(@NotNull final ProgressIndicator indicator) {
100         ApplicationManager.getApplication().runReadAction(new Runnable() {
101           public void run() {
102             final TextRange textRange = element.getTextRange();
103             final LocalInspectionsPass pass = new LocalInspectionsPass(file, document, textRange.getStartOffset(), textRange.getEndOffset());
104             pass.collectInformation(indicator);
105             for (HighlightInfo info : pass.getHighlights()) {
106               if (info.quickFixActionRanges != null) {
107                 final boolean isError = info.getSeverity() == HighlightSeverity.ERROR;
108                 for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> actionRanges : info.quickFixActionRanges) {
109                   if (actionRanges.second.contains(offset)) {
110                     final IntentionAction action = actionRanges.first.getAction();
111                     boolean available = action instanceof PsiElementBaseIntentionAction ?
112                                         ((PsiElementBaseIntentionAction)action).isAvailable(project, editor, element) :
113                                         action.isAvailable(project, editor, file);
114                     if (available) {
115                       if (isError) {
116                         intentions.errorFixesToShow.add(actionRanges.first);
117                       }
118                       else {
119                         intentions.inspectionFixesToShow.add(actionRanges.first);
120                       }
121                     }
122                   }
123                 }
124               }
125             }
126
127           }
128         });
129         SwingUtilities.invokeLater(new Runnable(){
130           public void run() {
131             if (editor.getComponent().isDisplayable()) {
132               if (!intentions.isEmpty()) {
133                 IntentionHintComponent.showIntentionHint(project, file, editor, intentions, true);
134               }
135             }
136           }
137         });
138       }
139     };
140     ProgressManager.getInstance().run(task);
141   }
142
143   public boolean startInWriteAction() {
144     return false;
145   }
146
147   // returns editor,file where the action is available or null if there are none
148   @Nullable
149   public static Pair<PsiFile, Editor> availableFor(PsiFile file, final Editor editor, final IntentionAction action, final PsiElement element) {
150     if (!file.isValid()) return null;
151     final Project project = file.getProject();
152
153     final Editor editorToApply;
154     final PsiFile fileToApply;
155
156     int offset = editor.getCaretModel().getOffset();
157     PsiElement injected = InjectedLanguageManager.getInstance(project).findInjectedElementAt(file, offset);
158     boolean inProject = file.getManager().isInProject(file);
159     if (injected != null) {
160       PsiFile injectedFile = injected.getContainingFile();
161       Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile);
162
163       if (isAvailableHere(injectedEditor, injectedFile, injected, inProject, action)) {
164         editorToApply = injectedEditor;
165         fileToApply = injectedFile;
166       }
167       else if (!isAvailableHere(editor, file, element, inProject, action)) {
168         return null;
169       }
170       else {
171         editorToApply = editor;
172         fileToApply = file;
173       }
174     }
175     else if (!isAvailableHere(editor, file, element, inProject, action)) {
176       return null;
177     }
178     else {
179       editorToApply = editor;
180       fileToApply = file;
181     }
182     return Pair.create(fileToApply, editorToApply);
183   }
184   
185   private static boolean isAvailableHere(Editor editor, PsiFile psiFile, PsiElement psiElement, boolean inProject, IntentionAction action) {
186     try {
187       Project project = psiFile.getProject();
188       if (action instanceof PsiElementBaseIntentionAction) {
189         if (!inProject || !((PsiElementBaseIntentionAction)action).isAvailable(project, editor, psiElement)) return false;
190       }
191       else if (!action.isAvailable(project, editor, psiFile)) {
192         return false;
193       }
194     }
195     catch (IndexNotReadyException e) {
196       return false;
197     }
198     return true;
199   }
200   
201   public static void chooseActionAndInvoke(PsiFile file, final Editor editor, final IntentionAction action, final String text) {
202     final Project project = file.getProject();
203
204     int offset = editor.getCaretModel().getOffset();
205     PsiElement element = file.findElementAt(offset);
206     Pair<PsiFile, Editor> pair = availableFor(file, editor, action, element);
207     if (pair == null) return;
208     final Editor editorToApply = pair.second;
209     final PsiFile fileToApply = pair.first;
210
211     Runnable runnable = new Runnable() {
212       public void run() {
213         try {
214           action.invoke(project, editorToApply, fileToApply);
215         }
216         catch (IncorrectOperationException e) {
217           LOG.error(e);
218         }
219         DaemonCodeAnalyzer.getInstance(project).updateVisibleHighlighters(editor);
220       }
221     };
222
223     if (action.startInWriteAction()) {
224       final Runnable _runnable = runnable;
225       runnable = new Runnable() {
226         public void run() {
227           ApplicationManager.getApplication().runWriteAction(_runnable);
228         }
229       };
230     }
231
232     CommandProcessor.getInstance().executeCommand(project, runnable, text, null);
233   }
234 }