mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
copy paste: add import sby default, add notification & allow to delete when wrong (IDEA-210515)
GitOrigin-RevId: 2c63c3ce112749a34dd18b176d7b8dd3d81df6b5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d99499f3f0
commit
d9ce39b812
+52
-8
@@ -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;
|
||||
}
|
||||
|
||||
+28
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-4
@@ -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
|
||||
|
||||
+17
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user