Skip to content

Commit 85d78db

Browse files
authored
feat(lsp): Addition of LSP for Amazon Q
feat(lsp): Addition of LSP for Amazon Q
2 parents 32c2ef6 + 2685de7 commit 85d78db

File tree

65 files changed

+5066
-50
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+5066
-50
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref =
106106
kotlin-stdLibJdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
107107
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
108108
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
109+
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
109110
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" }
110111
mockk = { module = "io.mockk:mockk", version.ref="mockk" }
111112
nimbus-jose-jwt = {module = "com.nimbusds:nimbus-jose-jwt", version.ref = "nimbus-jose-jwt"}
@@ -121,7 +122,7 @@ zjsonpatch = { module = "com.flipkart.zjsonpatch:zjsonpatch", version.ref = "zjs
121122
[bundles]
122123
jackson = ["jackson-datetime", "jackson-kotlin", "jackson-yaml", "jackson-xml"]
123124
kotlin = ["kotlin-stdLibJdk8", "kotlin-reflect"]
124-
mockito = ["mockito-core", "mockito-kotlin"]
125+
mockito = ["mockito-core", "mockito-junit-jupiter", "mockito-kotlin"]
125126
sshd = ["sshd-core", "sshd-scp", "sshd-sftp"]
126127

127128
[plugins]

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
1919
import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
2020
import software.aws.toolkits.jetbrains.core.gettingstarted.emitUserState
2121
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
22+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
2223
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
2324
import software.aws.toolkits.jetbrains.services.amazonq.project.ProjectContextController
2425
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow
@@ -56,6 +57,7 @@ class AmazonQStartupActivity : ProjectActivity {
5657

5758
QRegionProfileManager.getInstance().validateProfile(project)
5859

60+
AmazonQLspService.getInstance(project)
5961
startLsp(project)
6062
if (runOnce.get()) return
6163
emitUserState(project)

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
5353
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
5454
import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection
5555
import software.aws.toolkits.jetbrains.services.amazonq.SUPPLEMENTAL_CONTEXT_TIMEOUT
56+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
57+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.GetConfigurationFromServerParams
58+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerConfigurations
5659
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
5760
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
5861
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
@@ -92,6 +95,9 @@ import software.aws.toolkits.resources.message
9295
import software.aws.toolkits.telemetry.CodewhispererCompletionType
9396
import software.aws.toolkits.telemetry.CodewhispererSuggestionState
9497
import software.aws.toolkits.telemetry.CodewhispererTriggerType
98+
import java.net.URI
99+
import java.nio.file.Paths
100+
import java.util.concurrent.CompletableFuture
95101
import java.util.concurrent.TimeUnit
96102

97103
@Service
@@ -233,7 +239,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
233239
requestContext.fileContextInfo,
234240
requestContext.awaitSupplementalContext(),
235241
requestContext.customizationArn,
236-
requestContext.profileArn
242+
requestContext.profileArn,
243+
requestContext.workspaceId,
237244
)
238245
)
239246

@@ -670,10 +677,42 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
670677

671678
val profileArn = QRegionProfileManager.getInstance().activeProfile(project)?.arn
672679

680+
var workspaceId: String? = null
681+
try {
682+
val workspacesInfos = getWorkspaceIds(project).get().workspaces
683+
for (workspaceInfo in workspacesInfos) {
684+
val workspaceRootPath = Paths.get(URI(workspaceInfo.workspaceRoot)).toString()
685+
if (psiFile.virtualFile.path.startsWith(workspaceRootPath)) {
686+
workspaceId = workspaceInfo.workspaceId
687+
LOG.info { "Found workspaceId from LSP '$workspaceId'" }
688+
break
689+
}
690+
}
691+
} catch (e: Exception) {
692+
LOG.warn { "Cannot get workspaceId from LSP'$e'" }
693+
}
673694
return RequestContext(
674-
project, editor, triggerTypeInfo, caretPosition, fileContext,
675-
supplementalContext, connection, latencyContext, customizationArn, profileArn
695+
project,
696+
editor,
697+
triggerTypeInfo,
698+
caretPosition,
699+
fileContext,
700+
supplementalContext,
701+
connection,
702+
latencyContext,
703+
customizationArn,
704+
profileArn,
705+
workspaceId,
706+
)
707+
}
708+
709+
private fun getWorkspaceIds(project: Project): CompletableFuture<LspServerConfigurations> {
710+
val payload = GetConfigurationFromServerParams(
711+
section = "aws.q.workspaceContext"
676712
)
713+
return AmazonQLspService.executeIfRunning(project) { server ->
714+
server.getConfigurationFromServer(payload)
715+
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")))
677716
}
678717

679718
fun validateResponse(response: GenerateCompletionsResponse): GenerateCompletionsResponse {
@@ -808,6 +847,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
808847
supplementalContext: SupplementalContextInfo?,
809848
customizationArn: String?,
810849
profileArn: String?,
850+
workspaceId: String?,
811851
): GenerateCompletionsRequest {
812852
val programmingLanguage = ProgrammingLanguage.builder()
813853
.languageName(fileContextInfo.programmingLanguage.toCodeWhispererRuntimeLanguage().languageId)
@@ -837,6 +877,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
837877
.customizationArn(customizationArn)
838878
.optOutPreference(getTelemetryOptOutPreference())
839879
.profileArn(profileArn)
880+
.workspaceId(workspaceId)
840881
.build()
841882
}
842883
}
@@ -853,6 +894,7 @@ data class RequestContext(
853894
val latencyContext: LatencyContext,
854895
val customizationArn: String?,
855896
val profileArn: String?,
897+
val workspaceId: String?,
856898
) {
857899
// TODO: should make the entire getRequestContext() suspend function instead of making supplemental context only
858900
var supplementalContext: SupplementalContextInfo? = null

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.settings
55

66
import com.intellij.icons.AllIcons
77
import com.intellij.ide.DataManager
8+
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
89
import com.intellij.openapi.options.BoundConfigurable
910
import com.intellij.openapi.options.Configurable
1011
import com.intellij.openapi.options.SearchableConfigurable
1112
import com.intellij.openapi.options.ex.Settings
1213
import com.intellij.openapi.project.Project
14+
import com.intellij.openapi.ui.emptyText
1315
import com.intellij.ui.components.ActionLink
1416
import com.intellij.ui.components.fields.ExpandableTextField
17+
import com.intellij.ui.dsl.builder.Align
1518
import com.intellij.ui.dsl.builder.bindIntText
1619
import com.intellij.ui.dsl.builder.bindSelected
1720
import com.intellij.ui.dsl.builder.bindText
@@ -24,6 +27,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh
2427
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
2528
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled
2629
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
30+
import software.aws.toolkits.jetbrains.settings.LspSettings
2731
import software.aws.toolkits.resources.message
2832
import java.awt.Font
2933
import java.util.concurrent.TimeUnit
@@ -61,6 +65,24 @@ class CodeWhispererConfigurable(private val project: Project) :
6165
}
6266
}
6367

68+
group(message("amazonqFeatureDev.placeholder.lsp")) {
69+
row(message("amazonqFeatureDev.placeholder.select_lsp_artifact")) {
70+
val fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileDescriptor()
71+
fileChooserDescriptor.isForcedToUseIdeaFileChooser = true
72+
73+
textFieldWithBrowseButton(fileChooserDescriptor = fileChooserDescriptor)
74+
.bindText(
75+
{ LspSettings.getInstance().getArtifactPath().orEmpty() },
76+
{ LspSettings.getInstance().setArtifactPath(it) }
77+
)
78+
.applyToComponent {
79+
emptyText.text = message("executableCommon.auto_managed")
80+
}
81+
.resizableColumn()
82+
.align(Align.FILL)
83+
}
84+
}
85+
6486
group(message("aws.settings.codewhisperer.group.general")) {
6587
row {
6688
checkBox(message("aws.settings.codewhisperer.include_code_with_reference")).apply {
@@ -116,6 +138,20 @@ class CodeWhispererConfigurable(private val project: Project) :
116138
}
117139

118140
group(message("aws.settings.codewhisperer.group.q_chat")) {
141+
row {
142+
checkBox(message("aws.settings.codewhisperer.workspace_context")).apply {
143+
connect.subscribe(
144+
ToolkitConnectionManagerListener.TOPIC,
145+
object : ToolkitConnectionManagerListener {
146+
override fun activeConnectionChanged(newConnection: ToolkitConnection?) {
147+
enabled(isCodeWhispererEnabled(project))
148+
}
149+
}
150+
)
151+
enabled(invoke)
152+
bindSelected(codeWhispererSettings::isWorkspaceContextEnabled, codeWhispererSettings::toggleWorkspaceContextEnabled)
153+
}.comment(message("aws.settings.codewhisperer.workspace_context.tooltip"))
154+
}.visible(false)
119155
row {
120156
checkBox(message("aws.settings.codewhisperer.project_context")).apply {
121157
connect.subscribe(

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ internal class CodeWhispererCodeCoverageTrackerTestPython : CodeWhispererCodeCov
177177
null,
178178
mock(),
179179
aString(),
180-
aString()
180+
aString(),
181+
aString(),
181182
)
182183
val responseContext = ResponseContext("sessionId")
183184
val recommendationContext = RecommendationContext(

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,18 @@ class CodeWhispererConfigurableTest : CodeWhispererTestBase() {
3939

4040
val checkboxes = panel.components.filterIsInstance<JCheckBox>()
4141

42-
assertThat(checkboxes.size).isEqualTo(5)
42+
assertThat(checkboxes.size).isEqualTo(6)
4343
assertThat(checkboxes.map { it.text }).containsExactlyInAnyOrder(
4444
message("aws.settings.codewhisperer.include_code_with_reference"),
4545
message("aws.settings.codewhisperer.configurable.opt_out.title"),
4646
message("aws.settings.codewhisperer.automatic_import_adder"),
47+
"Workspace context",
4748
message("aws.settings.codewhisperer.project_context"),
4849
message("aws.settings.codewhisperer.project_context_gpu")
4950
)
5051

5152
val comments = panel.components.filterIsInstance<DslLabel>()
52-
assertThat(comments.size).isEqualTo(8)
53+
assertThat(comments.size).isEqualTo(9)
5354

5455
mockCodeWhispererEnabledStatus(false)
5556
ApplicationManager.getApplication().messageBus.syncPublisher(ToolkitConnectionManagerListener.TOPIC)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ class CodeWhispererServiceTest {
213213
connection = ToolkitConnectionManager.getInstance(projectRule.project).activeConnection(),
214214
latencyContext = LatencyContext(),
215215
customizationArn = "fake-arn",
216-
profileArn = "fake-arn"
216+
profileArn = "fake-arn",
217+
workspaceId = null,
217218
)
218219
)
219220

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

Lines changed: 20 additions & 0 deletions
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()
@@ -246,6 +254,18 @@ class CodeWhispererSettingsTest : CodeWhispererTestBase() {
246254
assertThat(sut.getProjectContextIndexMaxSize()).isEqualTo(expected)
247255
}
248256
}
257+
258+
@Test
259+
fun `toggleMetricOptIn should trigger LSP didChangeConfiguration`() {
260+
mockkObject(AmazonQLspService)
261+
every { AmazonQLspService.didChangeConfiguration(any()) } returns Unit
262+
settingsManager.toggleMetricOptIn(true)
263+
settingsManager.toggleMetricOptIn(false)
264+
265+
io.mockk.verify(atLeast = 2) {
266+
AmazonQLspService.didChangeConfiguration(any())
267+
}
268+
}
249269
}
250270

251271
class CodeWhispererSettingUnitTest {

plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ fun aRequestContext(
258258
aString()
259259
),
260260
customizationArn = null,
261-
profileArn = null
261+
profileArn = null,
262+
workspaceId = null,
262263
)
263264
}
264265

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>

0 commit comments

Comments
 (0)