diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index f876fa4bbf85..8adec5d30ed6 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -944,7 +944,7 @@ public class JavaCompletionContributor extends CompletionContributor { element = qualifier; } if (!(element.getParent() instanceof PsiMethodCallExpression) && element.multiResolve(true).length == 0) { - new ImportClassFix(element).doFix(editor, false, false); + new ImportClassFix(element).fixSilently(editor); PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument()); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaReferenceImporter.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaReferenceImporter.java index e3d1de9de8f1..f1f6cbec0b76 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaReferenceImporter.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaReferenceImporter.java @@ -51,7 +51,7 @@ public class JavaReferenceImporter implements ReferenceImporter { if (element instanceof PsiJavaCodeReferenceElement) { PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)element; if (ref.multiResolve(true).length == 0) { - new ImportClassFix(ref).doFix(editor, false, allowCaretNearRef); + new ImportClassFix(ref).doFix(editor, false, allowCaretNearRef, true); return true; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java index 08023ff4201e..70363969d306 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java @@ -2,14 +2,12 @@ package com.intellij.codeInsight.daemon.impl.quickfix; import com.intellij.application.options.CodeStyle; -import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.CodeInsightUtil; import com.intellij.codeInsight.FileModificationService; import com.intellij.codeInsight.ImportFilter; import com.intellij.codeInsight.completion.JavaCompletionUtil; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; import com.intellij.codeInsight.daemon.QuickFixBundle; -import com.intellij.codeInsight.daemon.impl.DaemonListeners; import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; import com.intellij.codeInsight.daemon.impl.actions.AddImportAction; import com.intellij.codeInsight.hint.HintManager; @@ -18,12 +16,10 @@ import com.intellij.codeInsight.intention.HighPriorityAction; import com.intellij.codeInsight.intention.impl.BaseIntentionAction; import com.intellij.codeInspection.HintAction; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.packageDependencies.DependencyRule; import com.intellij.packageDependencies.DependencyValidationManager; @@ -31,7 +27,6 @@ import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleSettings; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiShortNamesCache; -import com.intellij.psi.util.FileTypeUtils; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.containers.ContainerUtil; @@ -293,12 +288,13 @@ public abstract class ImportClassFixBase classesToImport = getClassesToImport(); //do not show popups for already imported classes when library is missing (show them for explicit action) @@ -332,7 +328,7 @@ public abstract class ImportClassFixBase action.execute()); return Result.CLASS_AUTO_IMPORTED; @@ -349,24 +345,6 @@ public abstract class ImportClassFixBase { + PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting(); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + ApplicationManager.getApplication().saveAll(); + }); + String text1After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X1.java")); + assertTrue(text1After, text1After.contains("import java.util.ArrayList;")); + String text2After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X2.java")); + assertTrue(text2After, text2After.contains("import java.util.ArrayList;")); + } + finally { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = importsOnTheFly; + } + } + public void testOptimizeImportsMustNotAddUnambiguousImportsIfTheCorrespondingSettingIsOff() throws Exception { + boolean importsOnTheFly = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = false; + try { + VirtualFile root = PsiTestUtil.createTestProjectStructure(myProject, myModule, OptimizeImportsTest.BASE_PATH + "/src1", myFilesToDelete, false); + PsiTestUtil.addSourceRoot(getModule(), root); + PsiDirectory directory = myPsiManager.findDirectory(root); + assertNotNull(directory); + new OptimizeImportsProcessor(getProject(), directory, true).run(); + WriteCommandAction.runWriteCommandAction(null, () -> { + PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting(); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + ApplicationManager.getApplication().saveAll(); + }); + String text1After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X1.java")); + assertFalse(text1After, text1After.contains("import java.util.ArrayList;")); + String text2After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X2.java")); + assertFalse(text2After, text2After.contains("import java.util.ArrayList;")); + } + finally { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = importsOnTheFly; + } + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java index 2e79a06add0a..4a16be44c8d0 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java @@ -3,8 +3,11 @@ package com.intellij.codeInsight.actions; import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; +import com.intellij.codeInspection.HintAction; import com.intellij.lang.ImportOptimizer; import com.intellij.lang.LanguageImportStatements; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; @@ -83,7 +86,10 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor { } } - Runnable runnable = runnables.isEmpty() ? EmptyRunnable.getInstance() : () -> { + List hints = ShowAutoImportPass.getImportHints(file); + + Runnable writeTask = runnables.isEmpty() ? EmptyRunnable.getInstance() : () -> { + ApplicationManager.getApplication().assertIsDispatchThread(); CodeStyleManagerImpl.setSequentialProcessingAllowed(false); try { for (Runnable runnable1 : runnables) { @@ -91,13 +97,14 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor { myOptimizerNotifications.add(getNotificationInfo(runnable1)); } putNotificationInfoIntoCollector(); + ShowAutoImportPass.fixAllImportsSilently(file, hints); } finally { CodeStyleManagerImpl.setSequentialProcessingAllowed(true); } }; - return new FutureTask<>(runnable, true); + return new FutureTask<>(writeTask, true); } @NotNull diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPass.java index ebda390bcdca..14a85c46e49b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPass.java @@ -4,6 +4,7 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.Pass; import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.daemon.DaemonBundle; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; @@ -15,23 +16,30 @@ import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorActivityManager; +import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; public class ShowAutoImportPass extends TextEditorHighlightingPass { private final Editor myEditor; @@ -101,13 +109,36 @@ public class ShowAutoImportPass extends TextEditorHighlightingPass { for (HighlightInfo info : infos) { for (HintAction action : extractHints(info)) { - if (action.isAvailable(myProject, myEditor, myFile) && action.fixSilently(myEditor)) { + if (action.isAvailable(myProject, myEditor, myFile) + && mayAutoImportNow(myFile) + && action.fixSilently(myEditor)) { break; } } } } + public static boolean mayAutoImportNow(@NotNull PsiFile psiFile) { + return isAddUnambiguousImportsOnTheFlyEnabled(psiFile) && + (ApplicationManager.getApplication().isUnitTestMode() || DaemonListeners.canChangeFileSilently(psiFile)) && + isInModelessContext(psiFile.getProject()); + } + + private static boolean isInModelessContext(@NotNull Project project) { + return Registry.is("ide.perProjectModality") ? + !LaterInvocator.isInModalContextForProject(project) : + !LaterInvocator.isInModalContext(); + } + + public static boolean isAddUnambiguousImportsOnTheFlyEnabled(@NotNull PsiFile psiFile) { + PsiFile templateFile = PsiUtilCore.getTemplateLanguageFile(psiFile); + if (templateFile == null) return false; + boolean isJsp = templateFile.getFileType().getName().equals("JSP") || templateFile.getFileType().getName().equals("JSPX"); + return isJsp ? + CodeInsightSettings.getInstance().JSP_ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY : + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; + } + @NotNull private static List getVisibleHighlights(final int startOffset, final int endOffset, @@ -141,6 +172,66 @@ public class ShowAutoImportPass extends TextEditorHighlightingPass { return false; } + public static void fixAllImportsSilently(@NotNull PsiFile file, @NotNull List actions) { + if (actions.isEmpty()) return; + Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + if (document == null) return; + Editor editor = EditorFactory.getInstance().createEditor(document, file.getProject()); + try { + for (HintAction action : actions) { + action.fixSilently(editor); + } + } + finally { + EditorFactory.getInstance().releaseEditor(editor); + } + } + @NotNull + public static List getImportHints(@NotNull PsiFile file) { + if (ApplicationManager.getApplication().isDispatchThread()) { + // really can't run highlighting from within EDT + // also, guard against recursive call optimize imports->add imports->optimize imports (in AddImportAction.doAddImport()) + return Collections.emptyList(); + } + Project project = file.getProject(); + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + if (document == null || !hasUnresolvedReferences(file)) return Collections.emptyList(); + + DaemonProgressIndicator progress = new DaemonProgressIndicator(); + AtomicReference> infos = new AtomicReference<>(Collections.emptyList()); + ((ApplicationImpl)ApplicationManager.getApplication()).executeByImpatientReader(() -> { + ProgressManager.getInstance().executeProcessUnderProgress(() -> { + infos.set(DaemonCodeAnalyzerEx.getInstanceEx(project).runMainPasses(file, document, progress)); + }, progress); + }); + + List result = new ArrayList<>(infos.get().size()); + Editor editor = null; + for (HighlightInfo info : infos.get()) { + for (HintAction action : extractHints(info)) { + if (action.isAvailable(project, null, file)) { + result.add(action); + } + } + } + return result; + } + + private static boolean hasUnresolvedReferences(@NotNull PsiFile file) { + Ref result = new Ref<>(false); + file.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(@NotNull PsiElement element) { + if (element instanceof PsiReference && ((PsiReference)element).resolve() == null) { + result.set(true); + stopWalking(); + } + super.visitElement(element); + } + }); + return result.get(); + } + private boolean isImportHintEnabled() { return DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled() && DaemonCodeAnalyzer.getInstance(myProject).isImportHintsEnabled(myFile);