diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt index d7b80ebd54..1c8e030fce 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/LspEditorUtil.kt @@ -47,13 +47,25 @@ object LspEditorUtil { private fun toUri(file: File): URI { try { // URI scheme specified by language server protocol - return URI("file", "", file.absoluteFile.toURI().path, null) + val uri = URI("file", "", file.absoluteFile.toURI().path, null) + val fallback = file.toPath().toAbsolutePath().normalize().toUri() + return if (uri.isCompliant()) uri else fallback } catch (e: URISyntaxException) { LOG.warn { "${e.localizedMessage}: $e" } return file.absoluteFile.toURI() } } + private fun URI.isCompliant(): Boolean { + if (!"file".equals(this.scheme, ignoreCase = true)) return true + + val path = this.rawPath ?: this.path ?: "" + val noAuthority = this.authority.isNullOrEmpty() + + // If the authority component is empty, the path cannot begin with two slash characters ("//") + return !(noAuthority && path.startsWith("//")) + } + /** * Works but is divergent from [FocusAreaContextExtrator] */ diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/FileUriUtilTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/FileUriUtilTest.kt index abefcf9f15..6329e682c1 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/FileUriUtilTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/FileUriUtilTest.kt @@ -8,6 +8,8 @@ import io.mockk.every import io.mockk.mockk import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.EnabledOnOs +import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(ApplicationExtension::class) @@ -96,6 +98,25 @@ class FileUriUtilTest { } } + @Test + @EnabledOnOs(OS.WINDOWS) + fun `test wsl-like path`() { + val virtualFile = createMockVirtualFile("//wsl.localhost/Ubuntu/home/user/file.sh") + val result = LspEditorUtil.toUriString(virtualFile) + val expected = normalizeFileUri("file://wsl.localhost/Ubuntu/home/user/file.sh") + assertThat(result).isEqualTo(expected) + } + + @Test + @EnabledOnOs(OS.WINDOWS) + fun `test UNC path`() { + val virtualFile = createMockVirtualFile("//server/share/path/to/file.txt") + val result = LspEditorUtil.toUriString(virtualFile) + val expected = normalizeFileUri("file://server/share/path/to/file.txt") + assertThat(result).isEqualTo(expected) + } + + @Test fun `test jar protocol conversion`() { val virtualFile = createMockVirtualFile(