cleanup
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / intention / impl / ShowIntentionActionsHandler.java
1 /*
2  * Copyright 2000-2015 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.TransactionGuard;
36 import com.intellij.openapi.application.WriteAction;
37 import com.intellij.openapi.command.CommandProcessor;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.editor.Editor;
40 import com.intellij.openapi.project.IndexNotReadyException;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.util.Pair;
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     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(project);
74     letAutoImportComplete(editor, file, codeAnalyzer);
75
76     ShowIntentionsPass.IntentionsInfo intentions = new ShowIntentionsPass.IntentionsInfo();
77     ShowIntentionsPass.getActionsToShow(editor, file, intentions, -1);
78     IntentionHintComponent hintComponent = codeAnalyzer.getLastIntentionHint();
79     if (hintComponent != null) {
80       IntentionHintComponent.PopupUpdateResult result = hintComponent.isForEditor(editor)
81                                                         ? hintComponent.updateActions(intentions)
82                                                         : IntentionHintComponent.PopupUpdateResult.HIDE_AND_RECREATE;
83       if (result == IntentionHintComponent.PopupUpdateResult.HIDE_AND_RECREATE) {
84         hintComponent.hide();
85       }
86     }
87
88     if (HintManagerImpl.getInstanceImpl().performCurrentQuestionAction()) return;
89
90     //intentions check isWritable before modification: if (!file.isWritable()) return;
91
92     TemplateState state = TemplateManagerImpl.getTemplateState(editor);
93     if (state != null && !state.isFinished()) {
94       return;
95     }
96
97     if (!intentions.isEmpty()) {
98       IntentionHintComponent.showIntentionHint(project, file, editor, intentions, true);
99     }
100   }
101
102   private static void letAutoImportComplete(@NotNull Editor editor, @NotNull PsiFile file, DaemonCodeAnalyzerImpl codeAnalyzer) {
103     CommandProcessor.getInstance().runUndoTransparentAction(() -> codeAnalyzer.autoImportReferenceAtCursor(editor, file));
104   }
105
106   @Override
107   public boolean startInWriteAction() {
108     return false;
109   }
110
111   public static boolean availableFor(@NotNull PsiFile psiFile, @NotNull Editor editor, @NotNull IntentionAction action) {
112     if (!psiFile.isValid()) return false;
113
114     int offset = editor.getCaretModel().getOffset();
115     PsiElement psiElement = psiFile.findElementAt(offset);
116     boolean inProject = psiFile.getManager().isInProject(psiFile);
117     try {
118       Project project = psiFile.getProject();
119       if (action instanceof SuppressIntentionActionFromFix) {
120         final ThreeState shouldBeAppliedToInjectionHost = ((SuppressIntentionActionFromFix)action).isShouldBeAppliedToInjectionHost();
121         if (editor instanceof EditorWindow && shouldBeAppliedToInjectionHost == ThreeState.YES) {
122           return false;
123         }
124         if (!(editor instanceof EditorWindow) && shouldBeAppliedToInjectionHost == ThreeState.NO) {
125           return false;
126         }
127       }
128       
129       if (action instanceof PsiElementBaseIntentionAction) {
130         if (!inProject || psiElement == null || !((PsiElementBaseIntentionAction)action).isAvailable(project, editor, psiElement)) return false;
131       }
132       else if (!action.isAvailable(project, editor, psiFile)) {
133         return false;
134       }
135     }
136     catch (IndexNotReadyException e) {
137       return false;
138     }
139     return true;
140   }
141
142   @Nullable
143   public static Pair<PsiFile,Editor> chooseBetweenHostAndInjected(@NotNull PsiFile hostFile, @NotNull Editor hostEditor, @NotNull PairProcessor<PsiFile, Editor> predicate) {
144     Editor editorToApply = null;
145     PsiFile fileToApply = null;
146
147     int offset = hostEditor.getCaretModel().getOffset();
148     PsiFile injectedFile = InjectedLanguageUtil.findInjectedPsiNoCommit(hostFile, offset);
149     if (injectedFile != null) {
150       Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(hostEditor, injectedFile);
151       if (predicate.process(injectedFile, injectedEditor)) {
152         editorToApply = injectedEditor;
153         fileToApply = injectedFile;
154       }
155     }
156
157     if (editorToApply == null && predicate.process(hostFile, hostEditor)) {
158       editorToApply = hostEditor;
159       fileToApply = hostFile;
160     }
161     if (editorToApply == null) return null;
162     return Pair.create(fileToApply, editorToApply);
163   }
164
165   public static boolean chooseActionAndInvoke(@NotNull PsiFile hostFile,
166                                               @NotNull final Editor hostEditor,
167                                               @NotNull final IntentionAction action,
168                                               @NotNull String text) {
169     final Project project = hostFile.getProject();
170     return chooseActionAndInvoke(hostFile, hostEditor, action, text, project);
171   }
172
173   static boolean chooseActionAndInvoke(@NotNull PsiFile hostFile,
174                                        @Nullable final Editor hostEditor,
175                                        @NotNull final IntentionAction action,
176                                        @NotNull String text,
177                                        @NotNull final Project project) {
178     FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.quickFix");
179     ((FeatureUsageTrackerImpl)FeatureUsageTracker.getInstance()).getFixesStats().registerInvocation();
180
181     final Pair<PsiFile, Editor> pair = hostEditor != null ? chooseBetweenHostAndInjected(hostFile, hostEditor,
182                                                                                          (psiFile, editor) -> availableFor(psiFile, editor, action)) : Pair.<PsiFile, Editor>create(hostFile, null);
183     if (pair == null) return false;
184
185     CommandProcessor.getInstance().executeCommand(project, () -> TransactionGuard.getInstance().submitTransactionAndWait(() -> {
186       Runnable r = () -> action.invoke(project, pair.second, pair.first);
187       try {
188         if (action.startInWriteAction()) {
189           WriteAction.run(r::run);
190         } else {
191           r.run();
192         }
193       }
194       catch (IncorrectOperationException e) {
195         LOG.error(e);
196       }
197       if (hostEditor != null) {
198         DaemonCodeAnalyzer.getInstance(project).updateVisibleHighlighters(hostEditor);
199       }
200     }), text, null);
201     return true;
202   }
203 }