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
This commit is contained in:
Evgeniy Stepanov
2026-08-18 15:23:56 +00:00
committed by intellij-monorepo-bot
parent 84ee87e36c
commit e581e5ac89
3 changed files with 63 additions and 3 deletions
@@ -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<String> = Key.create("com.intellij.platform.remoteProjectBasePath")
private val REMOTE_PROJECT_IDENTITY_PATH_KEY: Key<String> = 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()
}
}
/**
* 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
}
}
@@ -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)
}
}
}
@@ -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
}