Skip to content

Commit 05429e4

Browse files
committed
Merge remote-tracking branch 'origin/feature/q-lsp' into rli/lsp-start
Conflicts: plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
2 parents 9256837 + 013c54f commit 05429e4

File tree

5 files changed

+156
-75
lines changed

5 files changed

+156
-75
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Amazon Q /review: Unable to navigate to code location when selecting issues"
4+
}

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

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import com.intellij.icons.AllIcons
77
import com.intellij.openapi.actionSystem.ActionGroup
88
import com.intellij.openapi.actionSystem.ActionManager
99
import com.intellij.openapi.actionSystem.ActionToolbar
10+
import com.intellij.openapi.application.runInEdt
11+
import com.intellij.openapi.fileEditor.FileEditorManager
12+
import com.intellij.openapi.fileEditor.OpenFileDescriptor
1013
import com.intellij.openapi.project.Project
1114
import com.intellij.openapi.vfs.VirtualFile
1215
import com.intellij.ui.AnimatedIcon
13-
import com.intellij.ui.ClickListener
1416
import com.intellij.ui.OnePixelSplitter
1517
import com.intellij.ui.ScrollPaneFactory
1618
import com.intellij.ui.border.CustomLineBorder
@@ -19,7 +21,8 @@ import com.intellij.ui.treeStructure.Tree
1921
import com.intellij.util.ui.JBUI
2022
import icons.AwsIcons
2123
import kotlinx.coroutines.CoroutineScope
22-
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanTreeMouseListener
24+
import software.aws.toolkits.core.utils.error
25+
import software.aws.toolkits.core.utils.getLogger
2326
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.utils.IssueGroupingStrategy
2427
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.utils.IssueSeverity
2528
import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig
@@ -31,7 +34,6 @@ import java.awt.BorderLayout
3134
import java.awt.Component
3235
import java.awt.GridBagConstraints
3336
import java.awt.GridBagLayout
34-
import java.awt.event.MouseEvent
3537
import java.time.Instant
3638
import java.time.format.DateTimeFormatter
3739
import javax.swing.BorderFactory
@@ -52,15 +54,18 @@ internal class CodeWhispererCodeScanResultsView(private val project: Project, pr
5254

5355
private val codeScanTree: Tree = Tree().apply {
5456
isRootVisible = false
55-
CodeWhispererCodeScanTreeMouseListener(project).installOn(this)
56-
object : ClickListener() {
57-
override fun onClick(event: MouseEvent, clickCount: Int): Boolean {
58-
val issueNode = (event.source as Tree).selectionPath?.lastPathComponent as? DefaultMutableTreeNode
59-
val issue = issueNode?.userObject as? CodeWhispererCodeScanIssue ?: return false
60-
showIssueDetails(issue, defaultScope)
61-
return true
57+
58+
addTreeSelectionListener { e ->
59+
val issueNode = e.path.lastPathComponent as? DefaultMutableTreeNode
60+
val issue = issueNode?.userObject as? CodeWhispererCodeScanIssue ?: return@addTreeSelectionListener
61+
62+
showIssueDetails(issue, defaultScope)
63+
64+
synchronized(issueNode) {
65+
if (issueNode.userObject !is CodeWhispererCodeScanIssue) return@addTreeSelectionListener
66+
navigateToIssue(issueNode.userObject as CodeWhispererCodeScanIssue)
6267
}
63-
}.installOn(this)
68+
}
6469
cellRenderer = ColoredTreeCellRenderer()
6570
}
6671

@@ -390,8 +395,30 @@ internal class CodeWhispererCodeScanResultsView(private val project: Project, pr
390395
}
391396
}
392397

398+
private fun navigateToIssue(codeScanIssue: CodeWhispererCodeScanIssue) {
399+
val textRange = codeScanIssue.textRange ?: return
400+
val startOffset = textRange.startOffset
401+
402+
if (codeScanIssue.isInvalid) return
403+
404+
runInEdt {
405+
val editor = FileEditorManager.getInstance(project).openTextEditor(
406+
OpenFileDescriptor(project, codeScanIssue.file, startOffset),
407+
false
408+
)
409+
if (editor == null) {
410+
LOG.error { "Cannot fetch editor for the file ${codeScanIssue.file.path}" }
411+
return@runInEdt
412+
}
413+
if (codeScanIssue.rangeHighlighter == null) {
414+
codeScanIssue.rangeHighlighter = codeScanIssue.addRangeHighlighter(editor.markupModel)
415+
}
416+
}
417+
}
418+
393419
private companion object {
394420
const val ACTION_PLACE = "CodeScanResultsPanel"
395421
const val CODE_SCAN_SPLITTER_PROPORTION_KEY = "CODE_SCAN_SPLITTER_PROPORTION"
422+
private val LOG = getLogger<CodeWhispererCodeScanResultsView>()
396423
}
397424
}

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

Lines changed: 0 additions & 53 deletions
This file was deleted.

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

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,35 @@ import com.intellij.openapi.util.Disposer
1919
import com.intellij.openapi.util.Key
2020
import com.intellij.util.io.await
2121
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.TimeoutCancellationException
2223
import kotlinx.coroutines.launch
24+
import kotlinx.coroutines.time.withTimeout
25+
import org.eclipse.lsp4j.ClientCapabilities
26+
import org.eclipse.lsp4j.ClientInfo
27+
import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities
2328
import org.eclipse.lsp4j.InitializeParams
2429
import org.eclipse.lsp4j.InitializedParams
30+
import org.eclipse.lsp4j.SynchronizationCapabilities
31+
import org.eclipse.lsp4j.TextDocumentClientCapabilities
32+
import org.eclipse.lsp4j.WorkspaceClientCapabilities
33+
import org.eclipse.lsp4j.WorkspaceFolder
2534
import org.eclipse.lsp4j.jsonrpc.Launcher
2635
import org.eclipse.lsp4j.launch.LSPLauncher
2736
import org.slf4j.event.Level
2837
import software.aws.toolkits.core.utils.getLogger
2938
import software.aws.toolkits.core.utils.warn
3039
import software.aws.toolkits.jetbrains.isDeveloperMode
40+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata
41+
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
3142
import java.io.IOException
3243
import java.io.OutputStreamWriter
3344
import java.io.PipedInputStream
3445
import java.io.PipedOutputStream
3546
import java.io.PrintWriter
3647
import java.io.StringWriter
48+
import java.net.URI
3749
import java.nio.charset.StandardCharsets
50+
import java.time.Duration
3851
import java.util.concurrent.Future
3952

4053
// https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java
@@ -107,6 +120,57 @@ private class AmazonQServerInstance(private val project: Project, private val cs
107120
private val launcherFuture: Future<Void>
108121
private val launcherHandler: KillableProcessHandler
109122

123+
private fun createClientCapabilities(): ClientCapabilities =
124+
ClientCapabilities().apply {
125+
textDocument = TextDocumentClientCapabilities().apply {
126+
// For didSaveTextDocument, other textDocument/ messages always mandatory
127+
synchronization = SynchronizationCapabilities().apply {
128+
didSave = true
129+
}
130+
}
131+
132+
workspace = WorkspaceClientCapabilities().apply {
133+
applyEdit = false
134+
135+
// For workspace folder changes
136+
workspaceFolders = true
137+
138+
// For file operations (create, delete)
139+
fileOperations = FileOperationsWorkspaceCapabilities().apply {
140+
didCreate = true
141+
didDelete = true
142+
}
143+
}
144+
}
145+
146+
// needs case handling when project's base path is null: default projects/unit tests
147+
private fun createWorkspaceFolders(): List<WorkspaceFolder> =
148+
project.basePath?.let { basePath ->
149+
listOf(
150+
WorkspaceFolder(
151+
URI("file://$basePath").toString(),
152+
project.name
153+
)
154+
)
155+
}.orEmpty() // no folders to report or workspace not folder based
156+
157+
private fun createClientInfo(): ClientInfo {
158+
val metadata = ClientMetadata.getDefault()
159+
return ClientInfo().apply {
160+
name = metadata.awsProduct.toString()
161+
version = metadata.awsVersion
162+
}
163+
}
164+
165+
private fun createInitializeParams(): InitializeParams =
166+
InitializeParams().apply {
167+
processId = ProcessHandle.current().pid().toInt()
168+
capabilities = createClientCapabilities()
169+
clientInfo = createClientInfo()
170+
workspaceFolders = createWorkspaceFolders()
171+
initializationOptions = createExtendedClientMetadata()
172+
}
173+
110174
init {
111175
val cmd = GeneralCommandLine("amazon-q-lsp")
112176

@@ -143,18 +207,14 @@ private class AmazonQServerInstance(private val project: Project, private val cs
143207
launcherFuture = launcher.startListening()
144208

145209
cs.launch {
146-
val initializeResult = languageServer.initialize(
147-
InitializeParams().apply {
148-
// does this work on windows
149-
processId = ProcessHandle.current().pid().toInt()
150-
// capabilities
151-
// client info
152-
// trace?
153-
// workspace folders?
154-
// anything else we need?
210+
val initializeResult = try {
211+
withTimeout(Duration.ofSeconds(10)) {
212+
languageServer.initialize(createInitializeParams()).await()
155213
}
156-
// probably need a timeout
157-
).await()
214+
} catch (_: TimeoutCancellationException) {
215+
LOG.warn { "LSP initialization timed out" }
216+
null
217+
}
158218

159219
// then if this succeeds then we can allow the client to send requests
160220
if (initializeResult == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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
5+
6+
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
7+
8+
data class ExtendedClientMetadata(
9+
val aws: AwsMetadata,
10+
)
11+
12+
data class AwsMetadata(
13+
val clientInfo: ClientInfoMetadata,
14+
)
15+
16+
data class ClientInfoMetadata(
17+
val extension: ExtensionMetadata,
18+
val clientId: String,
19+
val version: String,
20+
val name: String,
21+
)
22+
23+
data class ExtensionMetadata(
24+
val name: String,
25+
val version: String,
26+
)
27+
28+
fun createExtendedClientMetadata(): ExtendedClientMetadata {
29+
val metadata = ClientMetadata.getDefault()
30+
return ExtendedClientMetadata(
31+
aws = AwsMetadata(
32+
clientInfo = ClientInfoMetadata(
33+
extension = ExtensionMetadata(
34+
name = metadata.awsProduct.toString(),
35+
version = metadata.awsVersion
36+
),
37+
clientId = metadata.clientId,
38+
version = metadata.parentProductVersion,
39+
name = metadata.parentProduct
40+
)
41+
)
42+
)
43+
}

0 commit comments

Comments
 (0)