Skip to content

Commit de6d12c

Browse files
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
2 parents 9ef15e7 + b6ae7b1 commit de6d12c

File tree

21 files changed

+610
-42
lines changed

21 files changed

+610
-42
lines changed

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ class CodeWhispererConfigurable(private val project: Project) :
7272

7373
textFieldWithBrowseButton(fileChooserDescriptor = fileChooserDescriptor)
7474
.bindText(
75-
{ LspSettings.getInstance().getArtifactPath() },
76-
{ LspSettings.getInstance().setArtifactPath(it.takeIf { v -> v.isNotBlank() }) }
75+
{ LspSettings.getInstance().getArtifactPath().orEmpty() },
76+
{ LspSettings.getInstance().setArtifactPath(it) }
7777
)
7878
.applyToComponent {
79-
emptyText.text = "Choose a file to upload"
79+
emptyText.text = message("executableCommon.auto_managed")
8080
}
8181
.resizableColumn()
8282
.align(Align.FILL)

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

+1
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

+4
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

+13-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import com.intellij.openapi.components.serviceIfCreated
1818
import com.intellij.openapi.project.Project
1919
import com.intellij.openapi.util.Disposer
2020
import com.intellij.openapi.util.Key
21+
import com.intellij.openapi.util.SystemInfo
2122
import com.intellij.util.io.await
2223
import kotlinx.coroutines.CoroutineScope
2324
import kotlinx.coroutines.Deferred
2425
import kotlinx.coroutines.TimeoutCancellationException
2526
import kotlinx.coroutines.async
27+
import kotlinx.coroutines.launch
2628
import kotlinx.coroutines.runBlocking
2729
import kotlinx.coroutines.sync.Mutex
2830
import kotlinx.coroutines.sync.withLock
@@ -44,13 +46,16 @@ import software.aws.toolkits.core.utils.getLogger
4446
import software.aws.toolkits.core.utils.info
4547
import software.aws.toolkits.core.utils.warn
4648
import software.aws.toolkits.jetbrains.isDeveloperMode
49+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager
4750
import software.aws.toolkits.jetbrains.services.amazonq.lsp.auth.DefaultAuthCredentialsService
51+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.DefaultModuleDependenciesService
4852
import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager
4953
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata
5054
import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDocumentServiceHandler
5155
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
5256
import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler
5357
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
58+
import software.aws.toolkits.jetbrains.settings.LspSettings
5459
import java.io.IOException
5560
import java.io.OutputStreamWriter
5661
import java.io.PipedInputStream
@@ -243,8 +248,12 @@ private class AmazonQServerInstance(private val project: Project, private val cs
243248
}
244249

245250
init {
251+
// will cause slow service init, but maybe fine for now. will not block UI since fetch/extract will be under background progress
252+
val artifact = runBlocking { ArtifactManager(project, manifestRange = null).fetchArtifact() }.toAbsolutePath()
253+
val node = if (SystemInfo.isWindows) "node.exe" else "node"
246254
val cmd = GeneralCommandLine(
247-
"amazon-q-lsp",
255+
artifact.resolve(node).toString(),
256+
LspSettings.getInstance().getArtifactPath() ?: artifact.resolve("aws-lsp-codewhisperer.js").toString(),
248257
"--stdio",
249258
"--set-credentials-encryption-key",
250259
)
@@ -310,6 +319,9 @@ private class AmazonQServerInstance(private val project: Project, private val cs
310319
DefaultAuthCredentialsService(project, encryptionManager, this)
311320
TextDocumentServiceHandler(project, this)
312321
WorkspaceServiceHandler(project, initializeResult, this)
322+
cs.launch {
323+
DefaultModuleDependenciesService(project, this@AmazonQServerInstance)
324+
}
313325
}
314326

315327
override fun dispose() {

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

+17-12
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ import com.intellij.openapi.util.SystemInfo
77
import com.intellij.openapi.util.text.StringUtil
88
import com.intellij.util.io.DigestUtil
99
import com.intellij.util.system.CpuArch
10+
import software.aws.toolkits.core.utils.ZIP_PROPERTY_POSIX
1011
import software.aws.toolkits.core.utils.createParentDirectories
1112
import software.aws.toolkits.core.utils.exists
13+
import software.aws.toolkits.core.utils.hasPosixFilePermissions
1214
import java.io.FileNotFoundException
13-
import java.io.FileOutputStream
15+
import java.net.URI
16+
import java.nio.file.FileSystems
1417
import java.nio.file.Files
1518
import java.nio.file.Path
1619
import java.nio.file.Paths
1720
import java.nio.file.StandardCopyOption
1821
import java.security.MessageDigest
19-
import java.util.zip.ZipFile
2022
import kotlin.io.path.isDirectory
2123
import kotlin.io.path.listDirectoryEntries
2224

@@ -78,17 +80,20 @@ fun extractZipFile(zipFilePath: Path, destDir: Path) {
7880
}
7981

8082
try {
81-
ZipFile(zipFilePath.toFile()).use { zipFile ->
82-
zipFile.entries()
83-
.asSequence()
84-
.filterNot { it.isDirectory }
85-
.map { zipEntry ->
86-
val destPath = destDir.resolve(zipEntry.name)
87-
destPath.createParentDirectories()
88-
FileOutputStream(destPath.toFile()).use { targetFile ->
89-
zipFile.getInputStream(zipEntry).copyTo(targetFile)
83+
FileSystems.newFileSystem(
84+
// jar prefix due to potentially ambiguous resolution to wrong fs impl for zipfs on windows
85+
URI("jar:${zipFilePath.toUri()}"),
86+
mapOf(ZIP_PROPERTY_POSIX to destDir.hasPosixFilePermissions())
87+
).use { zipfs ->
88+
Files.walk(zipfs.getPath("/")).use { paths ->
89+
paths
90+
.filter { !it.isDirectory() }
91+
.forEach { zipEntry ->
92+
val destPath = Paths.get(destDir.toString(), zipEntry.toString())
93+
destPath.createParentDirectories()
94+
Files.copy(zipEntry, destPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES)
9095
}
91-
}.toList()
96+
}
9297
}
9398
} catch (e: Exception) {
9499
throw LspException("Failed to extract zip file: ${e.message}", LspException.ErrorCode.UNZIP_FAILED, cause = e)
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+
)

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt

+3-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.intellij.openapi.components.Service
1010
import com.intellij.openapi.components.State
1111
import com.intellij.openapi.components.Storage
1212
import com.intellij.openapi.components.service
13-
import com.intellij.util.xmlb.annotations.Property
13+
import com.intellij.util.text.nullize
1414

1515
@Service
1616
@State(name = "lspSettings", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)])
@@ -26,11 +26,7 @@ class LspSettings : PersistentStateComponent<LspConfiguration> {
2626
fun getArtifactPath() = state.artifactPath
2727

2828
fun setArtifactPath(artifactPath: String?) {
29-
if (artifactPath == null) {
30-
state.artifactPath = ""
31-
} else {
32-
state.artifactPath = artifactPath
33-
}
29+
state.artifactPath = artifactPath.nullize(nullizeSpaces = true)
3430
}
3531

3632
companion object {
@@ -39,6 +35,5 @@ class LspSettings : PersistentStateComponent<LspConfiguration> {
3935
}
4036

4137
class LspConfiguration : BaseState() {
42-
@get:Property
43-
var artifactPath: String = ""
38+
var artifactPath by string()
4439
}

0 commit comments

Comments
 (0)