revert "[PKGS] fixing IDEA-309218 Dependencies toolwindow present in CLion" because it led to leaking thread errors in tests

GitOrigin-RevId: 9890ae45540ce45a9e4a3ee51395d4b54e67d208
This commit is contained in:
Alexey Kudravtsev
2022-12-27 13:41:46 +01:00
committed by intellij-monorepo-bot
parent a41576fea1
commit 8624385827
6 changed files with 87 additions and 154 deletions

View File

@@ -40,8 +40,10 @@ val <T : Any> ExtensionPointName<T>.extensionsFlow: Flow<List<T>>
@Service(Service.Level.PROJECT)
internal class DependencyToolwindowLifecycleScope : CoroutineScope, Disposable {
val dispatcher = AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher()
override val coroutineContext =
SupervisorJob() + CoroutineName(this::class.qualifiedName!!) + AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher()
SupervisorJob() + CoroutineName(this::class.qualifiedName!!) + dispatcher
override fun dispose() {
cancel("Disposing ${this::class.simpleName}")
@@ -51,7 +53,7 @@ internal class DependencyToolwindowLifecycleScope : CoroutineScope, Disposable {
internal val Project.lifecycleScope: DependencyToolwindowLifecycleScope
get() = service()
internal fun <L : Any, K> Project.messageBusFlow(
fun <L : Any, K> Project.messageBusFlow(
topic: Topic<L>,
initialValue: (suspend () -> K)? = null,
listener: suspend ProducerScope<K>.() -> L

View File

@@ -5,19 +5,15 @@ package com.intellij.dependencytoolwindow
import com.intellij.openapi.application.EDT
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.intellij.openapi.wm.RegisterToolWindowTask
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.ex.ToolWindowEx
import com.intellij.util.PlatformUtils.*
import icons.PlatformDependencyToolwindowIcons
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlin.time.Duration.Companion.milliseconds
class DependencyToolWindowFactory : StartupActivity {
class DependencyToolWindowFactory : ToolWindowFactory {
companion object {
@@ -33,83 +29,53 @@ class DependencyToolWindowFactory : StartupActivity {
?.provideTab(project)
?.let { toolWindow.contentManager.setSelectedContent(it) }
}
}
override fun runActivity(project: Project) {
project.lifecycleScope.launch {
DependenciesToolWindowTabProvider.availableTabsFlow(project)
.filter { it.isNotEmpty() }
.first()
withContext(Dispatchers.toolWindowManager(project)) {
val messagePointer = DependencyToolWindowBundle.messagePointer("toolwindow.stripe.Dependencies")
val toolWindowTask = RegisterToolWindowTask.closable(
id = messagePointer.get(),
stripeTitle = messagePointer,
icon = PlatformDependencyToolwindowIcons.ArtifactSmall
)
val toolWindow = ToolWindowManager.getInstance(project).registerToolWindow(toolWindowTask)
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
toolWindow.contentManager.removeAllContents(true)
toolWindow.isAvailable = false
toolWindow.contentManager.addSelectionChangedListener { event ->
val actionToolWindow = event.content.component as? HasToolWindowActions
if (toolWindow is ToolWindowEx) {
toolWindow.setAdditionalGearActions(null)
actionToolWindow?.also { toolWindow.setAdditionalGearActions(it.gearActions) }
}
toolWindow.setTitleActions(emptyList())
actionToolWindow?.titleActions
?.also { toolWindow.setTitleActions(it.toList()) }
}
val tabsFlow = DependenciesToolWindowTabProvider.availableTabsFlow(project)
.shareIn(project.lifecycleScope, SharingStarted.Eagerly, 1)
tabsFlow.onEach { toolWindow.isAvailable = it.isNotEmpty() }
.flowOn(Dispatchers.EDT)
.launchIn(project.lifecycleScope)
toolWindow.isVisibleFlow
.filter { it }
.take(1)
.flatMapLatest { tabsFlow }
.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()
.minus(removedContent)
.plus(newContent)
.sortedBy { it.toolwindowTitle }
.mapIndexed { index, content -> content to index }
.toMap()
removedContent.forEach { toolWindow.contentManager.removeContent(it, true) }
newContent.forEach { content ->
contentOrder[content]?.let { order -> toolWindow.contentManager.addContent(content, order) }
?: toolWindow.contentManager.addContent(content)
}
}
.flowOn(Dispatchers.EDT)
.launchIn(project.lifecycleScope)
project.lookAndFeelFlow
.onEach { toolWindow.contentManager.component.invalidate() }
.onEach { toolWindow.contentManager.component.repaint() }
.flowOn(Dispatchers.EDT)
.launchIn(project.lifecycleScope)
toolWindow.contentManager.addSelectionChangedListener { event ->
val actionToolWindow = event.content.component as? HasToolWindowActions
if (toolWindow is ToolWindowEx) {
toolWindow.setAdditionalGearActions(null)
actionToolWindow?.also { toolWindow.setAdditionalGearActions(it.gearActions) }
}
toolWindow.setTitleActions(emptyList())
actionToolWindow?.titleActions
?.also { toolWindow.setTitleActions(it.toList()) }
}
toolWindow.contentManager.removeAllContents(true)
DependenciesToolWindowTabProvider.availableTabsFlow(project)
.flowOn(project.lifecycleScope.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()
.minus(removedContent)
.plus(newContent)
.sortedBy { it.toolwindowTitle }
.mapIndexed { index, content -> content to index }
.toMap()
removedContent.forEach { toolWindow.contentManager.removeContent(it, true) }
newContent.forEach { content ->
contentOrder[content]?.let { order -> toolWindow.contentManager.addContent(content, order) }
?: toolWindow.contentManager.addContent(content)
}
}
.flowOn(Dispatchers.EDT)
.launchIn(project.lifecycleScope)
project.lookAndFeelFlow
.onEach { toolWindow.contentManager.component.invalidate() }
.onEach { toolWindow.contentManager.component.repaint() }
.flowOn(Dispatchers.EDT)
.launchIn(project.lifecycleScope)
}
}
internal val ToolWindow.isVisibleFlow
get() = flow {
while (currentCoroutineContext().isActive) {
emit(isVisible)
delay(50.milliseconds)
}
}

View File

@@ -1018,7 +1018,8 @@
<toolWindow id="Project" anchor="left" icon="AllIcons.Toolwindows.ToolWindowProject"
factoryClass="com.intellij.ide.projectView.impl.ProjectViewToolWindowFactory"/>
<postStartupActivity implementation="com.intellij.dependencytoolwindow.DependencyToolWindowFactory"/>
<toolWindow id="Dependencies" anchor="bottom" icon="PlatformDependencyToolwindowIcons.ArtifactSmall"
factoryClass="com.intellij.dependencytoolwindow.DependencyToolWindowFactory"/>
<toolWindowExtractor implementation="com.intellij.dependencytoolwindow.DependencyToolWindowViewModelExtractor"/>

View File

@@ -20,7 +20,10 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.appSystemDir
import com.intellij.openapi.application.readAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.*
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiManager
@@ -30,6 +33,7 @@ import com.jetbrains.packagesearch.intellij.plugin.getInstalledDependencies
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.InstalledDependenciesUsages
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.ProjectDataProvider
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.RepositoryModel
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.TargetModules
import com.jetbrains.packagesearch.intellij.plugin.util.BackgroundLoadingBarController
import com.jetbrains.packagesearch.intellij.plugin.util.PowerSaveModeState
import com.jetbrains.packagesearch.intellij.plugin.util.TraceInfo
@@ -37,7 +41,6 @@ import com.jetbrains.packagesearch.intellij.plugin.util.TraceInfo.TraceSource.SE
import com.jetbrains.packagesearch.intellij.plugin.util.catchAndLog
import com.jetbrains.packagesearch.intellij.plugin.util.combineLatest
import com.jetbrains.packagesearch.intellij.plugin.util.debounceBatch
import com.jetbrains.packagesearch.intellij.plugin.util.fileOpenedFlow
import com.jetbrains.packagesearch.intellij.plugin.util.filesChangedEventFlow
import com.jetbrains.packagesearch.intellij.plugin.util.lifecycleScope
import com.jetbrains.packagesearch.intellij.plugin.util.loadingContainer
@@ -62,17 +65,15 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import org.jetbrains.idea.packagesearch.api.PackageSearchApiClient
@@ -87,22 +88,20 @@ internal class PackageSearchProjectService(private val project: Project) : Dispo
)
private val restartChannel = Channel<Unit>()
private val restartFlow = restartChannel.receiveAsFlow()
.shareIn(project.lifecycleScope, SharingStarted.Lazily)
val allKnownRepositoriesFlow by timer(1.hours)
.map(project.loadingContainer) {
dataProvider.fetchKnownRepositories()
.map { RepositoryModel(it.id, it.friendlyName, it.url, it) }
}
.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Lazily, emptyList())
.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Eagerly, emptyList())
private val packageSearchModulesFlow by combine(
project.trustedProjectFlow,
ApplicationManager.getApplication().powerSaveModeFlow.map { it == PowerSaveModeState.DISABLED }
) { results: Array<Boolean> -> results.all { it } }
.flatMapLatest { isPkgsEnabled -> if (isPkgsEnabled) project.nativeModulesFlow else emptyFlow() }
.replayOn(project.moduleChangesSignalFlow, restartFlow)
.replayOn(project.moduleChangesSignalFlow)
.map(project.loadingContainer) { nativeModules ->
project.moduleTransformers.parallelMap { it.transformModules(project, nativeModules) }
.flatten()
@@ -110,7 +109,7 @@ internal class PackageSearchProjectService(private val project: Project) : Dispo
.catchAndLog()
val packageSearchModulesStateFlow = packageSearchModulesFlow
.stateIn(project.lifecycleScope, SharingStarted.Lazily, emptyList())
.stateIn(project.lifecycleScope, SharingStarted.Eagerly, emptyList())
val isAvailable
get() = packageSearchModulesStateFlow.value.isNotEmpty()
@@ -144,7 +143,7 @@ internal class PackageSearchProjectService(private val project: Project) : Dispo
repositoriesDeclarationsByModule.toMutableMap()
.apply { putAll(changes.associateWith { it.getDeclaredRepositories(knownRepositories) }) }
}
.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Lazily, emptyMap())
.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Eagerly, emptyMap())
private val declarationsChanges by moduleChangesFlow
.map { it.associateWith { it.getDependencies() } }
@@ -158,7 +157,7 @@ internal class PackageSearchProjectService(private val project: Project) : Dispo
) { declaredDependenciesByModule, changes ->
declaredDependenciesByModule.toMutableMap().apply { putAll(changes) }
}
.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Lazily, emptyMap())
.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Eagerly, emptyMap())
private val remoteData by declaredDependenciesByModuleFlow
.filter { it.isNotEmpty() }
@@ -182,23 +181,19 @@ internal class PackageSearchProjectService(private val project: Project) : Dispo
loadingContainer = project.loadingContainer
) { packageSearchModules, remoteData ->
installedDependenciesUsages(project, packageSearchModules, remoteData)
}.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Lazily, InstalledDependenciesUsages.EMPTY)
}.stateInAndCatchAndLog(project.lifecycleScope, SharingStarted.Eagerly, InstalledDependenciesUsages.EMPTY)
init {
val openedBuildFilesFlow = combine(
project.fileOpenedFlow,
packageSearchModulesFlow.map { it.mapNotNull { it.buildFile }.toSet() }
) { openedFiles, buildFiles ->
openedFiles intersect buildFiles
}.shareIn(project.lifecycleScope, SharingStarted.Eagerly, 1)
openedBuildFilesFlow
// allows rerunning PKGS inspections on already opened files
// when the data is finally available or changes for PackageUpdateInspection
// or when a build file changes
installedDependenciesFlow.flatMapLatest { packageSearchModulesFlow }
.map { it.mapNotNull { it.buildFile?.path } }
.filter { it.isNotEmpty() }
.take(1)
.flatMapLatest { installedDependenciesFlow }
.flatMapLatest { openedBuildFilesFlow }
.flatMapMerge { it.asFlow() }
.flatMapLatest { knownBuildFiles ->
FileEditorManager.getInstance(project).openFiles
.filter { it.path in knownBuildFiles }.asFlow()
}
.mapNotNull { readAction { PsiManager.getInstance(project).findFile(it) } }
.onEach { readAction { DaemonCodeAnalyzer.getInstance(project).restart(it) } }
.flowOn(Dispatchers.EDT)

View File

@@ -57,7 +57,6 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.shareIn
@@ -322,9 +321,9 @@ suspend fun ProducerScope<Unit>.send() = send(Unit)
internal fun <T> Flow<T>.collectIn(coroutineScope: CoroutineScope, sendChannel: SendChannel<T>) =
onEach { sendChannel.send(it) }.launchIn(coroutineScope)
internal fun <T> Flow<T>.replayOn(vararg replayFlows: Flow<*>) = channelFlow {
internal fun <T> Flow<T>.replayOn(replayFlow: Flow<*>) = channelFlow {
val mutex = Mutex()
var last: T? = null
onEach { mutex.withLock { last = it } }.collectIn(this, this)
merge(*replayFlows).collect { mutex.withLock { last?.let { send(it) } } }
replayFlow.collect { mutex.withLock { last?.let { send(it) } } }
}

View File

@@ -10,17 +10,16 @@ import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.LafManagerListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.ModuleListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.util.Function
import com.intellij.util.messages.Topic
import com.jetbrains.packagesearch.intellij.plugin.data.LoadingContainer
@@ -28,21 +27,21 @@ import com.jetbrains.packagesearch.intellij.plugin.data.PackageSearchCachesServi
import com.jetbrains.packagesearch.intellij.plugin.data.PackageSearchProjectCachesService
import com.jetbrains.packagesearch.intellij.plugin.data.PackageSearchProjectService
import com.jetbrains.packagesearch.intellij.plugin.extensibility.AsyncModuleTransformer
import com.jetbrains.packagesearch.intellij.plugin.extensibility.ModuleTransformer
import com.jetbrains.packagesearch.intellij.plugin.extensibility.FlowModuleChangesSignalProvider
import com.jetbrains.packagesearch.intellij.plugin.extensibility.ModuleChangesSignalProvider
import com.jetbrains.packagesearch.intellij.plugin.extensibility.ModuleTransformer
import com.jetbrains.packagesearch.intellij.plugin.extensibility.PackageSearchModule
import com.jetbrains.packagesearch.intellij.plugin.lifecycle.PackageSearchLifecycleScope
import com.jetbrains.packagesearch.intellij.plugin.ui.PkgsUiCommandsService
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.UiStateModifier
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.models.versions.PackageVersionNormalizer
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.management.PackageManagementOperationExecutor
import com.jetbrains.packagesearch.intellij.plugin.ui.toolwindow.panels.management.PackageManagementPanel
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import kotlin.coroutines.resume
@@ -60,6 +59,15 @@ internal val packageVersionNormalizer: PackageVersionNormalizer
internal val Project.packageSearchProjectCachesService: PackageSearchProjectCachesService
get() = service()
internal val Project.toolWindowManagerFlow: Flow<ToolWindow>
get() = messageBusFlow(ToolWindowManagerListener.TOPIC) {
object : ToolWindowManagerListener {
override fun toolWindowShown(toolWindow: ToolWindow) {
trySend(toolWindow)
}
}
}
fun <L : Any, K> Project.messageBusFlow(
topic: Topic<L>,
initialValue: (suspend () -> K)? = null,
@@ -169,42 +177,4 @@ val Project.dependencyModifierService
get() = DependencyModifierService.getInstance(this)
internal val Project.loadingContainer
get() = service<LoadingContainer>()
internal sealed interface FileEditorEvent {
val file: VirtualFile
@JvmInline
value class FileOpened(override val file: VirtualFile) : FileEditorEvent
@JvmInline
value class FileClosed(override val file: VirtualFile) : FileEditorEvent
}
private val Project.project
get() = this
internal val Project.fileOpenedFlow
get() = flow {
val buffer: MutableList<VirtualFile> = FileEditorManager.getInstance(project).openFiles
.toMutableList()
emit(buffer.toList())
messageBusFlow(FileEditorManagerListener.FILE_EDITOR_MANAGER) {
object : FileEditorManagerListener {
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
trySend(FileEditorEvent.FileOpened(file))
}
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
trySend(FileEditorEvent.FileClosed(file))
}
}
}.collect {
when (it) {
is FileEditorEvent.FileClosed -> buffer.remove(it.file)
is FileEditorEvent.FileOpened -> buffer.add(it.file)
}
emit(buffer.toList())
}
}
get() = service<LoadingContainer>()