Skip to content
Merged
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 @@ -10,9 +10,11 @@
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent
import org.eclipse.lsp4j.CreateFilesParams
import org.eclipse.lsp4j.DeleteFilesParams
Expand Down Expand Up @@ -59,10 +61,24 @@
private fun didCreateFiles(events: List<VFileEvent>) {
AmazonQLspService.executeIfRunning(project) { languageServer ->
val validFiles = events.mapNotNull { event ->
val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
toUriString(file)?.let { uri ->
FileCreate().apply {
this.uri = uri
when (event) {

Check warning on line 64 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L64 was not covered by tests
is VFileCopyEvent -> {
val newFile = event.newParent.findChild(event.newChildName)?.takeIf { shouldHandleFile(it) }
?: return@mapNotNull null

Check warning on line 67 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L67 was not covered by tests
toUriString(newFile)?.let { uri ->
FileCreate().apply {
this.uri = uri
}

Check warning on line 71 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L69 - L71 were not covered by tests
}
}
else -> {
val file = event.file?.takeIf { shouldHandleFile(it) }
?: return@mapNotNull null

Check warning on line 76 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L76 was not covered by tests
toUriString(file)?.let { uri ->
FileCreate().apply {
this.uri = uri
}

Check warning on line 80 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L78 - L80 were not covered by tests
}
}
}
}
Expand All @@ -80,8 +96,17 @@
private fun didDeleteFiles(events: List<VFileEvent>) {
AmazonQLspService.executeIfRunning(project) { languageServer ->
val validFiles = events.mapNotNull { event ->
val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
toUriString(file)?.let { uri ->
when (event) {

Check warning on line 99 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L99 was not covered by tests
is VFileDeleteEvent -> {
val file = event.file.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
toUriString(file)

Check warning on line 102 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L102 was not covered by tests
}
is VFileMoveEvent -> {
val oldFile = event.oldParent?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
toUriString(oldFile)

Check warning on line 106 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L106 was not covered by tests
}
else -> null

Check warning on line 108 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L108 was not covered by tests
}?.let { uri ->
FileDelete().apply {
this.uri = uri
}
Expand Down Expand Up @@ -129,15 +154,51 @@

private fun didChangeWatchedFiles(events: List<VFileEvent>) {
AmazonQLspService.executeIfRunning(project) { languageServer ->
val validChanges = events.mapNotNull { event ->
event.file?.let { toUriString(it) }?.let { uri ->
FileEvent().apply {
this.uri = uri
type = when (event) {
is VFileCreateEvent -> FileChangeType.Created
is VFileDeleteEvent -> FileChangeType.Deleted
else -> FileChangeType.Changed
}
val validChanges = events.flatMap { event ->
when (event) {

Check warning on line 158 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L157 - L158 were not covered by tests
is VFileCopyEvent -> {
event.newParent.findChild(event.newChildName)?.let { newFile ->
toUriString(newFile)?.let { uri ->
listOf(
FileEvent().apply {
this.uri = uri
type = FileChangeType.Created
}
)
}

Check warning on line 168 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L162 - L168 were not covered by tests
}.orEmpty()
}
is VFileMoveEvent -> {
listOfNotNull(

Check warning on line 172 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L172 was not covered by tests
toUriString(event.oldParent)?.let { oldUri ->
FileEvent().apply {
uri = oldUri
type = FileChangeType.Deleted
}

Check warning on line 177 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L174 - L177 were not covered by tests
},
toUriString(event.file)?.let { newUri ->
FileEvent().apply {
uri = newUri
type = FileChangeType.Created
}

Check warning on line 183 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L180 - L183 were not covered by tests
}
)
}
else -> {
event.file?.let { file ->
toUriString(file)?.let { uri ->
listOf(
FileEvent().apply {
this.uri = uri
type = when (event) {

Check warning on line 193 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L190 - L193 were not covered by tests
is VFileCreateEvent -> FileChangeType.Created
is VFileDeleteEvent -> FileChangeType.Deleted
else -> FileChangeType.Changed

Check warning on line 196 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L196 was not covered by tests
}
}
)
}

Check warning on line 200 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L198 - L200 were not covered by tests
}.orEmpty()
}
}
}
Expand All @@ -155,8 +216,8 @@
override fun after(events: List<VFileEvent>) {
// since we are using synchronous FileListener
pluginAwareExecuteOnPooledThread {
didCreateFiles(events.filterIsInstance<VFileCreateEvent>())
didDeleteFiles(events.filterIsInstance<VFileDeleteEvent>())
didCreateFiles(events.filter { it is VFileCreateEvent || it is VFileMoveEvent || it is VFileCopyEvent })
didDeleteFiles(events.filter { it is VFileMoveEvent || it is VFileDeleteEvent })
didRenameFiles(events.filterIsInstance<VFilePropertyChangeEvent>())
didChangeWatchedFiles(events)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.serviceIfCreated
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent
import com.intellij.util.messages.MessageBus
import com.intellij.util.messages.MessageBusConnection
Expand Down Expand Up @@ -167,6 +169,32 @@ class WorkspaceServiceHandlerTest {
verify(exactly = 0) { mockWorkspaceService.didCreateFiles(any()) }
}

@Test
fun `test didCreateFiles with move event`() = runTest {
val oldUri = URI("file:///test/oldPath")
val newUri = URI("file:///test/newPath")
val moveEvent = createMockVFileMoveEvent(oldUri, newUri, "test.py")

sut.after(listOf(moveEvent))

val paramsSlot = slot<CreateFilesParams>()
verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) }
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.files[0].uri)
}

@Test
fun `test didCreateFiles with copy event`() = runTest {
val originalUri = URI("file:///test/original")
val newUri = URI("file:///test/new")
val copyEvent = createMockVFileCopyEvent(originalUri, newUri, "test.py")

sut.after(listOf(copyEvent))

val paramsSlot = slot<CreateFilesParams>()
verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) }
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.files[0].uri)
}

@Test
fun `test didDeleteFiles with Python file`() = runTest {
val pyUri = URI("file:///test/path")
Expand Down Expand Up @@ -237,6 +265,48 @@ class WorkspaceServiceHandlerTest {
assertEquals(normalizeFileUri(dirUri.toString()), paramsSlot.captured.files[0].uri)
}

@Test
fun `test didDeleteFiles handles both delete and move events in same batch`() = runTest {
val deleteUri = URI("file:///test/deleteFile")
val oldMoveUri = URI("file:///test/oldMoveFile")
val newMoveUri = URI("file:///test/newMoveFile")

val deleteEvent = createMockVFileEvent(deleteUri, FileChangeType.Deleted, false, "py")
val moveEvent = createMockVFileMoveEvent(oldMoveUri, newMoveUri, "test.py")

sut.after(listOf(deleteEvent, moveEvent))

val deleteParamsSlot = slot<DeleteFilesParams>()
verify { mockWorkspaceService.didDeleteFiles(capture(deleteParamsSlot)) }
assertEquals(2, deleteParamsSlot.captured.files.size)
assertEquals(normalizeFileUri(deleteUri.toString()), deleteParamsSlot.captured.files[0].uri)
assertEquals(normalizeFileUri(oldMoveUri.toString()), deleteParamsSlot.captured.files[1].uri)
}

@Test
fun `test didDeleteFiles with move event of unsupported file type`() = runTest {
val oldUri = URI("file:///test/oldPath")
val newUri = URI("file:///test/newPath")
val moveEvent = createMockVFileMoveEvent(oldUri, newUri, "test.txt")

sut.after(listOf(moveEvent))

verify(exactly = 0) { mockWorkspaceService.didDeleteFiles(any()) }
}

@Test
fun `test didDeleteFiles with move event of directory`() = runTest {
val oldUri = URI("file:///test/oldDir")
val newUri = URI("file:///test/newDir")
val moveEvent = createMockVFileMoveEvent(oldUri, newUri, "", true)

sut.after(listOf(moveEvent))

val deleteParamsSlot = slot<DeleteFilesParams>()
verify { mockWorkspaceService.didDeleteFiles(capture(deleteParamsSlot)) }
assertEquals(normalizeFileUri(oldUri.toString()), deleteParamsSlot.captured.files[0].uri)
}

@Test
fun `test didChangeWatchedFiles with valid events`() = runTest {
// Arrange
Expand All @@ -262,6 +332,38 @@ class WorkspaceServiceHandlerTest {
assertEquals(FileChangeType.Changed, paramsSlot.captured.changes[2].type)
}

@Test
fun `test didChangeWatchedFiles with move event reports both delete and create`() = runTest {
val oldUri = URI("file:///test/oldPath")
val newUri = URI("file:///test/newPath")
val moveEvent = createMockVFileMoveEvent(oldUri, newUri, "test.py")

sut.after(listOf(moveEvent))

val paramsSlot = slot<DidChangeWatchedFilesParams>()
verify { mockWorkspaceService.didChangeWatchedFiles(capture(paramsSlot)) }

assertEquals(2, paramsSlot.captured.changes.size)
assertEquals(normalizeFileUri(oldUri.toString()), paramsSlot.captured.changes[0].uri)
assertEquals(FileChangeType.Deleted, paramsSlot.captured.changes[0].type)
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.changes[1].uri)
assertEquals(FileChangeType.Created, paramsSlot.captured.changes[1].type)
}

@Test
fun `test didChangeWatchedFiles with copy event`() = runTest {
val originalUri = URI("file:///test/original")
val newUri = URI("file:///test/new")
val copyEvent = createMockVFileCopyEvent(originalUri, newUri, "test.py")

sut.after(listOf(copyEvent))

val paramsSlot = slot<DidChangeWatchedFilesParams>()
verify { mockWorkspaceService.didChangeWatchedFiles(capture(paramsSlot)) }
assertEquals(normalizeFileUri(newUri.toString()), paramsSlot.captured.changes[0].uri)
assertEquals(FileChangeType.Created, paramsSlot.captured.changes[0].type)
}

@Test
fun `test no invoked messages when events are empty`() = runTest {
// Act
Expand Down Expand Up @@ -510,20 +612,28 @@ class WorkspaceServiceHandlerTest {
assertEquals("folder2", paramsSlot.captured.event.removed[0].name)
}

private fun createMockVFileEvent(uri: URI, type: FileChangeType = FileChangeType.Changed, isDirectory: Boolean, extension: String = "py"): VFileEvent {
private fun createMockVirtualFile(uri: URI, fileName: String, isDirectory: Boolean = false): VirtualFile {
val nioPath = mockk<Path> {
every { toUri() } returns uri
}
val virtualFile = mockk<VirtualFile> {
return mockk<VirtualFile> {
every { [email protected] } returns isDirectory
every { toNioPath() } returns nioPath
every { url } returns uri.path
every { path } returns "${uri.path}.$extension"
every { path } returns "${uri.path}/$fileName"
every { fileSystem } returns mockk {
every { protocol } returns "file"
}
}
}

private fun createMockVFileEvent(
uri: URI,
type: FileChangeType = FileChangeType.Changed,
isDirectory: Boolean = false,
extension: String = "py",
): VFileEvent {
val virtualFile = createMockVirtualFile(uri, "test.$extension", isDirectory)
return when (type) {
FileChangeType.Deleted -> mockk<VFileDeleteEvent>()
FileChangeType.Created -> mockk<VFileCreateEvent>()
Expand All @@ -533,46 +643,45 @@ class WorkspaceServiceHandlerTest {
}
}

// for didRename events
private fun createMockPropertyChangeEvent(
oldName: String,
newName: String,
isDirectory: Boolean = false,
): VFilePropertyChangeEvent {
val parentPath = mockk<Path>()
val filePath = mockk<Path>()
val oldUri = URI("file:///test/$oldName")
val newUri = URI("file:///test/$newName")
val file = createMockVirtualFile(newUri, newName, isDirectory)
every { file.parent } returns createMockVirtualFile(oldUri, oldName, isDirectory)

val parent = mockk<VirtualFile> {
every { toNioPath() } returns parentPath
every { [email protected] } returns isDirectory
every { path } returns "/test/$oldName"
every { url } returns "file:///test/$oldName"
every { fileSystem } returns mockk {
every { protocol } returns "file"
}
return mockk<VFilePropertyChangeEvent>().apply {
every { propertyName } returns VirtualFile.PROP_NAME
every { [email protected] } returns file
every { oldValue } returns oldName
every { newValue } returns newName
}
}

val file = mockk<VirtualFile> {
every { toNioPath() } returns filePath
every { [email protected] } returns parent
every { [email protected] } returns isDirectory
every { path } returns "/test/$newName"
every { url } returns "file:///test/$newName"
private fun createMockVFileMoveEvent(oldUri: URI, newUri: URI, fileName: String, isDirectory: Boolean = false): VFileMoveEvent {
val oldFile = createMockVirtualFile(oldUri, fileName, isDirectory)
val newFile = createMockVirtualFile(newUri, fileName, isDirectory)
return mockk<VFileMoveEvent>().apply {
every { file } returns newFile
every { oldPath } returns oldUri.path
every { oldParent } returns oldFile
}
}

private fun createMockVFileCopyEvent(originalUri: URI, newUri: URI, fileName: String): VFileCopyEvent {
val newParent = mockk<VirtualFile> {
every { findChild(any()) } returns createMockVirtualFile(newUri, fileName)
every { fileSystem } returns mockk {
every { protocol } returns "file"
}
}

every { parentPath.resolve(oldName) } returns mockk {
every { toUri() } returns URI("file:///test/$oldName")
}
every { filePath.toUri() } returns URI("file:///test/$newName")

return mockk<VFilePropertyChangeEvent>().apply {
every { propertyName } returns VirtualFile.PROP_NAME
every { [email protected] } returns file
every { oldValue } returns oldName
every { newValue } returns newName
return mockk<VFileCopyEvent>().apply {
every { file } returns createMockVirtualFile(originalUri, fileName)
every { [email protected] } returns newParent
every { newChildName } returns fileName
}
}

Expand Down
Loading