copy paste: add import sby default, add notification & allow to delete when wrong (IDEA-210515)

GitOrigin-RevId: 2c63c3ce112749a34dd18b176d7b8dd3d81df6b5
This commit is contained in:
Anna Kozlova
2019-06-11 17:57:36 +03:00
committed by intellij-monorepo-bot
parent d99499f3f0
commit d9ce39b812
7 changed files with 133 additions and 18 deletions
@@ -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<TRef extends PsiElement> extends CopyPastePostProcessor<ReferenceTransferableData> {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.CopyPasteReferenceProcessor");
@@ -121,7 +128,35 @@ public abstract class CopyPasteReferenceProcessor<TRef extends PsiElement> exten
askReferencesToRestore(project, refs, referenceData);
}
PsiDocumentManager.getInstance(project).commitAllDocuments();
ApplicationManager.getApplication().runWriteAction(() -> restoreReferences(referenceData, refs));
ApplicationManager.getApplication().runWriteAction(() -> {
Set<String> 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<String> imports);
private void reviewImports(Project project, PsiFile file, Set<String> 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<TRef extends PsiElement> 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<TRef extends PsiElement> exten
}
protected abstract void restoreReferences(ReferenceData[] referenceData,
TRef[] refs);
TRef[] refs,
Set<String> imported);
private static void askReferencesToRestore(Project project, @NotNull PsiElement[] refs,
ReferenceData[] referenceData) {
@@ -209,6 +245,14 @@ public abstract class CopyPasteReferenceProcessor<TRef extends PsiElement> 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;
}
@@ -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<String> 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<String> 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<String> 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));
}
}
}
@@ -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(){
@@ -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<? super Pair<String, Boolean>> 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<PsiElement> nonImports = new NotNullList<>();
// Note: this array may contain "<packageOrClassName>.*" for unresolved imports!
List<Pair<String, Boolean>> names = new ArrayList<>(collectNamesToImport(file, nonImports));
Collections.sort(names, Comparator.comparing(o -> o.getFirst()));
List<Pair<String, Boolean>> names =
collectNamesToImport(file, nonImports)
.stream()
.filter(filter)
.sorted(Comparator.comparing(o -> o.getFirst()))
.collect(Collectors.toList());
List<Pair<String, Boolean>> resultList = sortItemsAccordingToSettings(names, mySettings);
@@ -152,7 +152,7 @@ public class CodeInsightSettings implements PersistentStateComponent<Element>, 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;
@@ -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=<html>The code fragment which you have pasted uses classes that are not accessible by imports in the new context.<br/>Select classes that you want to import to the new file.</html>
dialog.paste.on.import.text2=<html>The code fragment which you have pasted uses elements that are not accessible by imports in the new context.<br/>Select elements that you want to import to the new file.</html>
dialog.paste.on.import.text3=<html>The code fragment which you have pasted introduced new imports in the current context.<br/>Select imports that you want to eliminate.</html>
copy.paste.reference.notification=<html>{0} {0, choice, 1#import was|2#imports were} added<p><span><a href='show'>Review Added Imports...</a></span></html>
paste.data.flavor.folding=FoldingData
paste.dataflavor.referencedata=ReferenceData
generate.constructor.fields.chooser.title=Choose Fields to Initialize by Constructor
@@ -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<String> 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<String> 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));
}
}
}