IJPL-217691 avoid extra launch - use ProjectActivity scope

GitOrigin-RevId: a47b513216df15561c56102e87195a806b657cf9
This commit is contained in:
Vladimir Krivosheev
2025-11-10 18:37:19 +00:00
committed by intellij-monorepo-bot
parent a7b9240cad
commit 47cea42589
4 changed files with 61 additions and 49 deletions
@@ -692,7 +692,7 @@ open class ProjectManagerImpl : ProjectManagerEx(), Disposable {
if (Registry.`is`("ide.create.project.root.entity") && options.projectRootDir != null) {
val root = options.projectRootDir!!.toVirtualFileUrl(project.workspaceModel.getVirtualFileUrlManager())
project.serviceAsync<ProjectRootPersistentStateComponent>().projectRootUrls += root.url
project.serviceAsync<ProjectRootPersistentStateComponent>().addProjectRoot(root.url)
// We also register the project root here, so project view will have "files" node immediately.
// Otherwise, it might appear too late causing issues like AMPER-4695
@@ -741,7 +741,7 @@ open class ProjectManagerImpl : ProjectManagerEx(), Disposable {
result?.let { project ->
try {
withContext(Dispatchers.EDT) {
closeProject(project, saveProject = false, checkCanClose = false)
closeProject(project = project, saveProject = false, checkCanClose = false)
}
}
catch (secondException: Throwable) {
@@ -784,16 +784,18 @@ open class ProjectManagerImpl : ProjectManagerEx(), Disposable {
protected open fun createFrameAllocator(projectStoreBaseDir: Path, options: OpenProjectTask): ProjectFrameAllocator {
val app = ApplicationManager.getApplication()
return if (app.isHeadlessEnvironment || app.isUnitTestMode) {
HeadlessProjectFrameAllocator()
if (app.isHeadlessEnvironment || app.isUnitTestMode) {
return HeadlessProjectFrameAllocator()
}
else {
IdeProjectFrameAllocator(options, projectStoreBaseDir)
return IdeProjectFrameAllocator(options, projectStoreBaseDir)
}
}
private suspend fun cancelProjectOpening(project: Project?, e: CancellationException? = null) {
if (project == null) return
if (project == null) {
return
}
try {
try {
@@ -806,7 +808,7 @@ open class ProjectManagerImpl : ProjectManagerEx(), Disposable {
withContext(Dispatchers.EDT) {
writeIntentReadAction {
closeProject(project, saveProject = false, checkCanClose = false)
closeProject(project = project, saveProject = false, checkCanClose = false)
}
}
}
@@ -847,30 +849,30 @@ open class ProjectManagerImpl : ProjectManagerEx(), Disposable {
}
override fun newProject(file: Path, options: OpenProjectTask): Project? {
return try {
try {
@Suppress("DEPRECATION")
runUnderModalProgressIfIsEdt {
return runUnderModalProgressIfIsEdt {
newProjectAsync(file, options)
}
}
catch (t: Throwable) {
handleErrorOnNewProject(t)
null
catch (e: Throwable) {
handleErrorOnNewProject(e)
return null
}
}
final override suspend fun newProjectAsync(file: Path, options: OpenProjectTask): Project {
TrustedProjects.setProjectTrusted(file, true)
TrustedProjects.setProjectTrusted(path = file, isTrusted = true)
return prepareNewProject(
file,
options.projectName,
options.beforeInit,
options.useDefaultProjectAsTemplate,
options.preloadServices,
options.isProjectCreatedWithWizard,
markAsNew = false
identityFle = file,
projectName = options.projectName,
beforeInit = options.beforeInit,
useDefaultProjectAsTemplate = options.useDefaultProjectAsTemplate,
preloadServices = options.preloadServices,
markAsNewlyCreated = options.isProjectCreatedWithWizard,
markAsNew = false,
).also { project ->
TrustedProjects.setProjectTrusted(project, true)
TrustedProjects.setProjectTrusted(project = project, isTrusted = true)
}
}
@@ -10,8 +10,12 @@ import org.jetbrains.annotations.ApiStatus
@Service(Service.Level.PROJECT)
@State(name = "ProjectRoots", storages = [Storage(StoragePathMacros.PRODUCT_WORKSPACE_FILE)])
@ApiStatus.Internal
class ProjectRootPersistentStateComponent(val scope: CoroutineScope) :
SerializablePersistentStateComponent<ProjectRootPersistentStateComponent.State>(State()) {
class ProjectRootPersistentStateComponent : SerializablePersistentStateComponent<ProjectRootPersistentStateComponent.State>(State()) {
fun addProjectRoot(projectRootUrl: String) {
updateState {
it.copy(projectRootUrls = it.projectRootUrls + projectRootUrl)
}
}
var projectRootUrls: List<String>
get() = state.projectRootUrls
@@ -1,7 +1,9 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.project.impl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.extensions.ExtensionNotApplicableException
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.util.registry.Registry
@@ -9,44 +11,48 @@ import com.intellij.platform.backend.workspace.WorkspaceModel
import com.intellij.platform.workspace.storage.entities
import com.intellij.workspaceModel.ide.ProjectRootEntity
import com.intellij.workspaceModel.ide.registerProjectRoot
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.VisibleForTesting
@ApiStatus.Internal
@Internal
class ProjectRootsSynchronizer : ProjectActivity {
override suspend fun execute(project: Project) {
if (!Registry.`is`("ide.create.project.root.entity")) return
companion object {
@VisibleForTesting
suspend fun doRegister(project: Project) {
if (!Registry.`is`("ide.create.project.root.entity")) {
return
}
val projectRootsComponent = project.serviceAsync<ProjectRootPersistentStateComponent>()
val roots = projectRootsComponent.projectRootUrls
val virtualFileUrlManager = project.serviceAsync<WorkspaceModel>().getVirtualFileUrlManager()
for (root in roots) {
registerProjectRoot(project, virtualFileUrlManager.getOrCreateFromUrl(root))
val projectRootsComponent = project.serviceAsync<ProjectRootPersistentStateComponent>()
val roots = projectRootsComponent.projectRootUrls
val virtualFileUrlManager = project.serviceAsync<WorkspaceModel>().getVirtualFileUrlManager()
for (root in roots) {
registerProjectRoot(project, virtualFileUrlManager.getOrCreateFromUrl(root))
}
}
}
launchListener(project, projectRootsComponent.scope)
override suspend fun execute(project: Project) {
doRegister(project)
return
launchListener(project)
}
/**
* Keeps [ProjectRootPersistentStateComponent] in sync with the actual ProjectRootEntities in the workspace model.
*/
private fun launchListener(project: Project, scope: CoroutineScope) {
private suspend fun launchListener(project: Project) {
val workspaceModel = project.serviceAsync<WorkspaceModel>()
val flow = workspaceModel.eventLog
.filter { change -> change.getChanges(ProjectRootEntity::class.java).isNotEmpty() }
scope.launch {
val workspaceModel = project.serviceAsync<WorkspaceModel>()
val flow = workspaceModel.eventLog
.filter { change -> change.getChanges(ProjectRootEntity::class.java).isNotEmpty() }
val component = project.serviceAsync<ProjectRootPersistentStateComponent>()
component.projectRootUrls = workspaceModel.currentSnapshot.entities<ProjectRootEntity>().map { it.root.url }.toList()
flow.collect { change ->
val newRoots = change.storageAfter.entities<ProjectRootEntity>().map { it.root.url }.toList()
val component = project.serviceAsync<ProjectRootPersistentStateComponent>()
component.projectRootUrls = newRoots.toList()
}
val component = project.serviceAsync<ProjectRootPersistentStateComponent>()
component.projectRootUrls = workspaceModel.currentSnapshot.entities<ProjectRootEntity>().map { it.root.url }.toList()
flow.collect { change ->
component.projectRootUrls = change.storageAfter.entities<ProjectRootEntity>().map { it.root.url }.toList()
}
}
}
@@ -31,7 +31,7 @@ class ProjectRootEntityTest {
projectFile.createDirectories()
val options = createTestOpenProjectOptions().copy(projectName = name, projectRootDir = projectFile)
ProjectUtil.openOrImportAsync(projectFile, options)!!.useProjectAsync { project ->
ProjectRootsSynchronizer().execute(project) // background startup activities are not executed in unit tests on project open
ProjectRootsSynchronizer.doRegister(project) // background startup activities are not executed in unit tests on project open
val roots = project.workspaceModel.currentSnapshot.entities(ProjectRootEntity::class.java).toList()
assertThat(roots.map { it.root }).isEqualTo(listOf(projectFile.toVirtualFileUrl(project.workspaceModel.getVirtualFileUrlManager())))
}