Skip to content

Commit ac81a2f

Browse files
feat(amazonq): Implement aws/syncModuleDependencies call (#5414)
* syncModuleDependencies call barebones * implement syncModuleDependencies logic * detekt * return completableFuture * ExtensionPoint impl * detekt * fix EachExtensionSafe * update ExtensionPoint namespace * private functions not needed * detekt * cs.launch for the ModuleDependencyServiceHandler * Tests * update xml * move xml impl to src root * feedback * change message to notification instead of Request
1 parent b179b37 commit ac81a2f

File tree

13 files changed

+446
-0
lines changed

13 files changed

+446
-0
lines changed

plugins/amazonq/shared/jetbrains-community/resources/META-INF/module-amazonq.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
<extensions defaultExtensionNs="com.intellij">
1010
<applicationService serviceImplementation="migration.software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager"/>
1111
</extensions>
12+
1213
</idea-plugin>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
88
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
99
import org.eclipse.lsp4j.services.LanguageServer
1010
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.UpdateCredentialsPayload
11+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams
1112
import java.util.concurrent.CompletableFuture
1213

1314
/**
1415
* Remote interface exposed by the Amazon Q language server
1516
*/
1617
@Suppress("unused")
1718
interface AmazonQLanguageServer : LanguageServer {
19+
@JsonNotification("aws/syncModuleDependencies")
20+
fun syncModuleDependencies(params: SyncModuleDependenciesParams): CompletableFuture<Unit>
21+
1822
@JsonRequest("aws/credentials/token/update")
1923
fun updateTokenCredentials(payload: UpdateCredentialsPayload): CompletableFuture<ResponseMessage>
2024

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope
2323
import kotlinx.coroutines.Deferred
2424
import kotlinx.coroutines.TimeoutCancellationException
2525
import kotlinx.coroutines.async
26+
import kotlinx.coroutines.launch
2627
import kotlinx.coroutines.runBlocking
2728
import kotlinx.coroutines.sync.Mutex
2829
import kotlinx.coroutines.sync.withLock
@@ -45,6 +46,7 @@ import software.aws.toolkits.core.utils.info
4546
import software.aws.toolkits.core.utils.warn
4647
import software.aws.toolkits.jetbrains.isDeveloperMode
4748
import software.aws.toolkits.jetbrains.services.amazonq.lsp.auth.DefaultAuthCredentialsService
49+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.DefaultModuleDependenciesService
4850
import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager
4951
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata
5052
import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDocumentServiceHandler
@@ -310,6 +312,9 @@ private class AmazonQServerInstance(private val project: Project, private val cs
310312
DefaultAuthCredentialsService(project, encryptionManager, this)
311313
TextDocumentServiceHandler(project, this)
312314
WorkspaceServiceHandler(project, this)
315+
cs.launch {
316+
DefaultModuleDependenciesService(project, this@AmazonQServerInstance)
317+
}
313318
}
314319

315320
override fun dispose() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies
5+
6+
import com.intellij.openapi.Disposable
7+
import com.intellij.openapi.module.ModuleManager
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.roots.ModuleRootEvent
10+
import com.intellij.openapi.roots.ModuleRootListener
11+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
12+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider.Companion.EP_NAME
13+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams
14+
import java.util.concurrent.CompletableFuture
15+
16+
class DefaultModuleDependenciesService(
17+
private val project: Project,
18+
serverInstance: Disposable,
19+
) : ModuleDependenciesService,
20+
ModuleRootListener {
21+
22+
init {
23+
project.messageBus.connect(serverInstance).subscribe(
24+
ModuleRootListener.TOPIC,
25+
this
26+
)
27+
// project initiation with initial list of dependencies
28+
syncAllModules()
29+
}
30+
31+
override fun rootsChanged(event: ModuleRootEvent) {
32+
if (event.isCausedByFileTypesChange) return
33+
// call on change with updated dependencies
34+
syncAllModules()
35+
}
36+
37+
override fun syncModuleDependencies(params: SyncModuleDependenciesParams): CompletableFuture<Unit> =
38+
AmazonQLspService.executeIfRunning(project) { languageServer ->
39+
languageServer.syncModuleDependencies(params)
40+
}?.toCompletableFuture() ?: CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))
41+
42+
private fun syncAllModules() {
43+
ModuleManager.getInstance(project).modules.forEach { module ->
44+
EP_NAME.forEachExtensionSafe {
45+
if (it.isApplicable(module)) {
46+
syncModuleDependencies(it.createParams(module))
47+
return@forEachExtensionSafe
48+
}
49+
}
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies
5+
6+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams
7+
import java.util.concurrent.CompletableFuture
8+
9+
interface ModuleDependenciesService {
10+
fun syncModuleDependencies(params: SyncModuleDependenciesParams): CompletableFuture<Unit>
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies
5+
6+
import com.intellij.openapi.extensions.ExtensionPointName
7+
import com.intellij.openapi.module.Module
8+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams
9+
10+
interface ModuleDependencyProvider {
11+
companion object {
12+
val EP_NAME = ExtensionPointName<ModuleDependencyProvider>("software.aws.toolkits.jetbrains.moduleDependencyProvider")
13+
}
14+
15+
fun isApplicable(module: Module): Boolean
16+
fun createParams(module: Module): SyncModuleDependenciesParams
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.providers
5+
6+
import com.intellij.openapi.module.Module
7+
import com.intellij.openapi.projectRoots.JavaSdkType
8+
import com.intellij.openapi.roots.ModuleRootManager
9+
import com.intellij.openapi.roots.OrderRootType
10+
import com.intellij.openapi.vfs.VfsUtil
11+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider
12+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams
13+
14+
internal class JavaModuleDependencyProvider : ModuleDependencyProvider {
15+
override fun isApplicable(module: Module): Boolean =
16+
ModuleRootManager.getInstance(module).sdk?.sdkType is JavaSdkType
17+
18+
override fun createParams(module: Module): SyncModuleDependenciesParams {
19+
val sourceRoots = getSourceRoots(module)
20+
val dependencies = mutableListOf<String>()
21+
22+
ModuleRootManager.getInstance(module).orderEntries().forEachLibrary { library ->
23+
library.getUrls(OrderRootType.CLASSES).forEach { url ->
24+
dependencies.add(VfsUtil.urlToPath(url))
25+
}
26+
true
27+
}
28+
29+
return SyncModuleDependenciesParams(
30+
moduleName = module.name,
31+
programmingLanguage = "Java",
32+
files = sourceRoots,
33+
dirs = dependencies,
34+
includePatterns = emptyList(),
35+
excludePatterns = emptyList()
36+
)
37+
}
38+
39+
private fun getSourceRoots(module: Module): List<String> =
40+
ModuleRootManager.getInstance(module).contentEntries
41+
.flatMap { contentEntry ->
42+
contentEntry.sourceFolders
43+
.filter { !it.isTestSource }
44+
.mapNotNull { it.file?.path }
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.providers
5+
6+
import com.intellij.openapi.module.Module
7+
import com.intellij.openapi.roots.ModuleRootManager
8+
import com.jetbrains.python.packaging.management.PythonPackageManager
9+
import com.jetbrains.python.sdk.PythonSdkUtil
10+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider
11+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams
12+
13+
internal class PythonModuleDependencyProvider : ModuleDependencyProvider {
14+
override fun isApplicable(module: Module): Boolean =
15+
PythonSdkUtil.findPythonSdk(module) != null
16+
17+
override fun createParams(module: Module): SyncModuleDependenciesParams {
18+
val sourceRoots = getSourceRoots(module)
19+
val dependencies = mutableListOf<String>()
20+
21+
PythonSdkUtil.findPythonSdk(module)?.let { sdk ->
22+
val packageManager = PythonPackageManager.forSdk(module.project, sdk)
23+
packageManager.installedPackages.forEach { pkg ->
24+
dependencies.add(pkg.name)
25+
}
26+
}
27+
28+
return SyncModuleDependenciesParams(
29+
moduleName = module.name,
30+
programmingLanguage = "Python",
31+
files = sourceRoots,
32+
dirs = dependencies,
33+
includePatterns = emptyList(),
34+
excludePatterns = emptyList()
35+
)
36+
}
37+
38+
private fun getSourceRoots(module: Module): List<String> =
39+
ModuleRootManager.getInstance(module).contentEntries
40+
.flatMap { contentEntry ->
41+
contentEntry.sourceFolders
42+
.filter { !it.isTestSource }
43+
.mapNotNull { it.file?.path }
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies
5+
6+
class SyncModuleDependenciesParams(
7+
val moduleName: String,
8+
val programmingLanguage: String,
9+
val files: List<String>,
10+
val dirs: List<String>,
11+
val includePatterns: List<String>,
12+
val excludePatterns: List<String>,
13+
)

0 commit comments

Comments
 (0)