diff --git a/.changes/next-release/feature-3155ac52-b98c-47ab-8f9d-83d6bf377076.json b/.changes/next-release/feature-3155ac52-b98c-47ab-8f9d-83d6bf377076.json new file mode 100644 index 00000000000..f9cfa9177b5 --- /dev/null +++ b/.changes/next-release/feature-3155ac52-b98c-47ab-8f9d-83d6bf377076.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Amazon Q inline: now display completions much more consistently at the user's current caret position" +} \ No newline at end of file diff --git a/.changes/next-release/feature-6a46f2ba-0d74-4385-afbc-896a9f13b90a.json b/.changes/next-release/feature-6a46f2ba-0d74-4385-afbc-896a9f13b90a.json new file mode 100644 index 00000000000..c704c17f1eb --- /dev/null +++ b/.changes/next-release/feature-6a46f2ba-0d74-4385-afbc-896a9f13b90a.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Amazon Q inline: now Q completions can co-exist with JetBrains' native IntelliSense completions, when both are showing, press Tab or your customized key shortcuts to accept Q completions and press Enter to accept IntelliSense completions." +} \ No newline at end of file diff --git a/.changes/next-release/feature-81882751-0f01-47db-8cd3-ee7955ef0c96.json b/.changes/next-release/feature-81882751-0f01-47db-8cd3-ee7955ef0c96.json new file mode 100644 index 00000000000..ce99e68895b --- /dev/null +++ b/.changes/next-release/feature-81882751-0f01-47db-8cd3-ee7955ef0c96.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Amazon Q inline: now shows in a JetBrains native UX of popup and inlay text style" +} \ No newline at end of file diff --git a/.changes/next-release/feature-b25741c4-9461-417f-979f-6203b3ce761c.json b/.changes/next-release/feature-b25741c4-9461-417f-979f-6203b3ce761c.json new file mode 100644 index 00000000000..ce0cdc1951b --- /dev/null +++ b/.changes/next-release/feature-b25741c4-9461-417f-979f-6203b3ce761c.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Amazon Q inline: The new UX allows configurable shortcuts for accepting completions and navigating through completions. *Caveat: for users using the previous versions, if you have configured your custom key shortcuts for the Q inline before, you will have to re-configure them again in Amazon Q settings due to a change in the keymap actions." +} \ No newline at end of file diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt index 5a4e03c9336..8b1844b55cb 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt @@ -26,34 +26,24 @@ class AuthController { */ fun getAuthNeededStates(project: Project): AuthNeededStates { val connectionState = checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) - val codeWhispererState = checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) // CW chat is enabled for Builder and IDC users, same for Amazon Q return AuthNeededStates( - chat = getAuthNeededState(connectionState, codeWhispererState), - amazonQ = getAuthNeededState(connectionState, codeWhispererState) + chat = getAuthNeededState(connectionState), + amazonQ = getAuthNeededState(connectionState) ) } private fun getAuthNeededState( amazonqConnectionState: ActiveConnection, - codeWhispererConnectionState: ActiveConnection, onlyIamIdcConnection: Boolean = false, ): AuthNeededState? = when (amazonqConnectionState) { ActiveConnection.NotConnected -> { - if (codeWhispererConnectionState == ActiveConnection.NotConnected) { - AuthNeededState( - message = message("q.connection.disconnected"), - authType = AuthFollowUpType.FullAuth, - ) - } else { - // There is a connection for codewhisperer, but it's not valid for Q - AuthNeededState( - message = message("q.connection.need_scopes"), - authType = AuthFollowUpType.MissingScopes, - ) - } + AuthNeededState( + message = message("q.connection.disconnected"), + authType = AuthFollowUpType.FullAuth, + ) } is ActiveConnection.ValidBearer -> { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml index 7fdcf371ad4..24de37b55d9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml +++ b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml @@ -57,11 +57,10 @@ instance="software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable" /> - - - + + + @@ -90,29 +89,6 @@ text="Invoke Amazon Q Inline Suggestions"> - - - - - - - - - - - - - - - diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src-242/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src-242/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt new file mode 100644 index 00000000000..345078fe203 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src-242/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt @@ -0,0 +1,21 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.popup +import com.intellij.codeInsight.inline.completion.InlineCompletionEvent +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.editor.Editor +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.QInlineCompletionProvider.Companion.DATA_KEY_Q_AUTO_TRIGGER_INTELLISENSE + +fun InlineCompletionEvent.isManualCall(): Boolean = + this is InlineCompletionEvent.DirectCall && this.context?.getData(DATA_KEY_Q_AUTO_TRIGGER_INTELLISENSE) == false + +fun getManualCallEvent(editor: Editor, isIntelliSenseAccept: Boolean): InlineCompletionEvent { + val dataContext = DataContext { dataId -> + when (dataId) { + DATA_KEY_Q_AUTO_TRIGGER_INTELLISENSE.name -> isIntelliSenseAccept + else -> null + } + } + return InlineCompletionEvent.DirectCall(editor, editor.caretModel.currentCaret, dataContext) +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt new file mode 100644 index 00000000000..1f6e8dd7c06 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src-243+/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QManualCall.kt @@ -0,0 +1,17 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.popup +import com.intellij.codeInsight.inline.completion.InlineCompletionEvent +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.UserDataHolderBase +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.QInlineCompletionProvider.Companion.KEY_Q_AUTO_TRIGGER_INTELLISENSE +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.QInlineCompletionProvider.Companion.Q_INLINE_PROVIDER_ID + +fun InlineCompletionEvent.isManualCall(): Boolean = + this is InlineCompletionEvent.ManualCall && this.additionalData.getUserData(KEY_Q_AUTO_TRIGGER_INTELLISENSE) == false + +fun getManualCallEvent(editor: Editor, isIntelliSenseAccept: Boolean): InlineCompletionEvent { + val data = UserDataHolderBase().apply { this.putUserData(KEY_Q_AUTO_TRIGGER_INTELLISENSE, isIntelliSenseAccept) } + return InlineCompletionEvent.ManualCall(editor, Q_INLINE_PROVIDER_ID, data) +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt index e5f07904e64..efb99a7aa8c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/migration/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/CodeWhispererExplorerActionManager.kt @@ -8,12 +8,9 @@ import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import org.jetbrains.annotations.ApiStatus -import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sono.isSono import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider @@ -38,7 +35,7 @@ class CodeWhispererExplorerActionManager : PersistentStateComponent() } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererAcceptAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererAcceptAction.kt deleted file mode 100644 index c7487cc80cc..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererAcceptAction.kt +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.codewhisperer.actions - -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.project.DumbAware -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus -import software.aws.toolkits.resources.message - -open class CodeWhispererAcceptAction(title: String = message("codewhisperer.inline.accept")) : AnAction(title), DumbAware { - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT - - override fun update(e: AnActionEvent) { - e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null && - CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() - } - - override fun actionPerformed(e: AnActionEvent) { - val states = e.project?.getUserData(CodeWhispererPopupManager.KEY_INVOCATION_CONTEXT) ?: return - if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return - ApplicationManager.getApplication().messageBus.syncPublisher( - CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).beforeAccept(states, CodeWhispererPopupManager.getInstance().sessionContext) - } -} - -// A same accept action but different key shortcut and different promoter logic -class CodeWhispererForceAcceptAction(title: String = message("codewhisperer.inline.force.accept")) : CodeWhispererAcceptAction(title) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererActionPromoter.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererActionPromoter.kt deleted file mode 100644 index 1e165f67f14..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererActionPromoter.kt +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.codewhisperer.actions - -import com.intellij.openapi.actionSystem.ActionPromoter -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.DataContext -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew - -class CodeWhispererActionPromoter : ActionPromoter { - override fun promote(actions: MutableList, context: DataContext): MutableList { - val results = actions.toMutableList() - if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive() && - !CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() - ) { - return results - } - - results.sortWith { a, b -> - if (isCodeWhispererForceAction(a)) { - return@sortWith -1 - } else if (isCodeWhispererForceAction(b)) { - return@sortWith 1 - } - - if (isCodeWhispererAcceptAction(a)) { - return@sortWith -1 - } else if (isCodeWhispererAcceptAction(b)) { - return@sortWith 1 - } - - 0 - } - - results.sortWith { a, b -> - if (isCodeWhispererNavigateAction(a)) { - return@sortWith -1 - } else if (isCodeWhispererNavigateAction(b)) { - return@sortWith 1 - } - - 0 - } - return results - } - - private fun isCodeWhispererAcceptAction(action: AnAction): Boolean = action is CodeWhispererAcceptAction - - private fun isCodeWhispererForceAcceptAction(action: AnAction): Boolean = - action is CodeWhispererForceAcceptAction - - private fun isCodeWhispererNavigateAction(action: AnAction): Boolean = - action is CodeWhispererNavigateNextAction || action is CodeWhispererNavigatePrevAction - - private fun isCodeWhispererForceAction(action: AnAction): Boolean = - isCodeWhispererForceAcceptAction(action) || isCodeWhispererNavigateAction(action) -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt deleted file mode 100644 index 3a0be77bc9b..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.codewhisperer.actions - -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.project.DumbAware -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus -import software.aws.toolkits.resources.message - -class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.navigate.next")), DumbAware { - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT - - override fun update(e: AnActionEvent) { - e.presentation.isEnabled = e.project != null && - e.getData(CommonDataKeys.EDITOR) != null && - CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() - } - - override fun actionPerformed(e: AnActionEvent) { - val states = e.project?.getUserData(CodeWhispererPopupManager.KEY_INVOCATION_CONTEXT) ?: return - if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return - ApplicationManager.getApplication().messageBus.syncPublisher( - CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigateNext(states) - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigatePrevAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigatePrevAction.kt deleted file mode 100644 index 8fc05cdc7a2..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigatePrevAction.kt +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.codewhisperer.actions - -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.project.DumbAware -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus -import software.aws.toolkits.resources.message - -class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.navigate.previous")), DumbAware { - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT - - override fun update(e: AnActionEvent) { - e.presentation.isEnabled = e.project != null && - e.getData(CommonDataKeys.EDITOR) != null && - CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() - } - - override fun actionPerformed(e: AnActionEvent) { - val states = e.project?.getUserData(CodeWhispererPopupManager.KEY_INVOCATION_CONTEXT) ?: return - if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return - ApplicationManager.getApplication().messageBus.syncPublisher( - CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigatePrevious(states) - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt index e4755cdabee..6243b1455a4 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt @@ -12,9 +12,7 @@ import com.intellij.openapi.util.Key import kotlinx.coroutines.Job import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.QInlineCompletionProvider import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew import software.aws.toolkits.resources.message @@ -33,8 +31,6 @@ class CodeWhispererRecommendationAction : AnAction(message("codewhisperer.trigge if (QRegionProfileManager.getInstance().hasValidConnectionButNoActiveProfile(project)) { return } - val latencyContext = LatencyContext() - latencyContext.codewhispererEndToEndStart = System.nanoTime() val editor = e.getRequiredData(CommonDataKeys.EDITOR) if (!( if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) { @@ -47,14 +43,7 @@ class CodeWhispererRecommendationAction : AnAction(message("codewhisperer.trigge return } - val triggerType = TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()) - val job = if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) { - CodeWhispererServiceNew.getInstance().showRecommendationsInPopup(editor, triggerType, latencyContext) - } else { - CodeWhispererService.getInstance().showRecommendationsInPopup(editor, triggerType, latencyContext) - } - - e.getData(CommonDataKeys.EDITOR)?.getUserData(ACTION_JOB_KEY)?.set(job) + QInlineCompletionProvider.invokeCompletion(editor) } companion object { 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..839c0c3b3e8 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 @@ -64,7 +64,7 @@ import software.aws.toolkits.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineUiContext import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanDocumentListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanEditorMouseMotionListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.listeners.CodeWhispererCodeScanFileListener @@ -397,7 +397,7 @@ class CodeWhispererCodeScanManager(val project: Project) { var codeScanStatus: Result = Result.Failed val startTime = Instant.now().toEpochMilli() var codeScanResponseContext = defaultCodeScanResponseContext() - val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) + val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) var language: CodeWhispererProgrammingLanguage = CodeWhispererUnknownLanguage.INSTANCE var skipTelemetry = false try { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEnterHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEnterHandler.kt deleted file mode 100644 index fbaec53e3d3..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEnterHandler.kt +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.codewhisperer.editor - -import com.intellij.codeInsight.editorActions.EnterHandler -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.editor.Caret -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.actionSystem.EditorActionHandler -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType -import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread - -class CodeWhispererEnterHandler(private val originalHandler: EditorActionHandler) : EnterHandler(originalHandler) { - override fun executeWriteAction(editor: Editor, caret: Caret?, dataContext: DataContext?) { - originalHandler.execute(editor, caret, dataContext) - - pluginAwareExecuteOnPooledThread { - CodeWhispererAutoTriggerService.getInstance().invoke(editor, CodeWhispererAutomatedTriggerType.Enter()) - } - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererTypedHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererTypedHandler.kt deleted file mode 100644 index e585bc5d486..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererTypedHandler.kt +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.codewhisperer.editor - -import com.intellij.codeInsight.editorActions.TypedHandlerDelegate -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiFile -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants - -class CodeWhispererTypedHandler : TypedHandlerDelegate() { - override fun charTyped(c: Char, project: Project, editor: Editor, psiFiles: PsiFile): Result { - // Special Char - if (CodeWhispererConstants.SPECIAL_CHARACTERS_LIST.contains(c.toString())) { - CodeWhispererAutoTriggerService.getInstance().invoke(editor, CodeWhispererAutomatedTriggerType.SpecialChar(c)) - return Result.CONTINUE - } - - CodeWhispererAutoTriggerService.getInstance().invoke(editor, CodeWhispererAutomatedTriggerType.Classifier()) - - return Result.CONTINUE - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt index d7c27c0137f..1330046dbe1 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/Customize.kt @@ -8,7 +8,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import icons.AwsIcons import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.resources.message @@ -20,7 +20,7 @@ class Customize : DumbAwareAction( override fun update(e: AnActionEvent) { val project = e.project ?: return - val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) + val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) val activeCustomization = CodeWhispererModelConfigurator.getInstance().activeCustomization(project) if (connection != null) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt index 623395afe49..d4657aa06f1 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt @@ -5,6 +5,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.importadder import com.intellij.openapi.editor.Editor import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile @@ -40,9 +41,13 @@ abstract class CodeWhispererImportAdder { } } - private fun insertImportStatement(states: InvocationContext, import: InlineCompletionImports) { - val project = states.requestContext.project - val editor = states.requestContext.editor + fun insertImportStatements(project: Project, editor: Editor, imports: List?) { + imports?.forEach { + insertImportStatement(project, editor, it) + } + } + + private fun insertImportStatement(project: Project, editor: Editor, import: InlineCompletionImports) { val document = editor.document val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return @@ -72,6 +77,12 @@ abstract class CodeWhispererImportAdder { LOG.info { "Added import: $added" } } + private fun insertImportStatement(states: InvocationContext, import: InlineCompletionImports) { + val project = states.requestContext.project + val editor = states.requestContext.editor + insertImportStatement(project, editor, import) + } + private fun insertImportStatement(states: InvocationContextNew, import: InlineCompletionImports) { val project = states.requestContext.project val editor = states.requestContext.editor @@ -154,5 +165,8 @@ abstract class CodeWhispererImportAdder { fun get(language: CodeWhispererProgrammingLanguage): CodeWhispererImportAdder? = EP.extensionList.firstOrNull { language in it.supportedLanguages } ?: EP.extensionList.find { it is CodeWhispererFallbackImportAdder } + + fun getFallback(): CodeWhispererImportAdder? = + EP.extensionList.find { it is CodeWhispererFallbackImportAdder } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt index 61157a2a1d1..3e647d564f0 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt @@ -3,6 +3,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.model +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement import com.intellij.openapi.Disposable import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.VisualPosition @@ -10,7 +11,9 @@ import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.UserDataHolderBase import com.intellij.util.concurrency.annotations.RequiresEdt +import kotlinx.coroutines.channels.Channel import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionItem import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionListWithReferences @@ -235,3 +238,21 @@ data class TryExampleRowContext( val description: String, val filename: String?, ) + +data class InlineCompletionSessionContext( + val itemContexts: MutableList = mutableListOf(), + var sessionId: String = "", + val triggerOffset: Int, + var counter: Int = 0, +) + +data class InlineCompletionItemContext( + val project: Project, + var item: InlineCompletionItem?, + val channel: Channel, + val data: UserDataHolderBase = UserDataHolderBase(), + var hasSeen: Boolean = false, + var isAccepted: Boolean = false, + var isDiscarded: Boolean = false, + var isEmpty: Boolean = false, +) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt index cd6d4bea4d5..d5f391e0897 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt @@ -44,7 +44,7 @@ import javax.swing.JLabel import javax.swing.JPanel class CodeWhispererPopupComponents { - val prevButton = createNavigationButton(prevButtonText()) + val prevButton = createNavigationButton("←") fun prevButtonText() = message( "codewhisperer.popup.button.prev", @@ -53,7 +53,7 @@ class CodeWhispererPopupComponents { ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous") ) ) - val nextButton = createNavigationButton(nextButtonText()).apply { preferredSize = prevButton.preferredSize } + val nextButton = createNavigationButton("→").apply { preferredSize = prevButton.preferredSize } fun nextButtonText() = message( "codewhisperer.popup.button.next", POPUP_DIM_HEX, diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionInsertHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionInsertHandler.kt new file mode 100644 index 00000000000..83f827d1946 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionInsertHandler.kt @@ -0,0 +1,11 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.popup + +import com.intellij.codeInsight.inline.completion.InlineCompletionInsertHandler +import com.intellij.openapi.editor.Editor + +interface QInlineCompletionInsertHandler : InlineCompletionInsertHandler { + fun afterTyped(editor: Editor, startOffset: Int) +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt new file mode 100644 index 00000000000..a092798cb67 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionProvider.kt @@ -0,0 +1,591 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.popup + +import com.intellij.codeInsight.hint.HintManager +import com.intellij.codeInsight.inline.completion.InlineCompletion +import com.intellij.codeInsight.inline.completion.InlineCompletionEvent +import com.intellij.codeInsight.inline.completion.InlineCompletionEventAdapter +import com.intellij.codeInsight.inline.completion.InlineCompletionEventType +import com.intellij.codeInsight.inline.completion.InlineCompletionHandler +import com.intellij.codeInsight.inline.completion.InlineCompletionInsertEnvironment +import com.intellij.codeInsight.inline.completion.InlineCompletionProvider +import com.intellij.codeInsight.inline.completion.InlineCompletionProviderID +import com.intellij.codeInsight.inline.completion.InlineCompletionProviderPresentation +import com.intellij.codeInsight.inline.completion.InlineCompletionRequest +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionGrayTextElement +import com.intellij.codeInsight.inline.completion.logs.InlineCompletionUsageTracker +import com.intellij.codeInsight.inline.completion.session.InlineCompletionSession +import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestion +import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestionUpdateManager +import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionVariant +import com.intellij.openapi.actionSystem.DataKey +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.Key +import com.intellij.ui.dsl.builder.Cell +import com.intellij.ui.dsl.builder.RightGap +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.text +import com.intellij.ui.dsl.gridLayout.UnscaledGaps +import com.intellij.ui.util.preferredHeight +import icons.AwsIcons +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.future.await +import kotlinx.coroutines.launch +import migration.software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager +import org.eclipse.lsp4j.jsonrpc.messages.Either +import software.aws.toolkits.core.utils.debug +import software.aws.toolkits.core.utils.getLogger +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService +import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager +import software.aws.toolkits.jetbrains.services.codewhisperer.importadder.CodeWhispererImportAdder +import software.aws.toolkits.jetbrains.services.codewhisperer.model.InlineCompletionItemContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.InlineCompletionSessionContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService +import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService +import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager +import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants +import software.aws.toolkits.jetbrains.utils.isQConnected +import software.aws.toolkits.resources.message +import software.aws.toolkits.telemetry.CodewhispererTriggerType +import java.awt.Dimension +import javax.swing.Icon +import javax.swing.JComponent +import javax.swing.JEditorPane +import javax.swing.JLabel + +class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompletionProvider { + // TODO: good news is that suggestionUpdateManager can be overridden, this means we can define custom behaviors + // such as on backspace when variants are showing, future improvements + override val suggestionUpdateManager: InlineCompletionSuggestionUpdateManager + get() = super.suggestionUpdateManager + override val insertHandler: QInlineCompletionInsertHandler + get() = object : QInlineCompletionInsertHandler { + override fun afterTyped(editor: Editor, startOffset: Int) { + insertCodeReferencesAndImports(editor, startOffset) + } + + override fun afterInsertion(environment: InlineCompletionInsertEnvironment, elements: List) { + insertCodeReferencesAndImports(environment.editor, environment.insertedRange.startOffset) + } + + private fun insertCodeReferencesAndImports(editor: Editor, startOffset: Int) { + currentAcceptedItemContext?.let { + CodeWhispererCodeReferenceManager.getInstance(it.project).insertCodeReference(editor, it.item, startOffset) + val importAdder = CodeWhispererImportAdder.getFallback() + if (importAdder == null) { + logInline(triggerSessionId) { + "No import adder found for JB inline" + } + return + } + importAdder.insertImportStatements(it.project, editor, it.item?.mostRelevantMissingImports) + logInline(triggerSessionId) { + "Accepted suggestion has ${it.item?.references?.size ?: 0} references and " + + "${it.item?.mostRelevantMissingImports?.size ?: 0} imports" + } + } + } + } + override val id: InlineCompletionProviderID = Q_INLINE_PROVIDER_ID + override val providerPresentation: InlineCompletionProviderPresentation + get() = object : InlineCompletionProviderPresentation { + override fun getTooltip(project: Project?): JComponent { + project ?: return JLabel() + val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return JLabel() + val session = InlineCompletionSession.getOrNull(editor) ?: return JLabel() + return qToolTip( + "Amazon Q", + AwsIcons.Logos.AWS_Q_GRADIENT_SMALL, + session + ) + } + } + private var cell: Cell? = null + private var triggerSessionId = 0 + private var currentAcceptedItemContext: InlineCompletionItemContext? = null + + // not needed for current implementation, will need this when we support concurrent triggers, so leave it here + private val activeTriggerSessions = mutableMapOf() + + fun qToolTip( + title: String, + icon: Icon, + session: InlineCompletionSession, + ): JComponent { + return panel { + row { + icon(icon).gap(RightGap.SMALL) + comment(title).gap(RightGap.SMALL) + cell(navigationButton(session, "←")).gap(RightGap.SMALL) + text(indexDisplayText(session)).gap(RightGap.SMALL).applyToComponent { + // workaround of text cutoff issue: 21 is the max width of all its possible strings (4/5) + this.preferredSize = Dimension(21, this.preferredHeight) + }.also { + cell = it + } + cell(navigationButton(session, "→")) + + // if there are imports and references, add them here + val item = session.capture()?.activeVariant?.data?.getUserData(KEY_Q_INLINE_ITEM_CONTEXT)?.item ?: return@row + val imports = item.mostRelevantMissingImports + val references = item.references + + if (!imports.isNullOrEmpty()) { + cell( + JLabel("${imports.size} imports").apply { + toolTipText = "${imports.joinToString("
") { it.statement }}" + } + ) + } + val components = CodeWhispererPopupManager.getInstance().popupComponents + if (!references.isNullOrEmpty()) { + cell(JLabel("Reference code under ")).customize(UnscaledGaps.EMPTY) + references.forEachIndexed { i, reference -> + cell(components.licenseLink(reference.licenseName)).customize(UnscaledGaps.EMPTY) + if (i == references.size - 1) return@forEachIndexed + cell(JLabel(", ")).customize(UnscaledGaps.EMPTY) + } + } + } + } + } + + private fun getDisplayVariantIndex(session: InlineCompletionSession, direction: Int) = + getCurrentValidVariantIndex(session, direction) + 1 + + private fun getCurrentValidVariantIndex(session: InlineCompletionSession, direction: Int): Int { + val variants = session.capture()?.variants ?: return -1 + // the variants snapshots at this point either 1) have elements(normal case) 2) have no elements but have + // data (trySend(elements) hasn't finished), so need to check both + val validVariants = variants.filter { isValidVariant(it) } + if (validVariants.isEmpty()) return 0 + return (validVariants.indexOfFirst { it.isActive } + direction + validVariants.size) % validVariants.size + } + + fun getAllValidVariantsCount(session: InlineCompletionSession) = + session.capture()?.variants?.count { isValidVariant(it) } ?: 0 + + private fun isValidVariant(variant: InlineCompletionVariant.Snapshot) = + ( + (!variant.isEmpty() && variant.elements.any { it.text.isNotEmpty() }) || + variant.data.getUserData(KEY_Q_INLINE_ITEM_CONTEXT)?.item != null + ) && + variant.state != InlineCompletionVariant.Snapshot.State.INVALIDATED + + companion object { + private const val MAX_CHANNELS = 5 + private val LOG = getLogger() + val Q_INLINE_PROVIDER_ID = InlineCompletionProviderID("Amazon Q") + val KEY_Q_INLINE_ITEM_CONTEXT = Key("amazon.q.inline.completion.item.context") + + val DATA_KEY_Q_AUTO_TRIGGER_INTELLISENSE = DataKey.create("amazon.q.auto.trigger.intellisense") + val KEY_Q_AUTO_TRIGGER_INTELLISENSE = Key("amazon.q.auto.trigger.intellisense") + + fun invokeCompletion(editor: Editor, isIntelliSenseAccept: Boolean = false) { + val event = getManualCallEvent(editor, isIntelliSenseAccept) + InlineCompletion.getHandlerOrNull(editor)?.invokeEvent(event) + } + + fun logInline(triggerSessionId: Int, e: Throwable? = null, block: () -> String) { + LOG.debug(e) { "Q inline: Trigger session $triggerSessionId: ${block()}" } + } + } + + private fun addQInlineCompletionListener( + project: Project, + editor: Editor, + session: InlineCompletionSession, + handler: InlineCompletionHandler, + triggerSessionId: Int, + latencyContext: LatencyContext, + sessionContext: InlineCompletionSessionContext, + ) { + handler.addEventListener( + object : InlineCompletionEventAdapter { + // when all computations are done + override fun onCompletion(event: InlineCompletionEventType.Completion) { + CodeWhispererInvocationStatus.getInstance().setIsInvokingQInline(session, false) + updateDisplayIndex(session) + logInline(triggerSessionId) { + "Session pagination progress is complete, now showing suggestions" + } + super.onCompletion(event) + } + + override fun onComputed(event: InlineCompletionEventType.Computed) { + updateDisplayIndex(session) + super.onComputed(event) + } + + override fun onShow(event: InlineCompletionEventType.Show) { + val isFirstTimeShowing = sessionContext.itemContexts.all { !it.hasSeen } + setSelectedVariantAsSeen(sessionContext, event.variantIndex) + if (isFirstTimeShowing) { + CodeWhispererInvocationStatus.getInstance().completionShown() + latencyContext.codewhispererEndToEndEnd = System.nanoTime() + + // For JB inline completion UX, no userInput + latencyContext.perceivedLatency = latencyContext.getCodeWhispererEndToEndLatency() + + logInline(triggerSessionId) { + "Session first time showing, perceived E2E latency: ${latencyContext.perceivedLatency}" + } + } + super.onShow(event) + } + + override fun onInvalidated(event: InlineCompletionEventType.Invalidated) { + updateDisplayIndex(session) + super.onInvalidated(event) + } + + override fun onEmpty(event: InlineCompletionEventType.Empty) { + setSelectedVariantAsDiscarded(sessionContext, event.variantIndex) + setSelectedVariantAsSeen(sessionContext, event.variantIndex, seen = false) + super.onEmpty(event) + } + + override fun onVariantSwitched(event: InlineCompletionEventType.VariantSwitched) { + updateDisplayIndex(session, event.toVariantIndex - event.fromVariantIndex) + setSelectedVariantAsSeen(sessionContext, event.toVariantIndex) + logInline(triggerSessionId) { + "Switching to variant ${event.toVariantIndex}" + } + super.onVariantSwitched(event) + } + + override fun onInsert(event: InlineCompletionEventType.Insert) { + currentAcceptedItemContext = session.capture()?.activeVariant?.data?.getUserData(KEY_Q_INLINE_ITEM_CONTEXT) + } + + override fun onHide(event: InlineCompletionEventType.Hide) { + logInline(triggerSessionId) { + "Exiting display session with reason: ${event.finishType}" + } + super.onHide(event) + when (event.finishType) { + InlineCompletionUsageTracker.ShownEvents.FinishType.CARET_CHANGED, + InlineCompletionUsageTracker.ShownEvents.FinishType.MOUSE_PRESSED, + InlineCompletionUsageTracker.ShownEvents.FinishType.EDITOR_REMOVED, + InlineCompletionUsageTracker.ShownEvents.FinishType.ESCAPE_PRESSED, + InlineCompletionUsageTracker.ShownEvents.FinishType.BACKSPACE_PRESSED, + -> { + setCurrentVariantAsRejected(session) + } + InlineCompletionUsageTracker.ShownEvents.FinishType.TYPED -> { + // TYPED finished type will not trigger insert hook from JB (to insert imports and references) so have to manually set and invoke here + currentAcceptedItemContext = session.capture()?.activeVariant?.data?.getUserData(KEY_Q_INLINE_ITEM_CONTEXT) + insertHandler.afterTyped(editor, sessionContext.triggerOffset) + setCurrentVariantAsAccepted(session) + } + InlineCompletionUsageTracker.ShownEvents.FinishType.EMPTY -> { + if (session.request.event.isManualCall()) { + runInEdt { + HintManager.getInstance().showInformationHint( + session.editor, + message("codewhisperer.popup.no_recommendations"), + HintManager.UNDER + ) + } + } + // language server returns EMPTY_RESULT, no need to send any telemetry. + } + InlineCompletionUsageTracker.ShownEvents.FinishType.INVALIDATED -> { + // TODO: for current version, always send discard event for invalidated sessions, language server + // also sends a discarded event on their side. In a future version + // we will support concurrent triggers first in language server and then here. + // as of now, for INVALIDATED JB can send discard to language server regardless + val atLeastOneSeen = sessionContext.itemContexts.any { it.hasSeen } + if (!atLeastOneSeen) { + repeat(sessionContext.itemContexts.size) { i -> + setSelectedVariantAsDiscarded(sessionContext, i) + } + } + } + InlineCompletionUsageTracker.ShownEvents.FinishType.SELECTED -> { + setCurrentVariantAsAccepted(session) + } + else -> { + logInline(triggerSessionId) { + "Closing session for unchecked reason: ${event.finishType}" + } + } + } + cs.launch { + CodeWhispererTelemetryService.getInstance().sendUserTriggerDecisionEventForTriggerSession( + project, + latencyContext, + sessionContext, + triggerSessionId, + ) + activeTriggerSessions.remove(triggerSessionId) + } + } + }, + session + ) + } + + private fun updateDisplayIndex(session: InlineCompletionSession, steps: Int = 0) { + cell?.text(indexDisplayText(session, steps)) + } + + private fun indexDisplayText(session: InlineCompletionSession, steps: Int = 0) = + "${getDisplayVariantIndex(session, steps)}/${getAllValidVariantsCount(session)}" + + private fun setCurrentVariantAsAccepted(session: InlineCompletionSession, isAccepted: Boolean = true) { + session.capture()?.activeVariant?.data?.getUserData(KEY_Q_INLINE_ITEM_CONTEXT)?.isAccepted = isAccepted + } + + private fun setCurrentVariantAsRejected(session: InlineCompletionSession) { + setCurrentVariantAsAccepted(session, false) + } + + private fun setSelectedVariantAsSeen(sessionContext: InlineCompletionSessionContext, index: Int, seen: Boolean = true) { + sessionContext.itemContexts[index].hasSeen = seen + } + + private fun setSelectedVariantAsDiscarded(sessionContext: InlineCompletionSessionContext, index: Int) { + sessionContext.itemContexts[index].isDiscarded = true + } + + private fun getTriggerTypeInfo(request: InlineCompletionRequest): TriggerTypeInfo { + val event = request.event + val triggerType = if (event.isManualCall()) CodewhispererTriggerType.OnDemand else CodewhispererTriggerType.AutoTrigger + val automatedTriggerType = when (triggerType) { + CodewhispererTriggerType.AutoTrigger -> { + val triggerString = request.document.charsSequence.substring(request.startOffset, request.endOffset) + if (triggerString.startsWith(System.lineSeparator())) { + CodeWhispererAutomatedTriggerType.Enter() + } else if (CodeWhispererConstants.SPECIAL_CHARACTERS_LIST.contains(triggerString)) { + CodeWhispererAutomatedTriggerType.SpecialChar(triggerString.single()) + } else if (event.isManualCall()) { + CodeWhispererAutomatedTriggerType.IntelliSense() + } else { + CodeWhispererAutomatedTriggerType.Classifier() + } + } + CodewhispererTriggerType.OnDemand, + CodewhispererTriggerType.Unknown, + -> { + CodeWhispererAutomatedTriggerType.Unknown() + } + } + return TriggerTypeInfo(triggerType, automatedTriggerType) + } + + override suspend fun getSuggestion(request: InlineCompletionRequest): InlineCompletionSuggestion { + val editor = request.editor + val document = editor.document + val project = editor.project ?: return InlineCompletionSuggestion.Empty + val handler = InlineCompletion.getHandlerOrNull(editor) ?: return InlineCompletionSuggestion.Empty + val session = InlineCompletionSession.getOrNull(editor) ?: return InlineCompletionSuggestion.Empty + val triggerSessionId = triggerSessionId++ + val latencyContext = LatencyContext(codewhispererEndToEndStart = System.nanoTime()) + val triggerTypeInfo = getTriggerTypeInfo(request) + + CodeWhispererInvocationStatus.getInstance().setIsInvokingQInline(session, true) + Disposer.register(session) { + CodeWhispererInvocationStatus.getInstance().setIsInvokingQInline(session, false) + } + + val sessionContext = InlineCompletionSessionContext(triggerOffset = request.endOffset) + + // Pagination workaround: Always return exactly 5 variants + // Create channel placeholder for upcoming pagination results + // this is the only known way paginated items can show later + repeat(MAX_CHANNELS) { + sessionContext.itemContexts.add(InlineCompletionItemContext(project, null, Channel(Channel.UNLIMITED))) + } + + activeTriggerSessions[triggerSessionId] = sessionContext + + addQInlineCompletionListener(project, editor, session, handler, triggerSessionId, latencyContext, sessionContext) + + runReadAction { + logInline(triggerSessionId) { + "Initializing trigger session, " + + "event type: ${request.event.javaClass.simpleName}, " + + "startOffset: ${request.startOffset}, " + + "endOffset: ${request.endOffset}, " + + "trigger text: ${document.charsSequence.subSequence(request.startOffset, request.endOffset)}, " + + "triggerTypeInfo: $triggerTypeInfo, " + + "left context of the current line: ${ + document.charsSequence.subSequence( + document.getLineStartOffset(document.getLineNumber(editor.caretModel.offset)), + editor.caretModel.offset + ) + }" + } + } + + try { + // Launch coroutine for background pagination progress + cs.launch { + var nextToken: Either? = null + do { + nextToken = startPaginationInBackground( + project, + editor, + triggerTypeInfo, + triggerSessionId, + nextToken, + sessionContext, + ) + } while (nextToken != null && !nextToken.left.isNullOrEmpty()) + + // closing all channels since pagination for this session has finished + logInline(triggerSessionId) { + "Pagination finished, closing all channels" + } + sessionContext.itemContexts.forEach { + it.channel.close() + } + if (session.context.isDisposed) { + logInline(triggerSessionId) { + "Current display session already disposed by a new trigger before pagination finishes, exiting" + } + } + } + + return object : InlineCompletionSuggestion { + override suspend fun getVariants(): List = + sessionContext.itemContexts.map { itemContext -> + itemContext.data.putUserData(KEY_Q_INLINE_ITEM_CONTEXT, itemContext) + InlineCompletionVariant.build(data = itemContext.data, elements = itemContext.channel.receiveAsFlow()) + } + } + } catch (e: Exception) { + logInline(triggerSessionId, e) { + "Error getting inline completion suggestion: ${e.message}" + } + if (e is CancellationException) { + throw e + } + return InlineCompletionSuggestion.Empty + } + } + + private suspend fun startPaginationInBackground( + project: Project, + editor: Editor, + triggerTypeInfo: TriggerTypeInfo, + triggerSessionId: Int, + nextToken: Either?, + sessionContext: InlineCompletionSessionContext, + ): Either? { + try { + logInline(triggerSessionId) { + "Fetching next paginated results with token: ${nextToken?.left}" + } + + val nextPageResult = AmazonQLspService.executeAsyncIfRunning(project) { server -> + val params = createInlineCompletionParams(editor, triggerTypeInfo, nextToken) + server.inlineCompletionWithReferences(params) + }?.await() ?: run { + logInline(triggerSessionId) { + "Received null completion response from LSP, stop pagination for the current session" + } + return null + } + + logInline(triggerSessionId) { + "Received ${nextPageResult.items.size} items from pagination with token: ${nextToken?.left}" + } + + // Update channels in order with new items from pagination + if (nextPageResult.sessionId.isEmpty()) { + activeTriggerSessions.remove(triggerSessionId) + logInline(triggerSessionId) { + "Ending pagination progress since LSP returned EMPTY_RESULT" + } + return null + } + + sessionContext.sessionId = nextPageResult.sessionId + nextPageResult.items.forEachIndexed { itemIndex, newItem -> + // Calculate which channel this item should go to, continue from where we left off + val channelIndex = sessionContext.counter % MAX_CHANNELS + if (channelIndex >= sessionContext.itemContexts.size) { + // service returned more than 5 suggestions, we don't have enough channel placeholders + return@forEachIndexed + } + sessionContext.itemContexts[channelIndex].item = newItem + val existingChannel = sessionContext.itemContexts[channelIndex].channel + + // try displaying the suggestions in the current session, if it's still valid + var discarded = false + val displayText = + runReadAction { + if (editor.caretModel.offset < sessionContext.triggerOffset) { + discarded = true + "" + } else { + val userInput = editor.document.charsSequence.subSequence(sessionContext.triggerOffset, editor.caretModel.offset) + if (newItem.insertText.startsWith(userInput)) { + newItem.insertText.substring(userInput.length) + } else { + discarded = true + "" + } + } + } + + if (newItem.insertText.isEmpty()) { + logInline(triggerSessionId) { + "Received variant ${channelIndex + 1} as an empty string" + } + } + + sessionContext.itemContexts[channelIndex].isDiscarded = discarded + val success = existingChannel.trySend(InlineCompletionGrayTextElement(displayText)) + logInline(triggerSessionId) { + "Adding paginated item '${newItem.itemId}' to channel $channelIndex, success: ${success.isSuccess}, discarded: $discarded" + } + sessionContext.counter++ + } + return nextPageResult.partialResultToken + } catch (e: Exception) { + logInline(triggerSessionId, e) { + "Error during pagination" + } + return null + } + } + + private fun createInlineCompletionParams( + editor: Editor, + triggerTypeInfo: TriggerTypeInfo, + nextToken: Either?, + ) = CodeWhispererService.getInstance().createInlineCompletionParams( + editor, + triggerTypeInfo, + nextToken + ) + + override fun isEnabled(event: InlineCompletionEvent): Boolean { + val request = event.toRequest() ?: return false + val editor = request.editor + val project = editor.project ?: return false + + if (!isQConnected(project)) return false + if (!CodeWhispererExplorerActionManager.getInstance().isAutoEnabled() && event.isManualCall()) return false + if (QRegionProfileManager.getInstance().hasValidConnectionButNoActiveProfile(project)) return false + return true + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionUtils.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionUtils.kt new file mode 100644 index 00000000000..59bd95663a5 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/QInlineCompletionUtils.kt @@ -0,0 +1,35 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.popup + +import com.intellij.codeInsight.inline.completion.session.InlineCompletionSession +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.Presentation +import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.actionSystem.impl.ActionButton +import com.intellij.openapi.project.DumbAware +import com.intellij.util.ui.JBUI + +fun navigationButton(session: InlineCompletionSession, direction: String): ActionButton { + val icon = if (direction == "←") AllIcons.Chooser.Left else AllIcons.Chooser.Right + val navigate = if (direction == "←") session::usePrevVariant else session::useNextVariant + return object : ActionButton( + object : AnAction(direction, null, icon), DumbAware { + override fun actionPerformed(e: AnActionEvent) { + navigate() + } + }, + Presentation().apply { + this.icon = icon + putClientProperty(ActionUtil.HIDE_DROPDOWN_ICON, true) + }, + ActionPlaces.EDITOR_POPUP, + JBUI.emptySize() + ) { + override fun isFocusable() = false + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt index 39644c1fd4f..3ba9003a51a 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt @@ -11,12 +11,8 @@ import com.intellij.openapi.editor.Editor import com.intellij.util.Alarm import com.intellij.util.AlarmFactory import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import org.apache.commons.collections4.queue.CircularFifoQueue -import software.aws.toolkits.jetbrains.core.coroutines.EDT -import software.aws.toolkits.jetbrains.core.coroutines.applicationCoroutineScope import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService -import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext import software.aws.toolkits.telemetry.CodewhispererPreviousSuggestionState import software.aws.toolkits.telemetry.CodewhispererTriggerType import java.time.Duration @@ -37,7 +33,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa } // real auto trigger logic - fun invoke(editor: Editor, triggerType: CodeWhispererAutomatedTriggerType): Job? { + fun invoke(editor: Editor): Job? { timeAtLastCharTyped = System.nanoTime() if (!( if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) { @@ -53,15 +49,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa lastInvocationTime = Instant.now() lastInvocationLineNum = runReadAction { editor.caretModel.visualPosition.line } - val latencyContext = LatencyContext().apply { - codewhispererEndToEndStart = System.nanoTime() - } - - val coroutineScope = applicationCoroutineScope() - - return coroutineScope.launch(EDT) { - performAutomatedTriggerAction(editor, triggerType, latencyContext) - } + return null } private fun scheduleReset() { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutomatedTriggerType.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutomatedTriggerType.kt index fbb006ca402..b4b4e556b9e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutomatedTriggerType.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutomatedTriggerType.kt @@ -20,4 +20,12 @@ sealed class CodeWhispererAutomatedTriggerType( class IdleTime : CodeWhispererAutomatedTriggerType(CodewhispererAutomatedTriggerType.IdleTime) class Unknown : CodeWhispererAutomatedTriggerType(CodewhispererAutomatedTriggerType.Unknown) + + override fun toString(): String { + val toString = when (this) { + is SpecialChar -> "SpecialChar($specialChar)" + else -> telemetryType.toString() + } + return toString + } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt index c58c4b4a466..69408ea4c5f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt @@ -3,6 +3,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service +import com.intellij.codeInsight.inline.completion.session.InlineCompletionSession import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.components.service @@ -15,7 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean @Service class CodeWhispererInvocationStatus { - private val isInvokingCodeWhisperer: AtomicBoolean = AtomicBoolean(false) + private val isInvokingQInline: AtomicBoolean = AtomicBoolean(false) private var invokingSessionId: String? = null private var timeAtLastInvocationComplete: Instant? = null var timeAtLastDocumentChanged: Instant = Instant.now() @@ -24,9 +25,18 @@ class CodeWhispererInvocationStatus { private var timeAtLastInvocationStart: Instant? = null var completionShownTime: Instant? = null private set + private var currentSession: InlineCompletionSession? = null + + fun setIsInvokingQInline(session: InlineCompletionSession, value: Boolean) { + if (!value && currentSession != session) return + currentSession = session + isInvokingQInline.set(value) + // TODO: set true or false? prob doesn't matter + ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(true) + } fun checkExistingInvocationAndSet(): Boolean = - if (isInvokingCodeWhisperer.getAndSet(true)) { + if (isInvokingQInline.getAndSet(true)) { LOG.debug { "Have existing CodeWhisperer invocation, sessionId: $invokingSessionId" } true } else { @@ -35,10 +45,10 @@ class CodeWhispererInvocationStatus { false } - fun hasExistingServiceInvocation(): Boolean = isInvokingCodeWhisperer.get() + fun hasExistingServiceInvocation(): Boolean = isInvokingQInline.get() fun finishInvocation() { - if (isInvokingCodeWhisperer.compareAndSet(true, false)) { + if (isInvokingQInline.compareAndSet(true, false)) { ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(false) LOG.debug { "Ending CodeWhisperer invocation" } invokingSessionId = null diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt index 9a62fcff440..44e19c9cf7f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt @@ -38,7 +38,7 @@ import software.aws.toolkits.jetbrains.core.coroutines.EDT import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.GetConfigurationFromServerParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LspServerConfigurations @@ -446,7 +446,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val caretPosition = runReadAction { getCaretPosition(editor) } // 4. connection - val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) + val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) // 5. customization val customizationArn = CodeWhispererModelConfigurator.getInstance().activeCustomization(project)?.arn diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt index 42c557b8b2a..81713e2ce47 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt @@ -40,7 +40,7 @@ import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionContext import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionListWithReferences @@ -532,7 +532,7 @@ class CodeWhispererServiceNew(private val cs: CoroutineScope) : Disposable { val caretPosition = runReadAction { getCaretPosition(editor) } // 4. connection - val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) + val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) return RequestContextNew(project, editor, triggerTypeInfo, caretPosition, fileContext, connection) } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt index dd10d1747b2..70ac9c104b2 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt @@ -326,6 +326,6 @@ class CodeWhispererConfigurable(private val project: Project) : } companion object { - private const val Q_INLINE_KEYBINDING_SEARCH_TEXT = "Amazon Q" + private const val Q_INLINE_KEYBINDING_SEARCH_TEXT = "Inline Proposal" } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererIntelliSenseAutoTriggerListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererIntelliSenseAutoTriggerListener.kt index 4609e14cdcf..798187d703a 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererIntelliSenseAutoTriggerListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererIntelliSenseAutoTriggerListener.kt @@ -11,8 +11,7 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl import com.intellij.openapi.application.runReadAction import com.intellij.openapi.observable.util.addMouseHoverListener import com.intellij.ui.hover.HoverListener -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.QInlineCompletionProvider import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew import java.awt.Component @@ -29,7 +28,7 @@ object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener { } // Classifier - CodeWhispererAutoTriggerService.getInstance().invoke(editor, CodeWhispererAutomatedTriggerType.IntelliSense()) + QInlineCompletionProvider.invokeCompletion(editor, isIntelliSenseAccept = true) cleanup() } override fun lookupCanceled(event: LookupEvent) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt index 80aa16950ac..2cd3197ebbc 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupSettingsListener.kt @@ -13,7 +13,7 @@ import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator @@ -58,7 +58,7 @@ class CodeWhispererProjectStartupSettingsListener(private val project: Project) CodeWhispererCodeScanManager.getInstance(project).removeCodeScanUI() } - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance())?.let { + ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { // re-check the allowlist status CodeWhispererModelConfigurator.getInstance().shouldDisplayCustomNode(project, forceUpdate = true) } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt index f2bccb3beef..06ce031c52b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt @@ -16,8 +16,10 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LogInlineC import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.model.CodeScanTelemetryEvent +import software.aws.toolkits.jetbrains.services.codewhisperer.model.InlineCompletionSessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.QInlineCompletionProvider import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCodeWhispererStartUrl @@ -68,6 +70,53 @@ class CodeWhispererTelemetryService(private val cs: CoroutineScope) { } } + suspend fun sendUserTriggerDecisionEventForTriggerSession( + project: Project, + latencyContext: LatencyContext, + sessionContext: InlineCompletionSessionContext, + triggerSessionId: Int, + ) { + if (sessionContext.sessionId.isEmpty()) { + QInlineCompletionProvider.logInline(triggerSessionId) { + "Did not receive a valid sessionId from language server, skipping telemetry" + } + return + } + QInlineCompletionProvider.logInline(triggerSessionId) { + "Sending UserTriggerDecision for ${sessionContext.sessionId}:" + } + sessionContext.itemContexts.forEachIndexed { i, itemContext -> + QInlineCompletionProvider.logInline(triggerSessionId) { + "Index: $i, item: ${itemContext.item?.itemId}, seen: ${itemContext.hasSeen}, " + + "accepted: ${itemContext.isAccepted}, discarded: ${itemContext.isDiscarded}" + } + } + QInlineCompletionProvider.logInline(triggerSessionId) { + "Perceived latency: ${latencyContext.perceivedLatency}, " + + "total session display time: ${CodeWhispererInvocationStatus.getInstance().completionShownTime?.let { Duration.between(it, Instant.now()) } + ?.toMillis()?.toDouble()}" + } + val params = LogInlineCompletionSessionResultsParams( + sessionId = sessionContext.sessionId, + completionSessionResult = sessionContext.itemContexts.filter { it.item != null }.associate { + it.item?.itemId.orEmpty() to InlineCompletionStates( + seen = it.hasSeen, + accepted = it.isAccepted, + discarded = it.isDiscarded + ) + }, + firstCompletionDisplayLatency = latencyContext.perceivedLatency, + totalSessionDisplayTime = CodeWhispererInvocationStatus.getInstance().completionShownTime?.let { Duration.between(it, Instant.now()) } + ?.toMillis()?.toDouble(), + // no userInput in JB inline completion API, every new char input will discard the previous trigger so + // user input is always 0 + typeaheadLength = 0 + ) + AmazonQLspService.executeAsyncIfRunning(project) { server -> + server.logInlineCompletionSessionResults(params) + } + } + private fun mapToTelemetryScope(codeAnalysisScope: CodeWhispererConstants.CodeAnalysisScope, initiatedByChat: Boolean): CodewhispererCodeScanScope = when (codeAnalysisScope) { CodeWhispererConstants.CodeAnalysisScope.FILE -> { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceManager.kt index 7981197416b..cd984ee7d61 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceManager.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.util.Key import com.intellij.openapi.util.TextRange import com.intellij.openapi.wm.ToolWindowManager import com.intellij.ui.awt.RelativePoint +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionItem import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionReference import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getPopupPositionAboveText import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getRelativePathToContentRoot @@ -68,6 +69,10 @@ class CodeWhispererCodeReferenceManager(private val project: Project) { fun insertCodeReference(originalCode: String, references: List?, editor: Editor, caretPosition: CaretPosition) { val startOffset = caretPosition.offset + insertCodeReference(originalCode, references, editor, startOffset) + } + + fun insertCodeReference(originalCode: String, references: List?, editor: Editor, startOffset: Int) { val relativePath = getRelativePathToContentRoot(editor) references?.forEachIndexed { i, reference -> // TODO YUX: validate this @@ -122,6 +127,10 @@ class CodeWhispererCodeReferenceManager(private val project: Project) { ) } + fun insertCodeReference(editor: Editor, item: InlineCompletionItem?, offset: Int) { + insertCodeReference(item?.insertText.orEmpty(), item?.references, editor, offset) + } + fun getReferenceLineNums(editor: Editor, start: Int, end: Int): String { val startLine = editor.document.getLineNumber(start) val endLine = editor.document.getLineNumber(end) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt index 20682bfbe96..d6fe3ba7019 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt @@ -30,7 +30,7 @@ import software.aws.toolkits.jetbrains.core.credentials.ReauthSource import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.maybeReauthProviderIfNeeded -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded import software.aws.toolkits.jetbrains.core.credentials.sono.isSono import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider @@ -62,7 +62,7 @@ import java.nio.file.Paths // 1. It will be sent for Builder ID users, only if they have optin telemetry sharing. // 2. It will be sent for IdC users, regardless of telemetry optout status. fun runIfIdcConnectionOrTelemetryEnabled(project: Project, callback: (connection: ToolkitConnection) -> Unit) = - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance())?.let { + ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { runIfIdcConnectionOrTelemetryEnabled(it, callback) } @@ -279,14 +279,14 @@ object CodeWhispererUtil { fun getCodeWhispererStartUrl(project: Project): String? { val connection = ToolkitConnectionManager.getInstance( project - ).activeConnectionForFeature(CodeWhispererConnection.getInstance()) as? AwsBearerTokenConnection? + ).activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection? return connection?.startUrl } private fun tokenConnection(project: Project) = ( ToolkitConnectionManager .getInstance(project) - .activeConnectionForFeature(CodeWhispererConnection.getInstance()) as? AwsBearerTokenConnection + .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection ) private fun tokenProvider(project: Project) = @@ -296,7 +296,7 @@ object CodeWhispererUtil { ?.delegate as? BearerTokenProvider fun reconnectCodeWhisperer(project: Project) { - val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) + val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) if (connection !is ManagedBearerSsoConnection) return pluginAwareExecuteOnPooledThread { reauthConnectionIfNeeded(project, connection, isReAuth = true, reauthSource = ReauthSource.CODEWHISPERER) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererAcceptTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererAcceptTest.kt index eecab3fc0a6..14b5f330b03 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererAcceptTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererAcceptTest.kt @@ -3,13 +3,12 @@ package software.aws.toolkits.jetbrains.services.codewhisperer -import com.intellij.ide.DataManager import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.junit.Before +import org.junit.Ignore import org.junit.Test import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionItem import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.textDocument.InlineCompletionListWithReferences @@ -17,13 +16,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestU import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.javaResponse import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.javaTestContext import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.testNextToken -import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererActionPromoter import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.QInlineActionId.qInlineAcceptActionId -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.QInlineActionId.qInlineForceAcceptActionId -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.QInlineActionId.qInlineNavigateNextActionId -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.QInlineActionId.qInlineNavigatePrevActionId -import kotlin.test.fail +@Ignore("This test suite needs a rewrite for JB inline completion API") class CodeWhispererAcceptTest : CodeWhispererTestBase() { @Before @@ -110,33 +105,6 @@ class CodeWhispererAcceptTest : CodeWhispererTestBase() { testAcceptRecommendationWithTypingAndMatchingBracketsOnTheRight("", "({test") } - @Test - fun `test CodeWhisperer keyboard shortcuts should be prioritized to be executed`() { - testCodeWhispererKeyboardShortcutShouldBePrioritized(qInlineAcceptActionId) - testCodeWhispererKeyboardShortcutShouldBePrioritized(qInlineNavigateNextActionId) - testCodeWhispererKeyboardShortcutShouldBePrioritized(qInlineNavigatePrevActionId) - testCodeWhispererKeyboardShortcutShouldBePrioritized(qInlineForceAcceptActionId) - } - - private fun testCodeWhispererKeyboardShortcutShouldBePrioritized(actionId: String) { - projectRule.fixture.configureByText(javaFileName, javaTestContext) - val wrongAction = object : AnAction() { - override fun actionPerformed(e: AnActionEvent) { - fail("the other action should not get executed first") - } - } - withCodeWhispererServiceInvokedAndWait { - val codeWhispererAction = ActionManager.getInstance().getAction(actionId) - val actions = listOf(wrongAction, codeWhispererAction) - val newActions = CodeWhispererActionPromoter().promote( - actions.toMutableList(), - DataManager.getInstance().getDataContext(projectRule.fixture.editor.contentComponent) - ) - assertThat(newActions[0]).isEqualTo(codeWhispererAction) - popupManagerSpy.popupComponents.acceptButton.doClick() - } - } - private fun testAcceptRecommendationWithTypingAndMatchingBracketsOnTheRight( typing: String, brackets: String, diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt index e73f8191a40..8330cd75c1d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererExplorerActionManagerTest.kt @@ -22,8 +22,8 @@ import software.aws.toolkits.jetbrains.core.MockClientManagerRule import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoConnection import software.aws.toolkits.jetbrains.core.credentials.MockToolkitAuthManagerRule import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.ConnectionPinningManager +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL import software.aws.toolkits.jetbrains.core.credentials.sso.DeviceAuthorizationGrantToken @@ -114,7 +114,7 @@ class CodeWhispererExplorerActionManagerTest { expectedIsCwEnabled = true, expectedIsCwExpired = false ) - assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(CodeWhispererConnection.getInstance())).isFalse + assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(QConnection.getInstance())).isFalse assertConnectionState( startUrl = SONO_URL, @@ -125,7 +125,7 @@ class CodeWhispererExplorerActionManagerTest { expectedIsCwEnabled = true, expectedIsCwExpired = true ) - assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(CodeWhispererConnection.getInstance())).isFalse + assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(QConnection.getInstance())).isFalse assertConnectionState( startUrl = SONO_URL, @@ -136,7 +136,7 @@ class CodeWhispererExplorerActionManagerTest { expectedIsCwEnabled = false, expectedIsCwExpired = false ) - assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(CodeWhispererConnection.getInstance())).isFalse + assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(QConnection.getInstance())).isFalse assertConnectionState( startUrl = aString(), @@ -147,7 +147,7 @@ class CodeWhispererExplorerActionManagerTest { expectedIsCwEnabled = true, expectedIsCwExpired = false ) - assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(CodeWhispererConnection.getInstance())).isFalse + assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(QConnection.getInstance())).isFalse assertConnectionState( startUrl = aString(), @@ -158,7 +158,7 @@ class CodeWhispererExplorerActionManagerTest { expectedIsCwEnabled = true, expectedIsCwExpired = true ) - assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(CodeWhispererConnection.getInstance())).isFalse + assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(QConnection.getInstance())).isFalse assertConnectionState( startUrl = aString(), @@ -169,7 +169,7 @@ class CodeWhispererExplorerActionManagerTest { expectedIsCwEnabled = false, expectedIsCwExpired = false ) - assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(CodeWhispererConnection.getInstance())).isFalse + assertThat(ConnectionPinningManager.getInstance().isFeaturePinned(QConnection.getInstance())).isFalse } @SuppressWarnings("UnusedParameter") @@ -205,10 +205,10 @@ class CodeWhispererExplorerActionManagerTest { ) ToolkitConnectionManager.getInstance(project).switchConnection(myConnection) - val activeCwConn = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) + val activateQConn = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) val myTokenProvider = myConnection.getConnectionSettings().tokenProvider.delegate as InteractiveBearerTokenProvider - assertThat(activeCwConn).isEqualTo(myConnection) + assertThat(activateQConn).isEqualTo(myConnection) assertThat(myTokenProvider.state()).isEqualTo(expectedState) assertThat(CodeWhispererExplorerActionManager.getInstance().checkActiveCodeWhispererConnectionType(project)).isEqualTo(expectedLoginType) assertThat(isCodeWhispererEnabled(project)).isEqualTo(expectedIsCwEnabled) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt index de3c11d6616..bb79a6608e1 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererModelConfiguratorTest.kt @@ -35,7 +35,7 @@ import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoCo import software.aws.toolkits.jetbrains.core.credentials.MockCredentialManagerRule import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerState -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_REGION import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL @@ -416,7 +416,7 @@ class CodeWhispererModelConfiguratorTest { val builderIdConn = LegacyManagedBearerSsoConnection(region = SONO_REGION, startUrl = SONO_URL, scopes = Q_SCOPES) connectionManager.switchConnection(builderIdConn) - assertThat(connectionManager.activeConnectionForFeature(CodeWhispererConnection.getInstance()).isSono()).isTrue + assertThat(connectionManager.activeConnectionForFeature(QConnection.getInstance()).isSono()).isTrue val actual = sut.listCustomizations(projectRule.project) assertThat(actual).isNull() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererNavigationTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererNavigationTest.kt index a470ff97e6c..df99209aef3 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererNavigationTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererNavigationTest.kt @@ -6,12 +6,14 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnActionEvent import org.assertj.core.api.Assertions.assertThat +import org.junit.Ignore import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.QInlineActionId.qInlineNavigateNextActionId import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.QInlineActionId.qInlineNavigatePrevActionId import javax.swing.JButton +@Ignore("This test suite needs a rewrite for JB inline completion API") class CodeWhispererNavigationTest : CodeWhispererTestBase() { @Test diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererStateTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererStateTest.kt index 3588cc4f707..ded8e057e46 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererStateTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererStateTest.kt @@ -5,6 +5,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.openapi.util.TextRange import org.assertj.core.api.Assertions.assertThat +import org.junit.Ignore import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse @@ -12,6 +13,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.telemetry.CodewhispererLanguage import software.aws.toolkits.telemetry.CodewhispererTriggerType +@Ignore("This test suite needs a rewrite for JB inline completion API") class CodeWhispererStateTest : CodeWhispererTestBase() { @Test diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTypeaheadTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTypeaheadTest.kt index ae2cb710687..d2f3e7585cd 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTypeaheadTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTypeaheadTest.kt @@ -5,10 +5,12 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat +import org.junit.Ignore import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext +@Ignore("This test suite needs a rewrite for JB inline completion API") class CodeWhispererTypeaheadTest : CodeWhispererTestBase() { @Test diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserActionsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserActionsTest.kt index abb05f74778..0f0f0a0913e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserActionsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserActionsTest.kt @@ -18,6 +18,7 @@ import com.intellij.openapi.ui.popup.JBPopup import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.mockito.Mockito.times import org.mockito.kotlin.any @@ -33,6 +34,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.Co import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus import java.awt.Rectangle +@Ignore("This test suite needs a rewrite for JB inline completion API") class CodeWhispererUserActionsTest : CodeWhispererTestBase() { @Before diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserInputTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserInputTest.kt index b2e086dc4ef..e0a62cf6761 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserInputTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUserInputTest.kt @@ -4,9 +4,11 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import org.assertj.core.api.Assertions.assertThat +import org.junit.Ignore import org.junit.Test import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse +@Ignore("This test suite needs a rewrite for JB inline completion API") class CodeWhispererUserInputTest : CodeWhispererTestBase() { @Test diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt index 55b6c46d7ef..516047bf209 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt @@ -12,12 +12,12 @@ import software.amazon.awssdk.services.codewhispererruntime.model.OperatingSyste import software.amazon.awssdk.services.codewhispererruntime.model.UserContext import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sono.isSono import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata fun calculateIfIamIdentityCenterConnection(project: Project, calculationTask: (connection: ToolkitConnection) -> T): T? = - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance())?.let { + ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { calculateIfIamIdentityCenterConnection(it, calculationTask) } @@ -29,7 +29,7 @@ fun calculateIfIamIdentityCenterConnection(connection: ToolkitConnection, ca } fun calculateIfBIDConnection(project: Project, calculationTask: (connection: ToolkitConnection) -> T): T? = - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance())?.let { + ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { if (it.isSono()) { calculationTask(it) } else { diff --git a/plugins/core/jetbrains-community/resources/META-INF/module-core.xml b/plugins/core/jetbrains-community/resources/META-INF/module-core.xml index 72b1c4f84da..e03f58657d8 100644 --- a/plugins/core/jetbrains-community/resources/META-INF/module-core.xml +++ b/plugins/core/jetbrains-community/resources/META-INF/module-core.xml @@ -9,7 +9,6 @@ -
diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/pinning/CodeWhispererConnection.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/pinning/CodeWhispererConnection.kt deleted file mode 100644 index 9bb50dcb454..00000000000 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/pinning/CodeWhispererConnection.kt +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.core.credentials.pinning - -import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection -import software.aws.toolkits.jetbrains.core.credentials.sono.CODEWHISPERER_SCOPES - -@Deprecated("pending removal, merge into QConnection & QSCOPES") -class CodeWhispererConnection : FeatureWithPinnedConnection { - override val featureId = "aws.codewhisperer" - override val featureName = "CodeWhisperer" - - override fun supportsConnectionType(connection: ToolkitConnection): Boolean { - if (connection is AwsBearerTokenConnection) { - return CODEWHISPERER_SCOPES.all { it in connection.scopes } - } - - return false - } - - companion object { - fun getInstance() = FeatureWithPinnedConnection.EP_NAME.findExtensionOrFail(CodeWhispererConnection::class.java) - } -} diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt index 52ba8eb02cb..cc62bf04203 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt @@ -11,7 +11,6 @@ import software.aws.toolkits.jetbrains.core.credentials.ProfileSsoManagedBearerS import software.aws.toolkits.jetbrains.core.credentials.ReauthSource import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.loginSso -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES @@ -131,9 +130,9 @@ fun requestCredentialsForQ( isReauth: Boolean, ): Boolean { // try to scope upgrade if we have a codewhisperer connection - val codeWhispererConnection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) - if (codeWhispererConnection is LegacyManagedBearerSsoConnection) { - codeWhispererConnection.let { + val qConnection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) + if (qConnection is LegacyManagedBearerSsoConnection) { + qConnection.let { return tryOrNull { loginSso(project, it.startUrl, it.region, Q_SCOPES) } != null @@ -141,7 +140,7 @@ fun requestCredentialsForQ( } val dialogState = SetupAuthenticationDialogState().apply { - (codeWhispererConnection as? ProfileSsoManagedBearerSsoConnection)?.let { connection -> + (qConnection as? ProfileSsoManagedBearerSsoConnection)?.let { connection -> idcTabState.apply { profileName = connection.configSessionName startUrl = connection.startUrl diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt index 34d545f7971..e1905109032 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanelUtils.kt @@ -15,7 +15,6 @@ import software.aws.toolkits.jetbrains.core.credentials.ToolkitAuthManager import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.lazyIsUnauthedBearerConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeCatalystConnection -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.profiles.SsoSessionConstants import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL @@ -30,7 +29,6 @@ enum class ActiveConnectionType { } enum class BearerTokenFeatureSet { - CODEWHISPERER, CODECATALYST, Q, } @@ -86,9 +84,6 @@ fun checkBearerConnectionValidity(project: Project, source: BearerTokenFeatureSe if (connections.isEmpty()) return ActiveConnection.NotConnected val activeConnection = when (source) { - BearerTokenFeatureSet.CODEWHISPERER -> ToolkitConnectionManager.getInstance(project).activeConnectionForFeature( - CodeWhispererConnection.getInstance() - ) BearerTokenFeatureSet.CODECATALYST -> ToolkitConnectionManager.getInstance(project).activeConnectionForFeature( CodeCatalystConnection.getInstance() ) @@ -141,7 +136,6 @@ fun checkConnectionValidity(project: Project): ActiveConnection { val tokenFeatureSets = listOf( BearerTokenFeatureSet.CODECATALYST, BearerTokenFeatureSet.Q, - BearerTokenFeatureSet.CODEWHISPERER ) var result = checkIamConnectionValidity(project) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt index 99ea1207ee9..03ae0e55e6d 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedTelemetryUtils.kt @@ -39,13 +39,6 @@ fun getEnabledConnectionsForTelemetry(project: Project?): Set { AuthFormId.BUILDERID_CODECATALYST ) - addConnectionInfoToSet( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER), - enabledConnections, - AuthFormId.IDENTITYCENTER_CODEWHISPERER, - AuthFormId.BUILDERID_CODEWHISPERER - ) - addConnectionInfoToSet( checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q), enabledConnections, @@ -137,8 +130,6 @@ enum class AuthFormId { IDENTITYCENTER_EXPLORER, BUILDERID_CODECATALYST, IDENTITYCENTER_CODECATALYST, - BUILDERID_CODEWHISPERER, - IDENTITYCENTER_CODEWHISPERER, BUILDERID_Q, IDENTITYCENTER_Q, UNKNOWN, diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/OpenTelemetryAction.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/OpenTelemetryAction.kt index b0abe63c5ed..ef0eeae1d9d 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/OpenTelemetryAction.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/OpenTelemetryAction.kt @@ -14,12 +14,18 @@ import com.intellij.openapi.project.DefaultProjectFactory import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.ui.FrameWrapper import com.intellij.openapi.util.Disposer +import com.intellij.ui.DocumentAdapter import com.intellij.ui.JBColor +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBTextField import com.intellij.util.ui.components.BorderLayoutPanel import software.aws.toolkits.core.telemetry.MetricEvent import software.aws.toolkits.jetbrains.isDeveloperMode +import java.awt.BorderLayout import javax.swing.BorderFactory import javax.swing.JComponent +import javax.swing.JPanel +import javax.swing.event.DocumentEvent class OpenTelemetryAction : DumbAwareAction() { override fun actionPerformed(event: AnActionEvent) { @@ -38,6 +44,8 @@ class OpenTelemetryAction : DumbAwareAction() { setViewer(true) }.console } + private val telemetryEvents = mutableListOf() + private var currentFilter = "" init { title = "Telemetry Viewer" @@ -48,11 +56,20 @@ class OpenTelemetryAction : DumbAwareAction() { val panel = BorderLayoutPanel() val consoleComponent = consoleView.component + // Add search/filter bar at the top + val filterPanel = JPanel(BorderLayout()) + val filterField = JBTextField().apply { + emptyText.text = "Filter telemetry events..." + } + filterPanel.add(JBLabel("Filter: "), BorderLayout.WEST) + filterPanel.add(filterField, BorderLayout.CENTER) + val actionGroup = DefaultActionGroup(*consoleView.createConsoleActions()) val toolbar = ActionManager.getInstance().createActionToolbar("AWS.TelemetryViewer", actionGroup, false) toolbar.setTargetComponent(consoleComponent) + panel.addToTop(filterPanel) panel.addToLeft(toolbar.component) panel.addToCenter(consoleComponent) @@ -64,11 +81,32 @@ class OpenTelemetryAction : DumbAwareAction() { Disposer.register(this) { telemetryService.removeListener(this) } Disposer.register(this, consoleView) + // Implement filtering logic + filterField.document.addDocumentListener(object : DocumentAdapter() { + override fun textChanged(e: DocumentEvent) { + // Filter console content based on filterField.text + applyFilter(filterField.text) + } + }) + return panel } + private fun applyFilter(filterText: String) { + currentFilter = filterText + consoleView.clear() + telemetryEvents.filter { + it.toString().contains(filterText, ignoreCase = true) + }.forEach { event -> + consoleView.print(event.toString() + "\n", ConsoleViewContentType.NORMAL_OUTPUT) + } + } + override fun onTelemetryEvent(event: MetricEvent) { - consoleView.print(event.toString() + "\n", ConsoleViewContentType.NORMAL_OUTPUT) + telemetryEvents.add(event) + if (event.toString().contains(currentFilter, ignoreCase = true)) { + consoleView.print(event.toString() + "\n", ConsoleViewContentType.NORMAL_OUTPUT) + } } } } diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/FunctionUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/FunctionUtils.kt index 0ba09a91f9f..5eb148010b7 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/FunctionUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/FunctionUtils.kt @@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory import software.aws.toolkits.core.utils.debug import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider @@ -42,21 +41,19 @@ suspend fun pollFor(func: () -> T): T? { fun isQConnected(project: Project): Boolean { val manager = ToolkitConnectionManager.getInstance(project) val qState = manager.connectionStateForFeature(QConnection.getInstance()) - val cwState = manager.connectionStateForFeature(CodeWhispererConnection.getInstance()) LOG.debug { - "qConnectionState: $qState; cwConnectionState: $cwState" + "qConnectionState: $qState" } - return qState != BearerTokenAuthState.NOT_AUTHENTICATED && cwState != BearerTokenAuthState.NOT_AUTHENTICATED + return qState != BearerTokenAuthState.NOT_AUTHENTICATED } fun isQExpired(project: Project): Boolean { val manager = ToolkitConnectionManager.getInstance(project) val qState = manager.connectionStateForFeature(QConnection.getInstance()) - val cwState = manager.connectionStateForFeature(CodeWhispererConnection.getInstance()) LOG.debug { - "qConnectionState: $qState; cwConnectionState: $cwState" + "qConnectionState: $qState" } - return qState == BearerTokenAuthState.NEEDS_REFRESH || cwState == BearerTokenAuthState.NEEDS_REFRESH + return qState == BearerTokenAuthState.NEEDS_REFRESH } fun AwsBearerTokenConnection.state(): BearerTokenAuthState = diff --git a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt b/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt index 7d1c55e021f..414614db6b5 100644 --- a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt +++ b/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt @@ -843,7 +843,7 @@ class GettingStartedPanel( ) } } - }.visible(checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) is ActiveConnection.NotConnected) + }.visible(checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) is ActiveConnection.NotConnected) panelConnectionInProgress = panel { row { @@ -879,16 +879,16 @@ class GettingStartedPanel( row { label(message("gettingstarted.auth.connected.builderid")).applyToComponent { this.icon = PanelConstants.CHECKMARK_ICON } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.BUILDER_ID + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.BUILDER_ID ) row { label(message("gettingstarted.auth.connected.idc")).applyToComponent { this.icon = PanelConstants.CHECKMARK_ICON } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.IAM_IDC + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.IAM_IDC ) row { link(message("toolkit.login.aws_builder_id.already_connected.reconnect")) { - val validConnection = checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) + val validConnection = checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) val connection = validConnection.activeConnectionBearer if (connection is ProfileSsoManagedBearerSsoConnection) { @@ -924,7 +924,7 @@ class GettingStartedPanel( ) } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.BUILDER_ID + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.BUILDER_ID ) row { text("${message("codewhisperer.gettingstarted.panel.login_button")}") { @@ -942,9 +942,9 @@ class GettingStartedPanel( ) } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.IAM_IDC + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.IAM_IDC ) - }.visible(checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) is ActiveConnection.ValidBearer) + }.visible(checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) is ActiveConnection.ValidBearer) panelReauthenticationRequired = panel { row { @@ -971,16 +971,16 @@ class GettingStartedPanel( row { label(message("gettingstarted.auth.builderid.expired")).applyToComponent { this.icon = PanelConstants.X_ICON } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.BUILDER_ID + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.BUILDER_ID ) row { label(message("gettingstarted.auth.idc.expired")).applyToComponent { this.icon = PanelConstants.X_ICON } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.IAM_IDC + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.IAM_IDC ) row { link(message("toolkit.login.aws_builder_id.already_connected.reconnect")) { - val validConnection = checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) + val validConnection = checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) val connection = validConnection.activeConnectionBearer if (connection is ProfileSsoManagedBearerSsoConnection) { if (validConnection.connectionType == ActiveConnectionType.IAM_IDC) { @@ -1009,7 +1009,7 @@ class GettingStartedPanel( ) } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.BUILDER_ID + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.BUILDER_ID ) row { text("${message("codewhisperer.gettingstarted.panel.login_button")}") { @@ -1027,9 +1027,9 @@ class GettingStartedPanel( ) } }.visible( - checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER).connectionType == ActiveConnectionType.IAM_IDC + checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q).connectionType == ActiveConnectionType.IAM_IDC ) - }.visible(checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) is ActiveConnection.ExpiredBearer) + }.visible(checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) is ActiveConnection.ExpiredBearer) } }.apply { isOpaque = false diff --git a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt b/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt index c0031a00e2f..d0b12a79236 100644 --- a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt +++ b/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/startup/QMigrationActivity.kt @@ -21,7 +21,6 @@ import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.jetbrains.AwsToolkit import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.services.amazonq.QConstants import software.aws.toolkits.jetbrains.settings.AwsSettings @@ -50,9 +49,8 @@ class QMigrationActivity : StartupActivity.DumbAware { private fun displayQMigrationInfo(project: Project) { if (AwsSettings.getInstance().isQMigrationNotificationShownOnce) return - val hasUsedCodeWhisperer = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance()) != null val hasUsedQ = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) != null - if (hasUsedCodeWhisperer || hasUsedQ) { + if (hasUsedQ) { // do auto-install installQPlugin(project, autoInstall = true) } else if (!PluginManager.isPluginInstalled(PluginId.getId(AwsToolkit.Q_PLUGIN_ID))) {