[ijent] IJPL-232220 extract windows path manipulation into separate class, add tests

GitOrigin-RevId: e0c538696d5eff165109cf056640efbc2992db89
This commit is contained in:
Mihail Buryakov
2026-02-14 01:24:54 +00:00
committed by intellij-monorepo-bot
parent 91a3a83da1
commit 230b3dc5c4
5 changed files with 167 additions and 28 deletions
@@ -9,9 +9,9 @@ import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.EelOsFamily
import com.intellij.platform.eel.EelPathBoundDescriptor
import com.intellij.platform.eel.annotations.MultiRoutingFileSystemPath
import com.intellij.platform.eel.isPosix
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.path.EelPathException
import com.intellij.platform.eel.provider.utils.WindowsPathUtils
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
import kotlin.io.path.pathString
@@ -44,8 +44,6 @@ fun EelPath.asNioPath(project: Project?): @MultiRoutingFileSystemPath Path {
return asNioPath()
}
private val WINDOWS_DRIVE_PREFIX_REGEX = Regex("^\\w:")
/** See docs for [asNioPath] */
@Deprecated("It never returns null anymore")
@ApiStatus.Experimental
@@ -68,12 +66,7 @@ fun EelPath.asNioPathOrNull(): @MultiRoutingFileSystemPath Path? {
@MultiRoutingFileSystemPath
val result = when (descriptor.osFamily) {
EelOsFamily.Windows -> {
if (WINDOWS_DRIVE_PREFIX_REGEX.containsMatchIn(this.root.toString())) {
(listOf("@", this.root.toString().take(1)) + parts).fold(root, Path::resolve)
}
else {
parts.fold(root, Path::resolve)
}
WindowsPathUtils.resolveEelPathOntoRoot(root, this)
}
EelOsFamily.Posix -> parts.fold(root, Path::resolve)
}
@@ -111,17 +104,17 @@ fun Path.asEelPath(): EelPath {
fun Path.asEelPath(descriptor: EelDescriptor): EelPath {
when (descriptor) {
is LocalEelDescriptor -> return EelPath.parse(toString(), descriptor)
is EelPathBoundDescriptor if (descriptor.osFamily.isPosix) -> {
val root = descriptor.rootPath
val relative = root.relativize(this)
return relative.fold(EelPath.parse("/", descriptor)) { path, part ->
part.toString().takeIf { it.isNotEmpty() }?.let { path.getChild(it) } ?: path
}
}
is EelPathBoundDescriptor -> {
val root = descriptor.rootPath
val relative = root.relativize(this)
return relative.drop(1).fold(EelPath.parse(relative.first().pathString, descriptor)) { path, part ->
val relative = descriptor.rootPath.relativize(this)
val (eelRoot, rest) = when (descriptor.osFamily) {
EelOsFamily.Posix -> {
"/" to relative
}
EelOsFamily.Windows -> {
WindowsPathUtils.rootRelativeToEelPath(relative)
}
}
return rest.fold(EelPath.parse(eelRoot, descriptor)) { path, part ->
part.toString().takeIf { it.isNotEmpty() }?.let { path.getChild(it) } ?: path
}
}
@@ -0,0 +1,81 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.eel.provider.utils
import com.intellij.platform.eel.path.EelPath
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.pathString
@ApiStatus.Internal
object WindowsPathUtils {
const val DRIVE_PATH_PREPEND: String = "@"
val WINDOWS_DRIVE_PREFIX_REGEX: Regex = Regex("^\\w:")
/**
* Converts Windows [eelPath] into nio path under virtual mount [root].
*
* If [root] is posix path `/$some.ij/mount/`, then the result will be`:
* `C:` -> `/$some.ij/mount/@/C`
* `C:\` -> `/$some.ij/mount/@/C/`
* `C:\Users` -> `/$some.ij/mount/@/C/Users`
* `\\server.share\dir\` -> `/$some.ij/mount/server.share/dir/`
*
* If [root] is Windows path `\\some.ij\mount\`, then the result will be:
* `C:` -> `\\some.ij\mount\@\C`
* `C:\` -> `\\some.ij\mount\@\C\`
* `C:\Users` -> `\\some.ij\mount\@\C\Users`
* `\\server.share\dir\` -> `\\some.ij\mount\server.share\dir\`
*/
fun resolveEelPathOntoRoot(root: Path, eelPath: EelPath): Path {
val rootParts = if (WINDOWS_DRIVE_PREFIX_REGEX.containsMatchIn(eelPath.root.toString())) {
listOf(DRIVE_PATH_PREPEND, eelPath.root.toString().take(1))
}
else if (eelPath.root.toString().startsWith("\\\\")) {
eelPath.root.toString().removePrefix("\\\\").removeSuffix("\\").split("\\", limit = 2)
} else {
error("Unsupported root: ${eelPath.root}")
}
return (rootParts + eelPath.parts).fold(root, Path::resolve)
}
/**
* Performs backward conversion.
*
* `@/C/Users` -> `C:\Users`
* `server.share/dir/` -> `\\server.share\dir\`
*
*/
fun rootRelativeToEelPath(relativePath: String): String {
return if (relativePath.startsWith("@/")) {
"${relativePath[2]}:${relativePath.drop(3)}"
}
else {
"\\\\$relativePath"
}
}
/**
* Performs backward conversion.
*
* `@/C/Users` -> `C:`, `Users`
* `server.share/dir/tmp` -> `\\server.share\dir`, `tmp`
*
*/
fun rootRelativeToEelPath(relativePath: Path): Pair<String, Path> {
val rest = when (relativePath.nameCount) {
in 0..1 -> error("windows relative path should contain at least : $relativePath")
2 -> Path("")
else -> relativePath.subpath(2, relativePath.nameCount)
}
val root = if (relativePath.startsWith(Path("@"))) {
"${relativePath.elementAt(1).pathString}:"
}
else {
"\\\\${relativePath.elementAt(0).pathString}\\${relativePath.elementAt(1).pathString}"
}
return root to rest
}
}
@@ -212,7 +212,7 @@ internal class ArrayListEelAbsolutePath private constructor(
if (error != null) throw EelPathException(raw, error)
}
return ArrayListEelAbsolutePath(descriptor, Root.Windows(raw.substring(0, index).replace("/", "\\")), parts)
return ArrayListEelAbsolutePath(descriptor, Root.Windows(raw.substring(0, index).replace("/", "\\") + "\\" + parts.first()), parts.drop(1))
}
/** https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#traditional-dos-paths */
@@ -12,6 +12,7 @@ import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.EelOsFamily
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.utils.EelPathUtils
import com.intellij.platform.eel.provider.utils.WindowsPathUtils
import com.intellij.platform.ijent.community.impl.nio.IjentNioPath
import com.intellij.util.text.nullize
import org.jetbrains.annotations.ApiStatus
@@ -33,7 +34,6 @@ import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileAttributeView
import java.nio.file.attribute.UserPrincipalLookupService
import java.nio.file.spi.FileSystemProvider
import kotlin.io.path.Path
import kotlin.io.path.invariantSeparatorsPathString
import kotlin.io.path.pathString
@@ -401,14 +401,14 @@ class IjentEphemeralRootAwareFileSystem(
}
EelOsFamily.Windows -> {
if (relativePath != null) {
if (relativePath.startsWith("/@/")) {
arrayOf("${relativePath[3]}:${relativePath.drop(4)}", *parts)
}
else {
arrayOf(relativePath.removePrefix("/"), *parts)
}
arrayOf(WindowsPathUtils.rootRelativeToEelPath(relativePath.removePrefix("/")), *parts)
}
else if (path.isNotEmpty()) {
parts
}
else {
error("Windows $path is not under any root of $root")
}
else parts
}
}
}
@@ -0,0 +1,65 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.eel
import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.EelOsFamily
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.provider.utils.WindowsPathUtils
import com.intellij.util.system.OS
import org.jetbrains.annotations.NonNls
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.pathString
class WindowsPathUtilsTest {
class TestEelDescriptor(override val osFamily: EelOsFamily) : EelDescriptor {
override val name: @NonNls String get() = "Test Descriptor"
}
@ParameterizedTest
@CsvSource(
textBlock = """
C:\Users\test, @\C\Users\test
C:\, @\C\
C:, @\C
D:\Program Files, @\D\Program Files
\\server\share\dir, server\share\dir
\\server\share\dir\, server\share\dir\
\\server\share\, server\share\
C:\Users\test, @\C\Users\test
C:\, @\C\
C:, @\C
\\server\share\dir, server\share\dir"""
)
fun `test resolveEelPathOntoRoot`(eelPathString: String, expected: String) {
val rootPath = if (OS.CURRENT == OS.Windows) {
Path("\\\\virtual.ij\\mount@")
}
else {
Path($$"""/$virtual.ij/mount@""")
}
val eelPath = EelPath.parse(eelPathString, TestEelDescriptor(EelOsFamily.Windows))
val result = WindowsPathUtils.resolveEelPathOntoRoot(rootPath, eelPath)
assertEquals(rootPath.resolve(expected.replace("\\", File.separator)), result)
}
@ParameterizedTest
@CsvSource(
textBlock = """
@/C, C:, ''
@/C/Users, C:, Users
@/D/Program Files, D:, Program Files
@/D/Program Files/JetBrains, D:, Program Files/JetBrains
server.share/dir, \\server.share\dir, ''
server.share/dir/tmp, \\server.share\dir, tmp
server.share/dir/tmp/nested/file, \\server.share\dir, tmp/nested/file"""
)
fun `test rootRelativeToEelPath with Path parameter`(relativePath: String, expectedRoot: String, expectedSubpath: String) {
assertEquals("$expectedRoot${if (expectedSubpath.isEmpty()) "" else "\\$expectedSubpath"}".replace("\\", File.separator), WindowsPathUtils.rootRelativeToEelPath(relativePath).replace("\\", File.separator))
assertEquals(expectedRoot to Path(expectedSubpath ?: ""), WindowsPathUtils.rootRelativeToEelPath(Path(relativePath)))
}
}