Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ object UriUtils {
// Remove query parameters if present
val uriStr = uri.substringBefore("?")

// Handle Windows file paths with authority component
// Handle Windows file paths with authority component.
// URI(scheme, authority, path, query) treats path as decoded and re-encodes `%`,
// so already-encoded paths (Unity%20Projects) must be parsed as a URI string.
if (uriStr.startsWith("file://") && !uriStr.startsWith("file:///")) {
val path = uriStr.substringAfter("file://")
return URI("file", "", "/$path", null)
return try {
URI("file:///$path")
} catch (e: Exception) {
URI("file", "", "/$path", null)
}
}

return try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.continuedev.continueintellijextension.`continue`.file

import com.github.continuedev.continueintellijextension.FileStats
import com.github.continuedev.continueintellijextension.FileType
import com.github.continuedev.continueintellijextension.`continue`.UriUtils
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.Logger
Expand All @@ -23,7 +24,8 @@ class FileUtils(
findFile(fileUri) != null

fun writeFile(fileUri: String, content: String) {
val path = VfsUtilCore.urlToPath(fileUri)
val path = toLocalPath(fileUri)
?: return
val pathDirectory = VfsUtil.getParentDir(path)
?: return LOG.warn("Parent directory is null for $path")
val vfsDirectory = VfsUtil.createDirectories(pathDirectory)
Expand Down Expand Up @@ -91,12 +93,22 @@ class FileUtils(
}.toMap()

private fun findFile(fileUri: String): VirtualFile? {
val noParams = fileUri.substringBefore("?")
val normalizedAuthority = normalizeWindowsAuthority(noParams)
val path = toLocalPath(fileUri)
?: return null
return VirtualFileManager.getInstance()
.refreshAndFindFileByUrl(normalizedAuthority)
.refreshAndFindFileByUrl(VfsUtilCore.pathToUrl(path))
}

// VfsUtilCore.urlToPath does not percent-decode, so file://.../Unity%20Projects
// would create/find a literal "%20" directory. UriUtils.uriToFile uses java.net.URI.
private fun toLocalPath(fileUri: String): String? =
try {
UriUtils.uriToFile(fileUri).path.replace('\\', '/')
} catch (e: Exception) {
LOG.warn("Could not resolve path for $fileUri", e)
null
}

private fun readDocument(file: VirtualFile, maxLength: Int): String? {
val document = FileDocumentManager.getInstance().getDocument(file)
?: return null
Expand All @@ -108,16 +120,6 @@ class FileUtils(
text.replace("\r\n", "\n")
.replace("\r", "\n")

private fun normalizeWindowsAuthority(fileUri: String): String {
val authorityPrefix = "file://"
val noAuthorityPrefix = "file:///"
if (fileUri.startsWith(authorityPrefix) && !fileUri.startsWith(noAuthorityPrefix)) {
val path = fileUri.substringAfter(authorityPrefix)
return "$noAuthorityPrefix$path"
}
return fileUri
}

private companion object {
private val LOG = Logger.getInstance(FileUtils::class.java)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class FileUtilsTest : UsefulTestCase() {
assertFalse(fileUtils.fileExists("file://dir/missing.txt"))
}

fun `test fileExists decodes percent-encoded path`() {
myFixture.createTempFile("Unity Projects/foo.txt")
assertTrue(fileUtils.fileExists("file://$tmp/Unity%20Projects/foo.txt"))
}

fun `test writeFile creates missing file`() {
fileUtils.writeFile("file://$tmp/file.txt", "text")
assertEquals("text", myFixture.readTempFile("file.txt"))
Expand All @@ -57,6 +62,12 @@ class FileUtilsTest : UsefulTestCase() {
assertEquals("new_text", myFixture.readTempFile("overwrite.txt"))
}

fun `test writeFile decodes percent-encoded path`() {
fileUtils.writeFile("file://$tmp/Unity%20Projects/foo.txt", "text")
assertEquals("text", myFixture.readTempFile("Unity Projects/foo.txt"))
assertNull(myFixture.tempDirFixture.getFile("Unity%20Projects/foo.txt"))
}

fun `test readFile`() {
myFixture.createTempFile("file.txt", "text")
assertEquals("text", fileUtils.readFile("file://$tmp/file.txt"))
Expand All @@ -77,6 +88,11 @@ class FileUtilsTest : UsefulTestCase() {
assertEmpty(fileUtils.readFile("file://missing.txt"))
}

fun `test readFile decodes percent-encoded path`() {
myFixture.createTempFile("Unity Projects/foo.txt", "text")
assertEquals("text", fileUtils.readFile("file://$tmp/Unity%20Projects/foo.txt"))
}

fun `test readFile normalizes line endings`() {
myFixture.createTempFile("file.txt", "line\r\nline\rline\nline")
val normalized = fileUtils.readFile("file://$tmp/file.txt")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,12 @@ class UriUtilsTest : TestCase() {
assertEquals("/C:/Users/user/projects/[gamemode]/file.lua", parsed.path)
assertEquals("file:///C:/Users/user/projects/%5Bgamemode%5D/file.lua", parsed.toString())
}

// file://C:/... (two slashes). URI(scheme, authority, path, query) re-encodes `%`.
fun `test Windows path with percent-encoded spaces`() {
val uri = "file://C:/Unity%20Projects/My%20Project/foo.txt"
val parsed = UriUtils.parseUri(uri)
assertEquals("file", parsed.scheme)
assertEquals("/C:/Unity Projects/My Project/foo.txt", parsed.path)
}
}
Loading