mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] MoveMemberIntoClassFix: better preview
GitOrigin-RevId: fab65ff4e1dd7038cc1110ac4b3b8d2f96fce59b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c9a1f639a9
commit
ef2fc85c6d
+43
-21
@@ -2,16 +2,17 @@
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.MemberModel;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -32,27 +33,15 @@ public class MoveMemberIntoClassFix extends LocalQuickFixAndIntentionActionOnPsi
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
if (editor == null) return;
|
||||
PsiJavaFile javaFile = ObjectUtils.tryCast(file, PsiJavaFile.class);
|
||||
if (javaFile == null) return;
|
||||
PsiErrorElement errorElement = ObjectUtils.tryCast(startElement, PsiErrorElement.class);
|
||||
if (errorElement == null) return;
|
||||
String className = FileUtilRt.getNameWithoutExtension(file.getName());
|
||||
if (!StringUtil.isJavaIdentifier(className)) return;
|
||||
MemberModel model = MemberModel.create(errorElement);
|
||||
if (model == null) return;
|
||||
PsiClass psiClass = ContainerUtil.find(javaFile.getClasses(), c -> className.equals(c.getName()));
|
||||
TextRange memberRange = model.textRange();
|
||||
Document document = editor.getDocument();
|
||||
String memberText = document.getText(memberRange);
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
Pair<TextRange, PsiMember> rangeAndMember = createMember(file);
|
||||
if (rangeAndMember == null) return;
|
||||
Document document = file.getViewProvider().getDocument();
|
||||
TextRange memberRange = rangeAndMember.getFirst();
|
||||
PsiMember member = rangeAndMember.getSecond();
|
||||
PsiClass psiClass = getPsiClass(file, member);
|
||||
if (psiClass == null) return;
|
||||
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
|
||||
MemberModel.MemberType memberType = model.memberType();
|
||||
PsiMember member = memberType.create(factory, memberText, file);
|
||||
if (psiClass == null) {
|
||||
psiClass = (PsiClass)file.add(createClass(factory, className, member));
|
||||
documentManager.doPostponedOperationsAndUnblockDocument(document);
|
||||
}
|
||||
documentManager.doPostponedOperationsAndUnblockDocument(document);
|
||||
SmartPsiElementPointer<PsiClass> classPtr = SmartPointerManager.createPointer(psiClass);
|
||||
document.deleteString(memberRange.getStartOffset(), memberRange.getEndOffset());
|
||||
documentManager.commitDocument(document);
|
||||
@@ -63,6 +52,18 @@ public class MoveMemberIntoClassFix extends LocalQuickFixAndIntentionActionOnPsi
|
||||
psiClass.addBefore(member, rBrace);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiClass getPsiClass(@NotNull PsiFile file, @NotNull PsiMember member) {
|
||||
String className = FileUtilRt.getNameWithoutExtension(file.getName());
|
||||
if (!StringUtil.isJavaIdentifier(className)) return null;
|
||||
PsiJavaFile javaFile = ObjectUtils.tryCast(file, PsiJavaFile.class);
|
||||
if (javaFile == null) return null;
|
||||
PsiClass psiClass = ContainerUtil.find(javaFile.getClasses(), c -> className.equals(c.getName()));
|
||||
if (psiClass != null) return psiClass;
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(file.getProject());
|
||||
return (PsiClass)file.add(createClass(factory, className, member));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getText() {
|
||||
return getFamilyName();
|
||||
@@ -85,4 +86,25 @@ public class MoveMemberIntoClassFix extends LocalQuickFixAndIntentionActionOnPsi
|
||||
}
|
||||
return factory.createInterface(className);
|
||||
}
|
||||
|
||||
private @Nullable Pair<@NotNull TextRange, @NotNull PsiMember> createMember(@NotNull PsiFile file) {
|
||||
PsiErrorElement errorElement = ObjectUtils.tryCast(getStartElement(), PsiErrorElement.class);
|
||||
if (errorElement == null) return null;
|
||||
MemberModel model = MemberModel.create(errorElement);
|
||||
if (model == null) return null;
|
||||
TextRange memberRange = model.textRange();
|
||||
String memberText = memberRange.substring(file.getText());
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(file.getProject());
|
||||
MemberModel.MemberType memberType = model.memberType();
|
||||
return Pair.create(memberRange, memberType.create(factory, memberText, file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
|
||||
Pair<TextRange, PsiMember> member = createMember(file);
|
||||
if (member == null || !(member.getSecond() instanceof PsiNamedElement)) return IntentionPreviewInfo.EMPTY;
|
||||
PsiClass psiClass = getPsiClass(file, member.getSecond());
|
||||
if (psiClass == null) return IntentionPreviewInfo.EMPTY;
|
||||
return IntentionPreviewInfo.movePsi((PsiNamedElement)member.getSecond(), psiClass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewPopupUpda
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInsight.template.TemplateManager;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import org.intellij.lang.regexp.inspection.DuplicateCharacterInClassInspection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@@ -153,14 +153,19 @@ public class IntentionPreviewTest extends LightQuickFixTestCase {
|
||||
|
||||
public void testRenameFile() {
|
||||
configureFromFileText("Test.java", "public class <caret>Best {}");
|
||||
IntentionAction action = findActionWithText("Rename File");
|
||||
assertNotNull(action);
|
||||
IntentionPreviewInfo info = IntentionPreviewPopupUpdateProcessor.getPreviewInfo(getProject(), action, getFile(), getEditor());
|
||||
assertTrue(info instanceof IntentionPreviewInfo.Html);
|
||||
HtmlChunk content = ((IntentionPreviewInfo.Html)info).content();
|
||||
IntentionPreviewInfo.Html info = getPreviewHtml("Rename File");
|
||||
assertEquals("<p><icon src=\"file\"/> Test.java → <icon src=\"file\"/> Best.java</p>",
|
||||
content.toString());
|
||||
assertNotNull(((IntentionPreviewInfo.Html)info).icon("file"));
|
||||
info.content().toString());
|
||||
assertNotNull(info.icon("file"));
|
||||
}
|
||||
|
||||
public void testMoveMemberIntoClass() {
|
||||
configureFromFileText("Test.java", "public class Test {} void <caret>method() {}");
|
||||
IntentionPreviewInfo.Html info = getPreviewHtml("Move member into class");
|
||||
assertEquals("<p><icon src=\"source\"/> method → <icon src=\"target\"/> Test</p>",
|
||||
info.content().toString());
|
||||
assertNotNull(info.icon("source"));
|
||||
assertNotNull(info.icon("target"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,6 +173,15 @@ public class IntentionPreviewTest extends LightQuickFixTestCase {
|
||||
// we want to stay at host editor
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private IntentionPreviewInfo.Html getPreviewHtml(String actionName) {
|
||||
IntentionAction action = findActionWithText(actionName);
|
||||
assertNotNull(action);
|
||||
IntentionPreviewInfo info = IntentionPreviewPopupUpdateProcessor.getPreviewInfo(getProject(), action, getFile(), getEditor());
|
||||
assertTrue(info instanceof IntentionPreviewInfo.Html);
|
||||
return (IntentionPreviewInfo.Html)info;
|
||||
}
|
||||
|
||||
private String getPreviewText(IntentionAction action) {
|
||||
// Run in background thread to catch accidental write-actions during preview generation
|
||||
try {
|
||||
|
||||
+41
-3
@@ -10,7 +10,10 @@ import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.vfs.VfsUtilCore;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.ui.DeferredIcon;
|
||||
import com.intellij.util.IconUtil;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -18,7 +21,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Possible result for IntentionPreview.
|
||||
@@ -178,9 +183,7 @@ public interface IntentionPreviewInfo {
|
||||
*/
|
||||
static @NotNull IntentionPreviewInfo rename(@NotNull PsiFile file, @NotNull @NlsSafe String newName) {
|
||||
Icon icon = file.getIcon(0);
|
||||
HtmlChunk iconChunk =
|
||||
icon == null ? HtmlChunk.empty() :
|
||||
new HtmlBuilder().append(HtmlChunk.tag("icon").attr("src", "file")).nbsp().toFragment();
|
||||
HtmlChunk iconChunk = getIconChunk(icon, "file");
|
||||
HtmlChunk fragment = new HtmlBuilder()
|
||||
.append(iconChunk)
|
||||
.append(file.getName())
|
||||
@@ -191,6 +194,12 @@ public interface IntentionPreviewInfo {
|
||||
return new Html(fragment.wrapWith("p"), icon == null ? Map.of() : Map.of("file", icon));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static HtmlChunk getIconChunk(@Nullable Icon icon, @NotNull String id) {
|
||||
return icon == null ? HtmlChunk.empty() :
|
||||
new HtmlBuilder().append(HtmlChunk.tag("icon").attr("src", id)).nbsp().toFragment();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file file to be moved
|
||||
* @param directory target directory
|
||||
@@ -214,4 +223,33 @@ public interface IntentionPreviewInfo {
|
||||
.toFragment();
|
||||
return new Html(fragment.wrapWith("p"), Map.of("file", fileIcon, "dir", dirIcon));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source PsiElement to move
|
||||
* @param target target PsiElement
|
||||
* @return a presentation describing moving of source element to the target
|
||||
*/
|
||||
static @NotNull IntentionPreviewInfo movePsi(@NotNull PsiNamedElement source, @NotNull PsiNamedElement target) {
|
||||
Icon sourceIcon = source.getIcon(0);
|
||||
if (sourceIcon instanceof DeferredIcon) {
|
||||
sourceIcon = ((DeferredIcon)sourceIcon).evaluate();
|
||||
}
|
||||
Icon targetIcon = target.getIcon(0);
|
||||
if (targetIcon instanceof DeferredIcon) {
|
||||
targetIcon = ((DeferredIcon)targetIcon).evaluate();
|
||||
}
|
||||
HtmlChunk sourceIconChunk = getIconChunk(sourceIcon, "source");
|
||||
HtmlChunk targetIconChunk = getIconChunk(targetIcon, "target");
|
||||
HtmlChunk fragment = new HtmlBuilder()
|
||||
.append(sourceIconChunk)
|
||||
.append(Objects.requireNonNull(source.getName()))
|
||||
.append(" ").append(HtmlChunk.htmlEntity("→")).append(" ")
|
||||
.append(targetIconChunk)
|
||||
.append(Objects.requireNonNull(target.getName()))
|
||||
.toFragment();
|
||||
Map<String, Icon> iconMap = new HashMap<>();
|
||||
iconMap.put("source", sourceIcon);
|
||||
iconMap.put("target", targetIcon);
|
||||
return new Html(fragment.wrapWith("p"), iconMap);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user