From e581e5ac896f75e1ac20de596453efadb6d967eb Mon Sep 17 00:00:00 2001 From: Evgeniy Stepanov Date: Sat, 8 Aug 2026 13:39:24 +0300 Subject: [PATCH] IJAI-809 use project identity for AIR session detection Transport the backend-resolved project identity to split frontends and expose product-managed project paths through RecentProjectsManager as internal API. AIR now resolves current-project session workspace ids from that identity, with a temporary 262 compatibility fallback to the previous remote base-path behavior. (cherry picked from commit 9083304df4a54d4af2d538fdcb24c580fe373b89) GitOrigin-RevId: 5f6dfe753b55d88b3c71ae9d3e20555a80f9518b --- .../eel/provider/RemoteProjectPathProvider.kt | 52 ++++++++++++++++++- .../com/intellij/ide/RecentProjectsManager.kt | 12 ++++- .../intellij/ide/RecentProjectsManagerBase.kt | 2 +- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/platform/eel-provider/src/com/intellij/platform/eel/provider/RemoteProjectPathProvider.kt b/platform/eel-provider/src/com/intellij/platform/eel/provider/RemoteProjectPathProvider.kt index 628a6fc63eab..04de076ac8da 100644 --- a/platform/eel-provider/src/com/intellij/platform/eel/provider/RemoteProjectPathProvider.kt +++ b/platform/eel-provider/src/com/intellij/platform/eel/provider/RemoteProjectPathProvider.kt @@ -2,6 +2,7 @@ @file:JvmName("RemoteProjectPathProviderKt") package com.intellij.platform.eel.provider +import com.intellij.ide.RecentProjectsManager import com.intellij.openapi.diagnostic.fileLogger import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir @@ -16,6 +17,7 @@ import java.nio.file.Path private val LOG = fileLogger() private val REMOTE_PROJECT_BASE_PATH_KEY: Key = Key.create("com.intellij.platform.remoteProjectBasePath") +private val REMOTE_PROJECT_IDENTITY_PATH_KEY: Key = Key.create("com.intellij.platform.remoteProjectIdentityPath") /** * Empty string is the "checked-absent" sentinel; the absence of any call is "not yet set" @@ -71,4 +73,52 @@ fun Project.getRemoteProjectBaseNioPath(): Path? { } return guessProjectDir()?.toNioPathOrNull() -} \ No newline at end of file +} + +/** + * Empty string is the "checked-absent" sentinel; the absence of any call is "not yet set" + * and treated as an init-order error on a thin client. + * + * The identity is the host-side project key used to open/reopen the project, and may be a project + * file rather than [getRemoteProjectBaseNioPath]'s directory. For example, Rider uses the `.sln` + * path as project identity while the base path is the containing workspace root. + */ +@ApiStatus.Internal +fun Project.setRemoteProjectIdentityNioPath(rawHostPath: String) { + putUserData(REMOTE_PROJECT_IDENTITY_PATH_KEY, rawHostPath) +} + +/** + * Returns the project's identity path as a [Path]. + * + * On a thin client the backend-provided host path is routed through `MultiRoutingFileSystem`. + * On a monolith it falls back to [RecentProjectsManager.getProjectPath], then to [Project.guessProjectDir] + * for products that do not expose a separate project identity. + */ +@ApiStatus.Internal +fun Project.getRemoteProjectIdentityNioPath(): Path? { + val identityPathFromBackend = getUserData(REMOTE_PROJECT_IDENTITY_PATH_KEY) + + if (identityPathFromBackend == null) { + if (PlatformUtils.isJetBrainsClient() && getEelDescriptor() !is LocalEelDescriptor) { + LOG.error("REMOTE_PROJECT_IDENTITY_PATH_KEY not set on thin client; init order is broken") + return null + } + return RecentProjectsManager.getInstance().getProjectPath(this) + ?: guessProjectDir()?.toNioPathOrNull() + } + + if (identityPathFromBackend.isEmpty()) { + return null + } + + val descriptor = getEelDescriptor() + + return try { + EelPath.parse(identityPathFromBackend, descriptor).asNioPath() + } + catch (e: EelPathException) { + LOG.error("Path '$identityPathFromBackend' inconsistent with descriptor $descriptor", e) + null + } +} diff --git a/platform/ide-core/src/com/intellij/ide/RecentProjectsManager.kt b/platform/ide-core/src/com/intellij/ide/RecentProjectsManager.kt index 774fbc1d9159..6cedda6b23a5 100644 --- a/platform/ide-core/src/com/intellij/ide/RecentProjectsManager.kt +++ b/platform/ide-core/src/com/intellij/ide/RecentProjectsManager.kt @@ -57,6 +57,16 @@ interface RecentProjectsManager { fun hasPath(path: @SystemIndependent String?): Boolean = false + /** + * Returns the product-managed project identity path used for recent-project persistence and reopen. + * + * This path can differ from the project base directory; product implementations may return a + * project file such as a Rider `.sln` or Bazel project-view file. The default implementation + * returns `null` for implementations that do not expose such identity. + */ + @ApiStatus.Internal + fun getProjectPath(project: Project): Path? = null + suspend fun willReopenProjectOnStart(): Boolean = false @ApiStatus.Internal @@ -85,4 +95,4 @@ interface RecentProjectsManager { @RequiresEdt fun lastProjectsReopened(activeProject: Project) } -} \ No newline at end of file +} diff --git a/platform/platform-impl/src/com/intellij/ide/RecentProjectsManagerBase.kt b/platform/platform-impl/src/com/intellij/ide/RecentProjectsManagerBase.kt index a3d9aafe7c78..62dc1fd1455b 100644 --- a/platform/platform-impl/src/com/intellij/ide/RecentProjectsManagerBase.kt +++ b/platform/platform-impl/src/com/intellij/ide/RecentProjectsManagerBase.kt @@ -451,7 +451,7 @@ open class RecentProjectsManagerBase(coroutineScope: CoroutineScope) : } @Internal - open fun getProjectPath(project: Project): Path? { + override fun getProjectPath(project: Project): Path? { return ((project as? ProjectStoreOwner)?.componentStore ?: return null).storeDescriptor.presentableUrl }