enable auto import on the fly for static methods (IDEA-103995)

This commit is contained in:
Anna Kozlova
2015-12-31 15:17:29 +01:00
parent c7d4db7011
commit 31f70a9eb1
4 changed files with 275 additions and 158 deletions
@@ -22,7 +22,6 @@ import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.actions.OptimizeImportsProcessor;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.hint.QuestionAction;
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
@@ -267,17 +266,6 @@ public class AddImportAction implements QuestionAction {
myEditor.getCaretModel().moveToLogicalPosition(new LogicalPosition(pos2.line, newCol));
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (!myProject.isDisposed() && myProject.isOpen()) {
DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
if (daemonCodeAnalyzer != null) {
daemonCodeAnalyzer.updateVisibleHighlighters(myEditor);
}
}
}
});
}
protected void bindReference(PsiReference ref, PsiClass targetClass) {
@@ -19,42 +19,32 @@ import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.impl.AddSingleMemberStaticImportAction;
import com.intellij.ide.util.MethodCellRenderer;
import com.intellij.codeInspection.HintAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.*;
import com.intellij.psi.util.proximity.PsiProximityComparator;
import com.intellij.ui.popup.list.ListPopupImpl;
import com.intellij.ui.popup.list.PopupListElementRenderer;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.Processor;
import com.intellij.util.containers.LinkedMultiMap;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
public class StaticImportMethodFix implements IntentionAction {
public class StaticImportMethodFix implements IntentionAction, HintAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.StaticImportMethodFix");
private final SmartPsiElementPointer<PsiMethodCallExpression> myMethodCall;
private List<PsiMethod> candidates;
@@ -68,9 +58,7 @@ public class StaticImportMethodFix implements IntentionAction {
public String getText() {
String text = QuickFixBundle.message("static.import.method.text");
if (candidates != null && candidates.size() == 1) {
text += " '" + PsiFormatUtil.formatMethod(candidates.get(0), PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME |
PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
PsiFormatUtilBase.SHOW_FQ_NAME, 0)+"'";
text += " '" + getMethodPresentableText() + "'";
}
else {
text += "...";
@@ -78,6 +66,13 @@ public class StaticImportMethodFix implements IntentionAction {
return text;
}
@NotNull
private String getMethodPresentableText() {
return PsiFormatUtil.formatMethod(candidates.get(0), PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME |
PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
PsiFormatUtilBase.SHOW_FQ_NAME, 0);
}
@Override
@NotNull
public String getFamilyName() {
@@ -91,6 +86,7 @@ public class StaticImportMethodFix implements IntentionAction {
&& myMethodCall.getElement() != null
&& myMethodCall.getElement().isValid()
&& myMethodCall.getElement().getMethodExpression().getQualifierExpression() == null
&& myMethodCall.getElement().resolveMethod() == null
&& file.getManager().isInProject(file)
&& !(candidates == null ? candidates = getMethodsToImport() : candidates).isEmpty()
;
@@ -263,146 +259,61 @@ public class StaticImportMethodFix implements IntentionAction {
@Override
public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) {
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
if (candidates.size() == 1) {
final PsiMethod toImport = candidates.get(0);
doImport(toImport);
}
else {
chooseAndImport(editor, project);
}
}
private void doImport(final PsiMethod toImport) {
CommandProcessor.getInstance().executeCommand(toImport.getProject(), new Runnable(){
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
PsiMethodCallExpression element = myMethodCall.getElement();
if (element != null) {
AddSingleMemberStaticImportAction.bindAllClassRefs(element.getContainingFile(), toImport, toImport.getName(), toImport.getContainingClass());
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
});
final List<PsiMethod> methodsToImport = getMethodsToImport();
if (methodsToImport.isEmpty()) return;
createQuestionAction(methodsToImport, project, editor).execute();
}
}, getText(), this);
});
}
private void chooseAndImport(Editor editor, final Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
doImport(candidates.get(0));
return;
@NotNull
private StaticImportMethodQuestionAction createQuestionAction(List<PsiMethod> methodsToImport, @NotNull Project project, Editor editor) {
return new StaticImportMethodQuestionAction(project, editor, methodsToImport, myMethodCall);
}
private ImportClassFixBase.Result doFix(Editor editor) {
if (candidates.isEmpty()) {
return ImportClassFixBase.Result.POPUP_NOT_SHOWN;
}
final BaseListPopupStep<PsiMethod> step =
new BaseListPopupStep<PsiMethod>(QuickFixBundle.message("class.to.import.chooser.title"), candidates) {
final StaticImportMethodQuestionAction action = createQuestionAction(candidates, myMethodCall.getProject(), editor);
if (candidates.size() == 1) {
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
@Override
public PopupStep onChosen(PsiMethod selectedValue, boolean finalChoice) {
if (selectedValue == null) {
return FINAL_CHOICE;
}
if (finalChoice) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
LOG.assertTrue(selectedValue.isValid());
doImport(selectedValue);
return FINAL_CHOICE;
}
String qname = PsiUtil.getMemberQualifiedName(selectedValue);
if (qname == null) return FINAL_CHOICE;
List<String> excludableStrings = AddImportAction.getAllExcludableStrings(qname);
return new BaseListPopupStep<String>(null, excludableStrings) {
@NotNull
@Override
public String getTextFor(String value) {
return "Exclude '" + value + "' from auto-import";
}
@Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) {
if (finalChoice) {
AddImportAction.excludeFromImport(project, selectedValue);
}
return super.onChosen(selectedValue, finalChoice);
}
};
public void run() {
action.execute();
}
});
return ImportClassFixBase.Result.CLASS_AUTO_IMPORTED;
}
@Override
public boolean hasSubstep(PsiMethod selectedValue) {
return true;
}
@NotNull
@Override
public String getTextFor(PsiMethod value) {
return ObjectUtils.assertNotNull(value.getName());
}
@Override
public Icon getIconFor(PsiMethod aValue) {
return aValue.getIcon(0);
}
};
final ListPopupImpl popup = new ListPopupImpl(step) {
final PopupListElementRenderer rightArrow = new PopupListElementRenderer(this);
@Override
protected ListCellRenderer getListElementRenderer() {
return new MethodCellRenderer(true, PsiFormatUtilBase.SHOW_NAME){
@Nullable
@Override
protected TextAttributes getNavigationItemAttributes(Object value) {
TextAttributes attrs = super.getNavigationItemAttributes(value);
if (value instanceof PsiMethod && !((PsiMethod)value).isDeprecated()) {
PsiClass psiClass = ((PsiMethod)value).getContainingClass();
if (psiClass != null && psiClass.isDeprecated()) {
return TextAttributes.merge(attrs, super.getNavigationItemAttributes(psiClass));
}
}
return attrs;
}
@Override
protected DefaultListCellRenderer getRightCellRenderer(final Object value) {
final DefaultListCellRenderer moduleRenderer = super.getRightCellRenderer(value);
return new DefaultListCellRenderer(){
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JPanel panel = new JPanel(new BorderLayout());
if (moduleRenderer != null) {
Component moduleComponent = moduleRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (!isSelected) {
moduleComponent.setBackground(getBackgroundColor(value));
}
panel.add(moduleComponent, BorderLayout.CENTER);
}
rightArrow.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
Component rightArrowComponent = rightArrow.getNextStepLabel();
panel.add(rightArrowComponent, BorderLayout.EAST);
return panel;
}
};
}
};
}
};
popup.showInBestPositionFor(editor);
String hintText = ShowAutoImportPass.getMessage(candidates.size() > 1, getMethodPresentableText());
if (!ApplicationManager.getApplication().isUnitTestMode() && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) {
final PsiMethodCallExpression element = myMethodCall.getElement();
final TextRange textRange = element.getTextRange();
HintManager.getInstance().showQuestionHint(editor, hintText,
textRange.getStartOffset(),
textRange.getEndOffset(), action);
}
return ImportClassFixBase.Result.POPUP_SHOWN;
}
@Override
public boolean startInWriteAction() {
return true;
return false;
}
@Override
public boolean showHint(@NotNull Editor editor) {
final PsiMethodCallExpression callExpression = myMethodCall.getElement();
if (callExpression == null || callExpression.getMethodExpression().getQualifierExpression() != null) {
return false;
}
ImportClassFixBase.Result result = doFix(editor);
return result == ImportClassFixBase.Result.POPUP_SHOWN || result == ImportClassFixBase.Result.CLASS_AUTO_IMPORTED;
}
}
@@ -0,0 +1,217 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
import com.intellij.codeInsight.hint.QuestionAction;
import com.intellij.codeInsight.intention.impl.AddSingleMemberStaticImportAction;
import com.intellij.ide.util.MethodCellRenderer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiFormatUtilBase;
import com.intellij.psi.util.PsiUtil;
import com.intellij.ui.popup.list.ListPopupImpl;
import com.intellij.ui.popup.list.PopupListElementRenderer;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.List;
public class StaticImportMethodQuestionAction implements QuestionAction {
private static final Logger LOG = Logger.getInstance("#" + StaticImportMethodQuestionAction.class.getName());
private final Project myProject;
private final Editor myEditor;
private List<PsiMethod> myCandidates;
private final SmartPsiElementPointer<PsiMethodCallExpression> myMethodCall;
public StaticImportMethodQuestionAction(Project project,
Editor editor,
List<PsiMethod> candidates,
SmartPsiElementPointer<PsiMethodCallExpression> methodCall) {
myProject = project;
myEditor = editor;
myCandidates = candidates;
myMethodCall = methodCall;
}
@Override
public boolean execute() {
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
final PsiMethodCallExpression element = myMethodCall.getElement();
if (element == null || !element.isValid()){
return false;
}
for (PsiMethod targetMethod : myCandidates) {
if (!targetMethod.isValid()) {
return false;
}
}
if (myCandidates.size() == 1){
doImport(myCandidates.get(0));
}
else{
chooseAndImport(myEditor, myProject);
}
return true;
}
private void doImport(final PsiMethod toImport) {
final Project project = toImport.getProject();
CommandProcessor.getInstance().executeCommand(project, new Runnable(){
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
PsiMethodCallExpression element = myMethodCall.getElement();
if (element != null) {
AddSingleMemberStaticImportAction.bindAllClassRefs(element.getContainingFile(), toImport, toImport.getName(), toImport.getContainingClass());
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
});
}
}, QuickFixBundle.message("add.import"), this);
}
private void chooseAndImport(final Editor editor, final Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
doImport(myCandidates.get(0));
return;
}
final BaseListPopupStep<PsiMethod> step =
new BaseListPopupStep<PsiMethod>(QuickFixBundle.message("method.to.import.chooser.title"), myCandidates) {
@Override
public PopupStep onChosen(PsiMethod selectedValue, boolean finalChoice) {
if (selectedValue == null) {
return FINAL_CHOICE;
}
if (finalChoice) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
LOG.assertTrue(selectedValue.isValid());
doImport(selectedValue);
return FINAL_CHOICE;
}
String qname = PsiUtil.getMemberQualifiedName(selectedValue);
if (qname == null) return FINAL_CHOICE;
List<String> excludableStrings = AddImportAction.getAllExcludableStrings(qname);
return new BaseListPopupStep<String>(null, excludableStrings) {
@NotNull
@Override
public String getTextFor(String value) {
return "Exclude '" + value + "' from auto-import";
}
@Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) {
if (finalChoice) {
AddImportAction.excludeFromImport(project, selectedValue);
}
return super.onChosen(selectedValue, finalChoice);
}
};
}
@Override
public boolean hasSubstep(PsiMethod selectedValue) {
return true;
}
@NotNull
@Override
public String getTextFor(PsiMethod value) {
return ObjectUtils.assertNotNull(value.getName());
}
@Override
public Icon getIconFor(PsiMethod aValue) {
return aValue.getIcon(0);
}
};
final ListPopupImpl popup = new ListPopupImpl(step) {
final PopupListElementRenderer rightArrow = new PopupListElementRenderer(this);
@Override
protected ListCellRenderer getListElementRenderer() {
return new MethodCellRenderer(true, PsiFormatUtilBase.SHOW_NAME){
@Nullable
@Override
protected TextAttributes getNavigationItemAttributes(Object value) {
TextAttributes attrs = super.getNavigationItemAttributes(value);
if (value instanceof PsiMethod && !((PsiMethod)value).isDeprecated()) {
PsiClass psiClass = ((PsiMethod)value).getContainingClass();
if (psiClass != null && psiClass.isDeprecated()) {
return TextAttributes.merge(attrs, super.getNavigationItemAttributes(psiClass));
}
}
return attrs;
}
@Override
protected DefaultListCellRenderer getRightCellRenderer(final Object value) {
final DefaultListCellRenderer moduleRenderer = super.getRightCellRenderer(value);
return new DefaultListCellRenderer(){
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JPanel panel = new JPanel(new BorderLayout());
if (moduleRenderer != null) {
Component moduleComponent = moduleRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (!isSelected) {
moduleComponent.setBackground(getBackgroundColor(value));
}
panel.add(moduleComponent, BorderLayout.CENTER);
}
rightArrow.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
Component rightArrowComponent = rightArrow.getNextStepLabel();
panel.add(rightArrowComponent, BorderLayout.EAST);
return panel;
}
};
}
};
}
};
popup.showInBestPositionFor(editor);
}
}
@@ -1,5 +1,6 @@
add.import=Add Import
class.to.import.chooser.title=Class to Import
method.to.import.chooser.title=Method to Import
access.static.via.class.reference.family=Access static via class reference
access.static.via.class.reference.text=Access static ''{1}.{0}'' via class ''{2}'' reference
add.default.constructor.family=Add Default Constructor