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 9088678aa61..c4a57f2312a 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 @@ -73,6 +73,7 @@ import software.aws.toolkits.core.utils.writeText import software.aws.toolkits.jetbrains.core.coroutines.ioDispatcher import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.auth.DefaultAuthCredentialsService +import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.DefaultModuleDependenciesService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AmazonQLspTypeAdapterFactory import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AwsExtendedInitializeResult @@ -592,9 +593,9 @@ private class AmazonQServerInstance(private val project: Project, private val cs WorkspaceServiceHandler(project, cs, lspInitResult).also { Disposer.register(this, it) } - // DefaultModuleDependenciesService(project, cs).also { - // Disposer.register(this, it) - // } + DefaultModuleDependenciesService(project, cs).also { + Disposer.register(this, it) + } } } } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt index f4f31165d7a..d534345b8c4 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootEvent import com.intellij.openapi.roots.ModuleRootListener import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider.Companion.EP_NAME @@ -44,14 +45,34 @@ class DefaultModuleDependenciesService( } private fun syncAllModules() { + val paramsMap = mutableMapOf, DidChangeDependencyPathsParams>() + ModuleManager.getInstance(project).modules.forEach { module -> EP_NAME.forEachExtensionSafe { if (it.isApplicable(module)) { - didChangeDependencyPaths(it.createParams(module)) + val params = it.createParams(module) + val key = params.moduleName to params.runtimeLanguage + + paramsMap.merge(key, params) { existing, new -> + DidChangeDependencyPathsParams( + moduleName = existing.moduleName, + runtimeLanguage = existing.runtimeLanguage, + paths = (existing.paths + new.paths).distinct(), + includePatterns = (existing.includePatterns + new.includePatterns).distinct(), + excludePatterns = (existing.excludePatterns + new.excludePatterns).distinct() + ) + } return@forEachExtensionSafe } } } + + paramsMap.values.chunked(10).forEachIndexed { index, chunk -> + cs.launch { + delay(index * 1000L) + chunk.forEach { didChangeDependencyPaths(it) } + } + } } override fun dispose() { diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt index cce9cbe3e0d..f886b797911 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt @@ -187,6 +187,56 @@ class DefaultModuleDependenciesServiceTest { verify(exactly = 2) { mockLanguageServer.didChangeDependencyPaths(params) } } + @Test + fun `test deduplication of same moduleName and runtimeLanguage`() = runTest { + // Arrange + val module1 = mockk() + val module2 = mockk() + val params1 = DidChangeDependencyPathsParams( + moduleName = "sameModule", + runtimeLanguage = "java", + paths = listOf("/path/to/dep1.jar"), + includePatterns = listOf("*.java"), + excludePatterns = listOf("test/**") + ) + val params2 = DidChangeDependencyPathsParams( + moduleName = "sameModule", + runtimeLanguage = "java", + paths = listOf("/path/to/dep2.jar"), + includePatterns = listOf("*.class"), + excludePatterns = listOf("build/**") + ) + + every { mockModuleManager.modules } returns arrayOf(module1, module2) + every { mockDependencyProvider.isApplicable(any()) } returns true + every { mockDependencyProvider.createParams(module1) } returns params1 + every { mockDependencyProvider.createParams(module2) } returns params2 + + prepDependencyProvider( + listOf( + Pair(module1, params1), + Pair(module2, params2) + ) + ) + + sut = DefaultModuleDependenciesService(project, this) + + advanceUntilIdle() + + // Verify only one call with merged paths + verify(exactly = 1) { + mockLanguageServer.didChangeDependencyPaths( + match { + it.moduleName == "sameModule" && + it.runtimeLanguage == "java" && + it.paths.containsAll(listOf("/path/to/dep1.jar", "/path/to/dep2.jar")) && + it.includePatterns.containsAll(listOf("*.java", "*.class")) && + it.excludePatterns.containsAll(listOf("test/**", "build/**")) + } + ) + } + } + private fun prepDependencyProvider(moduleParamPairs: List>) { every { mockModuleManager.modules } returns moduleParamPairs.map { it.first }.toTypedArray()