Skip to content

Commit 9ae4880

Browse files
committed
capture more events for didCreate
1 parent c0f84b1 commit 9ae4880

File tree

2 files changed

+167
-47
lines changed

2 files changed

+167
-47
lines changed

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

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import com.intellij.openapi.roots.ModuleRootListener
1010
import com.intellij.openapi.vfs.VirtualFile
1111
import com.intellij.openapi.vfs.VirtualFileManager
1212
import com.intellij.openapi.vfs.newvfs.BulkFileListener
13+
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
1314
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
1415
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
1516
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
17+
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
1618
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent
1719
import org.eclipse.lsp4j.CreateFilesParams
1820
import org.eclipse.lsp4j.DeleteFilesParams
@@ -59,10 +61,24 @@ class WorkspaceServiceHandler(
5961
private fun didCreateFiles(events: List<VFileEvent>) {
6062
AmazonQLspService.executeIfRunning(project) { languageServer ->
6163
val validFiles = events.mapNotNull { event ->
62-
val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
63-
toUriString(file)?.let { uri ->
64-
FileCreate().apply {
65-
this.uri = uri
64+
when (event) {
65+
is VFileCopyEvent -> {
66+
val newFile = event.newParent.findChild(event.newChildName)?.takeIf { shouldHandleFile(it) }
67+
?: return@mapNotNull null
68+
toUriString(newFile)?.let { uri ->
69+
FileCreate().apply {
70+
this.uri = uri
71+
}
72+
}
73+
}
74+
else -> {
75+
val file = event.file?.takeIf { shouldHandleFile(it) }
76+
?: return@mapNotNull null
77+
toUriString(file)?.let { uri ->
78+
FileCreate().apply {
79+
this.uri = uri
80+
}
81+
}
6682
}
6783
}
6884
}
@@ -80,8 +96,17 @@ class WorkspaceServiceHandler(
8096
private fun didDeleteFiles(events: List<VFileEvent>) {
8197
AmazonQLspService.executeIfRunning(project) { languageServer ->
8298
val validFiles = events.mapNotNull { event ->
83-
val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
84-
toUriString(file)?.let { uri ->
99+
when (event) {
100+
is VFileDeleteEvent -> {
101+
val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
102+
toUriString(file)
103+
}
104+
is VFileMoveEvent -> {
105+
val oldFile = event.oldParent?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
106+
toUriString(oldFile)
107+
}
108+
else -> null
109+
}?.let { uri ->
85110
FileDelete().apply {
86111
this.uri = uri
87112
}
@@ -129,15 +154,47 @@ class WorkspaceServiceHandler(
129154

130155
private fun didChangeWatchedFiles(events: List<VFileEvent>) {
131156
AmazonQLspService.executeIfRunning(project) { languageServer ->
132-
val validChanges = events.mapNotNull { event ->
133-
event.file?.let { toUriString(it) }?.let { uri ->
134-
FileEvent().apply {
135-
this.uri = uri
136-
type = when (event) {
137-
is VFileCreateEvent -> FileChangeType.Created
138-
is VFileDeleteEvent -> FileChangeType.Deleted
139-
else -> FileChangeType.Changed
140-
}
157+
val validChanges = events.flatMap { event ->
158+
when (event) {
159+
is VFileCopyEvent -> {
160+
event.newParent.findChild(event.newChildName)?.let { newFile ->
161+
toUriString(newFile)?.let { uri ->
162+
listOf(FileEvent().apply {
163+
this.uri = uri
164+
type = FileChangeType.Created
165+
})
166+
}
167+
} ?: emptyList()
168+
}
169+
is VFileMoveEvent -> {
170+
listOfNotNull(
171+
toUriString(event.oldParent)?.let { oldUri ->
172+
FileEvent().apply {
173+
uri = oldUri
174+
type = FileChangeType.Deleted
175+
}
176+
},
177+
toUriString(event.file)?.let { newUri ->
178+
FileEvent().apply {
179+
uri = newUri
180+
type = FileChangeType.Created
181+
}
182+
}
183+
)
184+
}
185+
else -> {
186+
event.file?.let { file ->
187+
toUriString(file)?.let { uri ->
188+
listOf(FileEvent().apply {
189+
this.uri = uri
190+
type = when (event) {
191+
is VFileCreateEvent -> FileChangeType.Created
192+
is VFileDeleteEvent -> FileChangeType.Deleted
193+
else -> FileChangeType.Changed
194+
}
195+
})
196+
}
197+
} ?: emptyList()
141198
}
142199
}
143200
}
@@ -155,7 +212,7 @@ class WorkspaceServiceHandler(
155212
override fun after(events: List<VFileEvent>) {
156213
// since we are using synchronous FileListener
157214
pluginAwareExecuteOnPooledThread {
158-
didCreateFiles(events.filterIsInstance<VFileCreateEvent>())
215+
didCreateFiles(events.filter { it is VFileCreateEvent || it is VFileMoveEvent || it is VFileCopyEvent })
159216
didDeleteFiles(events.filterIsInstance<VFileDeleteEvent>())
160217
didRenameFiles(events.filterIsInstance<VFilePropertyChangeEvent>())
161218
didChangeWatchedFiles(events)

plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt

Lines changed: 94 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import com.intellij.openapi.application.ApplicationManager
99
import com.intellij.openapi.components.serviceIfCreated
1010
import com.intellij.openapi.project.Project
1111
import com.intellij.openapi.vfs.VirtualFile
12+
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
1213
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
1314
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
1415
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
16+
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
1517
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent
1618
import com.intellij.util.messages.MessageBus
1719
import com.intellij.util.messages.MessageBusConnection
@@ -167,6 +169,32 @@ class WorkspaceServiceHandlerTest {
167169
verify(exactly = 0) { mockWorkspaceService.didCreateFiles(any()) }
168170
}
169171

172+
@Test
173+
fun `test didCreateFiles with move event`() = runTest {
174+
val oldUri = URI("file:///test/oldPath")
175+
val newUri = URI("file:///test/newPath")
176+
val moveEvent = createMockVFileMoveEvent(oldUri, newUri, "test.py")
177+
178+
sut.after(listOf(moveEvent))
179+
180+
val paramsSlot = slot<CreateFilesParams>()
181+
verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) }
182+
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.files[0].uri)
183+
}
184+
185+
@Test
186+
fun `test didCreateFiles with copy event`() = runTest {
187+
val originalUri = URI("file:///test/original")
188+
val newUri = URI("file:///test/new")
189+
val copyEvent = createMockVFileCopyEvent(originalUri, newUri, "test.py")
190+
191+
sut.after(listOf(copyEvent))
192+
193+
val paramsSlot = slot<CreateFilesParams>()
194+
verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) }
195+
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.files[0].uri)
196+
}
197+
170198
@Test
171199
fun `test didDeleteFiles with Python file`() = runTest {
172200
val pyUri = URI("file:///test/path")
@@ -262,6 +290,38 @@ class WorkspaceServiceHandlerTest {
262290
assertEquals(FileChangeType.Changed, paramsSlot.captured.changes[2].type)
263291
}
264292

293+
@Test
294+
fun `test didChangeWatchedFiles with move event reports both delete and create`() = runTest {
295+
val oldUri = URI("file:///test/oldPath")
296+
val newUri = URI("file:///test/newPath")
297+
val moveEvent = createMockVFileMoveEvent(oldUri, newUri, "test.py")
298+
299+
sut.after(listOf(moveEvent))
300+
301+
val paramsSlot = slot<DidChangeWatchedFilesParams>()
302+
verify { mockWorkspaceService.didChangeWatchedFiles(capture(paramsSlot)) }
303+
304+
assertEquals(2, paramsSlot.captured.changes.size)
305+
assertEquals(normalizeFileUri(oldUri.toString()), paramsSlot.captured.changes[0].uri)
306+
assertEquals(FileChangeType.Deleted, paramsSlot.captured.changes[0].type)
307+
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.changes[1].uri)
308+
assertEquals(FileChangeType.Created, paramsSlot.captured.changes[1].type)
309+
}
310+
311+
@Test
312+
fun `test didChangeWatchedFiles with copy event`() = runTest {
313+
val originalUri = URI("file:///test/original")
314+
val newUri = URI("file:///test/new")
315+
val copyEvent = createMockVFileCopyEvent(originalUri, newUri, "test.py")
316+
317+
sut.after(listOf(copyEvent))
318+
319+
val paramsSlot = slot<DidChangeWatchedFilesParams>()
320+
verify { mockWorkspaceService.didChangeWatchedFiles(capture(paramsSlot)) }
321+
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.changes[0].uri)
322+
assertEquals(FileChangeType.Created, paramsSlot.captured.changes[0].type)
323+
}
324+
265325
@Test
266326
fun `test no invoked messages when events are empty`() = runTest {
267327
// Act
@@ -510,20 +570,23 @@ class WorkspaceServiceHandlerTest {
510570
assertEquals("folder2", paramsSlot.captured.event.removed[0].name)
511571
}
512572

513-
private fun createMockVFileEvent(uri: URI, type: FileChangeType = FileChangeType.Changed, isDirectory: Boolean, extension: String = "py"): VFileEvent {
573+
private fun createMockVirtualFile(uri: URI, fileName: String, isDirectory: Boolean = false): VirtualFile {
514574
val nioPath = mockk<Path> {
515575
every { toUri() } returns uri
516576
}
517-
val virtualFile = mockk<VirtualFile> {
577+
return mockk<VirtualFile> {
518578
every { this@mockk.isDirectory } returns isDirectory
519579
every { toNioPath() } returns nioPath
520580
every { url } returns uri.path
521-
every { path } returns "${uri.path}.$extension"
581+
every { path } returns "${uri.path}/$fileName"
522582
every { fileSystem } returns mockk {
523583
every { protocol } returns "file"
524584
}
525585
}
586+
}
526587

588+
private fun createMockVFileEvent(uri: URI, type: FileChangeType = FileChangeType.Changed, isDirectory: Boolean = false, extension: String = "py"): VFileEvent {
589+
val virtualFile = createMockVirtualFile(uri, "test.$extension", isDirectory)
527590
return when (type) {
528591
FileChangeType.Deleted -> mockk<VFileDeleteEvent>()
529592
FileChangeType.Created -> mockk<VFileCreateEvent>()
@@ -533,49 +596,49 @@ class WorkspaceServiceHandlerTest {
533596
}
534597
}
535598

536-
// for didRename events
537599
private fun createMockPropertyChangeEvent(
538600
oldName: String,
539601
newName: String,
540602
isDirectory: Boolean = false,
541603
): VFilePropertyChangeEvent {
542-
val parentPath = mockk<Path>()
543-
val filePath = mockk<Path>()
604+
val oldUri = URI("file:///test/$oldName")
605+
val newUri = URI("file:///test/$newName")
606+
val file = createMockVirtualFile(newUri, newName, isDirectory)
607+
every { file.parent } returns createMockVirtualFile(oldUri, oldName, isDirectory)
544608

545-
val parent = mockk<VirtualFile> {
546-
every { toNioPath() } returns parentPath
547-
every { this@mockk.isDirectory } returns isDirectory
548-
every { path } returns "/test/$oldName"
549-
every { url } returns "file:///test/$oldName"
550-
every { fileSystem } returns mockk {
551-
every { protocol } returns "file"
552-
}
609+
return mockk<VFilePropertyChangeEvent>().apply {
610+
every { propertyName } returns VirtualFile.PROP_NAME
611+
every { this@apply.file } returns file
612+
every { oldValue } returns oldName
613+
every { newValue } returns newName
553614
}
615+
}
554616

555-
val file = mockk<VirtualFile> {
556-
every { toNioPath() } returns filePath
557-
every { this@mockk.parent } returns parent
558-
every { this@mockk.isDirectory } returns isDirectory
559-
every { path } returns "/test/$newName"
560-
every { url } returns "file:///test/$newName"
617+
private fun createMockVFileMoveEvent(oldUri: URI, newUri: URI, fileName: String): VFileMoveEvent {
618+
val newFile = createMockVirtualFile(newUri, fileName)
619+
return mockk<VFileMoveEvent>().apply {
620+
every { file } returns newFile
621+
every { oldPath } returns oldUri.path
622+
every { oldParent } returns createMockVirtualFile(oldUri, fileName)
623+
}
624+
}
625+
626+
private fun createMockVFileCopyEvent(originalUri: URI, newUri: URI, fileName: String): VFileCopyEvent {
627+
val newParent = mockk<VirtualFile> {
628+
every { findChild(any()) } returns createMockVirtualFile(newUri, fileName)
561629
every { fileSystem } returns mockk {
562630
every { protocol } returns "file"
563631
}
564632
}
565-
566-
every { parentPath.resolve(oldName) } returns mockk {
567-
every { toUri() } returns URI("file:///test/$oldName")
568-
}
569-
every { filePath.toUri() } returns URI("file:///test/$newName")
570-
571-
return mockk<VFilePropertyChangeEvent>().apply {
572-
every { propertyName } returns VirtualFile.PROP_NAME
573-
every { this@apply.file } returns file
574-
every { oldValue } returns oldName
575-
every { newValue } returns newName
633+
return mockk<VFileCopyEvent>().apply {
634+
every { file } returns createMockVirtualFile(originalUri, fileName)
635+
every { this@apply.newParent } returns newParent
636+
every { newChildName } returns fileName
576637
}
577638
}
578639

640+
641+
579642
// for windows unit tests
580643
private fun normalizeFileUri(uri: String): String {
581644
if (!System.getProperty("os.name").lowercase().contains("windows")) {

0 commit comments

Comments
 (0)