diff --git a/.idea/modules.xml b/.idea/modules.xml index 542d709a46db..415452a3e91b 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -794,6 +794,7 @@ + diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt index 6d1c96c8390e..4e7433211c10 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt @@ -101,6 +101,7 @@ private val PLATFORM_IMPLEMENTATION_MODULES = persistentListOf( "intellij.platform.core.ui", "intellij.platform.credentialStore", "intellij.platform.credentialStore.ui", + "intellij.platform.dependenciesToolwindow", "intellij.platform.rd.community", "intellij.platform.ml.impl", "intellij.remoteDev.util", diff --git a/platform/dependencies-toolwindow/intellij.platform.dependenciesToolwindow.iml b/platform/dependencies-toolwindow/intellij.platform.dependenciesToolwindow.iml new file mode 100644 index 000000000000..a1a573ed0c25 --- /dev/null +++ b/platform/dependencies-toolwindow/intellij.platform.dependenciesToolwindow.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/dependencies-toolwindow/resources/icons/artifactSmall.svg b/platform/dependencies-toolwindow/resources/icons/artifactSmall.svg new file mode 100644 index 000000000000..5c11759ecb1f --- /dev/null +++ b/platform/dependencies-toolwindow/resources/icons/artifactSmall.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/platform/dependencies-toolwindow/resources/icons/artifactSmall_dark.svg b/platform/dependencies-toolwindow/resources/icons/artifactSmall_dark.svg new file mode 100644 index 000000000000..e56bff39ba30 --- /dev/null +++ b/platform/dependencies-toolwindow/resources/icons/artifactSmall_dark.svg @@ -0,0 +1,5 @@ + + + + diff --git a/platform/dependencies-toolwindow/resources/messages/dependenciesToolwindow.properties b/platform/dependencies-toolwindow/resources/messages/dependenciesToolwindow.properties new file mode 100644 index 000000000000..7b8e3eb06009 --- /dev/null +++ b/platform/dependencies-toolwindow/resources/messages/dependenciesToolwindow.properties @@ -0,0 +1 @@ +toolwindow.stripe.Dependencies=Dependencies diff --git a/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/CoroutineUtils.kt b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/CoroutineUtils.kt new file mode 100644 index 000000000000..46e70e6f4e68 --- /dev/null +++ b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/CoroutineUtils.kt @@ -0,0 +1,86 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.dependencytoolwindow + +import com.intellij.ide.ui.LafManager +import com.intellij.ide.ui.LafManagerListener +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.extensions.ExtensionPointListener +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.extensions.PluginDescriptor +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.util.concurrency.AppExecutorUtil +import com.intellij.util.messages.Topic +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Runnable +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlin.coroutines.CoroutineContext + +val ExtensionPointName.extensionsFlow: Flow> + get() = callbackFlow { + val listener = object : ExtensionPointListener { + override fun extensionAdded(extension: T, pluginDescriptor: PluginDescriptor) { + trySendBlocking(extensions.toList()) + } + + override fun extensionRemoved(extension: T, pluginDescriptor: PluginDescriptor) { + trySendBlocking(extensions.toList()) + } + } + send(extensions.toList()) + addExtensionPointListener(listener) + awaitClose { removeExtensionPointListener(listener) } + } + +@Service(Service.Level.PROJECT) +internal class DependencyToolwindowLifecycleScope : CoroutineScope, Disposable { + internal val dispatcher = + AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher() + + private val supervisor = SupervisorJob() + + override val coroutineContext = SupervisorJob() + CoroutineName(this::class.qualifiedName!!) + dispatcher + + override fun dispose() { + cancel("Disposing ${this::class.simpleName}") + } +} + +internal fun getLifecycleScope(project: Project): DependencyToolwindowLifecycleScope = project.service() + +fun Project.messageBusFlow( + topic: Topic, + initialValue: (suspend () -> K)? = null, + listener: suspend ProducerScope.() -> L +): Flow { + return callbackFlow { + initialValue?.let { send(it()) } + val connection = messageBus.simpleConnect() + connection.subscribe(topic, listener()) + awaitClose { connection.disconnect() } + } +} + +internal val Project.lookAndFeelFlow: Flow + get() = messageBusFlow(LafManagerListener.TOPIC, { LafManager.getInstance()!! }) { + LafManagerListener { trySend(it) } + } + +internal fun DependenciesToolWindowTabProvider.isAvailableFlow(project: Project): Flow { + return callbackFlow { + val sub = addIsAvailableChangesListener(project) { trySend(it) } + awaitClose { sub.unsubscribe() } + } +} \ No newline at end of file diff --git a/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/DependenciesToolWindowTabProvider.kt b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/DependenciesToolWindowTabProvider.kt new file mode 100644 index 000000000000..45e0d3f89bbf --- /dev/null +++ b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/DependenciesToolWindowTabProvider.kt @@ -0,0 +1,50 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.dependencytoolwindow + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import com.intellij.ui.content.Content +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach + +interface DependenciesToolWindowTabProvider { + companion object { + private val extensionPointName = ExtensionPointName("com.intellij.dependenciesToolWindow.tabProvider") + + internal fun availableTabsFlow(project: Project): Flow> { + return extensionPointName.extensionsFlow.flatMapLatest { extensions -> + channelFlow { + send(extensions.filter { it.isAvailable(project) }) + extensions.map { extension -> extension.isAvailableFlow(project) } + .merge() + .onEach { + val element = extensions.filter { it.isAvailable(project) } + send(element) + } + .launchIn(this) + } + } + } + + internal fun extensions(project: Project): List { + return extensionPointName.extensionList.filter { it.isAvailable(project) } + } + } + + fun provideTab(project: Project): Content + + fun isAvailable(project: Project): Boolean + + fun addIsAvailableChangesListener(project: Project, callback: (Boolean) -> Unit): Subscription + + fun interface Subscription { + /** + * Stops the listeners that generated this subscription. + */ + fun unsubscribe() + } +} \ No newline at end of file diff --git a/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/DependencyToolWindowFactory.kt b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/DependencyToolWindowFactory.kt new file mode 100644 index 000000000000..8a7c362dc4c2 --- /dev/null +++ b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/DependencyToolWindowFactory.kt @@ -0,0 +1,134 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:Suppress("ReplaceGetOrSet") + +package com.intellij.dependencytoolwindow + +import com.intellij.DynamicBundle +import com.intellij.openapi.application.EDT +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectPostStartupActivity +import com.intellij.openapi.wm.RegisterToolWindowTask +import com.intellij.openapi.wm.ToolWindow +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.openapi.wm.ex.ToolWindowEx +import com.intellij.ui.content.Content +import com.intellij.ui.content.ContentManager +import com.intellij.ui.content.ContentManagerEvent +import com.intellij.ui.content.ContentManagerListener +import icons.PlatformDependencyToolwindowIcons +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.Nls +import org.jetbrains.annotations.PropertyKey +import java.util.function.Supplier +import kotlin.coroutines.CoroutineContext + +class DependencyToolWindowFactory : ProjectPostStartupActivity { + companion object { + const val toolWindowId = "Dependencies" + + private fun getToolWindow(project: Project) = ToolWindowManager.getInstance(project).getToolWindow(toolWindowId) + + fun activateToolWindow(project: Project, tab: Content? = null, action: () -> Unit) { + val toolWindow = getToolWindow(project) ?: return + toolWindow.activate(action, true, true) + tab?.let { toolWindow.contentManager.setSelectedContent(it) } + } + } + + override suspend fun execute(project: Project) { + withContext(Dispatchers.toolWindowManager(project)) { + DependenciesToolWindowTabProvider.availableTabsFlow(project) + .filter { it.isNotEmpty() } + .take(1) + .map { + RegisterToolWindowTask.closable( + id = toolWindowId, + stripeTitle = DependencyToolWindowBundle.messagePointer("toolwindow.stripe.Dependencies"), + icon = PlatformDependencyToolwindowIcons.ArtifactSmall + ) + } + .map { toolWindowTask -> ToolWindowManager.getInstance(project).registerToolWindow(toolWindowTask) } + .collect { toolWindow -> initializeToolWindow(toolWindow, project) } + } + } +} + +private fun initializeToolWindow(toolWindow: ToolWindow, project: Project) { + toolWindow.contentManager.addSelectionChangedListener { event -> + if (toolWindow is ToolWindowEx) { + toolWindow.setAdditionalGearActions(null) + (event.content.component as? HasToolWindowActions) + ?.also { toolWindow.setAdditionalGearActions(it.gearActions) } + } + toolWindow.setTitleActions(emptyList()) + (event.content.component as? HasToolWindowActions) + ?.titleActions + ?.also { toolWindow.setTitleActions(it.toList()) } + } + + toolWindow.isAvailable = false + toolWindow.contentManager.removeAllContents(true) + + DependenciesToolWindowTabProvider.availableTabsFlow(project) + .flowOn(getLifecycleScope(project).dispatcher) + .map { it.map { provider -> provider.provideTab(project) } } + .onEach { change -> + val removedContent = toolWindow.contentManager.contents.filter { it !in change }.toSet() + val newContent = change.filter { it !in toolWindow.contentManager.contents } + val contentOrder = (toolWindow.contentManager.contents.toList() - removedContent + newContent) + .sortedBy { it.toolwindowTitle } + .mapIndexed { index, content -> content to index } + .toMap() + removedContent.forEach { toolWindow.contentManager.removeContent(it, true) } + newContent.forEach { content -> + contentOrder.get(content)?.let { order -> toolWindow.contentManager.addContent(content, order) } + ?: toolWindow.contentManager.addContent(content) + } + toolWindow.isAvailable = change.isNotEmpty() + } + .flowOn(Dispatchers.EDT) + .launchIn(getLifecycleScope(project)) + + project.lookAndFeelFlow + .onEach { + toolWindow.contentManager.component.invalidate() + toolWindow.contentManager.component.repaint() + } + .flowOn(Dispatchers.EDT) + .launchIn(getLifecycleScope(project)) +} + +@Suppress("FunctionName") +private fun SelectionChangedListener(action: (ContentManagerEvent) -> Unit): ContentManagerListener { + return object : ContentManagerListener { + override fun selectionChanged(event: ContentManagerEvent) { + action(event) + } + } +} + +private fun ContentManager.addSelectionChangedListener(action: (ContentManagerEvent) -> Unit): ContentManagerListener { + return SelectionChangedListener(action).also(::addContentManagerListener) +} + +private const val BUNDLE_NAME = "messages.dependenciesToolwindow" + +private object DependencyToolWindowBundle : DynamicBundle(BUNDLE_NAME) { + @Nls + fun messagePointer(@PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg params: Any): Supplier { + return getLazyMessage(key, *params) + } +} + +@Suppress("UnusedReceiverParameter") +private fun Dispatchers.toolWindowManager(project: Project): CoroutineDispatcher = object : CoroutineDispatcher() { + override fun dispatch(context: CoroutineContext, block: Runnable) = ToolWindowManager.getInstance(project).invokeLater(block) +} diff --git a/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/HasToolWindowActions.kt b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/HasToolWindowActions.kt new file mode 100644 index 000000000000..ae8d8cf29606 --- /dev/null +++ b/platform/dependencies-toolwindow/src/com/intellij/dependencytoolwindow/HasToolWindowActions.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.dependencytoolwindow + +import com.intellij.openapi.actionSystem.ActionGroup +import com.intellij.openapi.actionSystem.AnAction + +interface HasToolWindowActions { + val gearActions: ActionGroup? + val titleActions: List +} \ No newline at end of file diff --git a/platform/dependencies-toolwindow/src/icons/PlatformDependencyToolwindowIcons.java b/platform/dependencies-toolwindow/src/icons/PlatformDependencyToolwindowIcons.java new file mode 100644 index 000000000000..17b1cfcd9f50 --- /dev/null +++ b/platform/dependencies-toolwindow/src/icons/PlatformDependencyToolwindowIcons.java @@ -0,0 +1,18 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package icons; + +import com.intellij.ui.IconManager; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * NOTE THIS FILE IS AUTO-GENERATED + * DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead + */ +public final class PlatformDependencyToolwindowIcons { + private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { + return IconManager.getInstance().loadRasterizedIcon(path, PlatformDependencyToolwindowIcons.class.getClassLoader(), cacheKey, flags); + } + /** 13x13 */ public static final @NotNull Icon ArtifactSmall = load("icons/artifactSmall.svg", 1537187804, 2); +} diff --git a/platform/platform-impl/intellij.platform.ide.impl.iml b/platform/platform-impl/intellij.platform.ide.impl.iml index 633129396d64..f81d99abbea6 100644 --- a/platform/platform-impl/intellij.platform.ide.impl.iml +++ b/platform/platform-impl/intellij.platform.ide.impl.iml @@ -63,6 +63,7 @@ + diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index cc91364ea50d..913982d72f59 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -1,4 +1,9 @@ + + + + + \ No newline at end of file diff --git a/plugins/package-search/resources/META-INF/plugin.xml b/plugins/package-search/resources/META-INF/plugin.xml index 474084d6b0b9..67098935c60d 100644 --- a/plugins/package-search/resources/META-INF/plugin.xml +++ b/plugins/package-search/resources/META-INF/plugin.xml @@ -16,10 +16,6 @@ Supports Maven and Gradle projects. interface="com.jetbrains.packagesearch.intellij.plugin.extensibility.ProjectModuleOperationProvider" dynamic="true"/> - - @@ -84,10 +80,10 @@ Supports Maven and Gradle projects. - - - - ().packageManagementPanel) { project.pkgsUiStateModifier.setTargetModules(TargetModules.One(selectedModule)) } } diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/data/PackageSearchProjectService.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/data/PackageSearchProjectService.kt index f115aecc58fb..89b04c4c89a3 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/data/PackageSearchProjectService.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/data/PackageSearchProjectService.kt @@ -17,6 +17,7 @@ package com.jetbrains.packagesearch.intellij.plugin.data import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.dependencytoolwindow.DependencyToolWindowFactory import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.readAction import com.intellij.openapi.components.Service @@ -26,7 +27,6 @@ import com.intellij.psi.PsiManager import com.jetbrains.packagesearch.intellij.plugin.PackageSearchBundle import com.jetbrains.packagesearch.intellij.plugin.PluginEnvironment import com.jetbrains.packagesearch.intellij.plugin.extensibility.ProjectModule -import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.PackageSearchToolWindowFactory import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.KnownRepositories import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.ModuleModel import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.ProjectDataProvider @@ -61,7 +61,6 @@ import com.jetbrains.packagesearch.intellij.plugin.util.whileLoading import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asFlow @@ -71,11 +70,9 @@ import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach @@ -170,7 +167,7 @@ internal class PackageSearchProjectService(private val project: Project) { val projectModulesStateFlow = projectModulesSharedFlow.stateIn(project.lifecycleScope, SharingStarted.Eagerly, emptyList()) val isAvailable - get() = projectModulesStateFlow.value.isNotEmpty() || !isComputationAllowed + get() = projectModulesStateFlow.value.isNotEmpty() private val knownRepositoriesFlow = timer(1.hours) .mapLatestTimedWithLoading("knownRepositoriesFlow", knownRepositoriesLoadingFlow) { dataProvider.fetchKnownRepositories() } @@ -306,7 +303,7 @@ internal class PackageSearchProjectService(private val project: Project) { var controller: BackgroundLoadingBarController? = null project.toolWindowManagerFlow - .filter { it.id == PackageSearchToolWindowFactory.ToolWindowId } + .filter { it.id == DependencyToolWindowFactory.toolWindowId } .take(1) .onEach { canShowLoadingBar.emit(true) } .launchIn(project.lifecycleScope) diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/intentions/PackageSearchUnresolvedReferenceQuickFix.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/intentions/PackageSearchUnresolvedReferenceQuickFix.kt index ebddd61a5f84..2f575d8d1b83 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/intentions/PackageSearchUnresolvedReferenceQuickFix.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/intentions/PackageSearchUnresolvedReferenceQuickFix.kt @@ -18,6 +18,8 @@ package com.jetbrains.packagesearch.intellij.plugin.intentions import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.dependencytoolwindow.DependencyToolWindowFactory +import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.Iconable @@ -25,7 +27,7 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiReference import com.jetbrains.packagesearch.PackageSearchIcons import com.jetbrains.packagesearch.intellij.plugin.PackageSearchBundle -import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.PackageSearchToolWindowFactory +import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.PackagesListPanelProvider import com.jetbrains.packagesearch.intellij.plugin.util.pkgsUiStateModifier class PackageSearchUnresolvedReferenceQuickFix(private val ref: PsiReference) : IntentionAction, LowPriorityAction, Iconable { @@ -34,7 +36,7 @@ class PackageSearchUnresolvedReferenceQuickFix(private val ref: PsiReference) : Regex("(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{Lu}\\p{javaJavaIdentifierPart}+") override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { - PackageSearchToolWindowFactory.activateToolWindow(project) { + DependencyToolWindowFactory.activateToolWindow(project, project.service().packageManagementPanel) { project.pkgsUiStateModifier.setSearchQuery(ref.canonicalText) } } diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/services/DependencyNavigationService.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/services/DependencyNavigationService.kt index 74a61a5dd9f0..a80b5bc56598 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/services/DependencyNavigationService.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/services/DependencyNavigationService.kt @@ -18,11 +18,12 @@ package com.jetbrains.packagesearch.intellij.plugin.ui.services import com.intellij.buildsystem.model.unified.UnifiedCoordinates import com.intellij.buildsystem.model.unified.UnifiedDependency +import com.intellij.dependencytoolwindow.DependencyToolWindowFactory import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project -import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.PackageSearchToolWindowFactory +import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.PackagesListPanelProvider import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.ModuleModel import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.TargetModules import com.jetbrains.packagesearch.intellij.plugin.util.lifecycleScope @@ -31,7 +32,7 @@ import com.jetbrains.packagesearch.intellij.plugin.util.pkgsUiStateModifier import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -class DependencyNavigationService(private val project: Project) { +internal class DependencyNavigationService(private val project: Project) { /** * Open the Dependency toolwindows at the selected [module] if found and searches for the first @@ -83,7 +84,7 @@ class DependencyNavigationService(private val project: Project) { private fun onSuccess(moduleModel: ModuleModel, dependency: UnifiedDependency): NavigationResult.Success { project.lifecycleScope.launch(Dispatchers.EDT) { - PackageSearchToolWindowFactory.activateToolWindow(project) { + DependencyToolWindowFactory.activateToolWindow(project, project.service().packageManagementPanel) { project.pkgsUiStateModifier.setTargetModules(TargetModules.from(moduleModel)) project.pkgsUiStateModifier.setDependency(dependency) } diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/DependenciesToolwindowTabProvider.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/DependenciesToolwindowTabProvider.kt deleted file mode 100644 index 649cae90ba6e..000000000000 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/DependenciesToolwindowTabProvider.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow - -import com.intellij.openapi.extensions.ExtensionPointName -import com.intellij.openapi.project.Project -import com.intellij.ui.content.Content -import com.jetbrains.packagesearch.intellij.plugin.extensibility.Subscription -import com.jetbrains.packagesearch.intellij.plugin.util.extensionsFlow -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onEach - -interface DependenciesToolwindowTabProvider { - - companion object { - - private val extensionPointName: ExtensionPointName - get() = ExtensionPointName.create("com.intellij.packagesearch.dependenciesToolwindowTabProvider") - - internal fun availableTabsFlow(project: Project): Flow> = - extensionPointName.extensionsFlow.flatMapLatest { extensions -> - channelFlow { - send(extensions.filter { it.isAvailable(project) }) - extensions.map { extension -> extension.isAvailableFlow(project) } - .merge() - .onEach { - val element = extensions.filter { it.isAvailable(project) } - send(element) - } - .launchIn(this) - } - } - - internal fun extensions(project: Project) = - extensionPointName.extensions.toList().filter { it.isAvailable(project) } - } - - fun provideTab(project: Project): Content - - fun isAvailable(project: Project): Boolean - - fun addIsAvailableChangesListener(project: Project, callback: (Boolean) -> Unit): Subscription -} \ No newline at end of file diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/PackageSearchToolWindowFactory.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/PackageSearchToolWindowFactory.kt deleted file mode 100644 index eaa5eecd3e2f..000000000000 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/PackageSearchToolWindowFactory.kt +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow - -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.ProjectPostStartupActivity -import com.intellij.openapi.wm.RegisterToolWindowTask -import com.intellij.openapi.wm.ToolWindowManager -import com.jetbrains.packagesearch.PackageSearchIcons -import com.jetbrains.packagesearch.intellij.plugin.PackageSearchBundle -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.take -import kotlinx.coroutines.withContext - -internal class PackageSearchToolWindowFactory : ProjectPostStartupActivity { - companion object { - internal val ToolWindowId = PackageSearchBundle.message("toolwindow.stripe.Dependencies") - - private fun getToolWindow(project: Project) = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId) - - fun activateToolWindow(project: Project, action: () -> Unit) { - getToolWindow(project)?.activate(action, true, true) - } - } - - override suspend fun execute(project: Project) { - withContext(Dispatchers.toolWindowManager(project)) { - DependenciesToolwindowTabProvider.availableTabsFlow(project) - .filter { it.isNotEmpty() } - .take(1) - .map { - RegisterToolWindowTask.closable( - ToolWindowId, - PackageSearchBundle.messagePointer("toolwindow.stripe.Dependencies"), - PackageSearchIcons.ArtifactSmall - ) - } - .map { toolWindowTask -> ToolWindowManager.getInstance(project).registerToolWindow(toolWindowTask) } - .collect { toolWindow -> toolWindow.initialize(project) } - } - } -} diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/PackagesListPanelProvider.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/PackagesListPanelProvider.kt index 1ddd34f144a9..05909d1e1c54 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/PackagesListPanelProvider.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/PackagesListPanelProvider.kt @@ -1,22 +1,23 @@ package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow +import com.intellij.dependencytoolwindow.DependenciesToolWindowTabProvider +import com.intellij.dependencytoolwindow.DependenciesToolWindowTabProvider.Subscription import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service.Level import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.ui.content.Content import com.intellij.ui.content.ContentFactory -import com.jetbrains.packagesearch.intellij.plugin.extensibility.Subscription import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.management.PackageManagementPanel import com.jetbrains.packagesearch.intellij.plugin.util.lifecycleScope import com.jetbrains.packagesearch.intellij.plugin.util.packageSearchProjectService import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -class PackagesListPanelProvider : DependenciesToolwindowTabProvider { +internal class PackagesListPanelProvider : DependenciesToolWindowTabProvider { @Service(Level.PROJECT) - private class PanelContainer(private val project: Project) { + internal class PanelContainer(private val project: Project) { val packageManagementPanel by lazy { PackageManagementPanel(project).initialize(ContentFactory.getInstance()) } } diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/RepositoryManagementPanelProvider.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/RepositoryManagementPanelProvider.kt index d5fcc6bebd38..ad9c56c06949 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/RepositoryManagementPanelProvider.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/RepositoryManagementPanelProvider.kt @@ -1,12 +1,13 @@ package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow +import com.intellij.dependencytoolwindow.DependenciesToolWindowTabProvider +import com.intellij.dependencytoolwindow.DependenciesToolWindowTabProvider.Subscription import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service.Level import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.ui.content.Content import com.intellij.ui.content.ContentFactory -import com.jetbrains.packagesearch.intellij.plugin.extensibility.Subscription import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.repositories.RepositoryManagementPanel import com.jetbrains.packagesearch.intellij.plugin.util.FeatureFlags import com.jetbrains.packagesearch.intellij.plugin.util.lifecycleScope @@ -18,7 +19,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -class RepositoryManagementPanelProvider : DependenciesToolwindowTabProvider { +class RepositoryManagementPanelProvider : DependenciesToolWindowTabProvider { @Service(Level.PROJECT) private class PanelContainer(private val project: Project) { diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/ToolWindowUtils.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/ToolWindowUtils.kt index 6c41d4aa3d34..3b15541e6395 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/ToolWindowUtils.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/ToolWindowUtils.kt @@ -19,71 +19,13 @@ package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.DataProvider -import com.intellij.openapi.application.EDT -import com.intellij.openapi.project.Project -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowManager -import com.intellij.openapi.wm.ex.ToolWindowEx import com.intellij.ui.content.Content import com.intellij.ui.content.ContentFactory -import com.intellij.util.castSafelyTo -import com.jetbrains.packagesearch.intellij.plugin.PackageSearchBundle -import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.HasToolWindowActions import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.PackageSearchPanelBase import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.SimpleToolWindowWithToolWindowActionsPanel import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.SimpleToolWindowWithTwoToolbarsPanel -import com.jetbrains.packagesearch.intellij.plugin.ui.updateAndRepaint -import com.jetbrains.packagesearch.intellij.plugin.util.addSelectionChangedListener -import com.jetbrains.packagesearch.intellij.plugin.util.lifecycleScope -import com.jetbrains.packagesearch.intellij.plugin.util.lookAndFeelFlow -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import org.jetbrains.annotations.Nls import javax.swing.JComponent -import kotlin.coroutines.CoroutineContext - -internal fun ToolWindow.initialize(project: Project) { - title = PackageSearchBundle.message("toolwindow.stripe.Dependencies") - - contentManager.addSelectionChangedListener { event -> - if (this is ToolWindowEx) { - setAdditionalGearActions(null) - event.content.component.castSafelyTo() - ?.also { setAdditionalGearActions(it.gearActions) } - } - setTitleActions(emptyList()) - event.content.component.castSafelyTo() - ?.titleActions - ?.also { setTitleActions(it.toList()) } - } - - isAvailable = false - contentManager.removeAllContents(true) - - DependenciesToolwindowTabProvider.availableTabsFlow(project) - .flowOn(project.lifecycleScope.coroutineDispatcher) - .map { it.map { it.provideTab(project) } } - .onEach { change -> - val removedContent = contentManager.contents.filter { it !in change } - val newContent = change.filter { it !in contentManager.contents } - removedContent.forEach { contentManager.removeContent(it, true) } - newContent.forEach { contentManager.addContent(it) } - isAvailable = change.isNotEmpty() - } - .flowOn(Dispatchers.EDT) - .launchIn(project.lifecycleScope) - - project.lookAndFeelFlow - .onEach { contentManager.component.updateAndRepaint() } - .flowOn(Dispatchers.EDT) - .launchIn(project.lifecycleScope) -} internal fun PackageSearchPanelBase.initialize(contentFactory: ContentFactory): Content { val panelContent = content // should be executed before toolbars @@ -93,20 +35,30 @@ internal fun PackageSearchPanelBase.initialize(contentFactory: ContentFactory): val titleActions = titleActions return if (topToolbar == null) { - createSimpleToolWindowWithToolWindowActionsPanel(title, panelContent, toolbar, gearActions, titleActions, contentFactory, this) - } else contentFactory.createContent( - toolbar?.let { - SimpleToolWindowWithTwoToolbarsPanel( - it, - topToolbar, - gearActions, - titleActions, - panelContent - ) - }, - title, - false - ).apply { isCloseable = false } + createSimpleToolWindowWithToolWindowActionsPanel( + title = title, + content = panelContent, + toolbar = toolbar, + gearActions = gearActions, + titleActions = titleActions, + contentFactory = contentFactory, + provider = this + ) + } else { + contentFactory.createContent( + toolbar?.let { + SimpleToolWindowWithTwoToolbarsPanel( + it, + topToolbar, + gearActions, + titleActions, + panelContent + ) + }, + title, + false + ).apply { isCloseable = false } + } } internal fun createSimpleToolWindowWithToolWindowActionsPanel( @@ -114,12 +66,17 @@ internal fun createSimpleToolWindowWithToolWindowActionsPanel( content: JComponent, toolbar: JComponent?, gearActions: ActionGroup?, - titleActions: Array?, + titleActions: List, contentFactory: ContentFactory, provider: DataProvider ): Content { val createContent = contentFactory.createContent(null, title, false) - val actionsPanel = SimpleToolWindowWithToolWindowActionsPanel(gearActions, titleActions, false, provider = provider) + val actionsPanel = SimpleToolWindowWithToolWindowActionsPanel( + gearActions = gearActions, + titleActions = titleActions, + vertical = false, + provider = provider + ) actionsPanel.setProvideQuickActions(true) actionsPanel.setContent(content) toolbar?.let { actionsPanel.toolbar = it } @@ -130,16 +87,3 @@ internal fun createSimpleToolWindowWithToolWindowActionsPanel( return createContent } -@Suppress("unused") -internal fun Dispatchers.toolWindowManager(project: Project): CoroutineDispatcher = object : CoroutineDispatcher() { - - private val executor = ToolWindowManager.getInstance(project) - - override fun dispatch(context: CoroutineContext, block: Runnable) = executor.invokeLater(block) -} - -fun DependenciesToolwindowTabProvider.isAvailableFlow(project: Project) = - callbackFlow { - val sub = addIsAvailableChangesListener(project) { trySend(it) } - awaitClose { sub.unsubscribe() } - } diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/HasToolWindowActions.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/HasToolWindowActions.kt deleted file mode 100644 index f385f310659d..000000000000 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/HasToolWindowActions.kt +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * Copyright 2000-2022 JetBrains s.r.o. and contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ - -package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels - -import com.intellij.openapi.actionSystem.ActionGroup -import com.intellij.openapi.actionSystem.AnAction - -internal interface HasToolWindowActions { - - val gearActions: ActionGroup? - val titleActions: Array? -} diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/PackageSearchPanelBase.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/PackageSearchPanelBase.kt index 3106d8b38899..382c149946ac 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/PackageSearchPanelBase.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/PackageSearchPanelBase.kt @@ -32,11 +32,11 @@ internal abstract class PackageSearchPanelBase(@Nls val title: String) : DataPro internal val gearActions: ActionGroup? by lazy { buildGearActions() } - internal val titleActions: Array? by lazy { buildTitleActions() } + internal val titleActions: List by lazy(::buildTitleActions) protected abstract fun build(): JComponent protected open fun buildToolbar(): JComponent? = null protected open fun buildTopToolbar(): JComponent? = null protected open fun buildGearActions(): ActionGroup? = null - protected open fun buildTitleActions(): Array? = null + protected open fun buildTitleActions(): List = emptyList() } diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithToolWindowActionsPanel.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithToolWindowActionsPanel.kt index 7236edd70209..51cf1e3fd259 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithToolWindowActionsPanel.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithToolWindowActionsPanel.kt @@ -16,6 +16,7 @@ package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels +import com.intellij.dependencytoolwindow.HasToolWindowActions import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.DataProvider @@ -23,7 +24,7 @@ import com.intellij.openapi.ui.SimpleToolWindowPanel internal class SimpleToolWindowWithToolWindowActionsPanel( override val gearActions: ActionGroup?, - override val titleActions: Array?, + override val titleActions: List, vertical: Boolean = false, borderless: Boolean = false, val provider: DataProvider diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithTwoToolbarsPanel.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithTwoToolbarsPanel.kt index 7523985403da..900bfd4b5cad 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithTwoToolbarsPanel.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/SimpleToolWindowWithTwoToolbarsPanel.kt @@ -16,11 +16,11 @@ package com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels +import com.intellij.dependencytoolwindow.HasToolWindowActions import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.ActionToolbar import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.DataProvider -import com.intellij.ui.JBColor import com.intellij.ui.switcher.QuickActionProvider import com.intellij.util.ui.UIUtil import com.jetbrains.packagesearch.intellij.plugin.ui.PackageSearchUI @@ -37,7 +37,7 @@ internal class SimpleToolWindowWithTwoToolbarsPanel( private val leftToolbar: JComponent, private val topToolbar: JComponent, override val gearActions: ActionGroup?, - override val titleActions: Array?, + override val titleActions: List, val content: JComponent ) : JPanel(), QuickActionProvider, DataProvider, HasToolWindowActions { diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/management/PackageManagementPanel.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/management/PackageManagementPanel.kt index 3ce9ad84df71..9585e8c5c069 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/management/PackageManagementPanel.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/ui/toolwindow/panels/management/PackageManagementPanel.kt @@ -192,10 +192,12 @@ internal class PackageManagementPanel( togglePackageDetailsAction ) - override fun buildTitleActions(): Array = arrayOf(togglePackageDetailsAction) + override fun buildTitleActions(): List = listOf(togglePackageDetailsAction) - override fun getData(dataId: String) = when { - PkgsToDAAction.PACKAGES_LIST_PANEL_DATA_KEY.`is`(dataId) -> dataModelStateFlow.value - else -> null + override fun getData(dataId: String): PackageModel.Installed? { + return when { + PkgsToDAAction.PACKAGES_LIST_PANEL_DATA_KEY.`is`(dataId) -> dataModelStateFlow.value + else -> null + } } } diff --git a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/util/ProjectExtensions.kt b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/util/ProjectExtensions.kt index ef6988174ffa..8abf088ed792 100644 --- a/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/util/ProjectExtensions.kt +++ b/plugins/package-search/src/com/jetbrains/packagesearch/intellij/plugin/util/ProjectExtensions.kt @@ -159,22 +159,6 @@ internal val Project.lookAndFeelFlow: Flow LafManagerListener { trySend(it) } } -val ExtensionPointName.extensionsFlow: Flow> - get() = callbackFlow { - val listener = object : ExtensionPointListener { - override fun extensionAdded(extension: T, pluginDescriptor: PluginDescriptor) { - trySendBlocking(extensions.toList()) - } - - override fun extensionRemoved(extension: T, pluginDescriptor: PluginDescriptor) { - trySendBlocking(extensions.toList()) - } - } - send(extensions.toList()) - addExtensionPointListener(listener) - awaitClose { removeExtensionPointListener(listener) } - } - fun Project.hasKotlinModules(): Boolean = ModuleManager.getInstance(this).modules.any { it.hasKotlinFacet() } internal fun Module.hasKotlinFacet(): Boolean {