Skip to content

fix(amazonq): fix code issues tab not appearing in remote #5737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

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

import com.intellij.analysis.problemsView.toolWindow.ProblemsView
import com.intellij.codeHighlighting.HighlightDisplayLevel
import com.intellij.codeInspection.util.InspectionMessage
import com.intellij.icons.AllIcons
import com.intellij.lang.Commenter
import com.intellij.lang.Language
import com.intellij.lang.LanguageCommenters
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
Expand All @@ -36,8 +36,10 @@
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.isFile
import com.intellij.openapi.wm.ToolWindow
import com.intellij.psi.PsiDocumentManager
import com.intellij.refactoring.suggested.range
import com.intellij.ui.content.Content
import com.intellij.ui.content.ContentManagerEvent
import com.intellij.ui.content.ContentManagerListener
import com.intellij.ui.treeStructure.Tree
Expand All @@ -60,6 +62,7 @@
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.info
import software.aws.toolkits.core.utils.warn
import software.aws.toolkits.jetbrains.ProblemsViewMutator
import software.aws.toolkits.jetbrains.core.coroutines.EDT
import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineUiContext
import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope
Expand Down Expand Up @@ -107,26 +110,10 @@

private val LOG = getLogger<CodeWhispererCodeScanManager>()

class CodeWhispererCodeScanManager(val project: Project) {
private val defaultScope = projectCoroutineScope(project)
class CodeWhispererCodeScanManager(val project: Project, private val defaultScope: CoroutineScope) : Disposable {
private val codeScanResultsPanel by lazy {
CodeWhispererCodeScanResultsView(project, defaultScope)
}
private val codeScanIssuesContent by lazy {
val contentManager = getProblemsWindow().contentManager
contentManager.factory.createContent(
codeScanResultsPanel,
message("codewhisperer.codescan.scan_display"),
false
).also {
Disposer.register(contentManager, it)
contentManager.addContentManagerListener(object : ContentManagerListener {
override fun contentRemoved(event: ContentManagerEvent) {
if (event.content == it) reset()
}
})
}
}

private var autoScanIssues = emptyList<CodeWhispererCodeScanIssue>()
private var ondemandScanIssues = emptyList<CodeWhispererCodeScanIssue>()
Expand Down Expand Up @@ -507,12 +494,19 @@
private fun refreshUi() {
val codeScanTreeModel = CodeWhispererCodeScanTreeModel(codeScanTreeNodeRoot)
val totalIssuesCount = codeScanTreeModel.getTotalIssuesCount()
if (totalIssuesCount > 0) {
codeScanIssuesContent.displayName =
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
val displayName = if (totalIssuesCount > 0) {
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
} else {
codeScanIssuesContent.displayName = message("codewhisperer.codescan.scan_display")
message("codewhisperer.codescan.scan_display")
}

withToolWindow { window ->
window.contentManager.contents.filter { it.isCodeScanView() }
.forEach {
it.displayName = displayName
}
}

codeScanResultsPanel.refreshUIWithUpdatedModel(codeScanTreeModel)
}

Expand Down Expand Up @@ -650,26 +644,45 @@
* This method adds code content to the problems view if not already added.
*/
fun buildCodeScanUI() = runInEdt {
val problemsWindow = getProblemsWindow()
if (!problemsWindow.contentManager.contents.contains(codeScanIssuesContent)) {
problemsWindow.contentManager.addContent(codeScanIssuesContent)
codeScanIssuesContent.displayName = message("codewhisperer.codescan.scan_display")
withToolWindow { problemsWindow ->
val contentManager = problemsWindow.contentManager
if (!contentManager.contents.any { it.isCodeScanView() }) {
contentManager.addContent(
contentManager.factory.createContent(
codeScanResultsPanel,
message("codewhisperer.codescan.scan_display"),
false
).also {
it.tabName = message("codewhisperer.codescan.scan_display")
Disposer.register(contentManager, it)
contentManager.addContentManagerListener(object : ContentManagerListener {
override fun contentRemoved(event: ContentManagerEvent) {
if (event.content == it) reset()
}
})
}
)
}
}
}

/**
* This method shows the code content panel in problems view
*/
fun showCodeScanUI() = runInEdt {
val problemsWindow = getProblemsWindow()
problemsWindow.contentManager.setSelectedContent(codeScanIssuesContent)
problemsWindow.show()
withToolWindow { problemsWindow ->
problemsWindow.contentManager.contents.firstOrNull { it.isCodeScanView() }
?.let { problemsWindow.contentManager.setSelectedContent(it) }
problemsWindow.show()
}
}

fun removeCodeScanUI() = runInEdt {
val problemsWindow = getProblemsWindow()
if (problemsWindow.contentManager.contents.contains(codeScanIssuesContent)) {
problemsWindow.contentManager.removeContent(codeScanIssuesContent, false)
withToolWindow { problemsWindow ->
problemsWindow.contentManager.contents.filter { it.isCodeScanView() }
.forEach {
problemsWindow.contentManager.removeContent(it, true)
}
}
}

Expand Down Expand Up @@ -735,8 +748,13 @@
)
}

private fun getProblemsWindow() = ProblemsView.getToolWindow(project)
?: error(message("codewhisperer.codescan.problems_window_not_found"))
private fun withToolWindow(runnable: (ToolWindow) -> Unit) {
ProblemsViewMutator.EP.forEachExtensionSafe { mutator ->
mutator.mutateProblemsView(project, runnable)
}
}

private fun Content.isCodeScanView() = component == codeScanResultsPanel

private fun reset() = runInEdt {
// clear the codeScanTreeNodeRoot
Expand Down Expand Up @@ -765,7 +783,7 @@
editorFactory.addEditorFactoryListener(fileListener, project)
editorFactory.eventMulticaster.addEditorMouseMotionListener(
editorMouseListener,
codeScanIssuesContent
this
)
}
}
Expand Down Expand Up @@ -955,8 +973,12 @@
val codeScanTreeModel = CodeWhispererCodeScanTreeModel(codeScanTreeNodeRoot)
val totalIssuesCount = codeScanTreeModel.getTotalIssuesCount()
if (totalIssuesCount > 0) {
codeScanIssuesContent.displayName =
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
withToolWindow {
it.contentManager.contents.filter { it.isCodeScanView() }

Check notice on line 977 in plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Nested lambda has shadowed implicit parameter

Implicit parameter 'it' of enclosing lambda is shadowed
.forEach {
it.displayName = message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)

Check notice on line 979 in plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Nested lambda has shadowed implicit parameter

Implicit parameter 'it' of enclosing lambda is shadowed
}
}
}
codeScanResultsPanel.refreshUIWithUpdatedModel(codeScanTreeModel)
}
Expand All @@ -971,8 +993,13 @@
val codeScanTreeModel = CodeWhispererCodeScanTreeModel(root)
val totalIssuesCount = codeScanTreeModel.getTotalIssuesCount()
if (totalIssuesCount > 0) {
codeScanIssuesContent.displayName =
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
withToolWindow { window ->
window.contentManager.contents.filter { it.isCodeScanView() }
.forEach {
it.displayName =
message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR)
}
}
}
codeScanResultsPanel.updateAndDisplayScanResults(codeScanTreeModel, scannedFiles, scope)
}
Expand Down Expand Up @@ -1004,6 +1031,9 @@
return isInsideWorkTree(projectDir)
}

override fun dispose() {
}

companion object {
fun getInstance(project: Project): CodeWhispererCodeScanManager = project.service()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnaly
import software.amazon.awssdk.services.codewhispererruntime.model.StartCodeAnalysisResponse
import software.aws.toolkits.core.utils.Waiters.waitUntil
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.error
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.info
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
Expand Down Expand Up @@ -291,7 +292,7 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
.build()
)
} catch (e: Exception) {
LOG.debug { "Getting code review failed: ${e.message}" }
LOG.error(e) { "Getting code review failed: ${e.message}" }
val errorMessage = getTelemetryErrorMessage(e, featureUseCase = CodeWhispererConstants.FeatureName.CODE_REVIEW)
throw codeScanServerException("GetCodeReviewException: $errorMessage")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains

import com.intellij.analysis.problemsView.toolWindow.ProblemsView
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow

class DefaultProblemsViewMutator : ProblemsViewMutator {

Check warning on line 10 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/DefaultProblemsViewMutator.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Extension class should be final and non-public

Extension class should not be public

Check warning

Code scanning / QDJVMC

Extension class should be final and non-public Warning

Extension class should not be public
override fun mutateProblemsView(project: Project, runnable: (ToolWindow) -> Unit) {
ProblemsView.getToolWindow(project)?.let { runnable(it) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains

import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow

interface ProblemsViewMutator {
fun mutateProblemsView(project: Project, runnable: (ToolWindow) -> Unit)

companion object {
val EP = ExtensionPointName<ProblemsViewMutator>("software.aws.toolkits.jetbrains.problemsViewMutator")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains

import com.intellij.analysis.problemsView.toolWindow.ProblemsView
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.jetbrains.rdserver.toolWindow.BackendToolWindowHost

class CwmProblemsViewMutator : ProblemsViewMutator {
override fun mutateProblemsView(project: Project, runnable: (ToolWindow) -> Unit) {
BackendToolWindowHost.getAllInstances(project).forEach { host ->
host.getToolWindow(ProblemsView.ID)?.let {
runnable(it)
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

<idea-plugin>
<extensions defaultExtensionNs="software.aws.toolkits.jetbrains">
<problemsViewMutator implementation="software.aws.toolkits.jetbrains.CwmProblemsViewMutator"/>
</extensions>
</idea-plugin>
10 changes: 10 additions & 0 deletions plugins/amazonq/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
<depends>aws.toolkit.core</depends>
<depends>com.intellij.modules.lang</depends>

<depends optional="true" config-file="amazonq-ext-codewithme.xml">com.jetbrains.codeWithMe</depends>

<depends optional="true" config-file="amazonq-ext-dart.xml">Dart</depends>
<depends optional="true" config-file="amazonq-ext-datagrip.xml">com.intellij.database</depends>
<depends optional="true" config-file="amazonq-ext-go.xml">org.jetbrains.plugins.go</depends>
Expand Down Expand Up @@ -97,8 +99,16 @@
<extensionPoint qualifiedName="software.aws.toolkits.jetbrains.moduleDependencyProvider"
interface="software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider"
dynamic="true"/>

<extensionPoint qualifiedName="software.aws.toolkits.jetbrains.problemsViewMutator"
interface="software.aws.toolkits.jetbrains.ProblemsViewMutator"
dynamic="true" />
</extensionPoints>

<extensions defaultExtensionNs="software.aws.toolkits.jetbrains">
<problemsViewMutator implementation="software.aws.toolkits.jetbrains.DefaultProblemsViewMutator"/>
</extensions>

<xi:include href="/META-INF/module-amazonq.xml" />

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