From 686fda297e0a6ec6ec48ac641b5d2e998e72f2e2 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Thu, 13 Feb 2025 15:17:03 -0800 Subject: [PATCH 01/18] add workspace handler --- .../services/amazonq/lsp/AmazonQLspService.kt | 3 + .../lsp/workspace/WorkspaceServiceHandler.kt | 90 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 4387f99ce8d..0f19ffc1720 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -39,6 +39,7 @@ import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.isDeveloperMode import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata +import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata import java.io.IOException import java.io.OutputStreamWriter @@ -235,6 +236,8 @@ private class AmazonQServerInstance(private val project: Project, private val cs } languageServer.initialized(InitializedParams()) } + val workspaceServiceHandler = WorkspaceServiceHandler(project, languageServer) + workspaceServiceHandler.startWorkspaceServiceListeners() } override fun dispose() { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt new file mode 100644 index 00000000000..73212926b1a --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -0,0 +1,90 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace + +import com.intellij.openapi.components.service +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.newvfs.BulkFileListener +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.platform.backend.workspace.WorkspaceModel +import com.intellij.platform.workspace.jps.entities.ContentRootEntity +import com.intellij.platform.workspace.storage.VersionedStorageChange +import kotlinx.coroutines.flow.onEach +import org.eclipse.lsp4j.CreateFilesParams +import org.eclipse.lsp4j.DeleteFilesParams +import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams +import org.eclipse.lsp4j.FileCreate +import org.eclipse.lsp4j.FileDelete +import org.eclipse.lsp4j.WorkspaceFolder +import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer +import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread + +class WorkspaceServiceHandler( + private val project: Project, + private val languageServer: AmazonQLanguageServer +) : Disposable{ + + fun startWorkspaceServiceListeners() { + didCreateFiles() + didDeleteFiles() + } + + private fun didCreateFiles() { + project.messageBus.connect(this).subscribe( + VirtualFileManager.VFS_CHANGES, + object : BulkFileListener { + override fun after(events: List) { + val createEvents = events.filterIsInstance() + // since we are using synchronous FileListener + pluginAwareExecuteOnPooledThread { + if (createEvents.isNotEmpty()) { + languageServer.workspaceService.didCreateFiles( + CreateFilesParams().apply { + files = createEvents.map { event -> + FileCreate().apply { + uri = event.file?.toNioPath()?.toUri().toString() + } + } + } + ) + } + } + } + } + ) + } + + private fun didDeleteFiles() { + project.messageBus.connect(this).subscribe( + VirtualFileManager.VFS_CHANGES, + object : BulkFileListener { + override fun after(events: List) { + val deleteEvents = events.filterIsInstance() + pluginAwareExecuteOnPooledThread { + if (deleteEvents.isNotEmpty()) { + languageServer.workspaceService.didDeleteFiles( + DeleteFilesParams().apply { + files = deleteEvents.map { event -> + FileDelete().apply { + uri = event.file.toNioPath().toUri().toString() + } + } + } + ) + } + } + } + } + ) + } + + override fun dispose() { + } +} + From a5250b599dc156764964ee883c0f5e0030be05e4 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Thu, 13 Feb 2025 17:24:09 -0800 Subject: [PATCH 02/18] break up repeat code --- .../services/amazonq/lsp/AmazonQLspService.kt | 2 + .../lsp/workspace/WorkspaceServiceHandler.kt | 81 ++++++++----------- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 0f19ffc1720..93cd7abc011 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -236,6 +236,8 @@ private class AmazonQServerInstance(private val project: Project, private val cs } languageServer.initialized(InitializedParams()) } + + //may need to register listeners differently so their messageBus' don't get garbage collected val workspaceServiceHandler = WorkspaceServiceHandler(project, languageServer) workspaceServiceHandler.startWorkspaceServiceListeners() } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 73212926b1a..07431a94df0 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -3,7 +3,6 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace -import com.intellij.openapi.components.service import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFileManager @@ -11,80 +10,70 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener 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.platform.backend.workspace.WorkspaceModel -import com.intellij.platform.workspace.jps.entities.ContentRootEntity -import com.intellij.platform.workspace.storage.VersionedStorageChange -import kotlinx.coroutines.flow.onEach import org.eclipse.lsp4j.CreateFilesParams import org.eclipse.lsp4j.DeleteFilesParams -import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams import org.eclipse.lsp4j.FileCreate import org.eclipse.lsp4j.FileDelete -import org.eclipse.lsp4j.WorkspaceFolder -import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread class WorkspaceServiceHandler( private val project: Project, - private val languageServer: AmazonQLanguageServer -) : Disposable{ + private val languageServer: AmazonQLanguageServer, +) : Disposable { fun startWorkspaceServiceListeners() { - didCreateFiles() - didDeleteFiles() + startFileLifecycleListener() } - private fun didCreateFiles() { - project.messageBus.connect(this).subscribe( - VirtualFileManager.VFS_CHANGES, - object : BulkFileListener { - override fun after(events: List) { - val createEvents = events.filterIsInstance() - // since we are using synchronous FileListener - pluginAwareExecuteOnPooledThread { - if (createEvents.isNotEmpty()) { - languageServer.workspaceService.didCreateFiles( - CreateFilesParams().apply { - files = createEvents.map { event -> - FileCreate().apply { - uri = event.file?.toNioPath()?.toUri().toString() - } - } - } - ) + private fun didCreateFiles(events: List){ + if (events.isNotEmpty()) { + languageServer.workspaceService.didCreateFiles( + CreateFilesParams().apply { + files = events.map { event -> + FileCreate().apply { + uri = event.file?.toNioPath()?.toUri().toString() } } } - } - ) + ) + } } - private fun didDeleteFiles() { + private fun didDeleteFiles(events: List) { + if (events.isNotEmpty()) { + languageServer.workspaceService.didDeleteFiles( + DeleteFilesParams().apply { + files = events.map { event -> + FileDelete().apply { + uri = event.file?.toNioPath()?.toUri().toString() + } + } + } + ) + } + } + + private fun startFileLifecycleListener() { project.messageBus.connect(this).subscribe( VirtualFileManager.VFS_CHANGES, object : BulkFileListener { override fun after(events: List) { - val deleteEvents = events.filterIsInstance() + // since we are using synchronous FileListener pluginAwareExecuteOnPooledThread { - if (deleteEvents.isNotEmpty()) { - languageServer.workspaceService.didDeleteFiles( - DeleteFilesParams().apply { - files = deleteEvents.map { event -> - FileDelete().apply { - uri = event.file.toNioPath().toUri().toString() - } - } - } - ) - } + didCreateFiles(events.filterIsInstance()) + didDeleteFiles(events.filterIsInstance()) } } } ) } + // still need to implement + //private fun didChangeWorkspaceFolders() { + // languageServer.workspaceService.didChangeWorkspaceFolders() + //} + override fun dispose() { } } - From 0efb633bd8985ad07935bc1dccb656f408409a4d Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Fri, 14 Feb 2025 09:57:11 -0800 Subject: [PATCH 03/18] use serverInstance as messageBus disposable --- .../jetbrains/services/amazonq/lsp/AmazonQLspService.kt | 8 +++++--- .../amazonq/lsp/workspace/WorkspaceServiceHandler.kt | 8 +++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 93cd7abc011..797f5f9ef51 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -237,9 +237,11 @@ private class AmazonQServerInstance(private val project: Project, private val cs languageServer.initialized(InitializedParams()) } - //may need to register listeners differently so their messageBus' don't get garbage collected - val workspaceServiceHandler = WorkspaceServiceHandler(project, languageServer) - workspaceServiceHandler.startWorkspaceServiceListeners() + WorkspaceServiceHandler( + project, + languageServer, + this + ).startWorkspaceServiceListeners() } override fun dispose() { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 07431a94df0..659f78fde77 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -20,7 +20,8 @@ import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread class WorkspaceServiceHandler( private val project: Project, private val languageServer: AmazonQLanguageServer, -) : Disposable { + private val serverInstance: Disposable +){ fun startWorkspaceServiceListeners() { startFileLifecycleListener() @@ -55,7 +56,7 @@ class WorkspaceServiceHandler( } private fun startFileLifecycleListener() { - project.messageBus.connect(this).subscribe( + project.messageBus.connect(serverInstance).subscribe( VirtualFileManager.VFS_CHANGES, object : BulkFileListener { override fun after(events: List) { @@ -73,7 +74,4 @@ class WorkspaceServiceHandler( //private fun didChangeWorkspaceFolders() { // languageServer.workspaceService.didChangeWorkspaceFolders() //} - - override fun dispose() { - } } From 788e833f10122ab49b8885f0af6a10b645067249 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Fri, 14 Feb 2025 10:46:17 -0800 Subject: [PATCH 04/18] init starts listeners --- .../jetbrains/services/amazonq/lsp/AmazonQLspService.kt | 2 +- .../services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 797f5f9ef51..a5cfbfe2c3b 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -241,7 +241,7 @@ private class AmazonQServerInstance(private val project: Project, private val cs project, languageServer, this - ).startWorkspaceServiceListeners() + ) } override fun dispose() { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 659f78fde77..cde34225dc4 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -23,7 +23,7 @@ class WorkspaceServiceHandler( private val serverInstance: Disposable ){ - fun startWorkspaceServiceListeners() { + init{ startFileLifecycleListener() } From 429f5e9b3566ef5aa5947c79267ffe53fc0b87c7 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Mon, 17 Feb 2025 15:01:27 -0800 Subject: [PATCH 05/18] update listeners --- .../services/amazonq/lsp/AmazonQLspService.kt | 6 +- .../lsp/workspace/WorkspaceServiceHandler.kt | 69 ++++++++++--------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index a5cfbfe2c3b..0bcc9dc37ff 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -87,7 +87,7 @@ internal class LSPProcessListener : ProcessListener { @Service(Service.Level.PROJECT) class AmazonQLspService(private val project: Project, private val cs: CoroutineScope) : Disposable { - private var instance: AmazonQServerInstance? = null + internal var instance: AmazonQServerInstance? = null init { cs.launch { @@ -112,12 +112,12 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS } } -private class AmazonQServerInstance(private val project: Project, private val cs: CoroutineScope) : Disposable { +internal class AmazonQServerInstance(private val project: Project, private val cs: CoroutineScope) : Disposable { private val encryptionManager = JwtEncryptionManager() private val launcher: Launcher - private val languageServer: AmazonQLanguageServer + internal val languageServer: AmazonQLanguageServer get() = launcher.remoteProxy @Suppress("ForbiddenVoid") diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index cde34225dc4..10f11134f15 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -15,59 +15,62 @@ import org.eclipse.lsp4j.DeleteFilesParams import org.eclipse.lsp4j.FileCreate import org.eclipse.lsp4j.FileDelete import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread class WorkspaceServiceHandler( private val project: Project, - private val languageServer: AmazonQLanguageServer, - private val serverInstance: Disposable -){ + serverInstance: Disposable +): BulkFileListener { init{ - startFileLifecycleListener() + project.messageBus.connect(serverInstance).subscribe( + VirtualFileManager.VFS_CHANGES, + this + ) } + private fun executeIfRunning(project: Project, runnable: (AmazonQLanguageServer) -> Unit) = + AmazonQLspService.getInstance(project).instance?.languageServer?.let { runnable(it) } + private fun didCreateFiles(events: List){ - if (events.isNotEmpty()) { - languageServer.workspaceService.didCreateFiles( - CreateFilesParams().apply { - files = events.map { event -> - FileCreate().apply { - uri = event.file?.toNioPath()?.toUri().toString() + executeIfRunning(project) { + if (events.isNotEmpty()) { + it.workspaceService.didCreateFiles( + CreateFilesParams().apply { + files = events.map { event -> + FileCreate().apply { + uri = event.file?.toNioPath()?.toUri().toString() + } } } - } - ) + ) + } } } private fun didDeleteFiles(events: List) { - if (events.isNotEmpty()) { - languageServer.workspaceService.didDeleteFiles( - DeleteFilesParams().apply { - files = events.map { event -> - FileDelete().apply { - uri = event.file?.toNioPath()?.toUri().toString() + executeIfRunning(project) { languageServer -> + if (events.isNotEmpty()) { + languageServer.workspaceService.didDeleteFiles( + DeleteFilesParams().apply { + files = events.map { event -> + FileDelete().apply { + uri = event.file?.toNioPath()?.toUri().toString() + } } } - } - ) + ) + } } } - private fun startFileLifecycleListener() { - project.messageBus.connect(serverInstance).subscribe( - VirtualFileManager.VFS_CHANGES, - object : BulkFileListener { - override fun after(events: List) { - // since we are using synchronous FileListener - pluginAwareExecuteOnPooledThread { - didCreateFiles(events.filterIsInstance()) - didDeleteFiles(events.filterIsInstance()) - } - } - } - ) + override fun after(events: List) { + // since we are using synchronous FileListener + pluginAwareExecuteOnPooledThread { + didCreateFiles(events.filterIsInstance()) + didDeleteFiles(events.filterIsInstance()) + } } // still need to implement From aababb63a1172d38b4775bbcbc45b1fdadaffc3b Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Mon, 17 Feb 2025 15:03:54 -0800 Subject: [PATCH 06/18] fix init params --- .../toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 0bcc9dc37ff..1ecbb052e41 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -239,7 +239,6 @@ internal class AmazonQServerInstance(private val project: Project, private val c WorkspaceServiceHandler( project, - languageServer, this ) } From c3d855a232458713f5f510e89fa3b882603d88fb Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Mon, 17 Feb 2025 16:39:35 -0800 Subject: [PATCH 07/18] didChangeWatchedFiles impl --- .../lsp/workspace/WorkspaceServiceHandler.kt | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 10f11134f15..9e27fd4b22d 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -5,15 +5,14 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.project.ProjectManagerListener import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.BulkFileListener 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 org.eclipse.lsp4j.CreateFilesParams -import org.eclipse.lsp4j.DeleteFilesParams -import org.eclipse.lsp4j.FileCreate -import org.eclipse.lsp4j.FileDelete +import org.eclipse.lsp4j.* import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread @@ -21,13 +20,19 @@ import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread class WorkspaceServiceHandler( private val project: Project, serverInstance: Disposable -): BulkFileListener { +): BulkFileListener, +ProjectManagerListener { init{ project.messageBus.connect(serverInstance).subscribe( VirtualFileManager.VFS_CHANGES, this ) + + project.messageBus.connect(serverInstance).subscribe( + ProjectManager.TOPIC, + this + ) } private fun executeIfRunning(project: Project, runnable: (AmazonQLanguageServer) -> Unit) = @@ -70,6 +75,7 @@ class WorkspaceServiceHandler( pluginAwareExecuteOnPooledThread { didCreateFiles(events.filterIsInstance()) didDeleteFiles(events.filterIsInstance()) + didChangeWatchedFiles(events) } } @@ -77,4 +83,28 @@ class WorkspaceServiceHandler( //private fun didChangeWorkspaceFolders() { // languageServer.workspaceService.didChangeWorkspaceFolders() //} + + //didChangeWorkspaceFolders impl + + //didChangeWatchedFiles + private fun didChangeWatchedFiles(events: List) { + executeIfRunning(project) { + if (events.isNotEmpty()) { + it.workspaceService.didChangeWatchedFiles( + DidChangeWatchedFilesParams().apply { + changes = events.map { event -> + FileEvent().apply { + uri = event.file?.toNioPath()?.toUri().toString() + type = when (event) { + is VFileCreateEvent -> FileChangeType.Created + is VFileDeleteEvent -> FileChangeType.Deleted + else -> FileChangeType.Changed + } + } + } + } + ) + } + } + } } From fad44674de17873e9a065921c3f9ad8639cf9b18 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Tue, 18 Feb 2025 13:21:24 -0800 Subject: [PATCH 08/18] detekt --- .../lsp/workspace/WorkspaceServiceHandler.kt | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 9e27fd4b22d..933ce140794 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -19,11 +19,11 @@ import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread class WorkspaceServiceHandler( private val project: Project, - serverInstance: Disposable -): BulkFileListener, -ProjectManagerListener { + serverInstance: Disposable, +) : BulkFileListener, + ProjectManagerListener { - init{ + init { project.messageBus.connect(serverInstance).subscribe( VirtualFileManager.VFS_CHANGES, this @@ -38,7 +38,7 @@ ProjectManagerListener { private fun executeIfRunning(project: Project, runnable: (AmazonQLanguageServer) -> Unit) = AmazonQLspService.getInstance(project).instance?.languageServer?.let { runnable(it) } - private fun didCreateFiles(events: List){ + private fun didCreateFiles(events: List) { executeIfRunning(project) { if (events.isNotEmpty()) { it.workspaceService.didCreateFiles( @@ -79,14 +79,6 @@ ProjectManagerListener { } } - // still need to implement - //private fun didChangeWorkspaceFolders() { - // languageServer.workspaceService.didChangeWorkspaceFolders() - //} - - //didChangeWorkspaceFolders impl - - //didChangeWatchedFiles private fun didChangeWatchedFiles(events: List) { executeIfRunning(project) { if (events.isNotEmpty()) { From ffa167a611b18ee6d63764c75416ff29f18b03e1 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Tue, 18 Feb 2025 14:45:05 -0800 Subject: [PATCH 09/18] move executeIfRunning --- .../amazonq/lsp/workspace/WorkspaceServiceHandler.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 933ce140794..12a9ee793b7 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -35,11 +35,8 @@ class WorkspaceServiceHandler( ) } - private fun executeIfRunning(project: Project, runnable: (AmazonQLanguageServer) -> Unit) = - AmazonQLspService.getInstance(project).instance?.languageServer?.let { runnable(it) } - private fun didCreateFiles(events: List) { - executeIfRunning(project) { + AmazonQLspService.executeIfRunning(project) { if (events.isNotEmpty()) { it.workspaceService.didCreateFiles( CreateFilesParams().apply { @@ -55,7 +52,7 @@ class WorkspaceServiceHandler( } private fun didDeleteFiles(events: List) { - executeIfRunning(project) { languageServer -> + AmazonQLspService.executeIfRunning(project) { languageServer -> if (events.isNotEmpty()) { languageServer.workspaceService.didDeleteFiles( DeleteFilesParams().apply { @@ -80,7 +77,7 @@ class WorkspaceServiceHandler( } private fun didChangeWatchedFiles(events: List) { - executeIfRunning(project) { + AmazonQLspService.executeIfRunning(project) { if (events.isNotEmpty()) { it.workspaceService.didChangeWatchedFiles( DidChangeWatchedFilesParams().apply { From 87817eaade92259c84683a4e1b06e080fb4d7172 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Tue, 18 Feb 2025 15:05:10 -0800 Subject: [PATCH 10/18] private class --- .../jetbrains/services/amazonq/lsp/AmazonQLspService.kt | 2 +- .../services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index f103dfbd0fd..bf21fa1086d 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -181,7 +181,7 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS } } -internal class AmazonQServerInstance(private val project: Project, private val cs: CoroutineScope) : Disposable { +private class AmazonQServerInstance(private val project: Project, private val cs: CoroutineScope) : Disposable { private val encryptionManager = JwtEncryptionManager() private val launcher: Launcher diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 12a9ee793b7..284f7fbe87d 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -13,7 +13,6 @@ 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 org.eclipse.lsp4j.* -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread From d4954503668bb5fbf5de8c9a76c5588d5638fd79 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Wed, 19 Feb 2025 09:39:40 -0800 Subject: [PATCH 11/18] null uri handling --- .../lsp/workspace/WorkspaceServiceHandler.kt | 81 +++++++++++-------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 284f7fbe87d..0776e8ca4d1 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -35,15 +35,19 @@ class WorkspaceServiceHandler( } private fun didCreateFiles(events: List) { - AmazonQLspService.executeIfRunning(project) { - if (events.isNotEmpty()) { - it.workspaceService.didCreateFiles( + AmazonQLspService.executeIfRunning(project) { languageServer -> + val validFiles = events.mapNotNull { event -> + event.file?.toNioPath()?.toUri()?.toString()?.takeIf { it.isNotEmpty() }?.let { uri -> + FileCreate().apply { + this.uri = uri + } + } + } + + if (validFiles.isNotEmpty()) { + languageServer.workspaceService.didCreateFiles( CreateFilesParams().apply { - files = events.map { event -> - FileCreate().apply { - uri = event.file?.toNioPath()?.toUri().toString() - } - } + files = validFiles } ) } @@ -52,15 +56,45 @@ class WorkspaceServiceHandler( private fun didDeleteFiles(events: List) { AmazonQLspService.executeIfRunning(project) { languageServer -> - if (events.isNotEmpty()) { + val validFiles = events.mapNotNull { event -> + event.file?.toNioPath()?.toUri()?.toString()?.takeIf { it.isNotEmpty() }?.let { uri -> + FileDelete().apply { + this.uri = uri + } + } + } + + if (validFiles.isNotEmpty()) { languageServer.workspaceService.didDeleteFiles( DeleteFilesParams().apply { - files = events.map { event -> - FileDelete().apply { - uri = event.file?.toNioPath()?.toUri().toString() - } + files = validFiles + } + ) + } + } + } + + + private fun didChangeWatchedFiles(events: List) { + AmazonQLspService.executeIfRunning(project) { languageServer -> + val validChanges = events.mapNotNull { event -> + event.file?.toNioPath()?.toUri()?.toString()?.takeIf { it.isNotEmpty() }?.let { uri -> + FileEvent().apply { + this.uri = uri + type = when (event) { + is VFileCreateEvent -> FileChangeType.Created + is VFileDeleteEvent -> FileChangeType.Deleted + else -> FileChangeType.Changed } } + } + } + + if (validChanges.isNotEmpty()) { + languageServer.workspaceService.didChangeWatchedFiles( + DidChangeWatchedFilesParams().apply { + changes = validChanges + } ) } } @@ -74,25 +108,4 @@ class WorkspaceServiceHandler( didChangeWatchedFiles(events) } } - - private fun didChangeWatchedFiles(events: List) { - AmazonQLspService.executeIfRunning(project) { - if (events.isNotEmpty()) { - it.workspaceService.didChangeWatchedFiles( - DidChangeWatchedFilesParams().apply { - changes = events.map { event -> - FileEvent().apply { - uri = event.file?.toNioPath()?.toUri().toString() - type = when (event) { - is VFileCreateEvent -> FileChangeType.Created - is VFileDeleteEvent -> FileChangeType.Deleted - else -> FileChangeType.Changed - } - } - } - } - ) - } - } - } } From 70b7e533bc4aa40aa5a0f3ba65a16d1e04b37cc9 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Wed, 19 Feb 2025 22:29:57 -0800 Subject: [PATCH 12/18] didChangeWorkspaceFolders --- .../services/amazonq/lsp/AmazonQLspService.kt | 16 +- .../amazonq/lsp/util/WorkspaceFolderUtil.kt | 22 +++ .../lsp/workspace/WorkspaceServiceHandler.kt | 37 +++- .../lsp/util/WorkspaceFolderUtilTest.kt | 64 +++++++ .../workspace/WorkspaceServiceHandlerTest.kt | 170 ++++++++++++++++++ 5 files changed, 290 insertions(+), 19 deletions(-) create mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtil.kt create mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt create mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index bf21fa1086d..98db0594809 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -37,7 +37,6 @@ import org.eclipse.lsp4j.InitializedParams import org.eclipse.lsp4j.SynchronizationCapabilities import org.eclipse.lsp4j.TextDocumentClientCapabilities import org.eclipse.lsp4j.WorkspaceClientCapabilities -import org.eclipse.lsp4j.WorkspaceFolder import org.eclipse.lsp4j.jsonrpc.Launcher import org.eclipse.lsp4j.launch.LSPLauncher import org.slf4j.event.Level @@ -47,6 +46,7 @@ import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.isDeveloperMode import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata +import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata import java.io.IOException @@ -55,7 +55,6 @@ import java.io.PipedInputStream import java.io.PipedOutputStream import java.io.PrintWriter import java.io.StringWriter -import java.net.URI import java.nio.charset.StandardCharsets import java.util.concurrent.Future import kotlin.time.Duration.Companion.seconds @@ -217,17 +216,6 @@ private class AmazonQServerInstance(private val project: Project, private val cs } } - // needs case handling when project's base path is null: default projects/unit tests - private fun createWorkspaceFolders(): List = - project.basePath?.let { basePath -> - listOf( - WorkspaceFolder( - URI("file://$basePath").toString(), - project.name - ) - ) - }.orEmpty() // no folders to report or workspace not folder based - private fun createClientInfo(): ClientInfo { val metadata = ClientMetadata.getDefault() return ClientInfo().apply { @@ -241,7 +229,7 @@ private class AmazonQServerInstance(private val project: Project, private val cs processId = ProcessHandle.current().pid().toInt() capabilities = createClientCapabilities() clientInfo = createClientInfo() - workspaceFolders = createWorkspaceFolders() + workspaceFolders = createWorkspaceFolders(project) initializationOptions = createExtendedClientMetadata() } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtil.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtil.kt new file mode 100644 index 00000000000..9722ab8c85e --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtil.kt @@ -0,0 +1,22 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.util + +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import org.eclipse.lsp4j.WorkspaceFolder + +object WorkspaceFolderUtil { + fun createWorkspaceFolders(project: Project): List = + if (project.isDefault) { + emptyList() + } else { + ProjectRootManager.getInstance(project).contentRoots.map { contentRoot -> + WorkspaceFolder().apply { + name = contentRoot.name + this.uri = contentRoot.url + } + } + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 0776e8ca4d1..6f8d4ed9965 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -5,8 +5,8 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project -import com.intellij.openapi.project.ProjectManager -import com.intellij.openapi.project.ProjectManagerListener +import com.intellij.openapi.roots.ModuleRootEvent +import com.intellij.openapi.roots.ModuleRootListener import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent @@ -14,13 +14,16 @@ import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent import com.intellij.openapi.vfs.newvfs.events.VFileEvent import org.eclipse.lsp4j.* import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService +import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread class WorkspaceServiceHandler( private val project: Project, serverInstance: Disposable, ) : BulkFileListener, - ProjectManagerListener { + ModuleRootListener { + + private var lastSnapshot: List = emptyList() init { project.messageBus.connect(serverInstance).subscribe( @@ -29,7 +32,7 @@ class WorkspaceServiceHandler( ) project.messageBus.connect(serverInstance).subscribe( - ProjectManager.TOPIC, + ModuleRootListener.TOPIC, this ) } @@ -74,7 +77,6 @@ class WorkspaceServiceHandler( } } - private fun didChangeWatchedFiles(events: List) { AmazonQLspService.executeIfRunning(project) { languageServer -> val validChanges = events.mapNotNull { event -> @@ -108,4 +110,29 @@ class WorkspaceServiceHandler( didChangeWatchedFiles(events) } } + + override fun beforeRootsChange(event: ModuleRootEvent) { + lastSnapshot = createWorkspaceFolders(project) + } + + override fun rootsChanged(event: ModuleRootEvent) { + AmazonQLspService.executeIfRunning(project) { languageServer -> + val currentSnapshot = createWorkspaceFolders(project) + val addedFolders = currentSnapshot.filter { folder -> lastSnapshot.none { it.uri == folder.uri } } + val removedFolders = lastSnapshot.filter { folder -> currentSnapshot.none { it.uri == folder.uri } } + + if (addedFolders.isNotEmpty() || removedFolders.isNotEmpty()) { + languageServer.workspaceService.didChangeWorkspaceFolders( + DidChangeWorkspaceFoldersParams().apply { + this.event = WorkspaceFoldersChangeEvent().apply { + added = addedFolders + removed = removedFolders + } + } + ) + } + + lastSnapshot = currentSnapshot + } + } } diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt new file mode 100644 index 00000000000..022ce73d731 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt @@ -0,0 +1,64 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.util + +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.vfs.VirtualFile +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Test + +class WorkspaceFolderUtilTest { + + @Test + fun `createWorkspaceFolders returns empty list when no workspace folders`() { + val mockProject = mockk() + every { mockProject.isDefault } returns true + + val result = WorkspaceFolderUtil.createWorkspaceFolders(mockProject) + + assertEquals(emptyList(), result) + } + + @Test + fun `createWorkspaceFolders returns workspace folders for non-default project`() { + val mockProject = mockk() + val mockProjectRootManager = mockk() + val mockContentRoot1 = mockk() + val mockContentRoot2 = mockk() + + every { mockProject.isDefault } returns false + every { ProjectRootManager.getInstance(mockProject) } returns mockProjectRootManager + every { mockProjectRootManager.contentRoots } returns arrayOf(mockContentRoot1, mockContentRoot2) + + every { mockContentRoot1.name } returns "root1" + every { mockContentRoot1.url } returns "file:///path/to/root1" + every { mockContentRoot2.name } returns "root2" + every { mockContentRoot2.url } returns "file:///path/to/root2" + + val result = WorkspaceFolderUtil.createWorkspaceFolders(mockProject) + + assertEquals(2, result.size) + assertEquals("file:///path/to/root1", result[0].uri) + assertEquals("file:///path/to/root2", result[1].uri) + assertEquals("root1", result[0].name) + assertEquals("root2", result[1].name) + } + + @Test + fun `reateWorkspaceFolders returns empty list when project has no content roots`() { + val mockProject = mockk() + val mockProjectRootManager = mockk() + + every { mockProject.isDefault } returns false + every { ProjectRootManager.getInstance(mockProject) } returns mockProjectRootManager + every { mockProjectRootManager.contentRoots } returns emptyArray() + + val result = WorkspaceFolderUtil.createWorkspaceFolders(mockProject) + + assertEquals(emptyList(), result) + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt new file mode 100644 index 00000000000..115715a5cdd --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt @@ -0,0 +1,170 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.Application +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.VFileCreateEvent +import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent +import com.intellij.openapi.vfs.newvfs.events.VFileEvent +import com.intellij.util.messages.MessageBus +import com.intellij.util.messages.MessageBusConnection +import io.mockk.coEvery +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.runs +import io.mockk.slot +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.eclipse.lsp4j.CreateFilesParams +import org.eclipse.lsp4j.DeleteFilesParams +import org.eclipse.lsp4j.DidChangeWatchedFilesParams +import org.eclipse.lsp4j.FileChangeType +import org.eclipse.lsp4j.services.WorkspaceService +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService +import java.net.URI +import java.nio.file.Path +import java.util.concurrent.Callable +import java.util.concurrent.CompletableFuture + +class WorkspaceServiceHandlerTest { + private lateinit var project: Project + private lateinit var mockLanguageServer: AmazonQLanguageServer + private lateinit var mockWorkspaceService: WorkspaceService + private lateinit var sut: WorkspaceServiceHandler + private lateinit var mockApplication: Application + + @Before + fun setup() { + project = mockk() + mockWorkspaceService = mockk() + mockLanguageServer = mockk() + + mockApplication = mockk() + mockkStatic(ApplicationManager::class) + every { ApplicationManager.getApplication() } returns mockApplication + every { mockApplication.executeOnPooledThread(any>()) } answers { + CompletableFuture.completedFuture(firstArg>().call()) + } + + // Mock the LSP service + val mockLspService = mockk() + + // Mock the service methods on Project + every { project.getService(AmazonQLspService::class.java) } returns mockLspService + every { project.serviceIfCreated() } returns mockLspService + + // Mock the LSP service's executeSync method as a suspend function + coEvery { + mockLspService.executeSync(any()) + } coAnswers { + val func = firstArg Unit>() + func.invoke(mockLanguageServer) + } + + // Mock workspace service + every { mockLanguageServer.workspaceService } returns mockWorkspaceService + every { mockWorkspaceService.didCreateFiles(any()) } returns Unit + every { mockWorkspaceService.didDeleteFiles(any()) } returns Unit + every { mockWorkspaceService.didChangeWatchedFiles(any()) } returns Unit + + // Mock message bus + val messageBus = mockk() + every { project.messageBus } returns messageBus + val mockConnection = mockk() + every { messageBus.connect(any()) } returns mockConnection + every { mockConnection.subscribe(any(), any()) } just runs + + sut = WorkspaceServiceHandler(project, mockk()) + } + + @Test + fun `test didCreateFiles with valid events`() = runTest { + val uri = URI("file:///test/path") + val event = createMockVFileEvent(uri, FileChangeType.Created) + + sut.after(listOf(event)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) } + assertEquals(uri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didDeleteFiles with valid event`() = runTest { + val uri = URI("file:///test/path") + val deleteEvent = createMockVFileEvent(uri, FileChangeType.Deleted) + + // Act + sut.after(listOf(deleteEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didDeleteFiles(capture(paramsSlot)) } + assertEquals(uri.toString(), paramsSlot.captured.files[0].uri) + + // Assert + } + + @Test + fun `test didChangeWatchedFiles with valid events`() = runTest { + // Arrange + val createURI = URI("file:///test/pathOfCreation") + val deleteURI = URI("file:///test/pathOfDeletion") + val changeURI = URI("file:///test/pathOfChange") + + val virtualFileCreate = createMockVFileEvent(createURI, FileChangeType.Created) + val virtualFileDelete = createMockVFileEvent(deleteURI, FileChangeType.Deleted) + val virtualFileChange = createMockVFileEvent(changeURI) + + // Act + sut.after(listOf(virtualFileCreate, virtualFileDelete, virtualFileChange)) + + // Assert + val paramsSlot = slot() + verify { mockWorkspaceService.didChangeWatchedFiles(capture(paramsSlot)) } + assertEquals(createURI.toString(), paramsSlot.captured.changes[0].uri) + assertEquals(FileChangeType.Created, paramsSlot.captured.changes[0].type) + assertEquals(deleteURI.toString(), paramsSlot.captured.changes[1].uri) + assertEquals(FileChangeType.Deleted, paramsSlot.captured.changes[1].type) + assertEquals(changeURI.toString(), paramsSlot.captured.changes[2].uri) + assertEquals(FileChangeType.Changed, paramsSlot.captured.changes[2].type) + } + + @Test + fun `test no invoked messages when events are empty`() = runTest { + // Act + sut.after(emptyList()) + + // Assert + verify(exactly = 0) { mockWorkspaceService.didCreateFiles(any()) } + verify(exactly = 0) { mockWorkspaceService.didDeleteFiles(any()) } + verify(exactly = 0) { mockWorkspaceService.didChangeWatchedFiles(any()) } + } + + private fun createMockVFileEvent(uri: URI, type: FileChangeType = FileChangeType.Changed): VFileEvent { + val virtualFile = mockk() + val nioPath = mockk() + + every { virtualFile.toNioPath() } returns nioPath + every { nioPath.toUri() } returns uri + + return when (type) { + FileChangeType.Deleted -> mockk() + FileChangeType.Created -> mockk() + else -> mockk() + }.apply { + every { file } returns virtualFile + } + } +} From d139dc478f473bce969d54b15b73ddd9a51f9cb9 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Mon, 24 Feb 2025 12:52:35 -0800 Subject: [PATCH 13/18] detekt --- .../amazonq/lsp/workspace/WorkspaceServiceHandler.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 6f8d4ed9965..e7e17ece409 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -12,7 +12,16 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener 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 org.eclipse.lsp4j.* +import org.eclipse.lsp4j.CreateFilesParams +import org.eclipse.lsp4j.DeleteFilesParams +import org.eclipse.lsp4j.DidChangeWatchedFilesParams +import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams +import org.eclipse.lsp4j.FileChangeType +import org.eclipse.lsp4j.FileCreate +import org.eclipse.lsp4j.FileDelete +import org.eclipse.lsp4j.FileEvent +import org.eclipse.lsp4j.WorkspaceFolder +import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread From 5458f0c50461c4932945dcce1506bde12d849843 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Mon, 24 Feb 2025 14:31:31 -0800 Subject: [PATCH 14/18] add tests for changeWorkspaceFolders --- .../workspace/WorkspaceServiceHandlerTest.kt | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt index 115715a5cdd..332ad7b3b6d 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt @@ -18,6 +18,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.just import io.mockk.mockk +import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.runs import io.mockk.slot @@ -26,13 +27,16 @@ import kotlinx.coroutines.test.runTest import org.eclipse.lsp4j.CreateFilesParams import org.eclipse.lsp4j.DeleteFilesParams import org.eclipse.lsp4j.DidChangeWatchedFilesParams +import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams import org.eclipse.lsp4j.FileChangeType +import org.eclipse.lsp4j.WorkspaceFolder import org.eclipse.lsp4j.services.WorkspaceService import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService +import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil import java.net.URI import java.nio.file.Path import java.util.concurrent.Callable @@ -78,6 +82,7 @@ class WorkspaceServiceHandlerTest { every { mockWorkspaceService.didCreateFiles(any()) } returns Unit every { mockWorkspaceService.didDeleteFiles(any()) } returns Unit every { mockWorkspaceService.didChangeWatchedFiles(any()) } returns Unit + every { mockWorkspaceService.didChangeWorkspaceFolders(any()) } returns Unit // Mock message bus val messageBus = mockk() @@ -152,6 +157,162 @@ class WorkspaceServiceHandlerTest { verify(exactly = 0) { mockWorkspaceService.didChangeWatchedFiles(any()) } } + @Test + fun `rootsChanged does not notify when no changes`() = runTest { + // Arrange + mockkObject(WorkspaceFolderUtil) + val folders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + } + ) + every { WorkspaceFolderUtil.createWorkspaceFolders(any()) } returns folders + + // Act + sut.beforeRootsChange(mockk()) + sut.rootsChanged(mockk()) + + // Assert + verify(exactly = 0) { mockWorkspaceService.didChangeWorkspaceFolders(any()) } + } + + // rootsChanged handles + @Test + fun `rootsChanged handles init`() = runTest { + // Arrange + mockkObject(WorkspaceFolderUtil) + val oldFolders = emptyList() + val newFolders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + } + ) + + // Act + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns oldFolders + sut.beforeRootsChange(mockk()) + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns newFolders + sut.rootsChanged(mockk()) + + // Assert + val paramsSlot = slot() + verify(exactly = 1) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) } + assertEquals(1, paramsSlot.captured.event.added.size) + assertEquals("folder1", paramsSlot.captured.event.added[0].name) + } + + // rootsChanged handles additional files added to root + @Test + fun `rootsChanged handles additional files added to root`() = runTest { + // Arrange + mockkObject(WorkspaceFolderUtil) + val oldFolders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + } + ) + val newFolders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + }, + WorkspaceFolder().apply { + name = "folder2" + uri = "file:///path/to/folder2" + } + ) + + // Act + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns oldFolders + sut.beforeRootsChange(mockk()) + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns newFolders + sut.rootsChanged(mockk()) + + // Assert + val paramsSlot = slot() + verify(exactly = 1) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) } + assertEquals(1, paramsSlot.captured.event.added.size) + assertEquals("folder2", paramsSlot.captured.event.added[0].name) + } + + // rootsChanged handles removal of files from root + @Test + fun `rootsChanged handles removal of files from root`() = runTest { + // Arrange + mockkObject(WorkspaceFolderUtil) + val oldFolders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + }, + WorkspaceFolder().apply { + name = "folder2" + uri = "file:///path/to/folder2" + } + ) + val newFolders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + } + ) + + // Act + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns oldFolders + sut.beforeRootsChange(mockk()) + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns newFolders + sut.rootsChanged(mockk()) + + // Assert + val paramsSlot = slot() + verify(exactly = 1) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) } + assertEquals(1, paramsSlot.captured.event.removed.size) + assertEquals("folder2", paramsSlot.captured.event.removed[0].name) + } + + @Test + fun `rootsChanged handles multiple simultaneous additions and removals`() = runTest { + // Arrange + mockkObject(WorkspaceFolderUtil) + val oldFolders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + }, + WorkspaceFolder().apply { + name = "folder2" + uri = "file:///path/to/folder2" + } + ) + val newFolders = listOf( + WorkspaceFolder().apply { + name = "folder1" + uri = "file:///path/to/folder1" + }, + WorkspaceFolder().apply { + name = "folder3" + uri = "file:///path/to/folder3" + } + ) + + // Act + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns oldFolders + sut.beforeRootsChange(mockk()) + every { WorkspaceFolderUtil.createWorkspaceFolders(project) } returns newFolders + sut.rootsChanged(mockk()) + + // Assert + val paramsSlot = slot() + verify(exactly = 1) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) } + assertEquals(1, paramsSlot.captured.event.added.size) + assertEquals(1, paramsSlot.captured.event.removed.size) + assertEquals("folder3", paramsSlot.captured.event.added[0].name) + assertEquals("folder2", paramsSlot.captured.event.removed[0].name) + } + private fun createMockVFileEvent(uri: URI, type: FileChangeType = FileChangeType.Changed): VFileEvent { val virtualFile = mockk() val nioPath = mockk() From 401826d837d55d2dcb71fef5de3cade78c99cc10 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Thu, 27 Feb 2025 20:03:22 -0800 Subject: [PATCH 15/18] fix test --- .../amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt index 332ad7b3b6d..aea0962b780 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt @@ -30,6 +30,7 @@ import org.eclipse.lsp4j.DidChangeWatchedFilesParams import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams import org.eclipse.lsp4j.FileChangeType import org.eclipse.lsp4j.WorkspaceFolder +import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage import org.eclipse.lsp4j.services.WorkspaceService import org.junit.Assert.assertEquals import org.junit.Before @@ -70,10 +71,10 @@ class WorkspaceServiceHandlerTest { every { project.serviceIfCreated() } returns mockLspService // Mock the LSP service's executeSync method as a suspend function - coEvery { - mockLspService.executeSync(any()) + every { + mockLspService.executeSync>(any()) } coAnswers { - val func = firstArg Unit>() + val func = firstArg CompletableFuture>() func.invoke(mockLanguageServer) } From 780395e16e37337fdf3ee4ffe8d11ced85cdb90a Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Fri, 28 Feb 2025 09:17:38 -0800 Subject: [PATCH 16/18] glob pattern matching --- .../lsp/workspace/WorkspaceServiceHandler.kt | 21 ++- .../workspace/WorkspaceServiceHandlerTest.kt | 148 ++++++++++++++++-- 2 files changed, 150 insertions(+), 19 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index e7e17ece409..91f96db9a2f 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -7,6 +7,7 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootEvent import com.intellij.openapi.roots.ModuleRootListener +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.VFileCreateEvent @@ -25,6 +26,8 @@ import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread +import java.nio.file.FileSystems +import java.nio.file.Paths class WorkspaceServiceHandler( private val project: Project, @@ -33,6 +36,9 @@ class WorkspaceServiceHandler( ModuleRootListener { private var lastSnapshot: List = emptyList() + private val supportedFilePatterns = FileSystems.getDefault().getPathMatcher( + "glob:**/*.{ts,js,py,java}" + ) init { project.messageBus.connect(serverInstance).subscribe( @@ -49,7 +55,8 @@ class WorkspaceServiceHandler( private fun didCreateFiles(events: List) { AmazonQLspService.executeIfRunning(project) { languageServer -> val validFiles = events.mapNotNull { event -> - event.file?.toNioPath()?.toUri()?.toString()?.takeIf { it.isNotEmpty() }?.let { uri -> + val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null + file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri -> FileCreate().apply { this.uri = uri } @@ -69,7 +76,8 @@ class WorkspaceServiceHandler( private fun didDeleteFiles(events: List) { AmazonQLspService.executeIfRunning(project) { languageServer -> val validFiles = events.mapNotNull { event -> - event.file?.toNioPath()?.toUri()?.toString()?.takeIf { it.isNotEmpty() }?.let { uri -> + val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null + file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri -> FileDelete().apply { this.uri = uri } @@ -144,4 +152,13 @@ class WorkspaceServiceHandler( lastSnapshot = currentSnapshot } } + + private fun shouldHandleFile(file: VirtualFile): Boolean { + if (file.isDirectory) { + return true // Matches "**/*" with matches: "folder" + } + val path = Paths.get(file.path) + val result = supportedFilePatterns.matches(path) + return result + } } diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt index aea0962b780..88fcc258233 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt @@ -14,7 +14,6 @@ import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.util.messages.MessageBus import com.intellij.util.messages.MessageBusConnection -import io.mockk.coEvery import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -96,30 +95,143 @@ class WorkspaceServiceHandlerTest { } @Test - fun `test didCreateFiles with valid events`() = runTest { - val uri = URI("file:///test/path") - val event = createMockVFileEvent(uri, FileChangeType.Created) + fun `test didCreateFiles with Python file`() = runTest { + val pyUri = URI("file:///test/path") + val pyEvent = createMockVFileEvent(pyUri, FileChangeType.Created, false, "py") - sut.after(listOf(event)) + sut.after(listOf(pyEvent)) val paramsSlot = slot() verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) } - assertEquals(uri.toString(), paramsSlot.captured.files[0].uri) + assertEquals(pyUri.toString(), paramsSlot.captured.files[0].uri) } @Test - fun `test didDeleteFiles with valid event`() = runTest { - val uri = URI("file:///test/path") - val deleteEvent = createMockVFileEvent(uri, FileChangeType.Deleted) + fun `test didCreateFiles with TypeScript file`() = runTest { + val tsUri = URI("file:///test/path") + val tsEvent = createMockVFileEvent(tsUri, FileChangeType.Created, false, "ts") - // Act - sut.after(listOf(deleteEvent)) + sut.after(listOf(tsEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) } + assertEquals(tsUri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didCreateFiles with JavaScript file`() = runTest { + val jsUri = URI("file:///test/path") + val jsEvent = createMockVFileEvent(jsUri, FileChangeType.Created, false, "js") + + sut.after(listOf(jsEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) } + assertEquals(jsUri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didCreateFiles with Java file`() = runTest { + val javaUri = URI("file:///test/path") + val javaEvent = createMockVFileEvent(javaUri, FileChangeType.Created, false, "java") + + sut.after(listOf(javaEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) } + assertEquals(javaUri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didCreateFiles called for directory`() = runTest { + val dirUri = URI("file:///test/directory/path") + val dirEvent = createMockVFileEvent(dirUri, FileChangeType.Created, true, "") + + sut.after(listOf(dirEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didCreateFiles(capture(paramsSlot)) } + assertEquals(dirUri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didCreateFiles not called for unsupported file extension`() = runTest { + val txtUri = URI("file:///test/path") + val txtEvent = createMockVFileEvent(txtUri, FileChangeType.Created, false, "txt") + + sut.after(listOf(txtEvent)) + + verify(exactly = 0) { mockWorkspaceService.didCreateFiles(any()) } + } + + @Test + fun `test didDeleteFiles with Python file`() = runTest { + val pyUri = URI("file:///test/path") + val pyEvent = createMockVFileEvent(pyUri, FileChangeType.Deleted, false, "py") + + sut.after(listOf(pyEvent)) val paramsSlot = slot() verify { mockWorkspaceService.didDeleteFiles(capture(paramsSlot)) } - assertEquals(uri.toString(), paramsSlot.captured.files[0].uri) + assertEquals(pyUri.toString(), paramsSlot.captured.files[0].uri) + } - // Assert + @Test + fun `test didDeleteFiles with TypeScript file`() = runTest { + val tsUri = URI("file:///test/path") + val tsEvent = createMockVFileEvent(tsUri, FileChangeType.Deleted, false, "ts") + + sut.after(listOf(tsEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didDeleteFiles(capture(paramsSlot)) } + assertEquals(tsUri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didDeleteFiles with JavaScript file`() = runTest { + val jsUri = URI("file:///test/path") + val jsEvent = createMockVFileEvent(jsUri, FileChangeType.Deleted, false, "js") + + sut.after(listOf(jsEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didDeleteFiles(capture(paramsSlot)) } + assertEquals(jsUri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didDeleteFiles with Java file`() = runTest { + val javaUri = URI("file:///test/path") + val javaEvent = createMockVFileEvent(javaUri, FileChangeType.Deleted, false, "java") + + sut.after(listOf(javaEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didDeleteFiles(capture(paramsSlot)) } + assertEquals(javaUri.toString(), paramsSlot.captured.files[0].uri) + } + + @Test + fun `test didDeleteFiles not called for unsupported file extension`() = runTest { + val txtUri = URI("file:///test/path") + val txtEvent = createMockVFileEvent(txtUri, FileChangeType.Deleted, false, "txt") + + sut.after(listOf(txtEvent)) + + verify(exactly = 0) { mockWorkspaceService.didDeleteFiles(any()) } + } + + @Test + fun `test didDeleteFiles called for directory`() = runTest { + val dirUri = URI("file:///test/directory/path") + val dirEvent = createMockVFileEvent(dirUri, FileChangeType.Deleted, true, "") + + sut.after(listOf(dirEvent)) + + val paramsSlot = slot() + verify { mockWorkspaceService.didDeleteFiles(capture(paramsSlot)) } + assertEquals(dirUri.toString(), paramsSlot.captured.files[0].uri) } @Test @@ -129,9 +241,9 @@ class WorkspaceServiceHandlerTest { val deleteURI = URI("file:///test/pathOfDeletion") val changeURI = URI("file:///test/pathOfChange") - val virtualFileCreate = createMockVFileEvent(createURI, FileChangeType.Created) - val virtualFileDelete = createMockVFileEvent(deleteURI, FileChangeType.Deleted) - val virtualFileChange = createMockVFileEvent(changeURI) + val virtualFileCreate = createMockVFileEvent(createURI, FileChangeType.Created, false) + val virtualFileDelete = createMockVFileEvent(deleteURI, FileChangeType.Deleted, false) + val virtualFileChange = createMockVFileEvent(changeURI, FileChangeType.Changed, false) // Act sut.after(listOf(virtualFileCreate, virtualFileDelete, virtualFileChange)) @@ -314,12 +426,14 @@ class WorkspaceServiceHandlerTest { assertEquals("folder2", paramsSlot.captured.event.removed[0].name) } - private fun createMockVFileEvent(uri: URI, type: FileChangeType = FileChangeType.Changed): VFileEvent { + private fun createMockVFileEvent(uri: URI, type: FileChangeType = FileChangeType.Changed, isDirectory: Boolean, extension: String = "py"): VFileEvent { val virtualFile = mockk() val nioPath = mockk() + every { virtualFile.isDirectory } returns isDirectory every { virtualFile.toNioPath() } returns nioPath every { nioPath.toUri() } returns uri + every { virtualFile.path } returns "${uri.path}.$extension" return when (type) { FileChangeType.Deleted -> mockk() From de16193862909f3fd97a5f2516af9f86c14f0148 Mon Sep 17 00:00:00 2001 From: Sam Stewart Date: Fri, 28 Feb 2025 16:12:04 -0800 Subject: [PATCH 17/18] add didRename --- .../services/amazonq/lsp/AmazonQLspService.kt | 1 + .../lsp/workspace/WorkspaceServiceHandler.kt | 34 +++++ .../lsp/util/WorkspaceFolderUtilTest.kt | 4 +- .../workspace/WorkspaceServiceHandlerTest.kt | 127 +++++++++++++++++- 4 files changed, 160 insertions(+), 6 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 7296bb9d9d1..0d774e5069a 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -211,6 +211,7 @@ private class AmazonQServerInstance(private val project: Project, private val cs fileOperations = FileOperationsWorkspaceCapabilities().apply { didCreate = true didDelete = true + didRename = true } } } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 91f96db9a2f..2ecd8469f63 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -13,6 +13,7 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener 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.VFilePropertyChangeEvent import org.eclipse.lsp4j.CreateFilesParams import org.eclipse.lsp4j.DeleteFilesParams import org.eclipse.lsp4j.DidChangeWatchedFilesParams @@ -21,6 +22,8 @@ import org.eclipse.lsp4j.FileChangeType import org.eclipse.lsp4j.FileCreate import org.eclipse.lsp4j.FileDelete import org.eclipse.lsp4j.FileEvent +import org.eclipse.lsp4j.FileRename +import org.eclipse.lsp4j.RenameFilesParams import org.eclipse.lsp4j.WorkspaceFolder import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService @@ -94,6 +97,36 @@ class WorkspaceServiceHandler( } } + private fun didRenameFiles(events: List) { + AmazonQLspService.executeIfRunning(project) { languageServer -> + val validRenames = events + .filter { it.propertyName == VirtualFile.PROP_NAME } + .mapNotNull { event -> + val file = event.file.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null + val oldName = event.oldValue as? String ?: return@mapNotNull null + val newName = event.newValue as? String ?: return@mapNotNull null + + // Construct old and new URIs + val parentPath = file.parent?.toNioPath() ?: return@mapNotNull null + val oldUri = parentPath.resolve(oldName).toUri().toString() + val newUri = file.toNioPath().toUri().toString() + + FileRename().apply { + this.oldUri = oldUri + this.newUri = newUri + } + } + + if (validRenames.isNotEmpty()) { + languageServer.workspaceService.didRenameFiles( + RenameFilesParams().apply { + files = validRenames + } + ) + } + } + } + private fun didChangeWatchedFiles(events: List) { AmazonQLspService.executeIfRunning(project) { languageServer -> val validChanges = events.mapNotNull { event -> @@ -124,6 +157,7 @@ class WorkspaceServiceHandler( pluginAwareExecuteOnPooledThread { didCreateFiles(events.filterIsInstance()) didDeleteFiles(events.filterIsInstance()) + didRenameFiles(events.filterIsInstance()) didChangeWatchedFiles(events) } } diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt index 022ce73d731..962d55c955e 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/util/WorkspaceFolderUtilTest.kt @@ -8,8 +8,8 @@ import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.vfs.VirtualFile import io.mockk.every import io.mockk.mockk -import org.junit.Assert.assertEquals -import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test class WorkspaceFolderUtilTest { diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt index 88fcc258233..0b8db471a09 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.vfs.VirtualFile 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.VFilePropertyChangeEvent import com.intellij.util.messages.MessageBus import com.intellij.util.messages.MessageBusConnection import io.mockk.every @@ -28,12 +29,13 @@ import org.eclipse.lsp4j.DeleteFilesParams import org.eclipse.lsp4j.DidChangeWatchedFilesParams import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams import org.eclipse.lsp4j.FileChangeType +import org.eclipse.lsp4j.RenameFilesParams import org.eclipse.lsp4j.WorkspaceFolder import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage import org.eclipse.lsp4j.services.WorkspaceService -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil @@ -49,7 +51,7 @@ class WorkspaceServiceHandlerTest { private lateinit var sut: WorkspaceServiceHandler private lateinit var mockApplication: Application - @Before + @BeforeEach fun setup() { project = mockk() mockWorkspaceService = mockk() @@ -81,6 +83,7 @@ class WorkspaceServiceHandlerTest { every { mockLanguageServer.workspaceService } returns mockWorkspaceService every { mockWorkspaceService.didCreateFiles(any()) } returns Unit every { mockWorkspaceService.didDeleteFiles(any()) } returns Unit + every { mockWorkspaceService.didRenameFiles(any()) } returns Unit every { mockWorkspaceService.didChangeWatchedFiles(any()) } returns Unit every { mockWorkspaceService.didChangeWorkspaceFolders(any()) } returns Unit @@ -270,6 +273,91 @@ class WorkspaceServiceHandlerTest { verify(exactly = 0) { mockWorkspaceService.didChangeWatchedFiles(any()) } } + @Test + fun `test didRenameFiles with supported file`() = runTest { + // Arrange + val oldName = "oldFile.java" + val newName = "newFile.java" + val propertyEvent = createMockPropertyChangeEvent( + oldName = oldName, + newName = newName, + isDirectory = false, + extension = "java" + ) + + // Act + sut.after(listOf(propertyEvent)) + + // Assert + val paramsSlot = slot() + verify { mockWorkspaceService.didRenameFiles(capture(paramsSlot)) } + with(paramsSlot.captured.files[0]) { + assertEquals("file:///test/$oldName", oldUri) + assertEquals("file:///test/$newName", newUri) + } + } + + @Test + fun `test didRenameFiles with unsupported file type`() = runTest { + // Arrange + val propertyEvent = createMockPropertyChangeEvent( + oldName = "oldFile.txt", + newName = "newFile.txt", + isDirectory = false, + extension = "txt" + ) + + // Act + sut.after(listOf(propertyEvent)) + + // Assert + verify(exactly = 0) { mockWorkspaceService.didRenameFiles(any()) } + } + + @Test + fun `test didRenameFiles with directory`() = runTest { + // Arrange + val propertyEvent = createMockPropertyChangeEvent( + oldName = "oldDir", + newName = "newDir", + isDirectory = true + ) + + // Act + sut.after(listOf(propertyEvent)) + + // Assert + val paramsSlot = slot() + verify { mockWorkspaceService.didRenameFiles(capture(paramsSlot)) } + with(paramsSlot.captured.files[0]) { + assertEquals("file:///test/oldDir", oldUri) + assertEquals("file:///test/newDir", newUri) + } + } + + @Test + fun `test didRenameFiles with multiple files`() = runTest { + // Arrange + val event1 = createMockPropertyChangeEvent( + oldName = "old1.java", + newName = "new1.java", + extension = "java" + ) + val event2 = createMockPropertyChangeEvent( + oldName = "old2.py", + newName = "new2.py", + extension = "py" + ) + + // Act + sut.after(listOf(event1, event2)) + + // Assert + val paramsSlot = slot() + verify { mockWorkspaceService.didRenameFiles(capture(paramsSlot)) } + assertEquals(2, paramsSlot.captured.files.size) + } + @Test fun `rootsChanged does not notify when no changes`() = runTest { // Arrange @@ -443,4 +531,35 @@ class WorkspaceServiceHandlerTest { every { file } returns virtualFile } } + + // for didRename events + private fun createMockPropertyChangeEvent( + oldName: String, + newName: String, + isDirectory: Boolean = false, + extension: String = "java", + ): VFilePropertyChangeEvent { + val file = mockk() + val parent = mockk() + val parentPath = mockk() + val filePath = mockk() + + every { file.parent } returns parent + every { parent.toNioPath() } returns parentPath + every { file.toNioPath() } returns filePath + every { file.isDirectory } returns isDirectory + every { file.path } returns "/test/$newName" + + every { parentPath.resolve(oldName) } returns mockk { + every { toUri() } returns URI("file:///test/$oldName") + } + every { filePath.toUri() } returns URI("file:///test/$newName") + + return mockk().apply { + every { propertyName } returns VirtualFile.PROP_NAME + every { this@apply.file } returns file + every { oldValue } returns oldName + every { newValue } returns newName + } + } } From 62adbc134cb4b6b0cd264a9447c6077790ed6817 Mon Sep 17 00:00:00 2001 From: samgst-amazon Date: Mon, 3 Mar 2025 11:42:09 -0800 Subject: [PATCH 18/18] detekt fixes --- .../amazonq/lsp/workspace/WorkspaceServiceHandler.kt | 2 +- .../amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt index 2ecd8469f63..30e1e08713c 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt @@ -104,7 +104,7 @@ class WorkspaceServiceHandler( .mapNotNull { event -> val file = event.file.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null val oldName = event.oldValue as? String ?: return@mapNotNull null - val newName = event.newValue as? String ?: return@mapNotNull null + if (event.newValue !is String) return@mapNotNull null // Construct old and new URIs val parentPath = file.parent?.toNioPath() ?: return@mapNotNull null diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt index 0b8db471a09..adb9e105f2c 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt @@ -282,7 +282,6 @@ class WorkspaceServiceHandlerTest { oldName = oldName, newName = newName, isDirectory = false, - extension = "java" ) // Act @@ -304,7 +303,6 @@ class WorkspaceServiceHandlerTest { oldName = "oldFile.txt", newName = "newFile.txt", isDirectory = false, - extension = "txt" ) // Act @@ -341,12 +339,10 @@ class WorkspaceServiceHandlerTest { val event1 = createMockPropertyChangeEvent( oldName = "old1.java", newName = "new1.java", - extension = "java" ) val event2 = createMockPropertyChangeEvent( oldName = "old2.py", newName = "new2.py", - extension = "py" ) // Act @@ -537,7 +533,6 @@ class WorkspaceServiceHandlerTest { oldName: String, newName: String, isDirectory: Boolean = false, - extension: String = "java", ): VFilePropertyChangeEvent { val file = mockk() val parent = mockk()