Skip to content

Commit d14bfe4

Browse files
Merge branch 'feature/q-lsp' into samgst/q-lsp-module-dependencies
2 parents e35e956 + b179b37 commit d14bfe4

File tree

17 files changed

+158
-46
lines changed

17 files changed

+158
-46
lines changed

.changes/3.58.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"date" : "2025-03-06",
3+
"version" : "3.58",
4+
"entries" : [ {
5+
"type" : "bugfix",
6+
"description" : "Amazon Q: Fix data isolation between tabs to prevent interference when using /doc in multiple tabs"
7+
}, {
8+
"type" : "removal",
9+
"description" : "The Amazon Q inline suggestion popup goes back to being under the suggestions and is always showing."
10+
} ]
11+
}

.changes/next-release/bugfix-79596dfc-37a0-44c8-adf2-a6c87ba806ac.json

-4
This file was deleted.

.changes/next-release/feature-0b164167-b04b-4a26-a59b-5ac41a717975.json

-4
This file was deleted.

.changes/next-release/removal-5b5b9cf3-701c-48b5-b232-96e726a9a860.json

-4
This file was deleted.

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# _3.58_ (2025-03-06)
2+
- **(Bug Fix)** Amazon Q: Fix data isolation between tabs to prevent interference when using /doc in multiple tabs
3+
- **(Removal)** The Amazon Q inline suggestion popup goes back to being under the suggestions and is always showing.
4+
15
# _3.57_ (2025-02-28)
26
- **(Bug Fix)** Fix suggestion not visible in remote for 2024.3
37
- **(Bug Fix)** /test: update capability card text

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
# Toolkit Version
5-
toolkitVersion=3.58-SNAPSHOT
5+
toolkitVersion=3.59-SNAPSHOT
66

77
# Publish Settings
88
publishToken=

gradle/libs.versions.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mockitoKotlin = "5.4.0"
2727
mockk = "1.13.10"
2828
nimbus-jose-jwt = "9.40"
2929
node-gradle = "7.0.2"
30-
telemetryGenerator = "1.0.301"
30+
telemetryGenerator = "1.0.307"
3131
testLogger = "4.0.0"
3232
testRetry = "1.5.10"
3333
# test-only; platform provides slf4j transitively at runtime

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class BrowserConnector(
6767
it.elementId(source.asText())
6868
}
6969
} else if (module != null && trigger != null) {
70-
Telemetry.toolkit.openModule.use {
70+
Telemetry.toolkit.willOpenModule.use {
7171
it.module(module.asText())
7272
it.source(trigger.asText())
7373
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt

+1-30
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,16 @@ import com.fasterxml.jackson.databind.DeserializationFeature
77
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
88
import com.fasterxml.jackson.module.kotlin.readValue
99
import com.intellij.openapi.application.ApplicationInfo
10-
import com.intellij.openapi.application.ApplicationManager
1110
import com.intellij.openapi.application.runInEdt
1211
import com.intellij.openapi.application.runReadAction
1312
import com.intellij.openapi.fileEditor.FileDocumentManager
1413
import com.intellij.openapi.project.Project
15-
import com.intellij.openapi.project.modules
16-
import com.intellij.openapi.roots.ModuleRootManager
1714
import com.intellij.openapi.vfs.LocalFileSystem
1815
import kotlinx.coroutines.delay
1916
import kotlinx.coroutines.ensureActive
2017
import kotlinx.coroutines.isActive
2118
import kotlinx.coroutines.time.withTimeout
2219
import kotlinx.coroutines.withContext
23-
import migration.software.aws.toolkits.jetbrains.settings.AwsSettings
24-
import org.apache.commons.codec.digest.DigestUtils
2520
import software.amazon.awssdk.services.codewhisperer.model.ArtifactType
2621
import software.amazon.awssdk.services.codewhisperer.model.CodeScanFindingsSchema
2722
import software.amazon.awssdk.services.codewhisperer.model.CodeScanStatus
@@ -38,14 +33,12 @@ import software.aws.toolkits.core.utils.Waiters.waitUntil
3833
import software.aws.toolkits.core.utils.debug
3934
import software.aws.toolkits.core.utils.getLogger
4035
import software.aws.toolkits.core.utils.info
41-
import software.aws.toolkits.core.utils.toHexString
4236
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig
4337
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext
4438
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
4539
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
4640
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CodeScanResponseContext
4741
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CreateUploadUrlServiceInvocationContext
48-
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
4942
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
5043
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_POLLING_INTERVAL_IN_SECONDS
5144
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FILE_SCANS_THROTTLING_MESSAGE
@@ -59,14 +52,12 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer
5952
import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage
6053
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
6154
import software.aws.toolkits.resources.message
62-
import software.aws.toolkits.telemetry.CodewhispererCodeScanScope
6355
import software.aws.toolkits.telemetry.CodewhispererLanguage
6456
import java.nio.file.Path
6557
import java.time.Duration
6658
import java.time.Instant
6759
import java.util.UUID
6860
import kotlin.coroutines.coroutineContext
69-
import kotlin.io.path.pathString
7061

7162
class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
7263
private val clientToken: UUID = UUID.randomUUID()
@@ -112,7 +103,7 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
112103
// 2 & 3. CreateUploadURL and upload the context.
113104
currentCoroutineContext.ensureActive()
114105
val artifactsUploadStartTime = now()
115-
val codeScanName = generateScanName()
106+
val codeScanName = UUID.randomUUID().toString()
116107

117108
val taskType = if (sessionContext.codeAnalysisScope == CodeWhispererConstants.CodeAnalysisScope.PROJECT) {
118109
CodeWhispererConstants.UploadTaskType.SCAN_PROJECT
@@ -375,26 +366,6 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
375366
private fun isAutoScan(): Boolean =
376367
sessionContext.codeAnalysisScope == CodeWhispererConstants.CodeAnalysisScope.FILE && !sessionContext.sessionConfig.isInitiatedByChat()
377368

378-
private fun generateScanName(): String {
379-
val clientId = AwsSettings.getInstance().clientId
380-
val filePath = sessionContext.sessionConfig.getSelectedFile()?.toNioPath()?.pathString
381-
val scope = CodeWhispererTelemetryService.getInstance().mapToTelemetryScope(
382-
sessionContext.codeAnalysisScope,
383-
sessionContext.sessionConfig.isInitiatedByChat()
384-
)
385-
val projectId = if (scope != CodewhispererCodeScanScope.PROJECT && filePath != null) {
386-
filePath
387-
} else {
388-
ApplicationManager.getApplication().runReadAction<String> {
389-
sessionContext.project.modules.map { module ->
390-
ModuleRootManager.getInstance(module).contentRoots.firstOrNull()?.path
391-
}.joinToString(",")
392-
}
393-
}
394-
395-
return DigestUtils.sha256("$clientId::$projectId::$scope").toHexString()
396-
}
397-
398369
companion object {
399370
private val LOG = getLogger<CodeWhispererCodeScanSession>()
400371
private val MAPPER = jacksonObjectMapper()

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ class CodeWhispererTelemetryService {
285285
)
286286
}
287287

288-
fun mapToTelemetryScope(codeAnalysisScope: CodeWhispererConstants.CodeAnalysisScope, initiatedByChat: Boolean): CodewhispererCodeScanScope =
288+
private fun mapToTelemetryScope(codeAnalysisScope: CodeWhispererConstants.CodeAnalysisScope, initiatedByChat: Boolean): CodewhispererCodeScanScope =
289289
when (codeAnalysisScope) {
290290
CodeWhispererConstants.CodeAnalysisScope.FILE -> {
291291
if (initiatedByChat) {

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt

+20
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@ import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
1313
import com.intellij.testFramework.replaceService
1414
import com.intellij.testFramework.runInEdtAndWait
1515
import com.intellij.util.xmlb.XmlSerializer
16+
import io.mockk.every
17+
import io.mockk.junit4.MockKRule
18+
import io.mockk.mockkObject
1619
import org.assertj.core.api.Assertions.assertThat
1720
import org.jdom.output.XMLOutputter
1821
import org.junit.Before
1922
import org.junit.Ignore
23+
import org.junit.Rule
2024
import org.junit.Test
2125
import org.mockito.kotlin.any
2226
import org.mockito.kotlin.never
2327
import org.mockito.kotlin.spy
2428
import org.mockito.kotlin.verify
2529
import org.mockito.kotlin.whenever
2630
import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl
31+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
2732
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType
2833
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState
2934
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled
@@ -40,6 +45,9 @@ class CodeWhispererSettingsTest : CodeWhispererTestBase() {
4045
private lateinit var codewhispererServiceSpy: CodeWhispererService
4146
private lateinit var toolWindowHeadlessManager: ToolWindowHeadlessManagerImpl
4247

48+
@get:Rule
49+
val mockkRule = MockKRule(this)
50+
4351
@Before
4452
override fun setUp() {
4553
super.setUp()
@@ -212,6 +220,18 @@ class CodeWhispererSettingsTest : CodeWhispererTestBase() {
212220
assertThat(actual.autoBuildSetting).hasSize(1)
213221
assertThat(actual.autoBuildSetting["project1"]).isTrue()
214222
}
223+
224+
@Test
225+
fun `toggleMetricOptIn should trigger LSP didChangeConfiguration`() {
226+
mockkObject(AmazonQLspService)
227+
every { AmazonQLspService.didChangeConfiguration(any()) } returns Unit
228+
settingsManager.toggleMetricOptIn(true)
229+
settingsManager.toggleMetricOptIn(false)
230+
231+
io.mockk.verify(atLeast = 2) {
232+
AmazonQLspService.didChangeConfiguration(any())
233+
}
234+
}
215235
}
216236

217237
class CodeWhispererSettingUnitTest {

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

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.eclipse.lsp4j.PublishDiagnosticsParams
1212
import org.eclipse.lsp4j.ShowMessageRequestParams
1313
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata
1414
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData
15+
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
1516
import java.util.concurrent.CompletableFuture
1617

1718
/**
@@ -58,6 +59,17 @@ class AmazonQLanguageClientImpl : AmazonQLanguageClient {
5859

5960
return CompletableFuture.completedFuture(
6061
buildList {
62+
params.items.forEach {
63+
when (it.section) {
64+
AmazonQLspConstants.LSP_CW_CONFIGURATION_KEY -> {
65+
add(
66+
CodeWhispererLspConfiguration(
67+
shouldShareData = CodeWhispererSettings.getInstance().isMetricOptIn()
68+
)
69+
)
70+
}
71+
}
72+
}
6173
}
6274
)
6375
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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
5+
6+
object AmazonQLspConstants {
7+
const val LSP_CW_CONFIGURATION_KEY = "aws.codeWhisperer"
8+
const val LSP_CW_OPT_OUT_KEY = "shareCodeWhispererContentWithAWS"
9+
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import kotlinx.coroutines.sync.withLock
3030
import kotlinx.coroutines.withTimeout
3131
import org.eclipse.lsp4j.ClientCapabilities
3232
import org.eclipse.lsp4j.ClientInfo
33+
import org.eclipse.lsp4j.DidChangeConfigurationParams
3334
import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities
3435
import org.eclipse.lsp4j.InitializeParams
3536
import org.eclipse.lsp4j.InitializeResult
@@ -180,6 +181,12 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS
180181

181182
fun <T> executeIfRunning(project: Project, runnable: AmazonQLspService.(AmazonQLanguageServer) -> T): T? =
182183
project.serviceIfCreated<AmazonQLspService>()?.executeSync(runnable)
184+
185+
fun didChangeConfiguration(project: Project) {
186+
executeIfRunning(project) {
187+
it.workspaceService.didChangeConfiguration(DidChangeConfigurationParams())
188+
}
189+
}
183190
}
184191
}
185192

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
5+
6+
import com.google.gson.annotations.SerializedName
7+
8+
data class CodeWhispererLspConfiguration(
9+
@SerializedName(AmazonQLspConstants.LSP_CW_OPT_OUT_KEY)
10+
val shouldShareData: Boolean? = null,
11+
)

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

+9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ 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.openapi.project.ProjectManager
1314
import com.intellij.util.xmlb.annotations.Property
15+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
1416
import software.aws.toolkits.jetbrains.utils.notifyInfo
1517
import software.aws.toolkits.resources.AmazonQBundle
1618

@@ -49,6 +51,13 @@ class CodeWhispererSettings : PersistentStateComponent<CodeWhispererConfiguratio
4951

5052
fun toggleMetricOptIn(value: Boolean) {
5153
state.value[CodeWhispererConfigurationType.OptInSendingMetric] = value
54+
ProjectManager.getInstance().openProjects.forEach {
55+
if (it.isDisposed) {
56+
return@forEach
57+
}
58+
59+
AmazonQLspService.didChangeConfiguration(it)
60+
}
5261
}
5362

5463
fun isMetricOptIn() = state.value.getOrDefault(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
@file:Suppress("BannedImports")
4+
5+
package software.aws.toolkits.jetbrains.services.amazonq.lsp
6+
7+
import com.google.gson.Gson
8+
import com.intellij.testFramework.ApplicationExtension
9+
import org.assertj.core.api.Assertions.assertThat
10+
import org.eclipse.lsp4j.ConfigurationItem
11+
import org.eclipse.lsp4j.ConfigurationParams
12+
import org.junit.jupiter.api.Test
13+
import org.junit.jupiter.api.extension.ExtendWith
14+
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
15+
16+
@ExtendWith(ApplicationExtension::class)
17+
class AmazonQLanguageClientImplTest {
18+
private val sut = AmazonQLanguageClientImpl()
19+
20+
@Test
21+
fun `configuration null if no attributes requested`() {
22+
assertThat(sut.configuration(configurationParams()).get()).isNull()
23+
}
24+
25+
@Test
26+
fun `configuration for codeWhisperer respects opt-out`() {
27+
CodeWhispererSettings.getInstance().toggleMetricOptIn(false)
28+
assertThat(sut.configuration(configurationParams("aws.codeWhisperer")).get())
29+
.singleElement()
30+
.isEqualTo(
31+
CodeWhispererLspConfiguration(shouldShareData = false)
32+
)
33+
}
34+
35+
@Test
36+
fun `configuration for codeWhisperer respects opt-in`() {
37+
CodeWhispererSettings.getInstance().toggleMetricOptIn(true)
38+
assertThat(sut.configuration(configurationParams("aws.codeWhisperer")).get())
39+
.singleElement()
40+
.isEqualTo(
41+
CodeWhispererLspConfiguration(shouldShareData = true)
42+
)
43+
}
44+
45+
@Test
46+
fun `configuration empty if attributes unknown`() {
47+
CodeWhispererSettings.getInstance().toggleMetricOptIn(true)
48+
assertThat(sut.configuration(configurationParams("something random")).get()).isEmpty()
49+
}
50+
51+
@Test
52+
fun `Gson serializes CodeWhispererLspConfiguration serializes correctly`() {
53+
val sut = CodeWhispererLspConfiguration(shouldShareData = true)
54+
assertThat(Gson().toJson(sut)).isEqualToIgnoringWhitespace(
55+
"""
56+
{
57+
"shareCodeWhispererContentWithAWS": true
58+
}
59+
""".trimIndent()
60+
)
61+
}
62+
63+
private fun configurationParams(vararg attributes: String) = ConfigurationParams(
64+
attributes.map {
65+
ConfigurationItem().apply {
66+
section = it
67+
}
68+
}
69+
)
70+
}

0 commit comments

Comments
 (0)