Skip to content

Commit e44cc15

Browse files
committed
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
# Conflicts: # plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt
2 parents b8d2f11 + c0f84b1 commit e44cc15

File tree

6 files changed

+170
-103
lines changed

6 files changed

+170
-103
lines changed

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.eclipse.lsp4j.TextDocumentIdentifier
2424
import org.eclipse.lsp4j.TextDocumentItem
2525
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier
2626
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
27+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.FileUriUtil.toUriString
2728
import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread
2829

2930
class TextDocumentServiceHandler(
@@ -56,7 +57,7 @@ class TextDocumentServiceHandler(
5657
override fun beforeDocumentSaving(document: Document) {
5758
AmazonQLspService.executeIfRunning(project) { languageServer ->
5859
val file = FileDocumentManager.getInstance().getFile(document) ?: return@executeIfRunning
59-
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
60+
toUriString(file)?.let { uri ->
6061
languageServer.textDocumentService.didSave(
6162
DidSaveTextDocumentParams().apply {
6263
textDocument = TextDocumentIdentifier().apply {
@@ -74,7 +75,7 @@ class TextDocumentServiceHandler(
7475
pluginAwareExecuteOnPooledThread {
7576
events.filterIsInstance<VFileContentChangeEvent>().forEach { event ->
7677
val document = FileDocumentManager.getInstance().getCachedDocument(event.file) ?: return@forEach
77-
event.file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
78+
toUriString(event.file)?.let { uri ->
7879
languageServer.textDocumentService.didChange(
7980
DidChangeTextDocumentParams().apply {
8081
textDocument = VersionedTextDocumentIdentifier().apply {
@@ -99,7 +100,7 @@ class TextDocumentServiceHandler(
99100
file: VirtualFile,
100101
) {
101102
AmazonQLspService.executeIfRunning(project) { languageServer ->
102-
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
103+
toUriString(file)?.let { uri ->
103104
languageServer.textDocumentService.didOpen(
104105
DidOpenTextDocumentParams().apply {
105106
textDocument = TextDocumentItem().apply {
@@ -117,7 +118,7 @@ class TextDocumentServiceHandler(
117118
file: VirtualFile,
118119
) {
119120
AmazonQLspService.executeIfRunning(project) { languageServer ->
120-
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
121+
toUriString(file)?.let { uri ->
121122
languageServer.textDocumentService.didClose(
122123
DidCloseTextDocumentParams().apply {
123124
textDocument = TextDocumentIdentifier().apply {

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.util
66
import com.intellij.openapi.project.Project
77
import com.intellij.openapi.roots.ProjectRootManager
88
import org.eclipse.lsp4j.WorkspaceFolder
9+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.FileUriUtil.toUriString
910

1011
object WorkspaceFolderUtil {
1112
fun createWorkspaceFolders(project: Project): List<WorkspaceFolder> =
@@ -15,7 +16,7 @@ object WorkspaceFolderUtil {
1516
ProjectRootManager.getInstance(project).contentRoots.map { contentRoot ->
1617
WorkspaceFolder().apply {
1718
name = contentRoot.name
18-
this.uri = contentRoot.url
19+
this.uri = toUriString(contentRoot)
1920
}
2021
}
2122
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import org.eclipse.lsp4j.RenameFilesParams
2929
import org.eclipse.lsp4j.WorkspaceFolder
3030
import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent
3131
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
32+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.FileUriUtil.toUriString
3233
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
3334
import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread
3435
import java.nio.file.FileSystems
@@ -97,7 +98,7 @@ class WorkspaceServiceHandler(
9798
AmazonQLspService.executeIfRunning(project) { languageServer ->
9899
val validFiles = events.mapNotNull { event ->
99100
val file = event.file?.takeIf { shouldHandleFile(it, FileOperationType.CREATE) } ?: return@mapNotNull null
100-
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
101+
toUriString(file)?.let { uri ->
101102
FileCreate().apply {
102103
this.uri = uri
103104
}
@@ -118,7 +119,7 @@ class WorkspaceServiceHandler(
118119
AmazonQLspService.executeIfRunning(project) { languageServer ->
119120
val validFiles = events.mapNotNull { event ->
120121
val file = event.file?.takeIf { shouldHandleFile(it, FileOperationType.DELETE) } ?: return@mapNotNull null
121-
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
122+
toUriString(file)?.let { uri ->
122123
FileDelete().apply {
123124
this.uri = uri
124125
}
@@ -141,13 +142,12 @@ class WorkspaceServiceHandler(
141142
.filter { it.propertyName == VirtualFile.PROP_NAME }
142143
.mapNotNull { event ->
143144
val file = event.file.takeIf { shouldHandleFile(it, FileOperationType.RENAME) } ?: return@mapNotNull null
144-
val oldName = event.oldValue as? String ?: return@mapNotNull null
145145
if (event.newValue !is String) return@mapNotNull null
146146

147147
// Construct old and new URIs
148-
val parentPath = file.parent?.toNioPath() ?: return@mapNotNull null
149-
val oldUri = parentPath.resolve(oldName).toUri().toString()
150-
val newUri = file.toNioPath().toUri().toString()
148+
val parentFile = file.parent ?: return@mapNotNull null
149+
val oldUri = toUriString(parentFile)
150+
val newUri = toUriString(file)
151151

152152
FileRename().apply {
153153
this.oldUri = oldUri
@@ -168,7 +168,7 @@ class WorkspaceServiceHandler(
168168
private fun didChangeWatchedFiles(events: List<VFileEvent>) {
169169
AmazonQLspService.executeIfRunning(project) { languageServer ->
170170
val validChanges = events.mapNotNull { event ->
171-
event.file?.toNioPath()?.toUri()?.toString()?.takeIf { it.isNotEmpty() }?.let { uri ->
171+
event.file?.let { toUriString(it) }?.let { uri ->
172172
FileEvent().apply {
173173
this.uri = uri
174174
type = when (event) {

plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.intellij.util.messages.MessageBusConnection
1818
import io.mockk.every
1919
import io.mockk.just
2020
import io.mockk.mockk
21+
import io.mockk.mockkObject
2122
import io.mockk.mockkStatic
2223
import io.mockk.runs
2324
import io.mockk.slot
@@ -34,6 +35,7 @@ import org.junit.Before
3435
import org.junit.Test
3536
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer
3637
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
38+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.FileUriUtil
3739
import java.net.URI
3840
import java.nio.file.Path
3941
import java.util.concurrent.Callable
@@ -99,14 +101,7 @@ class TextDocumentServiceHandlerTest {
99101
every { text } returns "test content"
100102
}
101103

102-
val path = mockk<Path> {
103-
every { toUri() } returns uri
104-
}
105-
106-
val file = mockk<VirtualFile> {
107-
every { this@mockk.path } returns uri.path
108-
every { toNioPath() } returns path
109-
}
104+
val file = createMockVirtualFile(uri)
110105

111106
// Mock FileDocumentManager
112107
val fileDocumentManager = mockk<FileDocumentManager> {
@@ -125,7 +120,7 @@ class TextDocumentServiceHandlerTest {
125120
verify { mockTextDocumentService.didSave(capture(paramsSlot)) }
126121

127122
with(paramsSlot.captured) {
128-
assertEquals(uri.toString(), textDocument.uri)
123+
assertEquals(normalizeFileUri(uri.toString()), textDocument.uri)
129124
assertEquals("test content", text)
130125
}
131126
}
@@ -136,17 +131,8 @@ class TextDocumentServiceHandlerTest {
136131
// Create test file
137132
val uri = URI.create("file:///test/path/file.txt")
138133
val content = "test content"
139-
val inputStream = content.byteInputStream()
140134

141-
val path = mockk<Path> {
142-
every { toUri() } returns uri
143-
}
144-
145-
val file = mockk<VirtualFile> {
146-
every { this@mockk.path } returns uri.path
147-
every { toNioPath() } returns path
148-
every { this@mockk.inputStream } returns inputStream
149-
}
135+
val file = createMockVirtualFile(uri, content)
150136

151137
// Call the handler method
152138
sut.fileOpened(mockk(), file)
@@ -156,28 +142,22 @@ class TextDocumentServiceHandlerTest {
156142
verify { mockTextDocumentService.didOpen(capture(paramsSlot)) }
157143

158144
with(paramsSlot.captured.textDocument) {
159-
assertEquals(uri.toString(), this.uri)
145+
assertEquals(normalizeFileUri(uri.toString()), this.uri)
160146
assertEquals(content, text)
161147
}
162148
}
163149

164150
@Test
165151
fun `didClose runs on fileClosed`() = runTest {
166152
val uri = URI.create("file:///test/path/file.txt")
167-
val path = mockk<Path> {
168-
every { toUri() } returns uri
169-
}
170-
val file = mockk<VirtualFile> {
171-
every { this@mockk.path } returns uri.path
172-
every { toNioPath() } returns path
173-
}
153+
val file = createMockVirtualFile(uri)
174154

175155
sut.fileClosed(mockk(), file)
176156

177157
val paramsSlot = slot<DidCloseTextDocumentParams>()
178158
verify { mockTextDocumentService.didClose(capture(paramsSlot)) }
179159

180-
assertEquals(uri.toString(), paramsSlot.captured.textDocument.uri)
160+
assertEquals(normalizeFileUri(uri.toString()), paramsSlot.captured.textDocument.uri)
181161
}
182162

183163
@Test
@@ -188,14 +168,7 @@ class TextDocumentServiceHandlerTest {
188168
every { modificationStamp } returns 123L
189169
}
190170

191-
val path = mockk<Path> {
192-
every { toUri() } returns uri
193-
}
194-
195-
val file = mockk<VirtualFile> {
196-
every { this@mockk.path } returns uri.path
197-
every { toNioPath() } returns path
198-
}
171+
val file = createMockVirtualFile(uri)
199172

200173
val changeEvent = mockk<VFileContentChangeEvent> {
201174
every { this@mockk.file } returns file
@@ -218,7 +191,7 @@ class TextDocumentServiceHandlerTest {
218191
verify { mockTextDocumentService.didChange(capture(paramsSlot)) }
219192

220193
with(paramsSlot.captured) {
221-
assertEquals(uri.toString(), textDocument.uri)
194+
assertEquals(normalizeFileUri(uri.toString()), textDocument.uri)
222195
assertEquals(123, textDocument.version)
223196
assertEquals("changed content", contentChanges[0].text)
224197
}
@@ -227,23 +200,22 @@ class TextDocumentServiceHandlerTest {
227200
@Test
228201
fun `didSave does not run when URI is empty`() = runTest {
229202
val document = mockk<Document>()
230-
val path = mockk<Path> {
231-
every { toUri() } returns URI.create("")
232-
}
233-
val file = mockk<VirtualFile> {
234-
every { toNioPath() } returns path
235-
}
203+
val file = createMockVirtualFile(URI.create(""))
236204

237-
val fileDocumentManager = mockk<FileDocumentManager> {
238-
every { getFile(document) } returns file
239-
}
205+
mockkObject(FileUriUtil) {
206+
every { FileUriUtil.toUriString(file) } returns null
240207

241-
mockkStatic(FileDocumentManager::class) {
242-
every { FileDocumentManager.getInstance() } returns fileDocumentManager
208+
val fileDocumentManager = mockk<FileDocumentManager> {
209+
every { getFile(document) } returns file
210+
}
243211

244-
sut.beforeDocumentSaving(document)
212+
mockkStatic(FileDocumentManager::class) {
213+
every { FileDocumentManager.getInstance() } returns fileDocumentManager
245214

246-
verify(exactly = 0) { mockTextDocumentService.didSave(any()) }
215+
sut.beforeDocumentSaving(document)
216+
217+
verify(exactly = 0) { mockTextDocumentService.didSave(any()) }
218+
}
247219
}
248220
}
249221

@@ -298,4 +270,33 @@ class TextDocumentServiceHandlerTest {
298270
verify(exactly = 0) { mockTextDocumentService.didChange(any()) }
299271
}
300272
}
273+
274+
private fun createMockVirtualFile(uri: URI, content: String = ""): VirtualFile {
275+
val path = mockk<Path> {
276+
every { toUri() } returns uri
277+
}
278+
val inputStream = content.byteInputStream()
279+
return mockk<VirtualFile> {
280+
every { url } returns uri.path
281+
every { toNioPath() } returns path
282+
every { isDirectory } returns false
283+
every { fileSystem } returns mockk {
284+
every { protocol } returns "file"
285+
}
286+
every { this@mockk.inputStream } returns inputStream
287+
}
288+
}
289+
290+
private fun normalizeFileUri(uri: String): String {
291+
if (!System.getProperty("os.name").lowercase().contains("windows")) {
292+
return uri
293+
}
294+
295+
if (!uri.startsWith("file:///")) {
296+
return uri
297+
}
298+
299+
val path = uri.substringAfter("file:///")
300+
return "file:///C:/$path"
301+
}
301302
}

plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import io.mockk.every
1010
import io.mockk.mockk
1111
import org.junit.jupiter.api.Assertions.assertEquals
1212
import org.junit.jupiter.api.Test
13+
import java.net.URI
14+
import java.nio.file.Path
1315

1416
class WorkspaceFolderUtilTest {
1517

@@ -27,29 +29,30 @@ class WorkspaceFolderUtilTest {
2729
fun `createWorkspaceFolders returns workspace folders for non-default project`() {
2830
val mockProject = mockk<Project>()
2931
val mockProjectRootManager = mockk<ProjectRootManager>()
30-
val mockContentRoot1 = mockk<VirtualFile>()
31-
val mockContentRoot2 = mockk<VirtualFile>()
32+
val mockContentRoot1 = createMockVirtualFile(
33+
URI("file:///path/to/root1"),
34+
name = "root1"
35+
)
36+
val mockContentRoot2 = createMockVirtualFile(
37+
URI("file:///path/to/root2"),
38+
name = "root2"
39+
)
3240

3341
every { mockProject.isDefault } returns false
3442
every { ProjectRootManager.getInstance(mockProject) } returns mockProjectRootManager
3543
every { mockProjectRootManager.contentRoots } returns arrayOf(mockContentRoot1, mockContentRoot2)
3644

37-
every { mockContentRoot1.name } returns "root1"
38-
every { mockContentRoot1.url } returns "file:///path/to/root1"
39-
every { mockContentRoot2.name } returns "root2"
40-
every { mockContentRoot2.url } returns "file:///path/to/root2"
41-
4245
val result = WorkspaceFolderUtil.createWorkspaceFolders(mockProject)
4346

4447
assertEquals(2, result.size)
45-
assertEquals("file:///path/to/root1", result[0].uri)
46-
assertEquals("file:///path/to/root2", result[1].uri)
48+
assertEquals(normalizeFileUri("file:///path/to/root1"), result[0].uri)
49+
assertEquals(normalizeFileUri("file:///path/to/root2"), result[1].uri)
4750
assertEquals("root1", result[0].name)
4851
assertEquals("root2", result[1].name)
4952
}
5053

5154
@Test
52-
fun `reateWorkspaceFolders returns empty list when project has no content roots`() {
55+
fun `createWorkspaceFolders returns empty list when project has no content roots`() {
5356
val mockProject = mockk<Project>()
5457
val mockProjectRootManager = mockk<ProjectRootManager>()
5558

@@ -61,4 +64,33 @@ class WorkspaceFolderUtilTest {
6164

6265
assertEquals(emptyList<VirtualFile>(), result)
6366
}
67+
68+
private fun createMockVirtualFile(uri: URI, name: String): VirtualFile {
69+
val path = mockk<Path> {
70+
every { toUri() } returns uri
71+
}
72+
return mockk<VirtualFile> {
73+
every { url } returns uri.toString()
74+
every { getName() } returns name
75+
every { toNioPath() } returns path
76+
every { isDirectory } returns false
77+
every { fileSystem } returns mockk {
78+
every { protocol } returns "file"
79+
}
80+
}
81+
}
82+
83+
// for windows unit tests
84+
private fun normalizeFileUri(uri: String): String {
85+
if (!System.getProperty("os.name").lowercase().contains("windows")) {
86+
return uri
87+
}
88+
89+
if (!uri.startsWith("file:///")) {
90+
return uri
91+
}
92+
93+
val path = uri.substringAfter("file:///")
94+
return "file:///C:/$path"
95+
}
6496
}

0 commit comments

Comments
 (0)