[java-intentions] IDEA-370071 Convert Java 'Optimize imports' action to ModCommand

GitOrigin-RevId: 5d6b55225f3b87f9a2b10599c6a6a3deeec3200f
This commit is contained in:
Tagir Valeev
2025-03-31 15:39:19 +02:00
committed by intellij-monorepo-bot
parent 360ae51ffe
commit 7ddc26cb54
7 changed files with 59 additions and 72 deletions

View File

@@ -282,7 +282,7 @@ public abstract class QuickFixFactory {
public abstract @NotNull IntentionAction createCreateAnnotationMethodFromUsageFix(@NotNull PsiNameValuePair pair);
public abstract @NotNull IntentionAction createOptimizeImportsFix(boolean fixOnTheFly, @NotNull PsiFile file);
public abstract @NotNull ModCommandAction createOptimizeImportsFix(boolean fixOnTheFly, @NotNull PsiFile file);
public abstract @NotNull IntentionAction createSafeDeleteUnusedParameterInHierarchyFix(@NotNull PsiParameter parameter, boolean excludingHierarchy);

View File

@@ -1,17 +1,24 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModCommand;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.modcommand.ModCommandExecutor;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AppUIExecutor;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiTreeAnyChangeAbstractAdapter;
import com.intellij.util.concurrency.AppExecutorUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -27,7 +34,9 @@ import java.util.List;
final class OptimizeImportRestarter implements Disposable {
private final Project myProject;
private final List<OptimizeRequest> queue = new ArrayList<>(); // guarded by queue
private record OptimizeRequest(@NotNull PsiFile psiFile, long modificationStampBefore, @NotNull IntentionAction optimizeFix) {}
private record OptimizeRequest(@NotNull PsiFile psiFile, long modificationStampBefore, @NotNull ModCommandAction optimizeFix) {
}
static OptimizeImportRestarter getInstance(Project project) {
return project.getService(OptimizeImportRestarter.class);
@@ -74,26 +83,25 @@ final class OptimizeImportRestarter implements Disposable {
if (!psiFile.isWritable()) {
continue;
}
ModCommandAction optimizeFix = request.optimizeFix();
if (!DaemonCodeAnalyzerEx.getInstanceEx(myProject).isErrorAnalyzingFinished(psiFile)) {
// re-fire when daemon is really finished
scheduleOnDaemonFinish(psiFile, request.optimizeFix());
scheduleOnDaemonFinish(psiFile, optimizeFix);
continue;
}
// later because should invoke when highlighting is finished (OptimizeImportsFix relies on that)
AppUIExecutor.onUiThread().later().withDocumentsCommitted(myProject).execute(() -> {
if (myProject.isDisposed()) return;
long stampAfter = psiFile.getModificationStamp();
if (stampAfter != request.modificationStampBefore()) {
return;
}
if (request.optimizeFix().isAvailable(myProject, null, psiFile)) {
request.optimizeFix().invoke(myProject, null, psiFile);
}
});
ActionContext context = ActionContext.from(null, psiFile);
if (optimizeFix.getPresentation(context) == null) continue;
ReadAction.nonBlocking(() -> optimizeFix.getPresentation(context) != null ? optimizeFix.perform(context) : ModCommand.nop())
.expireWhen(() -> myProject.isDisposed() || psiFile.getModificationStamp() != request.modificationStampBefore())
.finishOnUiThread(ModalityState.defaultModalityState(),
command -> CommandProcessor.getInstance().executeCommand(
myProject, () -> ModCommandExecutor.getInstance().executeInBatch(context, command),
CodeInsightBundle.message("process.optimize.imports"), null))
.submit(AppExecutorUtil.getAppExecutorService());
}
}
void scheduleOnDaemonFinish(@NotNull PsiFile psiFile, @NotNull IntentionAction optimizeFix) {
void scheduleOnDaemonFinish(@NotNull PsiFile psiFile, @NotNull ModCommandAction optimizeFix) {
long modificationStampBefore = psiFile.getModificationStamp();
synchronized (queue) {
queue.add(new OptimizeRequest(psiFile, modificationStampBefore, optimizeFix));

View File

@@ -18,6 +18,7 @@ import com.intellij.codeInspection.unusedImport.UnusedImportInspection;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.lang.annotation.ProblemGroup;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.colors.TextAttributesKey;
@@ -75,9 +76,9 @@ class UnusedImportsVisitor extends JavaElementVisitor {
FileStatusMap fileStatusMap = daemonCodeAnalyzer.getFileStatusMap();
fileStatusMap.setErrorFoundFlag(myDocument, myContext, true);
}
IntentionAction fixNotOnFly = null;
ModCommandAction fixNotOnFly = null;
if (requiresFix) {
IntentionAction fix = QuickFixFactory.getInstance().createOptimizeImportsFix(true, myFile);
ModCommandAction fix = QuickFixFactory.getInstance().createOptimizeImportsFix(true, myFile);
OptimizeImportRestarter.getInstance(myProject).scheduleOnDaemonFinish(myFile, fix);
fixNotOnFly = QuickFixFactory.getInstance().createOptimizeImportsFix(false, myFile);
}

View File

@@ -15,10 +15,10 @@
*/
package com.intellij.codeInspection.unusedImport;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.*;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiImportList;
@@ -41,7 +41,8 @@ public final class MissortedImportsInspection extends GlobalSimpleInspectionTool
@NotNull ProblemDescriptionsProcessor problemDescriptionsProcessor) {
if (!(file instanceof PsiJavaFile javaFile) || FileTypeUtils.isInServerPageFile(file)) return;
PsiImportList importList = javaFile.getImportList();
PsiImportStatementBase[] imports = importList == null ? PsiImportStatementBase.EMPTY_ARRAY : importList.getAllImportStatements();
if (importList == null) return;
PsiImportStatementBase[] imports = importList.getAllImportStatements();
int currentEntryIndex = 0;
for (PsiImportStatementBase importStatement : imports) {
ProgressManager.checkCanceled();
@@ -52,8 +53,8 @@ public final class MissortedImportsInspection extends GlobalSimpleInspectionTool
int entryIndex = JavaCodeStyleManager.getInstance(javaFile.getProject()).findEntryIndex(importStatement);
if (entryIndex < currentEntryIndex) {
// mis-sorted import found
IntentionAction fix = QuickFixFactory.getInstance().createOptimizeImportsFix(false, javaFile);
problemsHolder.registerProblem(importList, getDisplayNameText(), new IntentionWrapper(fix));
ModCommandAction fix = QuickFixFactory.getInstance().createOptimizeImportsFix(false, javaFile);
problemsHolder.problem(importList, getDisplayNameText()).fix(fix).register();
return;
}
currentEntryIndex = entryIndex;

View File

@@ -2,7 +2,6 @@
package com.intellij.codeInsight.intention.impl.config;
import com.intellij.codeInsight.CodeInsightWorkspaceSettings;
import com.intellij.codeInsight.actions.OptimizeImportsProcessor;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
@@ -29,17 +28,16 @@ import com.intellij.diagnostic.CoreAttachmentFactory;
import com.intellij.ide.scratch.ScratchUtil;
import com.intellij.java.JavaBundle;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.lang.java.JavaImportOptimizer;
import com.intellij.lang.java.request.CreateConstructorFromUsage;
import com.intellij.lang.java.request.CreateMethodFromUsage;
import com.intellij.modcommand.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.NonPhysicalFileSystem;
@@ -53,8 +51,6 @@ import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PropertyMemberType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.JavaRefactoringActionHandlerFactory;
import com.intellij.util.DocumentUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ThreeState;
import com.intellij.util.concurrency.ThreadingAssertions;
@@ -596,57 +592,58 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
}
@Override
public @NotNull IntentionAction createOptimizeImportsFix(final boolean fixOnTheFly, @NotNull PsiFile file) {
public @NotNull ModCommandAction createOptimizeImportsFix(final boolean fixOnTheFly, @NotNull PsiFile file) {
ApplicationManager.getApplication().assertIsNonDispatchThread();
VirtualFile virtualFile = file.getVirtualFile();
boolean isInContent = virtualFile != null && (ModuleUtilCore.projectContainsFile(file.getProject(), virtualFile, false) || ScratchUtil.isScratch(virtualFile));
return new OptimizeImportsFix(fixOnTheFly, isInContent, virtualFile == null ? ThreeState.UNSURE : SilentChangeVetoer.extensionsAllowToChangeFileSilently(file.getProject(), virtualFile));
}
private static final class OptimizeImportsFix implements IntentionAction {
private static final class OptimizeImportsFix implements ModCommandAction {
private final boolean myOnTheFly;
private final boolean myInContent;
private final ThreeState extensionsAllowToChangeFileSilently;
private OptimizeImportsFix(boolean onTheFly, boolean isInContent, @NotNull ThreeState extensionsAllowToChangeFileSilently) {
this.extensionsAllowToChangeFileSilently = extensionsAllowToChangeFileSilently;
ApplicationManager.getApplication().assertIsNonDispatchThread();
myOnTheFly = onTheFly;
myInContent = isInContent;
}
@Override
public @NotNull String getText() {
return QuickFixBundle.message("optimize.imports.fix");
}
@Override
public @NotNull String getFamilyName() {
return QuickFixBundle.message("optimize.imports.fix");
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
public @Nullable Presentation getPresentation(@NotNull ActionContext context) {
PsiFile file = context.file();
if (!(file instanceof PsiJavaFile)) {
return false;
return null;
}
if (ApplicationManager.getApplication().isDispatchThread() && myOnTheFly && !timeToOptimizeImports(file, myInContent, extensionsAllowToChangeFileSilently)) {
return false;
return null;
}
VirtualFile virtualFile = file.getViewProvider().getVirtualFile();
return myInContent ||
ScratchUtil.isScratch(virtualFile) ||
virtualFile.getFileSystem() instanceof NonPhysicalFileSystem;
boolean available = myInContent ||
ScratchUtil.isScratch(virtualFile) ||
virtualFile.getFileSystem() instanceof NonPhysicalFileSystem;
return available ? Presentation.of(getFamilyName()) : null;
}
@Override
public void invoke(final @NotNull Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
invokeOnTheFlyImportOptimizer(file);
}
@Override
public boolean startInWriteAction() {
return true;
public @NotNull ModCommand perform(@NotNull ActionContext context) {
PsiFile file = context.file();
ModCommand command = ModCommand.psiUpdate(file, f -> {
new JavaImportOptimizer().processFile(f).run();
});
if (command.isEmpty()) {
VirtualFile vFile = file.getViewProvider().getVirtualFile();
LOG.error("Import optimizer hasn't optimized any imports",
new Throwable(vFile.getPath()),
CoreAttachmentFactory.createAttachment(vFile));
}
return command;
}
}
@@ -730,24 +727,6 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
return OrderEntryFix.registerFixes(reference, registrar);
}
private static void invokeOnTheFlyImportOptimizer(@NotNull PsiFile file) {
final Project project = file.getProject();
final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
if (document == null) return;
String beforeText = file.getText();
long oldStamp = document.getModificationStamp();
DocumentUtil.writeInRunUndoTransparentAction(() -> new OptimizeImportsProcessor(project, file).run());
if (oldStamp != document.getModificationStamp()) {
String afterText = file.getText();
if (Comparing.strEqual(beforeText, afterText)) {
LOG.error("Import optimizer hasn't optimized any imports",
new Throwable(file.getViewProvider().getVirtualFile().getPath()),
CoreAttachmentFactory.createAttachment(file.getViewProvider().getVirtualFile()));
}
}
}
@Override
public @NotNull IntentionAction createAddMissingRequiredAnnotationParametersFix(final @NotNull PsiAnnotation annotation,
final PsiMethod @NotNull [] annotationMethods,

View File

@@ -41,10 +41,8 @@ public final class JavaImportOptimizer implements ImportOptimizer {
public void run() {
try {
final PsiDocumentManager manager = PsiDocumentManager.getInstance(file.getProject());
final Document document = manager.getDocument(file);
if (document != null) {
manager.commitDocument(document);
}
final Document document = file.getFileDocument();
manager.commitDocument(document);
final PsiImportList oldImportList = ((PsiJavaFile)file).getImportList();
assert oldImportList != null;
final List<String> oldImports = new ArrayList<>();

View File

@@ -400,7 +400,7 @@ public class OptimizeImportsTest extends OptimizeImportsTestCase {
myFixture.type('A'); // make file dirty
myFixture.type('\b');
IntentionAction fix = ReadAction.nonBlocking(() -> QuickFixFactory.getInstance().createOptimizeImportsFix(true, myFixture.getFile())).submit(
AppExecutorUtil.getAppExecutorService()).get();
AppExecutorUtil.getAppExecutorService()).get().asIntention();
myFixture.doHighlighting(); // wait until highlighting is finished to .isAvailable() return true
boolean old = CodeInsightWorkspaceSettings.getInstance(myFixture.getProject()).isOptimizeImportsOnTheFly();
CodeInsightWorkspaceSettings.getInstance(myFixture.getProject()).setOptimizeImportsOnTheFly(true);