From 31f70a9eb166e0d981bd9572e91c66b448f2f7bb Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Thu, 31 Dec 2015 15:15:15 +0100 Subject: [PATCH] enable auto import on the fly for static methods (IDEA-103995) --- .../daemon/impl/actions/AddImportAction.java | 12 - .../impl/quickfix/StaticImportMethodFix.java | 203 +++++----------- .../StaticImportMethodQuestionAction.java | 217 ++++++++++++++++++ .../src/messages/QuickFixBundle.properties | 1 + 4 files changed, 275 insertions(+), 158 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/actions/AddImportAction.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/actions/AddImportAction.java index 619a604dc8fc..ce827c34353c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/actions/AddImportAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/actions/AddImportAction.java @@ -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) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java index 2f0c016d0f75..4db2bbc90154 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java @@ -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 myMethodCall; private List 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 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 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 step = - new BaseListPopupStep(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 excludableStrings = AddImportAction.getAllExcludableStrings(qname); - return new BaseListPopupStep(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; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java new file mode 100644 index 000000000000..4f83e31e79ed --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java @@ -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 myCandidates; + private final SmartPsiElementPointer myMethodCall; + + public StaticImportMethodQuestionAction(Project project, + Editor editor, + List candidates, + SmartPsiElementPointer 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 step = + new BaseListPopupStep(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 excludableStrings = AddImportAction.getAllExcludableStrings(qname); + return new BaseListPopupStep(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); + } +} + + diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index b3775aa3095a..506d63d4b51f 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -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