mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[ijent] IJPL-232220 extract windows path manipulation into separate class, add tests
GitOrigin-RevId: e0c538696d5eff165109cf056640efbc2992db89
This commit is contained in:
committed by
intellij-monorepo-bot
parent
91a3a83da1
commit
230b3dc5c4
+12
-19
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+81
@@ -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 */
|
||||
|
||||
+8
-8
@@ -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)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user