diff --git a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/intellij/IdeVersions.kt b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/intellij/IdeVersions.kt index e0048684766..de331fa4415 100644 --- a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/intellij/IdeVersions.kt +++ b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/intellij/IdeVersions.kt @@ -22,7 +22,7 @@ object IdeVersions { private val commonPlugins = listOf( "Git4Idea", "org.jetbrains.plugins.terminal", - "org.jetbrains.plugins.yaml" + "org.jetbrains.plugins.yaml", ) private val ideProfiles = listOf( @@ -34,6 +34,7 @@ object IdeVersions { "com.intellij.java", "com.intellij.gradle", "org.jetbrains.idea.maven", + "com.jetbrains.codeWithMe", ), marketplacePlugins = listOf( "org.toml.lang:242.20224.155", @@ -71,6 +72,7 @@ object IdeVersions { "com.intellij.java", "com.intellij.gradle", "org.jetbrains.idea.maven", + "com.jetbrains.codeWithMe", ), marketplacePlugins = listOf( "org.toml.lang:243.21565.122", @@ -112,6 +114,7 @@ object IdeVersions { "com.intellij.java", "com.intellij.gradle", "org.jetbrains.idea.maven", + "com.jetbrains.codeWithMe", ), marketplacePlugins = listOf( "PythonCore:251.23774.460", diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt index 674bcdf9500..7fe6d8f1535 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt @@ -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 @@ -36,8 +36,10 @@ import com.intellij.openapi.util.Disposer 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 @@ -60,6 +62,7 @@ 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.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 @@ -107,26 +110,10 @@ import kotlin.coroutines.CoroutineContext private val LOG = getLogger() -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() private var ondemandScanIssues = emptyList() @@ -507,12 +494,19 @@ class CodeWhispererCodeScanManager(val project: Project) { 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) } @@ -650,10 +644,25 @@ class CodeWhispererCodeScanManager(val project: Project) { * 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() + } + }) + } + ) + } } } @@ -661,15 +670,19 @@ class CodeWhispererCodeScanManager(val project: Project) { * 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) + } } } @@ -735,8 +748,13 @@ class CodeWhispererCodeScanManager(val project: Project) { ) } - 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 @@ -765,7 +783,7 @@ class CodeWhispererCodeScanManager(val project: Project) { editorFactory.addEditorFactoryListener(fileListener, project) editorFactory.eventMulticaster.addEditorMouseMotionListener( editorMouseListener, - codeScanIssuesContent + this ) } } @@ -955,8 +973,12 @@ class CodeWhispererCodeScanManager(val project: Project) { 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 { problemsWindow -> + problemsWindow.contentManager.contents.filter { it.isCodeScanView() } + .forEach { + it.displayName = message("codewhisperer.codescan.scan_display_with_issues", totalIssuesCount, INACTIVE_TEXT_COLOR) + } + } } codeScanResultsPanel.refreshUIWithUpdatedModel(codeScanTreeModel) } @@ -971,8 +993,13 @@ class CodeWhispererCodeScanManager(val project: Project) { 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) } @@ -1004,6 +1031,9 @@ class CodeWhispererCodeScanManager(val project: Project) { return isInsideWorkTree(projectDir) } + override fun dispose() { + } + companion object { fun getInstance(project: Project): CodeWhispererCodeScanManager = project.service() } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt index ec3b1c30df7..65ef44f18dc 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt @@ -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 @@ -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") } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/CwmProblemsViewMutator.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/CwmProblemsViewMutator.kt new file mode 100644 index 00000000000..3346352e7ed --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/CwmProblemsViewMutator.kt @@ -0,0 +1,19 @@ +// 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) + } + } + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/DefaultProblemsViewMutator.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/DefaultProblemsViewMutator.kt new file mode 100644 index 00000000000..6d4702bfbc0 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/DefaultProblemsViewMutator.kt @@ -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 { + override fun mutateProblemsView(project: Project, runnable: (ToolWindow) -> Unit) { + ProblemsView.getToolWindow(project)?.let { runnable(it) } + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ProblemsViewMutator.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ProblemsViewMutator.kt new file mode 100644 index 00000000000..4da60db9d64 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ProblemsViewMutator.kt @@ -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("software.aws.toolkits.jetbrains.problemsViewMutator") + } +} diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-codewithme.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-codewithme.xml new file mode 100644 index 00000000000..d199d28324c --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-codewithme.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index 786974563a0..bea85c6ab6a 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -58,6 +58,8 @@ aws.toolkit.core com.intellij.modules.lang + com.jetbrains.codeWithMe + Dart com.intellij.database org.jetbrains.plugins.go @@ -97,8 +99,16 @@ + + + + + +