move PKGS tool window code into a new core module

GitOrigin-RevId: 25a7c51e5465f533e852a01bf321f61d3903c839
This commit is contained in:
Lamberto Basti
2022-09-12 14:28:32 +02:00
committed by intellij-monorepo-bot
parent 2afb98118d
commit 4a00935dc5
30 changed files with 407 additions and 252 deletions

1
.idea/modules.xml generated
View File

@@ -794,6 +794,7 @@
<module fileurl="file://$PROJECT_DIR$/platform/xdebugger-api/intellij.platform.debugger.iml" filepath="$PROJECT_DIR$/platform/xdebugger-api/intellij.platform.debugger.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/xdebugger-impl/intellij.platform.debugger.impl.iml" filepath="$PROJECT_DIR$/platform/xdebugger-impl/intellij.platform.debugger.impl.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/xdebugger-testFramework/intellij.platform.debugger.testFramework.iml" filepath="$PROJECT_DIR$/platform/xdebugger-testFramework/intellij.platform.debugger.testFramework.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/dependencies-toolwindow/intellij.platform.dependenciesToolwindow.iml" filepath="$PROJECT_DIR$/platform/dependencies-toolwindow/intellij.platform.dependenciesToolwindow.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/build-scripts/dev-server/intellij.platform.devBuildServer.iml" filepath="$PROJECT_DIR$/platform/build-scripts/dev-server/intellij.platform.devBuildServer.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/diagnostic/intellij.platform.diagnostic.iml" filepath="$PROJECT_DIR$/platform/diagnostic/intellij.platform.diagnostic.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/diagnostic/telemetry/intellij.platform.diagnostic.telemetry.iml" filepath="$PROJECT_DIR$/platform/diagnostic/telemetry/intellij.platform.diagnostic.telemetry.iml" />

View File

@@ -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",

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib-jdk8" level="project" />
<orderEntry type="module" module-name="intellij.platform.ide" />
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="library" name="kotlinx-coroutines-jdk8" level="project" />
</component>
</module>

View File

@@ -0,0 +1,8 @@
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg height="13" viewBox="0 0 13 13" width="13" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="m0-1h14v14h-14z"/>
<path d="m6.5.375 6.125 3.40277778-6.125 3.40277778-6.125-3.40277778zm4.9 5.44444444 1.225.68055556-6.125 3.40277778-6.125-3.40277778 1.225-.68055556 4.9 2.72222223zm0 2.72222223 1.225.68055555-6.125 3.40277778-6.125-3.40277778 1.225-.68055555 4.9 2.72222223z"
fill="#6e6e6e"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 603 B

View File

@@ -0,0 +1,5 @@
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg height="13" viewBox="0 0 13 13" width="13" xmlns="http://www.w3.org/2000/svg">
<path d="m6.5.375 6.125 3.40277778-6.125 3.40277778-6.125-3.40277778zm4.9 5.44444444 1.225.68055556-6.125 3.40277778-6.125-3.40277778 1.225-.68055556 4.9 2.72222223zm0 2.72222223 1.225.68055555-6.125 3.40277778-6.125-3.40277778 1.225-.68055555 4.9 2.72222223z"
fill="#afb1b3" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 530 B

View File

@@ -0,0 +1 @@
toolwindow.stripe.Dependencies=Dependencies

View File

@@ -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 <T : Any> ExtensionPointName<T>.extensionsFlow: Flow<List<T>>
get() = callbackFlow {
val listener = object : ExtensionPointListener<T> {
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 <L : Any, K> Project.messageBusFlow(
topic: Topic<L>,
initialValue: (suspend () -> K)? = null,
listener: suspend ProducerScope<K>.() -> L
): Flow<K> {
return callbackFlow {
initialValue?.let { send(it()) }
val connection = messageBus.simpleConnect()
connection.subscribe(topic, listener())
awaitClose { connection.disconnect() }
}
}
internal val Project.lookAndFeelFlow: Flow<LafManager>
get() = messageBusFlow(LafManagerListener.TOPIC, { LafManager.getInstance()!! }) {
LafManagerListener { trySend(it) }
}
internal fun DependenciesToolWindowTabProvider.isAvailableFlow(project: Project): Flow<Boolean> {
return callbackFlow {
val sub = addIsAvailableChangesListener(project) { trySend(it) }
awaitClose { sub.unsubscribe() }
}
}

View File

@@ -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<DependenciesToolWindowTabProvider>("com.intellij.dependenciesToolWindow.tabProvider")
internal fun availableTabsFlow(project: Project): Flow<List<DependenciesToolWindowTabProvider>> {
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<DependenciesToolWindowTabProvider> {
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()
}
}

View File

@@ -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<String> {
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)
}

View File

@@ -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<AnAction>
}

View File

@@ -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);
}

View File

@@ -63,6 +63,7 @@
<orderEntry type="module" module-name="intellij.platform.configurationStore.impl" scope="RUNTIME" />
<orderEntry type="library" scope="RUNTIME" name="imageio-tiff" level="project" />
<orderEntry type="module" module-name="intellij.platform.credentialStore.ui" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.platform.dependenciesToolwindow" scope="RUNTIME" />
<orderEntry type="library" name="StreamEx" level="project" />
<orderEntry type="library" name="jediterm-pty" level="project" />
<orderEntry type="library" name="kotlinx-coroutines-jdk8" level="project" />

View File

@@ -1,4 +1,9 @@
<idea-plugin>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.dependenciesToolWindow.tabProvider"
interface="com.intellij.dependencytoolwindow.DependenciesToolWindowTabProvider"
dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceInterface="com.intellij.openapi.project.impl.ProjectStoreFactory"
serviceImplementation="com.intellij.configurationStore.PlatformLangProjectStoreFactory"
@@ -1536,6 +1541,7 @@
<postStartupActivity implementation="com.intellij.lang.documentation.ide.impl.DocumentationAutoPopup"/>
<postStartupActivity implementation="com.intellij.codeInsight.documentation.DocumentationSettingsListener"/>
<backgroundPostStartupActivity implementation="com.intellij.dependencytoolwindow.DependencyToolWindowFactory"/>
<lang.documentation implementation="com.intellij.lang.documentation.symbol.impl.DefaultTargetSymbolDocumentationTargetProvider"/>
<lang.documentationLinkHandler implementation="com.intellij.lang.documentation.psi.PsiDocumentationLinkHandler"/>
<projectService serviceInterface="com.intellij.lang.documentation.ide.IdeDocumentationTargetProvider"

View File

@@ -67,5 +67,6 @@
<orderEntry type="module" module-name="intellij.platform.statistics" />
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<orderEntry type="module" module-name="intellij.repository.search" />
<orderEntry type="module" module-name="intellij.platform.dependenciesToolwindow" />
</component>
</module>

View File

@@ -16,10 +16,6 @@ Supports Maven and Gradle projects.
interface="com.jetbrains.packagesearch.intellij.plugin.extensibility.ProjectModuleOperationProvider"
dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.packagesearch.dependenciesToolwindowTabProvider"
interface="com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.DependenciesToolwindowTabProvider"
dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.packagesearch.coroutineProjectModuleOperationProvider"
interface="com.jetbrains.packagesearch.intellij.plugin.extensibility.CoroutineProjectModuleOperationProvider"
dynamic="true"/>
@@ -84,10 +80,10 @@ Supports Maven and Gradle projects.
<packagesearch.flowModuleChangesSignalProvider
implementation="com.jetbrains.packagesearch.intellij.plugin.extensibility.ExternalProjectSignalProvider"/>
<packagesearch.dependenciesToolwindowTabProvider
<dependenciesToolWindow.tabProvider
implementation="com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.PackagesListPanelProvider"/>
<packagesearch.dependenciesToolwindowTabProvider
<dependenciesToolWindow.tabProvider
implementation="com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.RepositoryManagementPanelProvider"/>
<projectService
@@ -129,8 +125,6 @@ Supports Maven and Gradle projects.
<statistics.validation.customValidationRule implementation="com.jetbrains.packagesearch.intellij.plugin.fus.TopPackageIdValidationRule"/>
<backgroundPostStartupActivity implementation="com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.PackageSearchToolWindowFactory"/>
<!--suppress PluginXmlCapitalization -->
<notificationGroup displayType="BALLOON"
id="packagesearch.notification"

View File

@@ -16,12 +16,14 @@
package com.jetbrains.packagesearch.intellij.plugin.actions
import com.intellij.dependencytoolwindow.DependencyToolWindowFactory
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.service
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiDirectory
@@ -30,7 +32,7 @@ import com.intellij.psi.util.PsiUtilBase
import com.jetbrains.packagesearch.PackageSearchIcons
import com.jetbrains.packagesearch.intellij.plugin.PackageSearchBundle
import com.jetbrains.packagesearch.intellij.plugin.extensibility.CoroutineProjectModuleOperationProvider
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.packageSearchProjectService
@@ -69,7 +71,7 @@ class AddDependencyAction : AnAction(
val selectedModule = findSelectedModule(e, modules) ?: return
PackageSearchToolWindowFactory.activateToolWindow(project) {
DependencyToolWindowFactory.activateToolWindow(project, project.service<PackagesListPanelProvider.PanelContainer>().packageManagementPanel) {
project.pkgsUiStateModifier.setTargetModules(TargetModules.One(selectedModule))
}
}

View File

@@ -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)

View File

@@ -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<PackagesListPanelProvider.PanelContainer>().packageManagementPanel) {
project.pkgsUiStateModifier.setSearchQuery(ref.canonicalText)
}
}

View File

@@ -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<PackagesListPanelProvider.PanelContainer>().packageManagementPanel) {
project.pkgsUiStateModifier.setTargetModules(TargetModules.from(moduleModel))
project.pkgsUiStateModifier.setDependency(dependency)
}

View File

@@ -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<DependenciesToolwindowTabProvider>
get() = ExtensionPointName.create("com.intellij.packagesearch.dependenciesToolwindowTabProvider")
internal fun availableTabsFlow(project: Project): Flow<List<DependenciesToolwindowTabProvider>> =
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
}

View File

@@ -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) }
}
}
}

View File

@@ -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()) }
}

View File

@@ -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) {

View File

@@ -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<HasToolWindowActions>()
?.also { setAdditionalGearActions(it.gearActions) }
}
setTitleActions(emptyList())
event.content.component.castSafelyTo<HasToolWindowActions>()
?.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<AnAction>?,
titleActions: List<AnAction>,
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() }
}

View File

@@ -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<AnAction>?
}

View File

@@ -32,11 +32,11 @@ internal abstract class PackageSearchPanelBase(@Nls val title: String) : DataPro
internal val gearActions: ActionGroup? by lazy { buildGearActions() }
internal val titleActions: Array<AnAction>? by lazy { buildTitleActions() }
internal val titleActions: List<AnAction> 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<AnAction>? = null
protected open fun buildTitleActions(): List<AnAction> = emptyList()
}

View File

@@ -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<AnAction>?,
override val titleActions: List<AnAction>,
vertical: Boolean = false,
borderless: Boolean = false,
val provider: DataProvider

View File

@@ -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<AnAction>?,
override val titleActions: List<AnAction>,
val content: JComponent
) : JPanel(), QuickActionProvider, DataProvider, HasToolWindowActions {

View File

@@ -192,10 +192,12 @@ internal class PackageManagementPanel(
togglePackageDetailsAction
)
override fun buildTitleActions(): Array<AnAction> = arrayOf(togglePackageDetailsAction)
override fun buildTitleActions(): List<AnAction> = 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
}
}
}

View File

@@ -159,22 +159,6 @@ internal val Project.lookAndFeelFlow: Flow<LafManager>
LafManagerListener { trySend(it) }
}
val <T : Any> ExtensionPointName<T>.extensionsFlow: Flow<List<T>>
get() = callbackFlow {
val listener = object : ExtensionPointListener<T> {
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 {