diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/ex/ProjectInspectionToolRegistrar.kt b/platform/analysis-impl/src/com/intellij/codeInspection/ex/ProjectInspectionToolRegistrar.kt index fd257ed88462..4bd4ad3193fa 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/ex/ProjectInspectionToolRegistrar.kt +++ b/platform/analysis-impl/src/com/intellij/codeInspection/ex/ProjectInspectionToolRegistrar.kt @@ -5,49 +5,66 @@ import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus @Service(Service.Level.PROJECT) -class ProjectInspectionToolRegistrar(private val project: Project, scope: CoroutineScope) : InspectionToolsSupplier() { +@ApiStatus.Internal +class ProjectInspectionToolRegistrar(project: Project, scope: CoroutineScope) : InspectionToolsSupplier() { companion object { + @JvmStatic fun getInstance(project: Project): ProjectInspectionToolRegistrar = project.service() } - private val dynamicInspectionsFlow: StateFlow> = dynamicInspectionsFlow(project) + private val dynamicInspectionsFlow: StateFlow?> = dynamicInspectionsFlow(project) .flowOn(Dispatchers.Default) - .stateIn(scope, SharingStarted.Eagerly, initialValue = emptySet()) + .stateIn(scope, SharingStarted.Lazily, initialValue = null) + + private val dynamicInspectionsWereInitialized = Job() + + private val updateInspectionProfilesSubscription = scope.launch(Dispatchers.Default, start = CoroutineStart.LAZY) { + var oldInspections = emptySet() + dynamicInspectionsFlow + .filterNotNull() + .collect { currentInspections -> + try { + if (oldInspections == currentInspections) return@collect + + val newInspections = currentInspections - oldInspections + val outdatedInspections = oldInspections - currentInspections + + listeners.forEach { listener -> + outdatedInspections.forEach { + listener.toolRemoved(it.toolWrapper) + } + } + listeners.forEach { listener -> + newInspections.forEach { + listener.toolAdded(it.toolWrapper) + } + } + oldInspections = currentInspections + DaemonCodeAnalyzerEx.getInstance(project).restart() + } + finally { + dynamicInspectionsWereInitialized.complete() + } + } + } init { InspectionToolRegistrar.getInstance() - - scope.launch(Dispatchers.Default) { - var oldInspections = emptySet() - dynamicInspectionsFlow.collectLatest { currentInspections -> - if (oldInspections == currentInspections) return@collectLatest - - val newInspections = currentInspections - oldInspections - val outdatedInspections = oldInspections - currentInspections - - listeners.forEach { listener -> - outdatedInspections.forEach { - listener.toolRemoved(it.toolWrapper) - } - } - listeners.forEach { listener -> - newInspections.forEach { - listener.toolAdded(it.toolWrapper) - } - } - oldInspections = currentInspections - DaemonCodeAnalyzerEx.getInstance(project).restart() - } - } } - override fun createTools(): MutableList> { - return (InspectionToolRegistrar.getInstance().createTools() + dynamicInspectionsFlow.value.map { it.toolWrapper }).toMutableList() + suspend fun waitForDynamicInspectionsInitialization() { + updateInspectionProfilesSubscription.start() + dynamicInspectionsWereInitialized.join() + } + + override fun createTools(): List> { + updateInspectionProfilesSubscription.start() + val dynamicTools = dynamicInspectionsFlow.value?.map { it.toolWrapper } ?: emptyList() + return InspectionToolRegistrar.getInstance().createTools() + dynamicTools } } \ No newline at end of file diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/ex/dynamic-inspections.kt b/platform/analysis-impl/src/com/intellij/codeInspection/ex/dynamic-inspections.kt index 883adb096638..35eb01212079 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/ex/dynamic-inspections.kt +++ b/platform/analysis-impl/src/com/intellij/codeInspection/ex/dynamic-inspections.kt @@ -14,13 +14,16 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.* +import org.jetbrains.annotations.ApiStatus private val EP_NAME = ExtensionPointName("com.intellij.dynamicInspectionsProvider") +@ApiStatus.Internal interface DynamicInspectionsProvider { - fun inspectionsFlow(project: Project): Flow> + fun inspections(project: Project): Flow> } +@ApiStatus.Internal sealed class DynamicInspectionDescriptor { companion object { fun fromTool(tool: InspectionProfileEntry): DynamicInspectionDescriptor { @@ -34,30 +37,27 @@ sealed class DynamicInspectionDescriptor { val toolWrapper: InspectionToolWrapper<*, *> by lazy { when(this) { - is Global -> object : GlobalInspectionToolWrapper(tool) { - override fun createCopy(): GlobalInspectionToolWrapper { - return GlobalInspectionToolWrapper(tool) - } - } - is Local -> object : LocalInspectionToolWrapper(tool) { - override fun createCopy(): LocalInspectionToolWrapper { - return LocalInspectionToolWrapper(tool) - } - } + is Local -> DynamicLocalInspectionToolWrapper(tool) + is Global -> DynamicGlobalInspectionToolWrapper(tool) } } + class Local(val tool: LocalInspectionTool) : DynamicInspectionDescriptor() + class Global(val tool: GlobalInspectionTool) : DynamicInspectionDescriptor() + private class DynamicLocalInspectionToolWrapper(tool: LocalInspectionTool) : LocalInspectionToolWrapper(tool) { + override fun createCopy(): LocalInspectionToolWrapper = DynamicLocalInspectionToolWrapper(tool) + } - class Local(val tool: LocalInspectionTool) : DynamicInspectionDescriptor() + private class DynamicGlobalInspectionToolWrapper(tool: GlobalInspectionTool) : GlobalInspectionToolWrapper(tool) { + override fun createCopy(): GlobalInspectionToolWrapper = DynamicGlobalInspectionToolWrapper(tool) + } } @OptIn(ExperimentalCoroutinesApi::class) internal fun dynamicInspectionsFlow(project: Project): Flow> { val epUpdatedFlow = callbackFlow { - trySendBlocking(Unit) - val disposable = Disposer.newDisposable() val listener = object : ExtensionPointListener { override fun extensionAdded(extension: DynamicInspectionsProvider, pluginDescriptor: PluginDescriptor) { @@ -73,14 +73,17 @@ internal fun dynamicInspectionsFlow(project: Project): Flow>> = EP_NAME.extensionList.map { provider -> - provider.inspectionsFlow(project) - .map { inspections -> inspections.toSet() } - .onStart { emit(emptySet()) } + val allDynamicInspectionsFlows: List>> = EP_NAME.extensionList.map { provider -> + // if returned flow is simply empty, do not block collection of others in combine + provider.inspections(project).onEmpty { emit(emptySet()) } } - combine(allDynamicCustomInspectionsFlows) { + combine(allDynamicInspectionsFlows) { it.toList().flatten().toSet() + }.onEmpty { + // if there are no EP impls, emit the empty set + emit(emptySet()) } }.distinctUntilChanged() } \ No newline at end of file diff --git a/platform/analysis-impl/src/com/intellij/profile/codeInspection/InspectionProfileLoadUtil.java b/platform/analysis-impl/src/com/intellij/profile/codeInspection/InspectionProfileLoadUtil.java index 562b9d696348..50500d307d11 100644 --- a/platform/analysis-impl/src/com/intellij/profile/codeInspection/InspectionProfileLoadUtil.java +++ b/platform/analysis-impl/src/com/intellij/profile/codeInspection/InspectionProfileLoadUtil.java @@ -29,7 +29,7 @@ public final class InspectionProfileLoadUtil { } public static @NotNull InspectionProfileImpl load(@NotNull Path file, - @NotNull InspectionToolRegistrar registrar, + @NotNull InspectionToolsSupplier registrar, @NotNull InspectionProfileManager profileManager) throws JDOMException, IOException { Element element = JDOMUtil.load(file); String profileName = getProfileName(file, element); diff --git a/platform/inspect/src/com/intellij/codeInspection/inspectionProfile/YamlInspectionProfileImpl.kt b/platform/inspect/src/com/intellij/codeInspection/inspectionProfile/YamlInspectionProfileImpl.kt index e08a98880537..3363f3cc4410 100644 --- a/platform/inspect/src/com/intellij/codeInspection/inspectionProfile/YamlInspectionProfileImpl.kt +++ b/platform/inspect/src/com/intellij/codeInspection/inspectionProfile/YamlInspectionProfileImpl.kt @@ -116,7 +116,7 @@ class YamlInspectionProfileImpl private constructor(override val profileName: St @JvmStatic fun loadFrom(project: Project, filePath: String = "${getDefaultProfileDirectory(project)}/profile.yaml", - toolsSupplier: InspectionToolsSupplier = InspectionToolRegistrar.getInstance(), + toolsSupplier: InspectionToolsSupplier = ProjectInspectionToolRegistrar.getInstance(project), profileManager: BaseInspectionProfileManager = ProjectInspectionProfileManager.getInstance(project) ): YamlInspectionProfileImpl { val configFile = File(filePath).absoluteFile diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/header/InspectionProfileSchemesPanel.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/header/InspectionProfileSchemesPanel.java index 527ad368b470..c5fe1f14987f 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/header/InspectionProfileSchemesPanel.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/header/InspectionProfileSchemesPanel.java @@ -8,9 +8,7 @@ import com.intellij.application.options.schemes.AbstractSchemeActions; import com.intellij.application.options.schemes.DescriptionAwareSchemeActions; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; -import com.intellij.codeInspection.ex.InspectionProfileImpl; -import com.intellij.codeInspection.ex.InspectionProfileModifiableModel; -import com.intellij.codeInspection.ex.InspectionToolRegistrar; +import com.intellij.codeInspection.ex.*; import com.intellij.lang.LangBundle; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.application.ApplicationManager; @@ -253,8 +251,9 @@ public final class InspectionProfileSchemesPanel extends AbstractDescriptionAwar final boolean isProjectLevel = selectedProfile.isProjectLevel() ^ modifyLevel; BaseInspectionProfileManager profileManager = isProjectLevel ? myProjectProfileManager : myAppProfileManager; - InspectionProfileImpl inspectionProfile = - new InspectionProfileImpl(newName, InspectionToolRegistrar.getInstance(), profileManager); + InspectionToolsSupplier inspectionsRegistrar = isProjectLevel ? ProjectInspectionToolRegistrar.getInstance(project) + : InspectionToolRegistrar.getInstance(); + InspectionProfileImpl inspectionProfile = new InspectionProfileImpl(newName, inspectionsRegistrar, profileManager); inspectionProfile.copyFrom(selectedProfile); inspectionProfile.setName(newName);