Skip to content

Commit 8593344

Browse files
authored
fix(amazonq): fix code issues tab not appearing in remote (#5737)
on remote, each client gets its own instance of the tool window. logic needs to handle this case, otherwise it will only make changes against the 'main' IDE, and will not be visible to remote clients
1 parent fe316fa commit 8593344

File tree

8 files changed

+142
-41
lines changed

8 files changed

+142
-41
lines changed

buildSrc/src/main/kotlin/software/aws/toolkits/gradle/intellij/IdeVersions.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ object IdeVersions {
2222
private val commonPlugins = listOf(
2323
"Git4Idea",
2424
"org.jetbrains.plugins.terminal",
25-
"org.jetbrains.plugins.yaml"
25+
"org.jetbrains.plugins.yaml",
2626
)
2727

2828
private val ideProfiles = listOf(
@@ -34,6 +34,7 @@ object IdeVersions {
3434
"com.intellij.java",
3535
"com.intellij.gradle",
3636
"org.jetbrains.idea.maven",
37+
"com.jetbrains.codeWithMe",
3738
),
3839
marketplacePlugins = listOf(
3940
"org.toml.lang:242.20224.155",
@@ -71,6 +72,7 @@ object IdeVersions {
7172
"com.intellij.java",
7273
"com.intellij.gradle",
7374
"org.jetbrains.idea.maven",
75+
"com.jetbrains.codeWithMe",
7476
),
7577
marketplacePlugins = listOf(
7678
"org.toml.lang:243.21565.122",
@@ -112,6 +114,7 @@ object IdeVersions {
112114
"com.intellij.java",
113115
"com.intellij.gradle",
114116
"org.jetbrains.idea.maven",
117+
"com.jetbrains.codeWithMe",
115118
),
116119
marketplacePlugins = listOf(
117120
"PythonCore:251.23774.460",

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

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
package software.aws.toolkits.jetbrains.services.codewhisperer.codescan
55

6-
import com.intellij.analysis.problemsView.toolWindow.ProblemsView
76
import com.intellij.codeHighlighting.HighlightDisplayLevel
87
import com.intellij.codeInspection.util.InspectionMessage
98
import com.intellij.icons.AllIcons
109
import com.intellij.lang.Commenter
1110
import com.intellij.lang.Language
1211
import com.intellij.lang.LanguageCommenters
12+
import com.intellij.openapi.Disposable
1313
import com.intellij.openapi.actionSystem.ActionManager
1414
import com.intellij.openapi.actionSystem.AnActionEvent
1515
import com.intellij.openapi.actionSystem.CommonDataKeys
@@ -36,8 +36,10 @@ import com.intellij.openapi.util.Disposer
3636
import com.intellij.openapi.util.TextRange
3737
import com.intellij.openapi.vfs.VirtualFile
3838
import com.intellij.openapi.vfs.isFile
39+
import com.intellij.openapi.wm.ToolWindow
3940
import com.intellij.psi.PsiDocumentManager
4041
import com.intellij.refactoring.suggested.range
42+
import com.intellij.ui.content.Content
4143
import com.intellij.ui.content.ContentManagerEvent
4244
import com.intellij.ui.content.ContentManagerListener
4345
import com.intellij.ui.treeStructure.Tree
@@ -60,6 +62,7 @@ import software.aws.toolkits.core.utils.error
6062
import software.aws.toolkits.core.utils.getLogger
6163
import software.aws.toolkits.core.utils.info
6264
import software.aws.toolkits.core.utils.warn
65+
import software.aws.toolkits.jetbrains.ProblemsViewMutator
6366
import software.aws.toolkits.jetbrains.core.coroutines.EDT
6467
import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineUiContext
6568
import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope
@@ -107,26 +110,10 @@ import kotlin.coroutines.CoroutineContext
107110

108111
private val LOG = getLogger<CodeWhispererCodeScanManager>()
109112

110-
class CodeWhispererCodeScanManager(val project: Project) {
111-
private val defaultScope = projectCoroutineScope(project)
113+
class CodeWhispererCodeScanManager(val project: Project, private val defaultScope: CoroutineScope) : Disposable {
112114
private val codeScanResultsPanel by lazy {
113115
CodeWhispererCodeScanResultsView(project, defaultScope)
114116
}
115-
private val codeScanIssuesContent by lazy {
116-
val contentManager = getProblemsWindow().contentManager
117-
contentManager.factory.createContent(
118-
codeScanResultsPanel,
119-
message("codewhisperer.codescan.scan_display"),
120-
false
121-
).also {
122-
Disposer.register(contentManager, it)
123-
contentManager.addContentManagerListener(object : ContentManagerListener {
124-
override fun contentRemoved(event: ContentManagerEvent) {
125-
if (event.content == it) reset()
126-
}
127-
})
128-
}
129-
}
130117

131118
private var autoScanIssues = emptyList<CodeWhispererCodeScanIssue>()
132119
private var ondemandScanIssues = emptyList<CodeWhispererCodeScanIssue>()
@@ -507,12 +494,19 @@ class CodeWhispererCodeScanManager(val project: Project) {
507494
private fun refreshUi() {
508495
val codeScanTreeModel = CodeWhispererCodeScanTreeModel(codeScanTreeNodeRoot)
509496
val totalIssuesCount = codeScanTreeModel.getTotalIssuesCount()
510-
if (totalIssuesCount > 0) {
511-
codeScanIssuesContent.displayName =
512-
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
497+
val displayName = if (totalIssuesCount > 0) {
498+
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
513499
} else {
514-
codeScanIssuesContent.displayName = message("codewhisperer.codescan.scan_display")
500+
message("codewhisperer.codescan.scan_display")
515501
}
502+
503+
withToolWindow { window ->
504+
window.contentManager.contents.filter { it.isCodeScanView() }
505+
.forEach {
506+
it.displayName = displayName
507+
}
508+
}
509+
516510
codeScanResultsPanel.refreshUIWithUpdatedModel(codeScanTreeModel)
517511
}
518512

@@ -650,26 +644,45 @@ class CodeWhispererCodeScanManager(val project: Project) {
650644
* This method adds code content to the problems view if not already added.
651645
*/
652646
fun buildCodeScanUI() = runInEdt {
653-
val problemsWindow = getProblemsWindow()
654-
if (!problemsWindow.contentManager.contents.contains(codeScanIssuesContent)) {
655-
problemsWindow.contentManager.addContent(codeScanIssuesContent)
656-
codeScanIssuesContent.displayName = message("codewhisperer.codescan.scan_display")
647+
withToolWindow { problemsWindow ->
648+
val contentManager = problemsWindow.contentManager
649+
if (!contentManager.contents.any { it.isCodeScanView() }) {
650+
contentManager.addContent(
651+
contentManager.factory.createContent(
652+
codeScanResultsPanel,
653+
message("codewhisperer.codescan.scan_display"),
654+
false
655+
).also {
656+
it.tabName = message("codewhisperer.codescan.scan_display")
657+
Disposer.register(contentManager, it)
658+
contentManager.addContentManagerListener(object : ContentManagerListener {
659+
override fun contentRemoved(event: ContentManagerEvent) {
660+
if (event.content == it) reset()
661+
}
662+
})
663+
}
664+
)
665+
}
657666
}
658667
}
659668

660669
/**
661670
* This method shows the code content panel in problems view
662671
*/
663672
fun showCodeScanUI() = runInEdt {
664-
val problemsWindow = getProblemsWindow()
665-
problemsWindow.contentManager.setSelectedContent(codeScanIssuesContent)
666-
problemsWindow.show()
673+
withToolWindow { problemsWindow ->
674+
problemsWindow.contentManager.contents.firstOrNull { it.isCodeScanView() }
675+
?.let { problemsWindow.contentManager.setSelectedContent(it) }
676+
problemsWindow.show()
677+
}
667678
}
668679

669680
fun removeCodeScanUI() = runInEdt {
670-
val problemsWindow = getProblemsWindow()
671-
if (problemsWindow.contentManager.contents.contains(codeScanIssuesContent)) {
672-
problemsWindow.contentManager.removeContent(codeScanIssuesContent, false)
681+
withToolWindow { problemsWindow ->
682+
problemsWindow.contentManager.contents.filter { it.isCodeScanView() }
683+
.forEach {
684+
problemsWindow.contentManager.removeContent(it, true)
685+
}
673686
}
674687
}
675688

@@ -735,8 +748,13 @@ class CodeWhispererCodeScanManager(val project: Project) {
735748
)
736749
}
737750

738-
private fun getProblemsWindow() = ProblemsView.getToolWindow(project)
739-
?: error(message("codewhisperer.codescan.problems_window_not_found"))
751+
private fun withToolWindow(runnable: (ToolWindow) -> Unit) {
752+
ProblemsViewMutator.EP.forEachExtensionSafe { mutator ->
753+
mutator.mutateProblemsView(project, runnable)
754+
}
755+
}
756+
757+
private fun Content.isCodeScanView() = component == codeScanResultsPanel
740758

741759
private fun reset() = runInEdt {
742760
// clear the codeScanTreeNodeRoot
@@ -765,7 +783,7 @@ class CodeWhispererCodeScanManager(val project: Project) {
765783
editorFactory.addEditorFactoryListener(fileListener, project)
766784
editorFactory.eventMulticaster.addEditorMouseMotionListener(
767785
editorMouseListener,
768-
codeScanIssuesContent
786+
this
769787
)
770788
}
771789
}
@@ -955,8 +973,12 @@ class CodeWhispererCodeScanManager(val project: Project) {
955973
val codeScanTreeModel = CodeWhispererCodeScanTreeModel(codeScanTreeNodeRoot)
956974
val totalIssuesCount = codeScanTreeModel.getTotalIssuesCount()
957975
if (totalIssuesCount > 0) {
958-
codeScanIssuesContent.displayName =
959-
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
976+
withToolWindow { problemsWindow ->
977+
problemsWindow.contentManager.contents.filter { it.isCodeScanView() }
978+
.forEach {
979+
it.displayName = message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
980+
}
981+
}
960982
}
961983
codeScanResultsPanel.refreshUIWithUpdatedModel(codeScanTreeModel)
962984
}
@@ -971,8 +993,13 @@ class CodeWhispererCodeScanManager(val project: Project) {
971993
val codeScanTreeModel = CodeWhispererCodeScanTreeModel(root)
972994
val totalIssuesCount = codeScanTreeModel.getTotalIssuesCount()
973995
if (totalIssuesCount > 0) {
974-
codeScanIssuesContent.displayName =
975-
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
996+
withToolWindow { window ->
997+
window.contentManager.contents.filter { it.isCodeScanView() }
998+
.forEach {
999+
it.displayName =
1000+
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
1001+
}
1002+
}
9761003
}
9771004
codeScanResultsPanel.updateAndDisplayScanResults(codeScanTreeModel, scannedFiles, scope)
9781005
}
@@ -1004,6 +1031,9 @@ class CodeWhispererCodeScanManager(val project: Project) {
10041031
return isInsideWorkTree(projectDir)
10051032
}
10061033

1034+
override fun dispose() {
1035+
}
1036+
10071037
companion object {
10081038
fun getInstance(project: Project): CodeWhispererCodeScanManager = project.service()
10091039
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnaly
3131
import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnalysisResponse
3232
import software.aws.toolkits.core.utils.Waiters.waitUntil
3333
import software.aws.toolkits.core.utils.debug
34+
import software.aws.toolkits.core.utils.error
3435
import software.aws.toolkits.core.utils.getLogger
3536
import software.aws.toolkits.core.utils.info
3637
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
@@ -291,7 +292,7 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
291292
.build()
292293
)
293294
} catch (e: Exception) {
294-
LOG.debug { "Getting code review failed: ${e.message}" }
295+
LOG.error(e) { "Getting code review failed: ${e.message}" }
295296
val errorMessage = getTelemetryErrorMessage(e, featureUseCase = CodeWhispererConstants.FeatureName.CODE_REVIEW)
296297
throw codeScanServerException("GetCodeReviewException: $errorMessage")
297298
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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
5+
6+
import com.intellij.analysis.problemsView.toolWindow.ProblemsView
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.wm.ToolWindow
9+
import com.jetbrains.rdserver.toolWindow.BackendToolWindowHost
10+
11+
class CwmProblemsViewMutator : ProblemsViewMutator {
12+
override fun mutateProblemsView(project: Project, runnable: (ToolWindow) -> Unit) {
13+
BackendToolWindowHost.getAllInstances(project).forEach { host ->
14+
host.getToolWindow(ProblemsView.ID)?.let {
15+
runnable(it)
16+
}
17+
}
18+
}
19+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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
5+
6+
import com.intellij.analysis.problemsView.toolWindow.ProblemsView
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.wm.ToolWindow
9+
10+
class DefaultProblemsViewMutator : ProblemsViewMutator {
11+
override fun mutateProblemsView(project: Project, runnable: (ToolWindow) -> Unit) {
12+
ProblemsView.getToolWindow(project)?.let { runnable(it) }
13+
}
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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
5+
6+
import com.intellij.openapi.extensions.ExtensionPointName
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.wm.ToolWindow
9+
10+
interface ProblemsViewMutator {
11+
fun mutateProblemsView(project: Project, runnable: (ToolWindow) -> Unit)
12+
13+
companion object {
14+
val EP = ExtensionPointName<ProblemsViewMutator>("software.aws.toolkits.jetbrains.problemsViewMutator")
15+
}
16+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!-- Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -->
2+
<!-- SPDX-License-Identifier: Apache-2.0 -->
3+
4+
<idea-plugin>
5+
<extensions defaultExtensionNs="software.aws.toolkits.jetbrains">
6+
<problemsViewMutator implementation="software.aws.toolkits.jetbrains.CwmProblemsViewMutator"/>
7+
</extensions>
8+
</idea-plugin>

plugins/amazonq/src/main/resources/META-INF/plugin.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
<depends>aws.toolkit.core</depends>
5959
<depends>com.intellij.modules.lang</depends>
6060

61+
<depends optional="true" config-file="amazonq-ext-codewithme.xml">com.jetbrains.codeWithMe</depends>
62+
6163
<depends optional="true" config-file="amazonq-ext-dart.xml">Dart</depends>
6264
<depends optional="true" config-file="amazonq-ext-datagrip.xml">com.intellij.database</depends>
6365
<depends optional="true" config-file="amazonq-ext-go.xml">org.jetbrains.plugins.go</depends>
@@ -97,8 +99,16 @@
9799
<extensionPoint qualifiedName="software.aws.toolkits.jetbrains.moduleDependencyProvider"
98100
interface="software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider"
99101
dynamic="true"/>
102+
103+
<extensionPoint qualifiedName="software.aws.toolkits.jetbrains.problemsViewMutator"
104+
interface="software.aws.toolkits.jetbrains.ProblemsViewMutator"
105+
dynamic="true" />
100106
</extensionPoints>
101107

108+
<extensions defaultExtensionNs="software.aws.toolkits.jetbrains">
109+
<problemsViewMutator implementation="software.aws.toolkits.jetbrains.DefaultProblemsViewMutator"/>
110+
</extensions>
111+
102112
<xi:include href="/META-INF/module-amazonq.xml" />
103113

104114
<xi:include href="/META-INF/change-notes.xml" xpointer="xpointer(/idea-plugin/*)">

0 commit comments

Comments
 (0)