diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/CopyPasteReferenceProcessor.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/CopyPasteReferenceProcessor.java index 943a153fcab1..aff9a984467a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/CopyPasteReferenceProcessor.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/CopyPasteReferenceProcessor.java @@ -15,9 +15,15 @@ */ package com.intellij.codeInsight.editorActions; +import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.hint.HintManagerImpl; +import com.intellij.codeInsight.hint.HintUtil; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; @@ -29,18 +35,19 @@ import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtilCore; +import com.intellij.ui.LightweightHint; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; public abstract class CopyPasteReferenceProcessor extends CopyPastePostProcessor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.CopyPasteReferenceProcessor"); @@ -121,7 +128,35 @@ public abstract class CopyPasteReferenceProcessor exten askReferencesToRestore(project, refs, referenceData); } PsiDocumentManager.getInstance(project).commitAllDocuments(); - ApplicationManager.getApplication().runWriteAction(() -> restoreReferences(referenceData, refs)); + ApplicationManager.getApplication().runWriteAction(() -> { + Set imported = new TreeSet<>(); + restoreReferences(referenceData, refs, imported); + if (CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE == CodeInsightSettings.YES && !imported.isEmpty()) { + String notificationText = CodeInsightBundle.message("copy.paste.reference.notification", imported.size()); + ApplicationManager.getApplication().invokeLater( + () -> showHint(editor, notificationText, e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + reviewImports(project, file, imported); + } + }), ModalityState.NON_MODAL); + } + }); + } + + protected abstract void removeImports(PsiFile file, Set imports); + + private void reviewImports(Project project, PsiFile file, Set importedClasses) { + RestoreReferencesDialog dialog = new RestoreReferencesDialog(project, ArrayUtil.toObjectArray(importedClasses), false); + dialog.setTitle(CodeInsightBundle.message("dialog.import.on.paste.title3")); + dialog.setExplanation(CodeInsightBundle.message("dialog.paste.on.import.text3")); + if (dialog.showAndGet()) { + Object[] selectedElements = dialog.getSelectedElements(); + if (selectedElements.length > 0) { + WriteCommandAction.runWriteCommandAction(project, "", null, () -> { + removeImports(file, Arrays.stream(selectedElements).map(o -> (String)o).collect(Collectors.toSet())); + }); + } + } } protected static void addReferenceData(final PsiElement element, @@ -138,8 +173,8 @@ public abstract class CopyPasteReferenceProcessor exten @NotNull protected abstract TRef[] findReferencesToRestore(PsiFile file, - RangeMarker bounds, - ReferenceData[] referenceData); + RangeMarker bounds, + ReferenceData[] referenceData); protected PsiElement resolveReferenceIgnoreOverriding(PsiPolyVariantReference reference) { PsiElement referent = reference.resolve(); @@ -153,7 +188,8 @@ public abstract class CopyPasteReferenceProcessor exten } protected abstract void restoreReferences(ReferenceData[] referenceData, - TRef[] refs); + TRef[] refs, + Set imported); private static void askReferencesToRestore(Project project, @NotNull PsiElement[] refs, ReferenceData[] referenceData) { @@ -209,6 +245,14 @@ public abstract class CopyPasteReferenceProcessor exten } } + private static void showHint(@NotNull Editor editor, @NotNull String info, HyperlinkListener hyperlinkListener) { + if (ApplicationManager.getApplication().isUnitTestMode()) return; + LightweightHint hint = new LightweightHint(HintUtil.createInformationLabel(info, hyperlinkListener, null, null)); + + int flags = HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE; + HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, HintManager.UNDER, flags, 0, false); + } + private static String getFQName(Object element) { return element instanceof PsiClass ? ((PsiClass)element).getQualifiedName() : (String)element; } diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaCopyPasteReferenceProcessor.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaCopyPasteReferenceProcessor.java index df243d294e26..d54fda2a5e83 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaCopyPasteReferenceProcessor.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaCopyPasteReferenceProcessor.java @@ -20,12 +20,17 @@ import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFixBase; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleSettings; +import com.intellij.psi.impl.source.codeStyle.ImportHelper; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Set; /** * @author peter @@ -59,6 +64,25 @@ public class JavaCopyPasteReferenceProcessor extends CopyPasteReferenceProcessor } } + @Override + protected void removeImports(PsiFile file, Set imports) { + removeImports((PsiJavaFile)file, imports); + } + + /** + * Remove imports on {@code imports} (including static imports in format Class_Name.Member_Name) + * To ensure that on-demand import expands when one of the import inside was deleted, let's do optimize imports. + * + * This may change some unrelated imports + */ + public static void removeImports(PsiJavaFile javaFile, Set imports) { + PsiImportList importList = new ImportHelper(JavaCodeStyleSettings.getInstance(javaFile)) + .prepareOptimizeImportsResult(javaFile, pair -> !imports.contains(pair.first)); + if (importList != null) { + ObjectUtils.notNull(javaFile.getImportList()).replace(importList); + } + } + @NotNull @Override @@ -124,7 +148,8 @@ public class JavaCopyPasteReferenceProcessor extends CopyPasteReferenceProcessor @Override protected void restoreReferences(ReferenceData[] referenceData, - PsiJavaCodeReferenceElement[] refs) { + PsiJavaCodeReferenceElement[] refs, + Set imported) { for (int i = 0; i < refs.length; i++) { PsiJavaCodeReferenceElement reference = refs[i]; if (reference == null || !reference.isValid()) continue; @@ -135,10 +160,12 @@ public class JavaCopyPasteReferenceProcessor extends CopyPasteReferenceProcessor if (refClass != null) { if (refData.staticMemberName == null) { reference.bindToElement(refClass); + imported.add(refData.qClassName); } else { LOG.assertTrue(reference instanceof PsiReferenceExpression); ((PsiReferenceExpression)reference).bindToElementViaStaticImport(refClass); + imported.add(StringUtil.getQualifiedName(refData.qClassName, refData.staticMemberName)); } } } diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/RestoreReferencesDialog.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/RestoreReferencesDialog.java index 04edb66be7f4..6f4dcfca4887 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/RestoreReferencesDialog.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/RestoreReferencesDialog.java @@ -40,8 +40,13 @@ class RestoreReferencesDialog extends DialogWrapper { private JList myList; private Object[] mySelectedElements = PsiClass.EMPTY_ARRAY; private boolean myContainsClassesOnly = true; + private JBLabel myExplanationLabel; RestoreReferencesDialog(final Project project, final Object[] elements) { + this(project, elements, true); + } + + RestoreReferencesDialog(final Project project, final Object[] elements, boolean preselect) { super(project, true); myNamedElements = elements; for (Object element : elements) { @@ -58,7 +63,9 @@ class RestoreReferencesDialog extends DialogWrapper { } init(); - myList.setSelectionInterval(0, myNamedElements.length - 1); + if (preselect) { + myList.setSelectionInterval(0, myNamedElements.length - 1); + } } @Override @@ -76,9 +83,10 @@ class RestoreReferencesDialog extends DialogWrapper { myList.setCellRenderer(new FQNameCellRenderer()); panel.add(ScrollPaneFactory.createScrollPane(myList), BorderLayout.CENTER); - panel.add(new JBLabel(myContainsClassesOnly ? - CodeInsightBundle.message("dialog.paste.on.import.text") : - CodeInsightBundle.message("dialog.paste.on.import.text2"), SMALL, BRIGHTER), BorderLayout.NORTH); + myExplanationLabel = new JBLabel(myContainsClassesOnly ? + CodeInsightBundle.message("dialog.paste.on.import.text") : + CodeInsightBundle.message("dialog.paste.on.import.text2"), SMALL, BRIGHTER); + panel.add(myExplanationLabel, BorderLayout.NORTH); final JPanel buttonPanel = new JPanel(new VerticalFlowLayout()); final JButton okButton = new JButton(CommonBundle.getOkButtonText()); @@ -92,6 +100,9 @@ class RestoreReferencesDialog extends DialogWrapper { return panel; } + public void setExplanation(String explanation) { + myExplanationLabel.setText(explanation); + } @Override protected String getDimensionServiceKey(){ diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java index 5fef92dd21c3..4573ac395ab0 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java @@ -44,6 +44,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toSet; @@ -67,7 +69,15 @@ public class ImportHelper{ } @Nullable("null means no need to replace the import list because they are the same") - PsiImportList prepareOptimizeImportsResult(@NotNull final PsiJavaFile file) { + public PsiImportList prepareOptimizeImportsResult(@NotNull final PsiJavaFile file) { + return prepareOptimizeImportsResult(file, pair -> true); + } + + /** + * @param filter pretend some references do not exist so the corresponding imports may be deleted + */ + @Nullable("null means no need to replace the import list because they are the same") + public PsiImportList prepareOptimizeImportsResult(@NotNull final PsiJavaFile file, Predicate> filter) { PsiImportList oldList = file.getImportList(); if (oldList == null) return null; @@ -78,8 +88,12 @@ public class ImportHelper{ // We want to preserve those comments then. List nonImports = new NotNullList<>(); // Note: this array may contain ".*" for unresolved imports! - List> names = new ArrayList<>(collectNamesToImport(file, nonImports)); - Collections.sort(names, Comparator.comparing(o -> o.getFirst())); + List> names = + collectNamesToImport(file, nonImports) + .stream() + .filter(filter) + .sorted(Comparator.comparing(o -> o.getFirst())) + .collect(Collectors.toList()); List> resultList = sortItemsAccordingToSettings(names, mySettings); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java index 5d5b3b86baf1..b893ad69a4d5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java @@ -152,7 +152,7 @@ public class CodeInsightSettings implements PersistentStateComponent, C public boolean INDENT_TO_CARET_ON_PASTE; @MagicConstant(intValues = {YES, NO, ASK}) - public int ADD_IMPORTS_ON_PASTE = ASK; + public int ADD_IMPORTS_ON_PASTE = YES; public static final int YES = 1; public static final int NO = 2; public static final int ASK = 3; diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index e0ce04372337..b88da847d227 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -43,8 +43,11 @@ dialog.reformat.files.reformat.selected.files.label=Reformat selected files? command.name.typing=Typing dialog.import.on.paste.title=Select Classes to Import dialog.import.on.paste.title2=Select Elements to Import +dialog.import.on.paste.title3=Select Import to Remove dialog.paste.on.import.text=The code fragment which you have pasted uses classes that are not accessible by imports in the new context.
Select classes that you want to import to the new file. dialog.paste.on.import.text2=The code fragment which you have pasted uses elements that are not accessible by imports in the new context.
Select elements that you want to import to the new file. +dialog.paste.on.import.text3=The code fragment which you have pasted introduced new imports in the current context.
Select imports that you want to eliminate. +copy.paste.reference.notification={0} {0, choice, 1#import was|2#imports were} added

Review Added Imports... paste.data.flavor.folding=FoldingData paste.dataflavor.referencedata=ReferenceData generate.constructor.fields.chooser.title=Choose Fields to Initialize by Constructor diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyReferenceCopyPasteProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyReferenceCopyPasteProcessor.java index b3e2d55ea964..4ad1b816a34e 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyReferenceCopyPasteProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/GroovyReferenceCopyPasteProcessor.java @@ -20,17 +20,20 @@ import com.intellij.codeInsight.editorActions.ReferenceData; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import java.util.ArrayList; +import java.util.Set; /** * @author peter @@ -67,6 +70,16 @@ public class GroovyReferenceCopyPasteProcessor extends CopyPasteReferenceProcess } } + @Override + protected void removeImports(PsiFile file, Set imports) { + GroovyFile groovyFile = (GroovyFile)file; + for (GrImportStatement statement : groovyFile.getImportStatements()) { + if (imports.contains(statement.getImportedName())) { + groovyFile.removeImport(statement); + } + } + } + @NotNull @Override @@ -117,7 +130,8 @@ public class GroovyReferenceCopyPasteProcessor extends CopyPasteReferenceProcess @Override protected void restoreReferences(ReferenceData[] referenceData, - GrReferenceElement[] refs) { + GrReferenceElement[] refs, + Set imported) { for (int i = 0; i < refs.length; i++) { GrReferenceElement reference = refs[i]; if (reference == null) continue; @@ -128,12 +142,14 @@ public class GroovyReferenceCopyPasteProcessor extends CopyPasteReferenceProcess if (refClass != null) { if (refData.staticMemberName == null) { reference.bindToElement(refClass); + imported.add(refData.qClassName); } else { LOG.assertTrue(reference instanceof GrReferenceExpression); PsiMember member = findMember(refData, refClass); if (member != null) { ((GrReferenceExpression)reference).bindToElementViaStaticImport(member); + imported.add(StringUtil.getQualifiedName(refData.qClassName, refData.staticMemberName)); } } }