From fe2da78da782343d4f8525f968e97df715817de0 Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Sat, 4 Jul 2020 10:28:23 +0200 Subject: [PATCH] IDEA-CR-64585: IDEA-245071: implement DaemonCodeAnalyzerImpl#serializeCodeInsightPasses() GitOrigin-RevId: 077ab6dc38924188f1017543763611915160f7ca --- .../impl/DaemonRespondToChangesTest.java | 127 +++++++++++++----- ...xtEditorHighlightingPassRegistrarImpl.java | 10 +- .../daemon/impl/DaemonCodeAnalyzerImpl.java | 22 ++- ...njectedGeneralHighlightingPassFactory.java | 7 +- .../impl/LocalInspectionsPassFactory.java | 10 +- .../util/resources/misc/registry.properties | 3 + 6 files changed, 130 insertions(+), 49 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java index 104c75958531..8638f07ac6ed 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java @@ -2122,7 +2122,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { } public void testAddRemoveHighlighterRaceInIncorrectAnnotatorsWhichUseFileRecursiveVisit() { - useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyRecordingAnnotator[]{new MyIncorrectlyRecursiveAnnotator()}, () -> { + useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyIncorrectlyRecursiveAnnotator(), () -> { @Language("JAVA") String text1 = "class X {\n" + " int foo(Object param) {\n" + @@ -2148,28 +2148,59 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { public static void useAnnotatorsIn(@NotNull com.intellij.lang.Language language, MyRecordingAnnotator @NotNull [] annotators, @NotNull Runnable runnable) { + useAnnotatorsIn(Collections.singletonMap(language, annotators), runnable); + } + + public static void useAnnotatorsIn(@NotNull com.intellij.lang.Language language, + @NotNull MyRecordingAnnotator annotator, + @NotNull Runnable runnable) { + useAnnotatorsIn(Collections.singletonMap(language, new MyRecordingAnnotator[]{annotator}), runnable); + } + + public static void useAnnotatorsIn(@NotNull Map annotatorsByLanguage, + @NotNull Runnable runnable) { MyRecordingAnnotator.clearAll(); - for (Annotator annotator : annotators) { - LanguageAnnotators.INSTANCE.addExplicitExtension(language, annotator); - } - try { - List list = LanguageAnnotators.INSTANCE.allForLanguage(language); - assertTrue(list.toString(), list.containsAll(Arrays.asList(annotators))); - runnable.run(); - for (MyRecordingAnnotator annotator : annotators) { - assertTrue(annotator +" must have done something but didn't", annotator.didIDoIt()); - } - } - finally { - for (int i = annotators.length - 1; i >= 0; i--) { - Annotator annotator = annotators[i]; - LanguageAnnotators.INSTANCE.removeExplicitExtension(language, annotator); + for (Map.Entry entry : annotatorsByLanguage.entrySet()) { + com.intellij.lang.Language language = entry.getKey(); + MyRecordingAnnotator[] annotators = entry.getValue(); + for (Annotator annotator : annotators) { + LanguageAnnotators.INSTANCE.addExplicitExtension(language, annotator); } } - List list = LanguageAnnotators.INSTANCE.allForLanguage(language); - for (Annotator annotator : annotators) { - assertFalse(list.toString(), list.contains(annotator)); + try { + for (Map.Entry entry : annotatorsByLanguage.entrySet()) { + com.intellij.lang.Language language = entry.getKey(); + MyRecordingAnnotator[] annotators = entry.getValue(); + List list = LanguageAnnotators.INSTANCE.allForLanguage(language); + assertTrue(list.toString(), list.containsAll(Arrays.asList(annotators))); + } + runnable.run(); + for (Map.Entry entry : annotatorsByLanguage.entrySet()) { + MyRecordingAnnotator[] annotators = entry.getValue(); + for (MyRecordingAnnotator annotator : annotators) { + assertTrue(annotator + " must have done something but didn't", annotator.didIDoIt()); + } + } + } + finally { + for (Map.Entry entry : annotatorsByLanguage.entrySet()) { + com.intellij.lang.Language language = entry.getKey(); + MyRecordingAnnotator[] annotators = entry.getValue(); + for (int i = annotators.length - 1; i >= 0; i--) { + Annotator annotator = annotators[i]; + LanguageAnnotators.INSTANCE.removeExplicitExtension(language, annotator); + } + } + } + + for (Map.Entry entry : annotatorsByLanguage.entrySet()) { + com.intellij.lang.Language language = entry.getKey(); + MyRecordingAnnotator[] annotators = entry.getValue(); + List list = LanguageAnnotators.INSTANCE.allForLanguage(language); + for (Annotator annotator : annotators) { + assertFalse(list.toString(), list.contains(annotator)); + } } } @@ -2509,38 +2540,49 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { public void testAddAnnotationViaBuilderEntailsCreatingCorrespondingRangeHighlighterImmediately() { if (!ensureEnoughParallelism()) return; - useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyRecordingAnnotator[]{new MyNewBuilderAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately); + useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyNewBuilderAnnotator(), this::checkSwearingAnnotationIsVisibleImmediately); } private static final AtomicBoolean annotated = new AtomicBoolean(); + private static final AtomicBoolean injectedAnnotated = new AtomicBoolean(); private static final AtomicBoolean inspected = new AtomicBoolean(); + public static class MySlowAnnotator extends MyRecordingAnnotator { + @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiFile) { + assertFalse("For this moment file has not to process injected fragments", injectedAnnotated.get()); + assertFalse("For this moment file has not to run inspections", inspected.get()); annotated.set(true); iDidIt(); } - assertFalse(inspected.get()); } } - public void test_RunInspectionsAfterCompletionOfGeneralHighlightPass_SecretSettingDoesWork() { + + public static class MyInjectedSlowAnnotator extends MyRecordingAnnotator { + + @Override + public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { + assertTrue("File already has to be annotated", annotated.get()); + injectedAnnotated.set(true); + iDidIt(); + } + } + + public void test_SerializeCodeInsightPasses_SecretSettingDoesWork() { if (!ensureEnoughParallelism()) return; + TextEditorHighlightingPassRegistrarImpl registrar = (TextEditorHighlightingPassRegistrarImpl)TextEditorHighlightingPassRegistrar.getInstance(myProject); - assertFalse("Somebody (rogue plugin?) has left the dangerous setting on", registrar.isRunInspectionsAfterCompletionOfGeneralHighlightPass()); + assertFalse("Somebody (rogue plugin?) has left the dangerous setting on", registrar.isSerializeCodeInsightPasses()); + registerInspection(new LocalInspectionTool() { @Override public @NotNull String getID() { return getTestName(false)+"MySlowInspectionTool"; } - @Override - public ProblemDescriptor @Nullable [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { - inspected.set(true); - return null; - } - @Override public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @@ -2548,21 +2590,34 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { return new PsiElementVisitor() { @Override public void visitElement(@NotNull PsiElement element) { - assertTrue(annotated.get()); + assertTrue("File has to be already annotated", annotated.get()); + inspected.set(true); } }; } }); try { - myDaemonCodeAnalyzer.runLocalInspectionPassAfterCompletionOfGeneralHighlightPass(true); + myDaemonCodeAnalyzer.serializeCodeInsightPasses(true); + + Map annotatorsByLanguage = new HashMap<>(); + annotatorsByLanguage.put(JavaLanguage.INSTANCE, new MyRecordingAnnotator[]{new MySlowAnnotator()}); + annotatorsByLanguage.put(XMLLanguage.INSTANCE, new MyRecordingAnnotator[]{new MyInjectedSlowAnnotator()}); + + useAnnotatorsIn(annotatorsByLanguage, () -> { + configureByText(JavaFileType.INSTANCE, + "class X{\n" + + "// language=XML\n" + + "String ql = \"1\";" + + "\n}"); - useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyRecordingAnnotator[]{new MySlowAnnotator(), }, () -> { - configureByText(JavaFileType.INSTANCE, "class X{}"); doHighlighting(); + assertTrue("File already has to be java annotated", annotated.get()); + assertTrue("File already has to annotate xml injection", injectedAnnotated.get()); + assertTrue("File already has to run inspections", inspected.get()); }); } finally { - myDaemonCodeAnalyzer.runLocalInspectionPassAfterCompletionOfGeneralHighlightPass(false); + myDaemonCodeAnalyzer.serializeCodeInsightPasses(false); } } @@ -2575,7 +2630,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { public void testTypingMustRescheduleDaemonBackByReparseDelayMillis() { EmptyAnnotator emptyAnnotator = new EmptyAnnotator(); executeWithReparseDelay(2000, () -> - useAnnotatorsIn(JavaLanguage.INSTANCE, new MyRecordingAnnotator[]{emptyAnnotator}, () -> { + useAnnotatorsIn(JavaLanguage.INSTANCE, emptyAnnotator, () -> { @Language("JAVA") String text = "class X {\n}"; configureByText(JavaFileType.INSTANCE, text); @@ -2606,7 +2661,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { } public void testDaemonDoesReportTheFirstProducedAnnotation() { - useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyRecordingAnnotator[]{new MyInfoAnnotator()}, () -> checkFirstAnnotation()); + useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyInfoAnnotator(), () -> checkFirstAnnotation()); } private void checkFirstAnnotation() { diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorHighlightingPassRegistrarImpl.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorHighlightingPassRegistrarImpl.java index 4272a9c4b703..158769f1f01f 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorHighlightingPassRegistrarImpl.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorHighlightingPassRegistrarImpl.java @@ -38,7 +38,7 @@ public final class TextEditorHighlightingPassRegistrarImpl extends TextEditorHig private final AtomicInteger nextAvailableId = new AtomicInteger(); private boolean checkedForCycles; private final Project myProject; - private boolean runInspectionsAfterCompletionOfGeneralHighlightPass; + private boolean serializeCodeInsightPasses; public TextEditorHighlightingPassRegistrarImpl(@NotNull Project project) { myProject = project; @@ -76,14 +76,14 @@ public final class TextEditorHighlightingPassRegistrarImpl extends TextEditorHig } @ApiStatus.Internal - void runInspectionsAfterCompletionOfGeneralHighlightPass(boolean flag) { - runInspectionsAfterCompletionOfGeneralHighlightPass = flag; + void serializeCodeInsightPasses(boolean flag) { + serializeCodeInsightPasses = flag; reRegisterFactories(); } @ApiStatus.Internal - boolean isRunInspectionsAfterCompletionOfGeneralHighlightPass() { - return runInspectionsAfterCompletionOfGeneralHighlightPass; + boolean isSerializeCodeInsightPasses() { + return serializeCodeInsightPasses; } private static final class PassConfig { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index 42ece01b46fd..c78e1fccb71b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -982,20 +982,38 @@ public final class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implement return result; } + /** + * @deprecated Use {@link DaemonCodeAnalyzerImpl#serializeCodeInsightPasses(boolean)} instead + */ + @Deprecated @ApiStatus.Internal public void runLocalInspectionPassAfterCompletionOfGeneralHighlightPass(boolean flag) { + serializeCodeInsightPasses(flag); + } + + /** + * This API is made {@code Internal} intentionally as it could lead to unpredictable highlighting performance behaviour. + * + * @param flag if {@code true}: enables code insight passes serialization: + * Injected fragments {@link InjectedGeneralHighlightingPass} highlighting and Inspections run after + * completion of Syntax analysis {@link GeneralHighlightingPass}. + * if {@code false} (default behaviour) code insight passes are running in parallel + */ + @ApiStatus.Internal + public void serializeCodeInsightPasses(boolean flag) { ApplicationManager.getApplication().assertIsDispatchThread(); setUpdateByTimerEnabled(false); try { - cancelUpdateProgress(false, "runLocalInspectionPassAfterCompletionOfGeneralHighlightPass"); + cancelUpdateProgress(false, "serializeCodeInsightPasses"); myPassExecutorService.cancelAll(true); TextEditorHighlightingPassRegistrarImpl registrar = (TextEditorHighlightingPassRegistrarImpl)TextEditorHighlightingPassRegistrar.getInstance(myProject); - registrar.runInspectionsAfterCompletionOfGeneralHighlightPass(flag); + registrar.serializeCodeInsightPasses(flag); } finally { setUpdateByTimerEnabled(true); } } + } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPassFactory.java index fa45581a169e..a276e6671821 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPassFactory.java @@ -7,13 +7,18 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; public final class InjectedGeneralHighlightingPassFactory implements MainHighlightingPassFactory, TextEditorHighlightingPassFactoryRegistrar { @Override public void registerHighlightingPassFactory(@NotNull TextEditorHighlightingPassRegistrar registrar, @NotNull Project project) { - registrar.registerTextEditorHighlightingPass(this, null, new int[]{Pass.UPDATE_ALL}, false, -1); + boolean serialized = Registry.is("editor.injected.highlighting.serialization.allowed") && + ((TextEditorHighlightingPassRegistrarImpl)registrar).isSerializeCodeInsightPasses(); + int[] ghl = {Pass.UPDATE_ALL}; + registrar.registerTextEditorHighlightingPass(this, serialized ? ghl : null, + serialized ? null : ghl, false, -1); } @NotNull diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java index 85892bbb2407..5c7d7de3e7d9 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java @@ -18,11 +18,11 @@ public final class LocalInspectionsPassFactory implements MainHighlightingPassFa @Override public void registerHighlightingPassFactory(@NotNull TextEditorHighlightingPassRegistrar registrar, @NotNull Project project) { - int[] GHP = {Pass.UPDATE_ALL}; - boolean runInspectionsAfterCompletionOfGeneralHighlightPass = - ((TextEditorHighlightingPassRegistrarImpl)registrar).isRunInspectionsAfterCompletionOfGeneralHighlightPass(); - registrar.registerTextEditorHighlightingPass(this, runInspectionsAfterCompletionOfGeneralHighlightPass ? GHP : null, - runInspectionsAfterCompletionOfGeneralHighlightPass ? null : GHP, true, Pass.LOCAL_INSPECTIONS); + int[] ghp = {Pass.UPDATE_ALL}; + boolean serializeCodeInsightPasses = + ((TextEditorHighlightingPassRegistrarImpl)registrar).isSerializeCodeInsightPasses(); + registrar.registerTextEditorHighlightingPass(this, serializeCodeInsightPasses ? ghp : null, + serializeCodeInsightPasses ? null : ghp, true, Pass.LOCAL_INSPECTIONS); } @NotNull diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index b477cd1ed2fa..ae45793e7715 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -937,6 +937,9 @@ ide.progress.indeterminate.by.default=true editor.injected.highlighting.enabled=true editor.injected.highlighting.enabled.description=Disables injected fragments highlighting (requires project reopening) +editor.injected.highlighting.serialization.allowed=true +editor.injected.highlighting.serialization.allowed.description=Allow injected fragments highlighting after syntax highlighting (requires project reopening) + # CPP-8395 Registry setting run.processes.with.pty not saved # Upper-case FALSE makes an option explicitly turned off distinct from the default value run.processes.with.pty=FALSE