From 870d5a0f97a82025ba9e51668fac0d4eb5197066 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 29 Aug 2024 12:52:51 -0700 Subject: [PATCH 01/11] auto trigger + popup changes without telemetry --- .../actions/CodeWhispererActionPromoter.kt | 18 +- .../editor/CodeWhispererEditorManager.kt | 20 +- .../editor/CodeWhispererEditorUtil.kt | 2 +- .../editor/CodeWhispererTypedHandler.kt | 7 +- .../importadder/CodeWhispererImportAdder.kt | 15 +- .../CodeWhispererImportAdderListener.kt | 7 +- .../inlay/CodeWhispererInlayManager.kt | 13 +- .../codewhisperer/model/CodeWhispererModel.kt | 66 +- .../popup/CodeWhispererPopupListener.kt | 8 +- .../popup/CodeWhispererPopupManager.kt | 617 +++++++++++++----- .../popup/CodeWhispererUIChangeListener.kt | 37 +- .../handlers/CodeWhispererPopupTabHandler.kt | 14 +- ...CodeWhispererAcceptButtonActionListener.kt | 5 +- .../listeners/CodeWhispererScrollListener.kt | 5 +- .../CodeWhispererAutoTriggerService.kt | 25 +- .../service/CodeWhispererInvocationStatus.kt | 1 + .../CodeWhispererRecommendationManager.kt | 6 +- .../service/CodeWhispererService.kt | 197 ++++-- ...hispererIntelliSenseAutoTriggerListener.kt | 16 + .../CodeWhispererCodeCoverageTracker.kt | 5 +- .../CodeWhispererTelemetryService.kt | 96 +-- ...odeWhispererCodeReferenceActionListener.kt | 7 +- .../CodeWhispererCodeReferenceManager.kt | 17 +- .../util/CodeWhispererConstants.kt | 4 +- .../CodeWhispererTelemetryTest.kt | 2 +- .../codewhisperer/CodeWhispererTestBase.kt | 4 +- .../CodeWhispererUserActionsTest.kt | 2 +- 27 files changed, 860 insertions(+), 356 deletions(-) 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 index 692d1ceb5e5..0c0cd4dcf69 100644 --- 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 @@ -14,15 +14,15 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.Cod class CodeWhispererActionPromoter : ActionPromoter { override fun promote(actions: MutableList, context: DataContext): MutableList { val results = actions.toMutableList() - results.sortWith { a, b -> - if (isCodeWhispererPopupAction(a)) { - return@sortWith -1 - } else if (isCodeWhispererPopupAction(b)) { - return@sortWith 1 - } else { - 0 - } - } +// results.sortWith { a, b -> +// if (isCodeWhispererPopupAction(a)) { +// return@sortWith -1 +// } else if (isCodeWhispererPopupAction(b)) { +// return@sortWith 1 +// } else { +// 0 +// } +// } return results } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt index b7353364c9a..2d551eeea53 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt @@ -10,10 +10,13 @@ import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager +import groovy.lang.Tuple3 +import groovy.lang.Tuple4 import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager +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.util.CaretMovement import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_BRACKETS @@ -24,16 +27,23 @@ import java.util.Stack @Service class CodeWhispererEditorManager { fun updateEditorWithRecommendation(states: InvocationContext, sessionContext: SessionContext) { - val (requestContext, responseContext, recommendationContext) = states + val (requestContext, responseContext) = states val (project, editor) = requestContext val document = editor.document val primaryCaret = editor.caretModel.primaryCaret val selectedIndex = sessionContext.selectedIndex - val typeahead = sessionContext.typeahead - val detail = recommendationContext.details[selectedIndex] + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) + } + } + val typeahead = details[selectedIndex].v3 + val detail = details[selectedIndex].v1 + val userInput = details[selectedIndex].v2 val reformatted = CodeWhispererPopupManager.getInstance().getReformattedRecommendation( detail, - recommendationContext.userInputSinceInvocation + userInput ) val remainingRecommendation = reformatted.substring(typeahead.length) val originalOffset = primaryCaret.offset - typeahead.length @@ -67,7 +77,7 @@ class CodeWhispererEditorManager { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED, - ).afterAccept(states, sessionContext, rangeMarker) + ).afterAccept(states, details, sessionContext, rangeMarker) } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt index e699e97e856..6fec203cffb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt @@ -95,7 +95,7 @@ object CodeWhispererEditorUtil { } fun shouldSkipInvokingBasedOnRightContext(editor: Editor): Boolean { - val caretContext = runReadAction { CodeWhispererEditorUtil.extractCaretContext(editor) } + val caretContext = runReadAction { extractCaretContext(editor) } val rightContextLines = caretContext.rightFileContext.split(Regex("\r?\n")) val rightContextCurrentLine = if (rightContextLines.isEmpty()) "" else rightContextLines[0] 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 index 18845694094..bcc48644992 100644 --- 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 @@ -14,12 +14,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants class CodeWhispererTypedHandler : TypedHandlerDelegate() { - private var triggerOnIdle: Job? = null override fun charTyped(c: Char, project: Project, editor: Editor, psiFiles: PsiFile): Result { - triggerOnIdle?.cancel() - - if (shouldSkipInvokingBasedOnRightContext(editor) - ) { +// println("try triggering at character ${c}") + if (shouldSkipInvokingBasedOnRightContext(editor)) { return Result.CONTINUE } 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 ce56ea8fc60..7c627065353 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 @@ -8,21 +8,30 @@ import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import groovy.lang.Tuple3 +import groovy.lang.Tuple4 import software.amazon.awssdk.services.codewhispererruntime.model.Import import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService abstract class CodeWhispererImportAdder { abstract val supportedLanguages: List abstract val dummyFileName: String - fun insertImportStatements(states: InvocationContext, sessionContext: SessionContext) { - val imports = states.recommendationContext.details[sessionContext.selectedIndex] - .recommendation.mostRelevantMissingImports() + fun insertImportStatements(states: InvocationContext, details: List>, sessionContext: SessionContext) { +// val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> +// val context = element.recommendationContext +// context.details.map { +// Tuple4(it, context.userInputSinceInvocation, context.typeaheadOriginal, context.typeahead) +// } +// } + val imports = details[sessionContext.selectedIndex].v1.recommendation.mostRelevantMissingImports() LOG.info { "Adding ${imports.size} imports for completions, sessionId: ${states.responseContext.sessionId}" } imports.forEach { insertImportStatement(states, it) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt index 0a2f8110b5d..cdfd594f0a5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt @@ -4,8 +4,11 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.importadder import com.intellij.openapi.editor.RangeMarker +import groovy.lang.Tuple3 +import groovy.lang.Tuple4 import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger +import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener @@ -13,7 +16,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhisp object CodeWhispererImportAdderListener : CodeWhispererUserActionListener { internal val LOG = getLogger() - override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) { + override fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) { if (!CodeWhispererSettings.getInstance().isImportAdderEnabled()) { LOG.debug { "Import adder not enabled in user settings" } return @@ -28,6 +31,6 @@ object CodeWhispererImportAdderListener : CodeWhispererUserActionListener { LOG.debug { "No import adder found for $language" } return } - importAdder.insertImportStatements(states, sessionContext) + importAdder.insertImportStatements(states, details, sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt index e72269ce2f7..8e38b443805 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt @@ -13,21 +13,22 @@ import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.util.Disposer import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext @Service class CodeWhispererInlayManager { private val existingInlays = mutableListOf>() - fun updateInlays(states: InvocationContext, chunks: List) { - val editor = states.requestContext.editor + fun updateInlays(states: InvocationContext, sessionContext: SessionContext, chunks: List) { clearInlays() chunks.forEach { chunk -> - createCodeWhispererInlays(editor, chunk.inlayOffset, chunk.text, states.popup) + createCodeWhispererInlays(states, sessionContext, chunk.inlayOffset, chunk.text) } } - private fun createCodeWhispererInlays(editor: Editor, startOffset: Int, inlayText: String, popup: JBPopup) { + private fun createCodeWhispererInlays(states: InvocationContext, sessionContext: SessionContext, startOffset: Int, inlayText: String) { if (inlayText.isEmpty()) return + val editor = states.requestContext.editor val firstNewlineIndex = inlayText.indexOf("\n") val firstLine: String val otherLines: String @@ -49,7 +50,7 @@ class CodeWhispererInlayManager { val inlineInlay = editor.inlayModel.addInlineElement(startOffset, true, firstLineRenderer) inlineInlay?.let { existingInlays.add(it) - Disposer.register(popup, it) + Disposer.register(states, it) } } @@ -73,7 +74,7 @@ class CodeWhispererInlayManager { ) blockInlay?.let { existingInlays.add(it) - Disposer.register(popup, it) + Disposer.register(sessionContext, it) } } } 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 33cf5d77878..d54d0a24ff3 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 @@ -6,14 +6,18 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.model import com.intellij.openapi.Disposable import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.markup.RangeHighlighter -import com.intellij.openapi.ui.popup.JBPopup +import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.concurrency.annotations.RequiresEdt import software.amazon.awssdk.services.codewhispererruntime.model.Completion import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsResponse import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager 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.service.RequestContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants @@ -81,10 +85,13 @@ data class SupplementalContextInfo( } data class RecommendationContext( - val details: List, + val details: MutableList, val userInputOriginal: String, val userInputSinceInvocation: String, - val position: VisualPosition + val position: VisualPosition, + val jobId: Int, +// var typeahead: String = "", + var typeaheadOriginal: String = "", ) data class DetailContext( @@ -98,14 +105,35 @@ data class DetailContext( ) data class SessionContext( - val typeahead: String = "", - val typeaheadOriginal: String = "", - val selectedIndex: Int = 0, + var selectedIndex: Int = 0, val seen: MutableSet = mutableSetOf(), - val isFirstTimeShowingPopup: Boolean = true, + var isFirstTimeShowingPopup: Boolean = true, var toBeRemovedHighlighter: RangeHighlighter? = null, - var insertEndOffset: Int = -1 -) + var insertEndOffset: Int = -1, + var popupDisplayOffset: Int = -1 +) : Disposable { + private var isDisposed = false + + @RequiresEdt + override fun dispose() { + println("disposing the session") + val jobIds = CodeWhispererService.getInstance().ongoingRequests.keys.toList() + jobIds.forEach { jobId -> + val job = CodeWhispererService.getInstance().ongoingRequests[jobId] ?: return@forEach + Disposer.dispose(job) + } + CodeWhispererService.getInstance().ongoingRequests.clear() + CodeWhispererService.getInstance().ongoingRequestsContext.clear() + CodeWhispererPopupManager.getInstance().cancelPopup() + CodeWhispererPopupManager.getInstance().sessionContext = null + CodeWhispererInvocationStatus.getInstance().finishInvocation() + println("current ongoingRequestContext:") + CodeWhispererService.getInstance().ongoingRequestsContext.keys.forEach { print("$it ") } + isDisposed = true + } + + fun isDisposed() = isDisposed +} data class RecommendationChunk( val text: String, @@ -124,16 +152,30 @@ data class InvocationContext( val requestContext: RequestContext, val responseContext: ResponseContext, val recommendationContext: RecommendationContext, - val popup: JBPopup ) : Disposable { - override fun dispose() {} + private var isDisposed = false + + @RequiresEdt + override fun dispose() { + +// CodeWhispererPopupManager.getInstance().cancelPopup() + CodeWhispererService.getInstance().ongoingRequests.remove(recommendationContext.jobId) + CodeWhispererService.getInstance().ongoingRequestsContext.remove(recommendationContext.jobId) + + // TODO: send userTriggerDecision telemetry + + println("state for jobId ${recommendationContext.jobId} is disposed") + isDisposed = true + } + + fun isDisposed() = isDisposed } data class WorkerContext( val requestContext: RequestContext, val responseContext: ResponseContext, val response: GenerateCompletionsResponse, - val popup: JBPopup +// val popup: JBPopup ) data class CodeScanTelemetryEvent( diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt index c485c52acbe..00e3400db86 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt @@ -5,13 +5,16 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup import com.intellij.openapi.ui.popup.JBPopupListener import com.intellij.openapi.ui.popup.LightweightWindowEvent +import com.intellij.openapi.util.Disposer import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext 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 java.time.Duration import java.time.Instant -class CodeWhispererPopupListener(private val states: InvocationContext) : JBPopupListener { +class CodeWhispererPopupListener(private val states: InvocationContext, private val sessionContext: SessionContext) : JBPopupListener { override fun beforeShown(event: LightweightWindowEvent) { super.beforeShown(event) CodeWhispererInvocationStatus.getInstance().setPopupStartTimestamp() @@ -24,11 +27,12 @@ class CodeWhispererPopupListener(private val states: InvocationContext) : JBPopu requestContext, responseContext, recommendationContext, - CodeWhispererPopupManager.getInstance().sessionContext, + sessionContext, event.isOk, CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) } ) CodeWhispererInvocationStatus.getInstance().setPopupActive(false) + Disposer.dispose(sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index c5ad37e5271..3f6be398c4c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -27,6 +27,8 @@ import com.intellij.openapi.editor.event.CaretEvent import com.intellij.openapi.editor.event.CaretListener import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.editor.event.EditorMouseEvent +import com.intellij.openapi.editor.event.EditorMouseMotionListener import com.intellij.openapi.editor.event.SelectionEvent import com.intellij.openapi.editor.event.SelectionListener import com.intellij.openapi.fileEditor.FileEditorManager @@ -39,8 +41,11 @@ import com.intellij.ui.ComponentUtil import com.intellij.ui.awt.RelativePoint import com.intellij.ui.popup.AbstractPopup import com.intellij.ui.popup.PopupFactoryImpl +import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.messages.Topic import com.intellij.util.ui.UIUtil +import com.jetbrains.rd.swing.awtMousePoint +import groovy.lang.Tuple3 import software.amazon.awssdk.services.codewhispererruntime.model.Import import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.aws.toolkits.core.utils.debug @@ -65,6 +70,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.Co import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererPrevButtonActionListener import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererScrollListener 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.CodeWhispererColorUtil.POPUP_DIM_HEX @@ -85,8 +91,7 @@ class CodeWhispererPopupManager { var shouldListenerCancelPopup: Boolean = true private set - var sessionContext = SessionContext() - private set + var sessionContext: SessionContext? = null private var myPopup: JBPopup? = null @@ -115,40 +120,21 @@ class CodeWhispererPopupManager { ) } - fun changeStates( - states: InvocationContext, - indexChange: Int, - typeaheadChange: String, - typeaheadAdded: Boolean, - recommendationAdded: Boolean = false - ) { - val (_, _, recommendationContext, popup) = states - val (details) = recommendationContext - if (recommendationAdded) { - LOG.debug { - "Add recommendations to the existing CodeWhisperer session, current number of recommendations: ${details.size}" + @RequiresEdt + fun changeStatesForNavigation(states: InvocationContext, indexChange: Int) { + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + Triple(it, context.userInputSinceInvocation, context.typeaheadOriginal) } - ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED) - .recommendationAdded(states, sessionContext) - return } - val typeaheadOriginal = - if (typeaheadAdded) { - sessionContext.typeaheadOriginal + typeaheadChange - } else { - if (typeaheadChange.length > sessionContext.typeaheadOriginal.length) { - cancelPopup(popup) - return - } - sessionContext.typeaheadOriginal.substring( - 0, - sessionContext.typeaheadOriginal.length - typeaheadChange.length - ) - } + var sessionContext = sessionContext ?: SessionContext() + this.sessionContext = sessionContext + val isReverse = indexChange < 0 val userInput = states.recommendationContext.userInputSinceInvocation - val validCount = getValidCount(details, userInput, typeaheadOriginal) - val validSelectedIndex = getValidSelectedIndex(details, userInput, sessionContext.selectedIndex, typeaheadOriginal) + val validCount = getValidCount(emptyList(), userInput, "") + val validSelectedIndex = getValidSelectedIndex(emptyList(), userInput, sessionContext.selectedIndex, "") if ((validSelectedIndex == validCount - 1 && indexChange == 1) || (validSelectedIndex == 0 && indexChange == -1) ) { @@ -156,26 +142,76 @@ class CodeWhispererPopupManager { } val selectedIndex = findNewSelectedIndex( isReverse, - details, - userInput, - sessionContext.selectedIndex + indexChange, - typeaheadOriginal + sessionContext.selectedIndex + indexChange ) - if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex], userInput, typeaheadOriginal)) { + if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex].first, details[selectedIndex].second, details[selectedIndex].third)) { LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } - cancelPopup(popup) + Disposer.dispose(sessionContext) return } - val typeahead = resolveTypeahead(states, selectedIndex, typeaheadOriginal) - val isFirstTimeShowingPopup = indexChange == 0 && typeaheadChange.isEmpty() - sessionContext = SessionContext( - typeahead, - typeaheadOriginal, - selectedIndex, - sessionContext.seen, - isFirstTimeShowingPopup, - sessionContext.toBeRemovedHighlighter + + sessionContext.selectedIndex = selectedIndex + sessionContext.isFirstTimeShowingPopup = false + + ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( + states, + sessionContext ) + } + + @RequiresEdt + fun changeStatesForTypeahead( + states: InvocationContext, + typeaheadChange: String, + typeaheadAdded: Boolean + ) { + var sessionContext = sessionContext ?: SessionContext() + this.sessionContext = sessionContext + val recos = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull() + val userInput = states.recommendationContext.userInputSinceInvocation + + sessionContext.selectedIndex + recos.forEach { + val typeaheadOriginal = + if (typeaheadAdded) { + it.recommendationContext.typeaheadOriginal + typeaheadChange + } else { + if (typeaheadChange.length > it.recommendationContext.typeaheadOriginal.length) { + Disposer.dispose(sessionContext) + println("exit 7, ") + return + } + it.recommendationContext.typeaheadOriginal.substring( + 0, + it.recommendationContext.typeaheadOriginal.length - typeaheadChange.length + ) + } + it.recommendationContext.typeaheadOriginal = typeaheadOriginal + } + val isReverse = false + val selectedIndex = findNewSelectedIndex( + isReverse, + sessionContext.selectedIndex + ) + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + element.recommendationContext.details.map { + Triple(it, element.recommendationContext.userInputSinceInvocation, element.recommendationContext.typeaheadOriginal) + } + } + if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex].first, details[selectedIndex].second, details[selectedIndex].third)) { + LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } + Disposer.dispose(sessionContext) +// cancelPopup() + return + } + +// recos.forEach { +// it.recommendationContext.typeahead = resolveTypeahead(it, details, selectedIndex, it.recommendationContext.typeaheadOriginal) +// } +// sessionContext.typeahead = resolveTypeahead(states, selectedIndex, typeaheadOriginal) +// sessionContext.typeaheadOriginal = typeaheadOriginal + sessionContext.selectedIndex = selectedIndex + sessionContext.isFirstTimeShowingPopup = false ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( states, @@ -183,8 +219,116 @@ class CodeWhispererPopupManager { ) } - private fun resolveTypeahead(states: InvocationContext, selectedIndex: Int, typeahead: String): String { - val recommendation = states.recommendationContext.details[selectedIndex].reformatted.content() + @RequiresEdt + fun changeStatesForShowing( + states: InvocationContext, + recommendationAdded: Boolean = false + ) { + var sessionContext = sessionContext ?: SessionContext() + this.sessionContext = sessionContext + if (recommendationAdded) { +// LOG.debug { +// "Add recommendations to the existing CodeWhisperer session, current number of recommendations: ${details.size}" +// } + ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED) + .recommendationAdded(states, sessionContext) + return + } + val isReverse = false + + val selectedIndex = findNewSelectedIndex( + isReverse, + sessionContext.selectedIndex, + ) + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + element.recommendationContext.details.map { + Triple(it, element.recommendationContext.userInputSinceInvocation, element.recommendationContext.typeaheadOriginal) + } + } + if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex].first, details[selectedIndex].second, details[selectedIndex].third)) { + LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } + Disposer.dispose(sessionContext) +// cancelPopup() + return + } + + sessionContext.selectedIndex = selectedIndex + sessionContext.isFirstTimeShowingPopup = true + if (sessionContext.popupDisplayOffset == -1) { + sessionContext.popupDisplayOffset = states.requestContext.editor.caretModel.offset + } + + ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( + states, + sessionContext + ) + } + +// fun changeStates( +// states: InvocationContext, +// indexChange: Int, +// typeaheadChange: String, +// typeaheadAdded: Boolean, +// recommendationAdded: Boolean = false +// ) { +// val (_, _, recommendationContext) = states +// val (details) = recommendationContext +// var sessionContext = sessionContext ?: SessionContext() +// if (recommendationAdded) { +// LOG.debug { +// "Add recommendations to the existing CodeWhisperer session, current number of recommendations: ${details.size}" +// } +// ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED) +// .recommendationAdded(states, sessionContext) +// return +// } +// val typeaheadOriginal = +// if (typeaheadAdded) { +// recommendationContext.typeaheadOriginal + typeaheadChange +// } else { +// if (typeaheadChange.length > recommendationContext.typeaheadOriginal.length) { +// Disposer.dispose(sessionContext) +// println("exit 7, ") +// return +// } +// sessionContext.typeaheadOriginal.substring( +// 0, +// sessionContext.typeaheadOriginal.length - typeaheadChange.length +// ) +// } +// val isReverse = indexChange < 0 +// val userInput = states.recommendationContext.userInputSinceInvocation +// val validCount = getValidCount(details, userInput, typeaheadOriginal) +// val validSelectedIndex = getValidSelectedIndex(details, userInput, sessionContext.selectedIndex, typeaheadOriginal) +// if ((validSelectedIndex == validCount - 1 && indexChange == 1) || +// (validSelectedIndex == 0 && indexChange == -1) +// ) { +// return +// } +// val selectedIndex = findNewSelectedIndex( +// isReverse, +// sessionContext.selectedIndex + indexChange +// ) +// if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex], userInput, typeaheadOriginal)) { +// LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } +// Disposer.dispose(sessionContext) +// return +// } +// +// sessionContext.typeahead = resolveTypeahead(states, selectedIndex, typeaheadOriginal) +// sessionContext.typeaheadOriginal = typeaheadOriginal +// sessionContext.selectedIndex = selectedIndex +// sessionContext.isFirstTimeShowingPopup = indexChange == 0 && typeaheadChange.isEmpty() +// +// ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( +// states, +// sessionContext +// ) +// this.sessionContext = sessionContext +// } + + private fun resolveTypeahead(states: InvocationContext, details: List>, selectedIndex: Int, typeahead: String): String { + val recommendation = details[selectedIndex].first.reformatted.content() val userInput = states.recommendationContext.userInputSinceInvocation var indexOfFirstNonWhiteSpace = typeahead.indexOfFirst { !it.isWhitespace() } if (indexOfFirstNonWhiteSpace == -1) { @@ -198,17 +342,24 @@ class CodeWhispererPopupManager { return typeahead } - fun updatePopupPanel(states: InvocationContext, sessionContext: SessionContext) { + fun updatePopupPanel(states: InvocationContext?, sessionContext: SessionContext?) { + if (states == null || sessionContext == null) return val userInput = states.recommendationContext.userInputSinceInvocation - val details = states.recommendationContext.details +// val details = states.recommendationContext.details val selectedIndex = sessionContext.selectedIndex - val typeaheadOriginal = sessionContext.typeaheadOriginal - val validCount = getValidCount(details, userInput, typeaheadOriginal) - val validSelectedIndex = getValidSelectedIndex(details, userInput, selectedIndex, typeaheadOriginal) + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) + } + } + val typeaheadOriginal = details[selectedIndex].v3 + val validCount = getValidCount(emptyList(), userInput, typeaheadOriginal) + val validSelectedIndex = getValidSelectedIndex(emptyList(), userInput, selectedIndex, typeaheadOriginal) updateSelectedRecommendationLabelText(validSelectedIndex, validCount) updateNavigationPanel(validSelectedIndex, validCount) - updateImportPanel(details[selectedIndex].recommendation.mostRelevantMissingImports()) - updateCodeReferencePanel(states.requestContext.project, details[selectedIndex].recommendation.references()) + updateImportPanel(details[selectedIndex].v1.recommendation.mostRelevantMissingImports()) + updateCodeReferencePanel(states.requestContext.project, details[selectedIndex].v1.recommendation.references()) } fun render( @@ -220,7 +371,6 @@ class CodeWhispererPopupManager { ) { updatePopupPanel(states, sessionContext) - val caretPoint = states.requestContext.editor.offsetToXY(states.requestContext.caretPosition.offset) sessionContext.seen.add(sessionContext.selectedIndex) // There are four cases that render() is called: @@ -233,7 +383,7 @@ class CodeWhispererPopupManager { // 4. User navigating through the completions or typing as the completion shows. We should not update the latency // end time and should not emit any events in this case. if (!isRecommendationAdded) { - showPopup(states, sessionContext, states.popup, caretPoint, overlappingLinesCount) + showPopup(states, sessionContext) if (!isScrolling) { states.requestContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime() states.requestContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime() @@ -257,49 +407,67 @@ class CodeWhispererPopupManager { } } - fun reset() { - sessionContext = SessionContext() - } - - fun cancelPopup(popup: JBPopup) { - popup.cancel() - Disposer.dispose(popup) + fun resetSession() { + sessionContext?.let { + Disposer.dispose(it) + } + sessionContext = null } - fun closePopup(popup: JBPopup) { - popup.closeOk(null) - Disposer.dispose(popup) + fun cancelPopup() { + myPopup?.let { + it.cancel() + Disposer.dispose(it) + } + myPopup = null } fun closePopup() { - myPopup?.let { + myPopup?.let { it.closeOk(null) Disposer.dispose(it) } + myPopup = null } fun showPopup( states: InvocationContext, sessionContext: SessionContext, - popup: JBPopup, - p: Point, - overlappingLinesCount: Int + force: Boolean = false, ) { +// popup = initPopup() + val p = states.requestContext.editor.offsetToXY(sessionContext.popupDisplayOffset) + var popup: JBPopup? = null + if (myPopup == null) { + popup = initPopup() + initPopupListener(states, sessionContext, popup) + } else { + popup = myPopup + } +// val popup = myPopup + if (popup == null) { + val a = 1 + } val editor = states.requestContext.editor - val detailContexts = states.recommendationContext.details +// val detailContexts = states.recommendationContext.details val userInputOriginal = states.recommendationContext.userInputOriginal - val userInput = states.recommendationContext.userInputSinceInvocation +// val userInput = states.recommendationContext.userInputSinceInvocation val selectedIndex = sessionContext.selectedIndex - val typeaheadOriginal = sessionContext.typeaheadOriginal - val typeahead = sessionContext.typeahead + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) + } + } + val detail = details[selectedIndex].v1 +// val typeahead = details[selectedIndex].v4 val userInputLines = userInputOriginal.split("\n").size - 1 - val lineCount = getReformattedRecommendation(detailContexts[selectedIndex], userInput).split("\n").size - val additionalLines = typeaheadOriginal.split("\n").size - typeahead.split("\n").size +// val lineCount = getReformattedRecommendation(detail, userInput).split("\n").size val popupSize = (popup as AbstractPopup).preferredContentSize - val yBelowLastLine = p.y + (lineCount + additionalLines + userInputLines - overlappingLinesCount) * editor.lineHeight - val yAboveFirstLine = p.y - popupSize.height + (additionalLines + userInputLines) * editor.lineHeight +// val yBelowLastLine = p.y + (lineCount + additionalLines + userInputLines - overlappingLinesCount) * editor.lineHeight + val yAboveFirstLine = p.y - popupSize.height + userInputLines * editor.lineHeight + val popupRect = Rectangle(p.x, yAboveFirstLine, popupSize.width, popupSize.height) val editorRect = editor.scrollingModel.visibleArea - var popupRect = Rectangle(p.x, yBelowLastLine, popupSize.width, popupSize.height) var shouldHidePopup = false CodeWhispererInvocationStatus.getInstance().setPopupActive(true) @@ -310,31 +478,24 @@ class CodeWhispererPopupManager { } else { FileEditorManager.getInstance(states.requestContext.project).selectedTextEditorWithRemotes.firstOrNull() == editor } - if (!isSameEditorAsTrigger) { + if (!isSameEditorAsTrigger && false) { LOG.debug { "Current editor no longer has focus, not showing the popup" } - cancelPopup(popup) + Disposer.dispose(sessionContext) +// cancelPopup() return } - val popupLocation = - if (!editorRect.contains(popupRect)) { - popupRect = Rectangle(p.x, yAboveFirstLine, popupSize.width, popupSize.height) - if (!editorRect.contains(popupRect)) { - // both popup location (below last line and above first line) don't work, so don't show the popup - shouldHidePopup = true - } - LOG.debug { - "Show popup above the first line of recommendation. " + - "Editor position: $editorRect, popup position: $popupRect" - } - Point(p.x, yAboveFirstLine) - } else { - LOG.debug { - "Show popup below the last line of recommendation. " + - "Editor position: $editorRect, popup position: $popupRect" - } - Point(p.x, yBelowLastLine) + if (!editorRect.contains(popupRect)) { + // popup location above first line don't work, so don't show the popup + shouldHidePopup = true + } else { + LOG.debug { + "Show popup above the first line of recommendation. " + + "Editor position: $editorRect, popup position: $popupRect" } + } + + val popupLocation = Point(p.x, yAboveFirstLine) val relativePopupLocationToEditor = RelativePoint(editor.contentComponent, popupLocation) @@ -347,7 +508,9 @@ class CodeWhispererPopupManager { } } else { if (!AppMode.isRemoteDevHost()) { - popup.show(relativePopupLocationToEditor) + if (force && !shouldHidePopup) { + popup.show(relativePopupLocationToEditor) + } } else { // TODO: For now, the popup will always display below the suggestions, without checking // if the location the popup is about to show at stays in the editor window or not, due to @@ -365,28 +528,51 @@ class CodeWhispererPopupManager { } val perceivedLatency = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged() CodeWhispererTelemetryService.getInstance().sendPerceivedLatencyEvent( - detailContexts[selectedIndex].requestId, + detail.requestId, states.requestContext, states.responseContext, perceivedLatency ) } + val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) + + if (a != null) { + val alpha = if (force) 0.8f else 0f + WindowManager.getInstance().setAlphaModeRatio(a, alpha) + } + // popup.popupWindow is null in remote host if (!AppMode.isRemoteDevHost()) { - if (shouldHidePopup) { - WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 1f) - } else { + if (force) { WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 0.1f) + } else { + if (shouldHidePopup) { + popup.popupWindow?.let { + WindowManager.getInstance().setAlphaModeRatio(it, 1f) + } + } else { +// WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 1f) + } } } } + fun hidePopup(editor: Editor) { + val popupWindow = (myPopup as AbstractPopup?)?.popupWindow ?: return + WindowManager.getInstance().setAlphaModeRatio(popupWindow, 1f) + + val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) + if (a != null) { + WindowManager.getInstance().setAlphaModeRatio(a, 0f) + } + } + fun initPopup(): JBPopup = JBPopupFactory.getInstance() .createComponentPopupBuilder(popupComponents.panel, null) .setAlpha(0.1F) .setCancelOnClickOutside(true) - .setCancelOnOtherWindowOpen(true) +// .setCancelOnOtherWindowOpen(true) .setCancelKeyEnabled(true) .setCancelOnWindowDeactivation(true) .createPopup().also { @@ -396,40 +582,43 @@ class CodeWhispererPopupManager { fun getReformattedRecommendation(detailContext: DetailContext, userInput: String) = detailContext.reformatted.content().substring(userInput.length) - fun initPopupListener(states: InvocationContext) { - addPopupListener(states) - states.requestContext.editor.scrollingModel.addVisibleAreaListener(CodeWhispererScrollListener(states), states) - addButtonActionListeners(states) - addMessageSubscribers(states) - setPopupActionHandlers(states) - addComponentListeners(states) + fun initPopupListener(states: InvocationContext, sessionContext: SessionContext, popup: JBPopup) { + addPopupListener(states, sessionContext, popup) + states.requestContext.editor.scrollingModel.addVisibleAreaListener(CodeWhispererScrollListener(states, sessionContext), sessionContext) + addButtonActionListeners(states, sessionContext) + addMessageSubscribers(states, sessionContext) + setPopupActionHandlers(states, sessionContext) + addComponentListeners(states, sessionContext) } - private fun addPopupListener(states: InvocationContext) { - val listener = CodeWhispererPopupListener(states) - states.popup.addListener(listener) - Disposer.register(states) { states.popup.removeListener(listener) } + private fun addPopupListener(states: InvocationContext, sessionContext: SessionContext, popup: JBPopup) { + val listener = CodeWhispererPopupListener(states, sessionContext) + popup.addListener(listener) + Disposer.register(popup) { + println("listener is removed") + popup.removeListener(listener) + } } - private fun addMessageSubscribers(states: InvocationContext) { - val connect = ApplicationManager.getApplication().messageBus.connect(states) + private fun addMessageSubscribers(states: InvocationContext, sessionContext: SessionContext) { + val connect = ApplicationManager.getApplication().messageBus.connect(sessionContext) connect.subscribe( CODEWHISPERER_USER_ACTION_PERFORMED, object : CodeWhispererUserActionListener { override fun navigateNext(states: InvocationContext) { - changeStates(states, 1, "", true) + changeStatesForNavigation(states, 1) } override fun navigatePrevious(states: InvocationContext) { - changeStates(states, -1, "", true) + changeStatesForNavigation(states, -1) } override fun backspace(states: InvocationContext, diff: String) { - changeStates(states, 0, diff, false) + changeStatesForTypeahead(states, diff, false) } override fun enter(states: InvocationContext, diff: String) { - changeStates(states, 0, diff, true) + changeStatesForTypeahead(states, diff, true) } override fun type(states: InvocationContext, diff: String) { @@ -442,73 +631,88 @@ class CodeWhispererPopupManager { document.deleteString(caretOffset, caretOffset + 1) } } - changeStates(states, 0, diff, true) + changeStatesForTypeahead(states, diff, true) } override fun beforeAccept(states: InvocationContext, sessionContext: SessionContext) { dontClosePopupAndRun { CodeWhispererEditorManager.getInstance().updateEditorWithRecommendation(states, sessionContext) } - closePopup(states.popup) + closePopup() } } ) } - private fun addButtonActionListeners(states: InvocationContext) { - popupComponents.prevButton.addButtonActionListener(CodeWhispererPrevButtonActionListener(states)) - popupComponents.nextButton.addButtonActionListener(CodeWhispererNextButtonActionListener(states)) - popupComponents.acceptButton.addButtonActionListener(CodeWhispererAcceptButtonActionListener(states)) + private fun addButtonActionListeners(states: InvocationContext, sessionContext: SessionContext) { + popupComponents.prevButton.addButtonActionListener(CodeWhispererPrevButtonActionListener(states), sessionContext) + popupComponents.nextButton.addButtonActionListener(CodeWhispererNextButtonActionListener(states), sessionContext) + popupComponents.acceptButton.addButtonActionListener(CodeWhispererAcceptButtonActionListener(states, sessionContext), sessionContext) } - private fun JButton.addButtonActionListener(listener: CodeWhispererActionListener) { + private fun JButton.addButtonActionListener(listener: CodeWhispererActionListener, sessionContext: SessionContext) { this.addActionListener(listener) - Disposer.register(listener.states) { this.removeActionListener(listener) } + Disposer.register(sessionContext) { this.removeActionListener(listener) } } - private fun setPopupActionHandlers(states: InvocationContext) { + private fun setPopupActionHandlers(states: InvocationContext, sessionContext: SessionContext) { val actionManager = EditorActionManager.getInstance() - setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, states)) - setPopupActionHandler(ACTION_EDITOR_TAB, CodeWhispererPopupTabHandler(states)) - setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_LEFT, CodeWhispererPopupLeftArrowHandler(states)) - setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_RIGHT, CodeWhispererPopupRightArrowHandler(states)) + setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, states), sessionContext) + setPopupActionHandler(ACTION_EDITOR_TAB, CodeWhispererPopupTabHandler(states, sessionContext), sessionContext) + setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_LEFT, CodeWhispererPopupLeftArrowHandler(states), sessionContext) + setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_RIGHT, CodeWhispererPopupRightArrowHandler(states), sessionContext) setPopupActionHandler( ACTION_EDITOR_ENTER, - CodeWhispererPopupEnterHandler(actionManager.getActionHandler(ACTION_EDITOR_ENTER), states) + CodeWhispererPopupEnterHandler(actionManager.getActionHandler(ACTION_EDITOR_ENTER), states), + sessionContext ) setPopupActionHandler( ACTION_EDITOR_BACKSPACE, - CodeWhispererPopupBackspaceHandler(actionManager.getActionHandler(ACTION_EDITOR_BACKSPACE), states) + CodeWhispererPopupBackspaceHandler(actionManager.getActionHandler(ACTION_EDITOR_BACKSPACE), states), + sessionContext ) } - private fun setPopupTypedHandler(newHandler: CodeWhispererPopupTypedHandler) { + private fun setPopupTypedHandler(newHandler: CodeWhispererPopupTypedHandler, sessionContext: SessionContext) { val oldTypedHandler = TypedAction.getInstance().setupRawHandler(newHandler) - Disposer.register(newHandler.states) { TypedAction.getInstance().setupRawHandler(oldTypedHandler) } + Disposer.register(sessionContext) { TypedAction.getInstance().setupRawHandler(oldTypedHandler) } } - private fun setPopupActionHandler(id: String, newHandler: CodeWhispererEditorActionHandler) { + private fun setPopupActionHandler(id: String, newHandler: CodeWhispererEditorActionHandler, sessionContext: SessionContext) { val oldHandler = EditorActionManager.getInstance().setActionHandler(id, newHandler) - Disposer.register(newHandler.states) { EditorActionManager.getInstance().setActionHandler(id, oldHandler) } + Disposer.register(sessionContext) { EditorActionManager.getInstance().setActionHandler(id, oldHandler) } } - private fun addComponentListeners(states: InvocationContext) { + private fun addComponentListeners(states: InvocationContext, sessionContext: SessionContext) { val editor = states.requestContext.editor val codewhispererSelectionListener: SelectionListener = object : SelectionListener { override fun selectionChanged(event: SelectionEvent) { if (shouldListenerCancelPopup) { - cancelPopup(states.popup) + cancelPopup() } super.selectionChanged(event) } } editor.selectionModel.addSelectionListener(codewhispererSelectionListener) - Disposer.register(states) { editor.selectionModel.removeSelectionListener(codewhispererSelectionListener) } + Disposer.register(sessionContext) { editor.selectionModel.removeSelectionListener(codewhispererSelectionListener) } val codewhispererDocumentListener: DocumentListener = object : DocumentListener { override fun documentChanged(event: DocumentEvent) { + val statesWithin = states if (shouldListenerCancelPopup) { - cancelPopup(states.popup) + // handle IntelliSense accept case + if (editor.document == event.document && + editor.caretModel.offset == event.offset && + event.newLength > event.oldLength) { + dontClosePopupAndRun { + super.documentChanged(event) + editor.caretModel.moveCaretRelatively(event.newLength, 0, false, false, true) + changeStatesForTypeahead(states, event.newFragment.toString(), true) + } + return + } else { + cancelPopup() + } } super.documentChanged(event) } @@ -517,31 +721,58 @@ class CodeWhispererPopupManager { val codewhispererCaretListener: CaretListener = object : CaretListener { override fun caretPositionChanged(event: CaretEvent) { - if (shouldListenerCancelPopup) { - cancelPopup(states.popup) - } +// if (shouldListenerCancelPopup) { +// cancelPopup() +// } super.caretPositionChanged(event) } } editor.caretModel.addCaretListener(codewhispererCaretListener) - Disposer.register(states) { editor.caretModel.removeCaretListener(codewhispererCaretListener) } + Disposer.register(sessionContext) { editor.caretModel.removeCaretListener(codewhispererCaretListener) } val editorComponent = editor.contentComponent if (editorComponent.isShowing) { val window = ComponentUtil.getWindow(editorComponent) val windowListener: ComponentListener = object : ComponentAdapter() { override fun componentMoved(event: ComponentEvent) { - cancelPopup(states.popup) + cancelPopup() } override fun componentShown(e: ComponentEvent?) { - cancelPopup(states.popup) + cancelPopup() super.componentShown(e) } } window?.addComponentListener(windowListener) Disposer.register(states) { window?.removeComponentListener(windowListener) } } + + + val suggestionHoverEnterListener: EditorMouseMotionListener = object : EditorMouseMotionListener { + override fun mouseMoved(e: EditorMouseEvent) { + e.mouseEvent.component + println("current mouse offset : ${e.offset}, point: ${e.mouseEvent.point}") + val startOffset = editor.offsetToXY(editor.caretModel.offset) + println("caret x y: ${startOffset}") + val point = e.mouseEvent.point + val right = startOffset.x + (e.inlay?.widthInPixels ?: 0) + val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) + val aPoint = + if (a != null) { + RelativePoint(Point(a.bounds.x, a.bounds.y)).getPoint(editor.contentComponent) + } else { + Point(0, 0) + } + + if (e.inlay != null) { + showPopup(states, sessionContext, force = true) + } else { + hidePopup(editor) + } + super.mouseMoved(e) + } + } + editor.addEditorMouseMotionListener(suggestionHoverEnterListener, states) } private fun updateSelectedRecommendationLabelText(validSelectedIndex: Int, validCount: Int) { @@ -619,17 +850,23 @@ class CodeWhispererPopupManager { } fun hasConflictingPopups(editor: Editor): Boolean = - ParameterInfoController.existsWithVisibleHintForEditor(editor, true) || - LookupManager.getActiveLookup(editor) != null + (ParameterInfoController.existsWithVisibleHintForEditor(editor, true) || + LookupManager.getActiveLookup(editor) != null) && false - private fun findNewSelectedIndex( + fun findNewSelectedIndex( isReverse: Boolean, - detailContexts: List, - userInput: String, - start: Int, - typeahead: String + start: Int ): Int { - val count = detailContexts.size + val recos = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + element.recommendationContext.details.map { + Triple(it, element.recommendationContext.userInputSinceInvocation, element.recommendationContext.typeaheadOriginal) + } + } + + + +// .flatMap { Triple(it.recommendationContext.details, it.recommendationContext. } + val count = recos.size val unit = if (isReverse) -1 else 1 var currIndex: Int for (i in 0 until count) { @@ -637,15 +874,38 @@ class CodeWhispererPopupManager { if (currIndex < 0) { currIndex += count } - if (isValidRecommendation(detailContexts[currIndex], userInput, typeahead)) { + val triple = recos[currIndex] + if (isValidRecommendation(triple.first, triple.second, triple.third)) { return currIndex } } + +// val count = detailContexts.size +// val unit = if (isReverse) -1 else 1 +// var currIndex: Int +// for (i in 0 until count) { +// currIndex = (start + i * unit) % count +// if (currIndex < 0) { +// currIndex += count +// } +// if (isValidRecommendation(detailContexts[currIndex], userInput, typeahead)) { +// return currIndex +// } +// } return -1 } - private fun getValidCount(detailContexts: List, userInput: String, typeahead: String): Int = - detailContexts.filter { isValidRecommendation(it, userInput, typeahead) }.size + private fun getValidCount(detailContexts: List, userInput: String, typeahead: String): Int { + var count = 0 + CodeWhispererService.getInstance().ongoingRequests.forEach { t, u -> + if (u == null) return@forEach + count += u.recommendationContext.details.filter { + isValidRecommendation(it, u.recommendationContext.userInputSinceInvocation, u.recommendationContext.typeaheadOriginal) + }.size + } + return count +// detailContexts.filter { isValidRecommendation(it, userInput, typeahead) }.size + } private fun getValidSelectedIndex( detailContexts: List, @@ -653,14 +913,22 @@ class CodeWhispererPopupManager { selectedIndex: Int, typeahead: String ): Int { - var currIndexIgnoreInvalid = 0 - detailContexts.forEachIndexed { index, value -> + var curr = 0 + + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + Triple(it, context.userInputSinceInvocation, context.typeaheadOriginal) + } + } + details.forEachIndexed { index, triple -> if (index == selectedIndex) { - return currIndexIgnoreInvalid + return curr } - if (isValidRecommendation(value, userInput, typeahead)) { - currIndexIgnoreInvalid++ + if (isValidRecommendation(triple.first, triple.second, triple.third)) { + curr++ } + } return -1 } @@ -668,14 +936,7 @@ class CodeWhispererPopupManager { private fun isValidRecommendation(detailContext: DetailContext, userInput: String, typeahead: String): Boolean { if (detailContext.isDiscarded) return false if (detailContext.recommendation.content().isEmpty()) return false - val indexOfFirstNonWhiteSpace = typeahead.indexOfFirst { !it.isWhitespace() } - if (indexOfFirstNonWhiteSpace == -1) return true - - for (i in 0..indexOfFirstNonWhiteSpace) { - val subTypeahead = typeahead.substring(i) - if (detailContext.reformatted.content().startsWith(userInput + subTypeahead)) return true - } - return false + return detailContext.recommendation.content().startsWith(userInput + typeahead) } companion object { @@ -705,5 +966,5 @@ interface CodeWhispererUserActionListener { fun navigatePrevious(states: InvocationContext) {} fun navigateNext(states: InvocationContext) {} fun beforeAccept(states: InvocationContext, sessionContext: SessionContext) {} - fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {} + fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) {} } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt index e4bb87feec8..eac1547b86b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt @@ -9,29 +9,38 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.util.Disposer import com.intellij.xdebugger.ui.DebuggerColors +import groovy.lang.Tuple3 +import groovy.lang.Tuple4 import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager import software.aws.toolkits.jetbrains.services.codewhisperer.inlay.CodeWhispererInlayManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererRecommendationManager +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { override fun stateChanged(states: InvocationContext, sessionContext: SessionContext) { val editor = states.requestContext.editor val editorManager = CodeWhispererEditorManager.getInstance() + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) + } + } val selectedIndex = sessionContext.selectedIndex - val typeahead = sessionContext.typeahead - val detail = states.recommendationContext.details[selectedIndex] + val typeaheadOriginal = details[selectedIndex].v3 + val detail = details[selectedIndex].v1 val caretOffset = editor.caretModel.primaryCaret.offset val document = editor.document val lineEndOffset = document.getLineEndOffset(document.getLineNumber(caretOffset)) // get matching brackets from recommendations to the brackets after caret position val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation( - detail, - states.recommendationContext.userInputSinceInvocation - ).substring(typeahead.length) + details[selectedIndex].v1, + details[selectedIndex].v2, + ).substring(typeaheadOriginal.length) val remainingLines = remaining.split("\n") val firstLineOfRemaining = remainingLines.first() @@ -61,7 +70,7 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { }, HighlighterTargetArea.EXACT_RANGE ) - Disposer.register(states.popup) { + Disposer.register(states) { editor.markupModel.removeHighlighter(rangeHighlighter) } sessionContext.toBeRemovedHighlighter = rangeHighlighter @@ -87,7 +96,7 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { // inlay chunks are chunks from first line(chunks) and an additional chunk from other lines val inlayChunks = chunks + listOf(RecommendationChunk(otherLinesInlayText, 0, chunks.last().inlayOffset)) - CodeWhispererInlayManager.getInstance().updateInlays(states, inlayChunks) + CodeWhispererInlayManager.getInstance().updateInlays(states, sessionContext, inlayChunks) CodeWhispererPopupManager.getInstance().render( states, sessionContext, @@ -98,18 +107,24 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { } override fun scrolled(states: InvocationContext, sessionContext: SessionContext) { - if (states.popup.isDisposed) return + if (states.isDisposed()) return val editor = states.requestContext.editor val editorManager = CodeWhispererEditorManager.getInstance() val selectedIndex = sessionContext.selectedIndex - val typeahead = sessionContext.typeahead - val detail = states.recommendationContext.details[selectedIndex] + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) + } + } + val typeaheadOriginal = details[selectedIndex].v3 + val detail = details[selectedIndex].v1 // get matching brackets from recommendations to the brackets after caret position val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation( detail, states.recommendationContext.userInputSinceInvocation - ).substring(typeahead.length) + ).substring(typeaheadOriginal.length) val remainingLines = remaining.split("\n") val otherLinesOfRemaining = remainingLines.drop(1) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt index c92eae91062..da004835c7d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt @@ -3,17 +3,27 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers +import com.intellij.codeInsight.lookup.Lookup +import com.intellij.codeInsight.lookup.LookupFocusDegree +import com.intellij.codeInsight.lookup.LookupManager +import com.intellij.codeInsight.lookup.impl.LookupImpl +import com.intellij.codeInsight.template.impl.editorActions.ExpandLiveTemplateCustomAction +import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.actionSystem.EditorAction +import com.intellij.openapi.editor.actionSystem.EditorActionHandler +import com.intellij.openapi.editor.actionSystem.EditorActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager -class CodeWhispererPopupTabHandler(states: InvocationContext) : CodeWhispererEditorActionHandler(states) { +class CodeWhispererPopupTabHandler(states: InvocationContext, private val sessionContext: SessionContext) : CodeWhispererEditorActionHandler(states) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).beforeAccept(states, CodeWhispererPopupManager.getInstance().sessionContext) + ).beforeAccept(states, sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt index 7bc77295a99..d64d745c6bc 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt @@ -5,13 +5,14 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import com.intellij.openapi.application.ApplicationManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import java.awt.event.ActionEvent -class CodeWhispererAcceptButtonActionListener(states: InvocationContext) : CodeWhispererActionListener(states) { +class CodeWhispererAcceptButtonActionListener(states: InvocationContext, private val sessionContext: SessionContext) : CodeWhispererActionListener(states) { override fun actionPerformed(e: ActionEvent?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).beforeAccept(states, CodeWhispererPopupManager.getInstance().sessionContext) + ).beforeAccept(states, sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt index f1dfab068a8..4a17ac5a9c0 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt @@ -7,10 +7,11 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.event.VisibleAreaEvent import com.intellij.openapi.editor.event.VisibleAreaListener import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus -class CodeWhispererScrollListener(private val states: InvocationContext) : VisibleAreaListener { +class CodeWhispererScrollListener(private val states: InvocationContext, private val sessionContext: SessionContext) : VisibleAreaListener { override fun visibleAreaChanged(e: VisibleAreaEvent) { val oldRect = e.oldRectangle val newRect = e.newRectangle @@ -19,7 +20,7 @@ class CodeWhispererScrollListener(private val states: InvocationContext) : Visib ) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_POPUP_STATE_CHANGED - ).scrolled(states, CodeWhispererPopupManager.getInstance().sessionContext) + ).scrolled(states, sessionContext) } } } 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 68c578d092e..3ea0367f20b 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 @@ -3,6 +3,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service +import com.intellij.codeWithMe.clientId import com.intellij.openapi.Disposable import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runReadAction @@ -21,6 +22,7 @@ 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.codewhisperer.editor.CodeWhispererEditorUtil +import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.extractCaretContext import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext @@ -29,6 +31,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer import software.aws.toolkits.telemetry.CodewhispererAutomatedTriggerType import software.aws.toolkits.telemetry.CodewhispererPreviousSuggestionState import software.aws.toolkits.telemetry.CodewhispererTriggerType +import java.lang.Thread.sleep import java.time.Duration import java.time.Instant import kotlin.math.exp @@ -42,6 +45,8 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa private var lastInvocationTime: Instant? = null private var lastInvocationLineNum: Int? = null + private var lastCharTypedTime: Instant? = null + private var lastTrigger: Job? = null init { scheduleReset() @@ -55,9 +60,6 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa fun tryInvokeAutoTrigger(editor: Editor, triggerType: CodeWhispererAutomatedTriggerType): Job? { // only needed for Classifier group, thus calculate it lazily val classifierResult: ClassifierResult by lazy { shouldTriggerClassifier(editor, triggerType.telemetryType) } - val language = runReadAction { - FileDocumentManager.getInstance().getFile(editor.document)?.programmingLanguage() - } ?: CodeWhispererUnknownLanguage.INSTANCE // we need classifier result for any type of triggering for classifier group for supported languages triggerType.calculationResult = classifierResult.calculatedResult @@ -84,8 +86,13 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa if (!CodeWhispererService.getInstance().canDoInvocation(editor, CodewhispererTriggerType.AutoTrigger)) { return null } + if (lastCharTypedTime != null && lastCharTypedTime?.plusMillis(0)?.isAfter(Instant.now()) == true) { +// println("cancelling last trigger job ${lastTrigger.hashCode()} since user has immediately typed since then") + lastTrigger?.cancel() + } lastInvocationTime = Instant.now() + lastCharTypedTime = Instant.now() lastInvocationLineNum = runReadAction { editor.caretModel.visualPosition.line } val latencyContext = LatencyContext().apply { @@ -110,12 +117,24 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa if (CodeWhispererInvocationStatus.getInstance().isPopupActive()) return@runInEdt performAutomatedTriggerAction(editor, CodeWhispererAutomatedTriggerType.IdleTime(), latencyContext) } + }.also { + lastTrigger = it } } else -> run { +// if (lastCharTypedTime != null && lastCharTypedTime?.plusMillis(100)?.isAfter(Instant.now()) != false) return null coroutineScope.launch(EDT) { + while (lastCharTypedTime != null && lastCharTypedTime?.plusMillis(0)?.isAfter(Instant.now()) == true) { + if (!isActive) return@launch + delay(CodeWhispererConstants.IDLE_TIME_CHECK_INTERVAL) + } + +// println("time now: ${System.currentTimeMillis()}") performAutomatedTriggerAction(editor, triggerType, latencyContext) + }.also { + lastTrigger = it +// println("create trigger job ${lastTrigger.hashCode()}, time: ${System.currentTimeMillis()}") } } } 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 2a2d70bd903..b537522dcb8 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 @@ -73,6 +73,7 @@ class CodeWhispererInvocationStatus { fun setPopupActive(value: Boolean) { isPopupActive = value + println("set popup active to $value") } fun setInvocationStart() { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt index 3f6305c1f7d..d05f28e42e3 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt @@ -9,6 +9,7 @@ import org.jetbrains.annotations.VisibleForTesting import software.amazon.awssdk.services.codewhispererruntime.model.Completion import software.amazon.awssdk.services.codewhispererruntime.model.Span import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType import kotlin.math.max @@ -16,6 +17,7 @@ import kotlin.math.min @Service class CodeWhispererRecommendationManager { + var states: MutableList = mutableListOf() fun reformatReference(requestContext: RequestContext, recommendation: Completion): Completion { // startOffset is the offset at the start of user input since invocation val invocationStartOffset = requestContext.caretPosition.offset @@ -63,7 +65,7 @@ class CodeWhispererRecommendationManager { userInput: String, recommendations: List, requestId: String, - ): List { + ): MutableList { val seen = mutableSetOf() return recommendations.map { val isDiscardedByUserInput = !it.content().startsWith(userInput) || it.content() == userInput @@ -126,7 +128,7 @@ class CodeWhispererRecommendationManager { overlap, getCompletionType(it) ) - } + }.toMutableList() } fun findRightContextOverlap( 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 e8dd0e96562..8467b1dcecc 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 @@ -15,16 +15,18 @@ import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.util.Disposer import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.messages.Topic -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job import kotlinx.coroutines.async +import io.ktor.utils.io.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -57,6 +59,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getCaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled +import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo @@ -93,6 +96,9 @@ import java.util.concurrent.TimeUnit class CodeWhispererService(private val cs: CoroutineScope) : Disposable { private val codeInsightSettingsFacade = CodeInsightsSettingsFacade() private var refreshFailure: Int = 0 + val ongoingRequests = mutableMapOf() + val ongoingRequestsContext = mutableMapOf() + private var jobId = 0 init { Disposer.register(this, codeInsightSettingsFacade) @@ -147,6 +153,14 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { CodeWhispererTelemetryService.getInstance().sendFailedServiceInvocationEvent(project, e::class.simpleName) return } + val caretContext = requestContext.fileContextInfo.caretContext + ongoingRequestsContext.forEach { (k, v) -> + val vCaretContext = v.fileContextInfo.caretContext + if (vCaretContext == caretContext) { + println("same caretContext found from job: $k, left context ${vCaretContext.leftContextOnCurrentLine}") + return + } + } val language = requestContext.fileContextInfo.programmingLanguage val leftContext = requestContext.fileContextInfo.caretContext.leftFileContext @@ -171,7 +185,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } val invocationStatus = CodeWhispererInvocationStatus.getInstance() - if (invocationStatus.checkExistingInvocationAndSet()) { + if (invocationStatus.checkExistingInvocationAndSet() && false) { return } @@ -179,19 +193,23 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } internal fun invokeCodeWhispererInBackground(requestContext: RequestContext): Job { - val popup = CodeWhispererPopupManager.getInstance().initPopup() - Disposer.register(popup) { CodeWhispererInvocationStatus.getInstance().finishInvocation() } +// val popup = CodeWhispererPopupManager.getInstance().initPopup() + val currentJobId = jobId++ + // a placeholder to indicate a session has started + ongoingRequests[currentJobId] = null + ongoingRequestsContext[currentJobId] = requestContext val workerContexts = mutableListOf() // When popup is disposed we will cancel this coroutine. The only places popup can get disposed should be // from CodeWhispererPopupManager.cancelPopup() and CodeWhispererPopupManager.closePopup(). // It's possible and ok that coroutine will keep running until the next time we check it's state. // As long as we don't show to the user extra info we are good. - val coroutineScope = disposableCoroutineScope(popup) + val coroutineScope = projectCoroutineScope(requestContext.project) - var states: InvocationContext? = null var lastRecommendationIndex = -1 + val line = requestContext.fileContextInfo.caretContext.leftContextOnCurrentLine + println("triggering, last char ${line}, jobId: $currentJobId") val job = coroutineScope.launch { try { val responseIterable = CodeWhispererClientAdaptor.getInstance(requestContext.project).generateCompletionsPaginator( @@ -250,21 +268,29 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // task hasn't been finished yet, in this case simply add another task to the queue. If they // see worker queue is empty, the previous tasks must have been finished before this. In this // case render CodeWhisperer UI directly. - val workerContext = WorkerContext(requestContext, responseContext, validatedResponse, popup) + val workerContext = WorkerContext(requestContext, responseContext, validatedResponse) if (workerContexts.isNotEmpty()) { workerContexts.add(workerContext) } else { - if (states == null && !popup.isDisposed && + if (ongoingRequests.isEmpty() && !CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer() ) { // It's the first response, and no enough delay before showing projectCoroutineScope(requestContext.project).launch { - while (!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer()) { + while (!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer() && false) { delay(CodeWhispererConstants.POPUP_DELAY_CHECK_INTERVAL) } runInEdt { workerContexts.forEach { - states = processCodeWhispererUI(it, states) + ongoingRequests[currentJobId] = processCodeWhispererUI( + it, + ongoingRequests[currentJobId], + coroutineScope, + currentJobId + ) + if (ongoingRequests[currentJobId] == null) { + coroutineScope.coroutineContext.cancel() + } } workerContexts.clear() } @@ -272,14 +298,25 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { workerContexts.add(workerContext) } else { // Have enough delay before showing for the first response, or it's subsequent responses - states = processCodeWhispererUI(workerContext, states) + ongoingRequests[currentJobId] = processCodeWhispererUI( + workerContext, + ongoingRequests[currentJobId], + coroutineScope, + currentJobId + ) } } } + if (!ongoingRequests.contains(currentJobId)) { + LOG.debug { "Skipping sending remaining requests on jobId removed" } + println("exit 1 , jobId: $currentJobId") + break + } if (!isActive) { // If job is cancelled before we do another request, don't bother making // another API call to save resources LOG.debug { "Skipping sending remaining requests on CodeWhisperer session exit" } + println("exit 2 , jobId: $currentJobId") break } } @@ -397,12 +434,10 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } CodeWhispererInvocationStatus.getInstance().finishInvocation() runInEdt { - states?.let { - CodeWhispererPopupManager.getInstance().updatePopupPanel( - it, - CodeWhispererPopupManager.getInstance().sessionContext - ) - } + CodeWhispererPopupManager.getInstance().updatePopupPanel( + ongoingRequests[currentJobId], + CodeWhispererPopupManager.getInstance().sessionContext + ) } } finally { CodeWhispererInvocationStatus.getInstance().setInvocationComplete() @@ -413,25 +448,33 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } @RequiresEdt - private fun processCodeWhispererUI(workerContext: WorkerContext, currStates: InvocationContext?): InvocationContext? { + private fun processCodeWhispererUI( + workerContext: WorkerContext, + currStates: InvocationContext?, + coroutine: CoroutineScope, + jobId: Int + ): InvocationContext? { val requestContext = workerContext.requestContext val responseContext = workerContext.responseContext val response = workerContext.response - val popup = workerContext.popup +// val popup = workerContext.popup val requestId = response.responseMetadata().requestId() // At this point when we are in EDT, the state of the popup will be thread-safe // across this thread execution, so if popup is disposed, we will stop here. // This extra check is needed because there's a time between when we get the response and // when we enter the EDT. - if (popup.isDisposed) { + if (!ongoingRequests.contains(jobId)) { LOG.debug { "Stop showing CodeWhisperer recommendations on CodeWhisperer session exit. RequestId: $requestId" } + println("exit 3 , jobId: $jobId") return null } if (requestContext.editor.isDisposed) { - LOG.debug { "Stop showing CodeWhisperer recommendations since editor is disposed. RequestId: $requestId" } - CodeWhispererPopupManager.getInstance().cancelPopup(popup) + LOG.debug { "Stop showing all CodeWhisperer recommendations since editor is disposed. RequestId: $requestId" } + CodeWhispererPopupManager.getInstance().sessionContext?.let { Disposer.dispose(it) } +// CodeWhispererPopupManager.getInstance().cancelPopup(popup) + println("exit 4 , jobId: $jobId") return null } @@ -443,27 +486,40 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { requestContext.editor, requestContext.caretPosition ) - val isPopupShowing: Boolean + val isPopupShowing = checkRecommendationsValidity(currStates, false, jobId) val nextStates: InvocationContext? if (currStates == null) { - // first response - nextStates = initStates(requestContext, responseContext, response, caretMovement, popup) - isPopupShowing = false + // first response for the jobId + nextStates = initStates(requestContext, responseContext, response, caretMovement, coroutine, jobId) // receiving a null state means caret has moved backward or there's a conflict with - // Intellisense popup, so we are going to cancel the job + // Intellisense popup, so we are going to cancel the current job if (nextStates == null) { - LOG.debug { "Cancelling popup and exiting CodeWhisperer session. RequestId: $requestId" } - CodeWhispererPopupManager.getInstance().cancelPopup(popup) + LOG.debug { "Exiting CodeWhisperer session. RequestId: $requestId" } + ongoingRequests[jobId]?.let { Disposer.dispose(it) } +// CodeWhispererPopupManager.getInstance().cancelPopup(popup) + println("exit 5 , jobId: $jobId") return null } } else { - // subsequent responses + // subsequent responses for the jobId nextStates = updateStates(currStates, response) - isPopupShowing = checkRecommendationsValidity(currStates, false) } + println("adding ${response.completions().size} completions from job ${jobId}") + ongoingRequests[jobId] = nextStates + + val hasAtLeastOneValid = checkRecommendationsValidity(nextStates, response.nextToken().isEmpty(), jobId) +// println("details for $jobId:") +// println("current states have ${nextStates.recommendationContext.details.size} total suggestions, recently added by ${jobId}") + val allSuggestions = ongoingRequests.values.filterNotNull().flatMap { it.recommendationContext.details } + val valid = allSuggestions.filter { !it.isDiscarded }.size + println("total: $valid valid, ${allSuggestions.size - valid} discarded") +// nextStates.recommendationContext.details.forEach { +// println(it.recommendation.content()) +// } + + - val hasAtLeastOneValid = checkRecommendationsValidity(nextStates, response.nextToken().isEmpty()) // If there are no recommendations at all in this session, we need to manually send the user decision event here // since it won't be sent automatically later @@ -489,7 +545,13 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { if (!hasAtLeastOneValid) { if (response.nextToken().isEmpty()) { LOG.debug { "None of the recommendations are valid, exiting CodeWhisperer session" } - CodeWhispererPopupManager.getInstance().cancelPopup(popup) + // TODO: decide whether or not to dispose what here +// CodeWhispererPopupManager.getInstance().cancelPopup(popup) + ongoingRequests[jobId]?.let { Disposer.dispose(it) } + CodeWhispererPopupManager.getInstance().sessionContext?.let { + it.selectedIndex = CodeWhispererPopupManager.getInstance().findNewSelectedIndex(true, it.selectedIndex) + } + println("exit 6 , jobId: $jobId") return null } } else { @@ -503,7 +565,9 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { responseContext: ResponseContext, response: GenerateCompletionsResponse, caretMovement: CaretMovement, - popup: JBPopup +// popup: JBPopup, + coroutine: CoroutineScope, + jobId: Int ): InvocationContext? { val requestId = response.responseMetadata().requestId() val recommendations = response.completions() @@ -542,8 +606,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { recommendations, requestId ) - val recommendationContext = RecommendationContext(detailContexts, userInputOriginal, userInput, visualPosition) - return buildInvocationContext(requestContext, responseContext, recommendationContext, popup) + val recommendationContext = RecommendationContext(detailContexts, userInputOriginal, userInput, visualPosition, jobId) + return buildInvocationContext(requestContext, responseContext, recommendationContext, coroutine) } private fun updateStates( @@ -551,24 +615,28 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { response: GenerateCompletionsResponse ): InvocationContext { val recommendationContext = states.recommendationContext - val details = recommendationContext.details +// val details = recommendationContext.details val newDetailContexts = CodeWhispererRecommendationManager.getInstance().buildDetailContext( states.requestContext, recommendationContext.userInputSinceInvocation, response.completions(), response.responseMetadata().requestId() ) - Disposer.dispose(states) +// Disposer.dispose(states) - val updatedStates = states.copy( - recommendationContext = recommendationContext.copy(details = details + newDetailContexts) - ) - Disposer.register(states.popup, updatedStates) - CodeWhispererPopupManager.getInstance().initPopupListener(updatedStates) - return updatedStates + recommendationContext.details.addAll(newDetailContexts) + +// Disposer.dispose(states) +// val updatedStates = states.copy( +// recommendationContext = recommendationContext.copy(details = details + newDetailContexts) +// ) +// Disposer.register(states.popup, updatedStates) +// CodeWhispererPopupManager.getInstance().initPopupListener(updatedStates) + return states } - private fun checkRecommendationsValidity(states: InvocationContext, showHint: Boolean): Boolean { + private fun checkRecommendationsValidity(states: InvocationContext?, showHint: Boolean, jobId: Int): Boolean { + if (states == null) return false val details = states.recommendationContext.details // set to true when at least one is not discarded or empty @@ -584,7 +652,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } private fun updateCodeWhisperer(states: InvocationContext, recommendationAdded: Boolean) { - CodeWhispererPopupManager.getInstance().changeStates(states, 0, "", true, recommendationAdded) + CodeWhispererPopupManager.getInstance().changeStatesForShowing(states, recommendationAdded) } private fun sendDiscardedUserDecisionEventForAll( @@ -594,8 +662,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { ) { val detailContexts = recommendations.map { DetailContext("", it, it, true, false, "", getCompletionType(it)) - } - val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0)) + }.toMutableList() + val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0), -1) CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll( requestContext, @@ -662,25 +730,38 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { requestContext: RequestContext, responseContext: ResponseContext, recommendationContext: RecommendationContext, - popup: JBPopup + coroutine: CoroutineScope +// popup: JBPopup ): InvocationContext { - addPopupChildDisposables(popup) // Creating a disposable for managing all listeners lifecycle attached to the popup. // previously(before pagination) we use popup as the parent disposable. // After pagination, listeners need to be updated as states are updated, for the same popup, // so disposable chain becomes popup -> disposable -> listeners updates, and disposable gets replaced on every // state update. - val states = InvocationContext(requestContext, responseContext, recommendationContext, popup) - Disposer.register(popup, states) - CodeWhispererPopupManager.getInstance().initPopupListener(states) + val states = InvocationContext(requestContext, responseContext, recommendationContext) +// addSessionDisposables(states) +// if (CodeWhispererPopupManager.getInstance().sessionContext == null) { +// CodeWhispererPopupManager.getInstance().sessionContext = SessionContext() +// } +// val sessionContext = CodeWhispererPopupManager.getInstance().sessionContext ?: SessionContext() +// CodeWhispererPopupManager.getInstance().sessionContext = sessionContext +// Disposer.register(sessionContext) { +// CodeWhispererInvocationStatus.getInstance().finishInvocation() +// } + Disposer.register(states) { + coroutine.cancel(CancellationException("hahahah")) + } +// Disposer.register(states, popup) +// Disposer.register(popup, states) +// CodeWhispererRecommendationManager.getInstance().states = states return states } - private fun addPopupChildDisposables(popup: JBPopup) { - codeInsightSettingsFacade.disableCodeInsightUntil(popup) + private fun addSessionDisposables(disposable: Disposable) { +// codeInsightSettingsFacade.disableCodeInsightUntil(disposable) - Disposer.register(popup) { - CodeWhispererPopupManager.getInstance().reset() + Disposer.register(disposable) { + CodeWhispererPopupManager.getInstance().resetSession() } } 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 3f2c06fce86..6bc7008cf1e 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 @@ -8,8 +8,13 @@ import com.intellij.codeInsight.lookup.LookupEvent import com.intellij.codeInsight.lookup.LookupListener import com.intellij.codeInsight.lookup.LookupManagerListener 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.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType +import java.awt.Component object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener { override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) { @@ -35,5 +40,16 @@ object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener { newLookup.removeLookupListener(this) } }) + + (newLookup as LookupImpl).component.addMouseHoverListener( + newLookup, + object : HoverListener() { + override fun mouseEntered(component: Component, x: Int, y: Int) { + runReadAction { CodeWhispererPopupManager.getInstance().hidePopup(newLookup.editor) } + } + override fun mouseMoved(component: Component, x: Int, y: Int) {} + override fun mouseExited(component: Component) {} + } + ) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt index dc4a42fede9..c5f0f54999e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt @@ -14,6 +14,8 @@ import com.intellij.openapi.util.Key import com.intellij.refactoring.suggested.range import com.intellij.util.Alarm import com.intellij.util.AlarmFactory +import groovy.lang.Tuple3 +import groovy.lang.Tuple4 import info.debatty.java.stringsimilarity.Levenshtein import org.jetbrains.annotations.TestOnly import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException @@ -22,6 +24,7 @@ import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext @@ -81,7 +84,7 @@ abstract class CodeWhispererCodeCoverageTracker( conn.subscribe( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED, object : CodeWhispererUserActionListener { - override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) { + override fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) { if (states.requestContext.fileContextInfo.programmingLanguage != language) return rangeMarkers.add(rangeMarker) val originalRecommendation = extractRangeMarkerString(rangeMarker) ?: return 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 29c0902052a..76438adb8ef 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 @@ -10,6 +10,8 @@ import com.intellij.openapi.components.service import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile +import groovy.lang.Tuple3 +import groovy.lang.Tuple4 import kotlinx.coroutines.launch import org.apache.commons.collections4.queue.CircularFifoQueue import org.jetbrains.annotations.TestOnly @@ -29,6 +31,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererFeatureConfigService 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.service.CodeWhispererUserGroupSettings import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext @@ -397,53 +400,64 @@ class CodeWhispererTelemetryService { hasUserAccepted: Boolean, popupShownTime: Duration? = null ) { - val detailContexts = recommendationContext.details val decisions = mutableListOf() - detailContexts.forEachIndexed { index, detailContext -> - val suggestionState = recordSuggestionState( - index, - sessionContext.selectedIndex, - sessionContext.seen.contains(index), - hasUserAccepted, - detailContext.isDiscarded, - detailContext.recommendation.content().isEmpty() - ) - sendUserDecisionEvent(requestContext, responseContext, detailContext, index, suggestionState, detailContexts.size) + CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().forEach { + val detailContexts = it.recommendationContext.details + + detailContexts.forEachIndexed { index, detailContext -> + val suggestionState = recordSuggestionState( + index, + sessionContext.selectedIndex, + sessionContext.seen.contains(index), + hasUserAccepted, + detailContext.isDiscarded, + detailContext.recommendation.content().isEmpty() + ) + sendUserDecisionEvent(it.requestContext, it.responseContext, detailContext, index, suggestionState, detailContexts.size) + + decisions.add(suggestionState) + } - decisions.add(suggestionState) - } + with(aggregateUserDecision(decisions)) { + // the order of the following matters + // step 1, send out current decision + previousUserTriggerDecisionTimestamp = Instant.now() - with(aggregateUserDecision(decisions)) { - // the order of the following matters - // step 1, send out current decision - previousUserTriggerDecisionTimestamp = Instant.now() - - val referenceCount = if (hasUserAccepted && detailContexts[sessionContext.selectedIndex].recommendation.hasReferences()) 1 else 0 - val acceptedContent = - if (hasUserAccepted) { - detailContexts[sessionContext.selectedIndex].recommendation.content() - } else { - "" + val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { detail -> + Tuple3(detail, context.userInputSinceInvocation, context.typeaheadOriginal) + } } - val generatedLineCount = if (acceptedContent.isEmpty()) 0 else acceptedContent.split("\n").size - val acceptedCharCount = acceptedContent.length - sendUserTriggerDecisionEvent( - requestContext, - responseContext, - recommendationContext, - CodewhispererSuggestionState.from(this.toString()), - popupShownTime, - referenceCount, - generatedLineCount, - acceptedCharCount - ) - - // step 2, put current decision into queue for later reference - previousUserTriggerDecisions.add(this) - // we need this as well because AutotriggerService will reset the queue periodically - CodeWhispererAutoTriggerService.getInstance().addPreviousDecision(this) + val referenceCount = if (hasUserAccepted && details[sessionContext.selectedIndex].v1.recommendation.hasReferences()) 1 else 0 + val acceptedContent = + if (hasUserAccepted) { + details[sessionContext.selectedIndex].v1.recommendation.content() + } else { + "" + } + val generatedLineCount = if (acceptedContent.isEmpty()) 0 else acceptedContent.split("\n").size + val acceptedCharCount = acceptedContent.length + sendUserTriggerDecisionEvent( + requestContext, + responseContext, + recommendationContext, + CodewhispererSuggestionState.from(this.toString()), + popupShownTime, + referenceCount, + generatedLineCount, + acceptedCharCount + ) + + // step 2, put current decision into queue for later reference + previousUserTriggerDecisions.add(this) + // we need this as well because AutoTriggerService will reset the queue periodically + CodeWhispererAutoTriggerService.getInstance().addPreviousDecision(this) + } } + + } /** diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt index 9576c002e05..4c23d0d7355 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt @@ -4,15 +4,18 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow import com.intellij.openapi.editor.RangeMarker +import groovy.lang.Tuple3 +import groovy.lang.Tuple4 +import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener class CodeWhispererCodeReferenceActionListener : CodeWhispererUserActionListener { - override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) { + override fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) { val (project, editor) = states.requestContext val manager = CodeWhispererCodeReferenceManager.getInstance(project) - manager.insertCodeReference(states, sessionContext.selectedIndex) + manager.insertCodeReference(states, details, sessionContext.selectedIndex) manager.addListeners(editor) } } 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 f1ffacc8d2b..0f3ba23fdca 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,8 @@ 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 groovy.lang.Tuple3 +import groovy.lang.Tuple4 import software.amazon.awssdk.services.codewhispererruntime.model.Completion import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.amazon.awssdk.services.codewhispererruntime.model.Span @@ -30,7 +32,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getRelativePathToContentRoot import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.horizontalPanelConstraints import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition +import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.EDITOR_CODE_REFERENCE_HOVER import software.aws.toolkits.resources.message import javax.swing.JLabel @@ -109,11 +113,18 @@ class CodeWhispererCodeReferenceManager(private val project: Project) { } } - fun insertCodeReference(states: InvocationContext, selectedIndex: Int) { + fun insertCodeReference(states: InvocationContext, details: List>, selectedIndex: Int) { val (requestContext, _, recommendationContext) = states val (_, editor, _, caretPosition) = requestContext - val (_, detail, reformattedDetail) = recommendationContext.details[selectedIndex] - insertCodeReference(detail.content(), reformattedDetail.references(), editor, caretPosition, detail) +// val (_, detail, reformattedDetail) = recommendationContext.details[selectedIndex] +// val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> +// val context = element.recommendationContext +// context.details.map { +// Tuple4(it, context.userInputSinceInvocation, context.typeaheadOriginal, context.typeahead) +// } +// } + val detail = details[selectedIndex].v1 + insertCodeReference(details[selectedIndex].v1.recommendation.content(), detail.reformatted.references(), editor, caretPosition, detail.recommendation) } fun getReferenceLineNums(editor: Editor, start: Int, end: Int): String { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt index 4eb9980e216..f8ac8f4cfe4 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt @@ -38,7 +38,7 @@ object CodeWhispererConstants { // TODO: this is currently set to 2050 to account for the server side 0.5 TPS and and extra 50 ms buffer to // avoid ThrottlingException as much as possible. - const val INVOCATION_INTERVAL: Long = 2050 + const val INVOCATION_INTERVAL: Long = 100 const val Q_CUSTOM_LEARN_MORE_URI = "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/customizations.html" const val Q_SUPPORTED_LANG_URI = "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-language-ide-support.html" @@ -105,7 +105,7 @@ object CodeWhispererConstants { } object Config { - const val CODEWHISPERER_ENDPOINT = "https://codewhisperer.us-east-1.amazonaws.com/" // PROD + const val CODEWHISPERER_ENDPOINT = "https://rts.gamma-us-east-1.codewhisperer.ai.aws.dev/" // PROD const val CODEWHISPERER_IDPOOL_ID = "us-east-1:70717e99-906f-4add-908c-bd9074a2f5b9" val Sigv4ClientRegion = Region.US_EAST_1 val BearerClientRegion = Region.US_EAST_1 diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt index a10681923ed..1c803c33377 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt @@ -195,7 +195,7 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { fun `test cancelling popup will send user decision event for all unseen but one rejected`() { val userGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup() withCodeWhispererServiceInvokedAndWait { states -> - popupManagerSpy.cancelPopup(states.popup) + popupManagerSpy.cancelPopup() val count = pythonResponse.completions().size argumentCaptor().apply { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt index bc5e09dd52d..50e684132bd 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt @@ -97,7 +97,7 @@ open class CodeWhispererTestBase { } popupManagerSpy = spy(CodeWhispererPopupManager.getInstance()) - popupManagerSpy.reset() + popupManagerSpy.resetSession() doNothing().`when`(popupManagerSpy).showPopup(any(), any(), any(), any(), any()) ApplicationManager.getApplication().replaceService(CodeWhispererPopupManager::class.java, popupManagerSpy, disposableRule.disposable) @@ -155,7 +155,7 @@ open class CodeWhispererTestBase { open fun tearDown() { stateManager.loadState(originalExplorerActionState) settingsManager.loadState(originalSettings) - popupManagerSpy.reset() + popupManagerSpy.resetSession() runInEdtAndWait { popupManagerSpy.closePopup() } 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 d46a603e61b..eecebf46ae1 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 @@ -95,7 +95,7 @@ class CodeWhispererUserActionsTest : CodeWhispererTestBase() { } withCodeWhispererServiceInvokedAndWait { projectRule.fixture.performEditorAction(actionId) - verify(popupManagerSpy, timeout(5000)).cancelPopup(any()) + verify(popupManagerSpy, timeout(5000)).cancelPopup() } } From 691bdb4e1a830591cceb40b95deb339b6d979538 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 29 Aug 2024 22:47:55 -0700 Subject: [PATCH 02/11] state -> sessionContext changes --- .../credentials/CodeWhispererClientAdaptor.kt | 11 +- .../editor/CodeWhispererEditorManager.kt | 25 +- .../importadder/CodeWhispererImportAdder.kt | 13 +- .../CodeWhispererImportAdderListener.kt | 8 +- .../inlay/CodeWhispererInlayManager.kt | 13 +- .../codewhisperer/model/CodeWhispererModel.kt | 83 ++- .../popup/CodeWhispererPopupListener.kt | 22 +- .../popup/CodeWhispererPopupManager.kt | 562 +++++------------- .../popup/CodeWhispererUIChangeListener.kt | 77 +-- .../CodeWhispererEditorActionHandler.kt | 3 +- .../CodeWhispererPopupBackspaceHandler.kt | 7 +- .../CodeWhispererPopupEnterHandler.kt | 7 +- .../handlers/CodeWhispererPopupEscHandler.kt | 18 + .../CodeWhispererPopupLeftArrowHandler.kt | 5 +- .../CodeWhispererPopupRightArrowHandler.kt | 5 +- .../handlers/CodeWhispererPopupTabHandler.kt | 4 +- .../CodeWhispererPopupTypedHandler.kt | 5 +- ...CodeWhispererAcceptButtonActionListener.kt | 4 +- .../listeners/CodeWhispererActionListener.kt | 3 +- .../CodeWhispererNextButtonActionListener.kt | 5 +- .../CodeWhispererPrevButtonActionListener.kt | 5 +- .../listeners/CodeWhispererScrollListener.kt | 6 +- .../CodeWhispererAutoTriggerService.kt | 38 +- .../service/CodeWhispererInvocationStatus.kt | 16 +- .../service/CodeWhispererService.kt | 281 +++++---- ...hispererIntelliSenseAutoTriggerListener.kt | 5 +- .../CodeWhispererCodeCoverageTracker.kt | 9 +- .../CodeWhispererTelemetryService.kt | 139 +++-- ...odeWhispererCodeReferenceActionListener.kt | 8 +- .../CodeWhispererCodeReferenceManager.kt | 26 +- .../util/CodeWhispererConstants.kt | 8 +- .../CodeWhispererTelemetryServiceTest.kt | 12 +- .../CodeWhispererTelemetryTest.kt | 36 +- .../CodeWhispererUserActionsTest.kt | 2 +- 34 files changed, 569 insertions(+), 902 deletions(-) create mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt index e2508153b21..81688f728e6 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt @@ -46,6 +46,7 @@ import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererCon import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants @@ -92,6 +93,7 @@ interface CodeWhispererClientAdaptor : Disposable { fun listAvailableCustomizations(): List fun sendUserTriggerDecisionTelemetry( + sessionContext: SessionContext, requestContext: RequestContext, responseContext: ResponseContext, completionType: CodewhispererCompletionType, @@ -293,6 +295,7 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW } override fun sendUserTriggerDecisionTelemetry( + sessionContext: SessionContext, requestContext: RequestContext, responseContext: ResponseContext, completionType: CodewhispererCompletionType, @@ -303,24 +306,24 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW ): SendTelemetryEventResponse { val fileContext = requestContext.fileContextInfo val programmingLanguage = fileContext.programmingLanguage - var e2eLatency = requestContext.latencyContext.getCodeWhispererEndToEndLatency() + var e2eLatency = sessionContext.latencyContext.getCodeWhispererEndToEndLatency() // When we send a userTriggerDecision of Empty or Discard, we set the time users see the first // suggestion to be now. if (e2eLatency < 0) { e2eLatency = TimeUnit.NANOSECONDS.toMillis( - System.nanoTime() - requestContext.latencyContext.codewhispererEndToEndStart + System.nanoTime() - sessionContext.latencyContext.codewhispererEndToEndStart ).toDouble() } return bearerClient().sendTelemetryEvent { requestBuilder -> requestBuilder.telemetryEvent { telemetryEventBuilder -> telemetryEventBuilder.userTriggerDecisionEvent { - it.requestId(requestContext.latencyContext.firstRequestId) + it.requestId(sessionContext.latencyContext.firstRequestId) it.completionType(completionType.toCodeWhispererSdkType()) it.programmingLanguage { builder -> builder.languageName(programmingLanguage.toCodeWhispererRuntimeLanguage().languageId) } it.sessionId(responseContext.sessionId) it.recommendationLatencyMilliseconds(e2eLatency) - it.triggerToResponseLatencyMilliseconds(requestContext.latencyContext.paginationFirstCompletionTime) + it.triggerToResponseLatencyMilliseconds(sessionContext.latencyContext.paginationFirstCompletionTime) it.suggestionState(suggestionState.toCodeWhispererSdkType()) it.timestamp(Instant.now()) it.suggestionReferenceCount(suggestionReferenceCount) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt index 2d551eeea53..742d6ad782e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt @@ -10,8 +10,6 @@ import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager -import groovy.lang.Tuple3 -import groovy.lang.Tuple4 import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext @@ -26,21 +24,18 @@ import java.util.Stack @Service class CodeWhispererEditorManager { - fun updateEditorWithRecommendation(states: InvocationContext, sessionContext: SessionContext) { + fun updateEditorWithRecommendation(sessionContext: SessionContext) { + val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() + val selectedIndex = sessionContext.selectedIndex + val preview = previews[selectedIndex] + val states = CodeWhispererService.getInstance().getAllPaginationSessions()[preview.jobId] ?: return val (requestContext, responseContext) = states - val (project, editor) = requestContext + val (project, editor) = sessionContext val document = editor.document val primaryCaret = editor.caretModel.primaryCaret - val selectedIndex = sessionContext.selectedIndex - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { - Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } - val typeahead = details[selectedIndex].v3 - val detail = details[selectedIndex].v1 - val userInput = details[selectedIndex].v2 + val typeahead = preview.typeahead + val detail = preview.detail + val userInput = preview.userInput val reformatted = CodeWhispererPopupManager.getInstance().getReformattedRecommendation( detail, userInput @@ -77,7 +72,7 @@ class CodeWhispererEditorManager { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED, - ).afterAccept(states, details, sessionContext, rangeMarker) + ).afterAccept(states, previews, sessionContext, rangeMarker) } } } 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 7c627065353..a83dd75b812 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 @@ -9,7 +9,6 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import groovy.lang.Tuple3 -import groovy.lang.Tuple4 import software.amazon.awssdk.services.codewhispererruntime.model.Import import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger @@ -17,21 +16,15 @@ import software.aws.toolkits.core.utils.info import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService abstract class CodeWhispererImportAdder { abstract val supportedLanguages: List abstract val dummyFileName: String - fun insertImportStatements(states: InvocationContext, details: List>, sessionContext: SessionContext) { -// val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> -// val context = element.recommendationContext -// context.details.map { -// Tuple4(it, context.userInputSinceInvocation, context.typeaheadOriginal, context.typeahead) -// } -// } - val imports = details[sessionContext.selectedIndex].v1.recommendation.mostRelevantMissingImports() + fun insertImportStatements(states: InvocationContext, previews: List, sessionContext: SessionContext) { + val imports = previews[sessionContext.selectedIndex].detail.recommendation.mostRelevantMissingImports() LOG.info { "Adding ${imports.size} imports for completions, sessionId: ${states.responseContext.sessionId}" } imports.forEach { insertImportStatement(states, it) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt index cdfd594f0a5..a583bd54cde 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt @@ -4,19 +4,17 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.importadder import com.intellij.openapi.editor.RangeMarker -import groovy.lang.Tuple3 -import groovy.lang.Tuple4 import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger -import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererSettings object CodeWhispererImportAdderListener : CodeWhispererUserActionListener { internal val LOG = getLogger() - override fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) { + override fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) { if (!CodeWhispererSettings.getInstance().isImportAdderEnabled()) { LOG.debug { "Import adder not enabled in user settings" } return @@ -31,6 +29,6 @@ object CodeWhispererImportAdderListener : CodeWhispererUserActionListener { LOG.debug { "No import adder found for $language" } return } - importAdder.insertImportStatements(states, details, sessionContext) + importAdder.insertImportStatements(states, previews, sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt index 8e38b443805..60daf84c948 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt @@ -6,29 +6,26 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.inlay import com.intellij.idea.AppMode import com.intellij.openapi.components.Service import com.intellij.openapi.components.service -import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorCustomElementRenderer import com.intellij.openapi.editor.Inlay -import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.util.Disposer -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext @Service class CodeWhispererInlayManager { private val existingInlays = mutableListOf>() - fun updateInlays(states: InvocationContext, sessionContext: SessionContext, chunks: List) { + fun updateInlays(sessionContext: SessionContext, chunks: List) { clearInlays() chunks.forEach { chunk -> - createCodeWhispererInlays(states, sessionContext, chunk.inlayOffset, chunk.text) + createCodeWhispererInlays(sessionContext, chunk.inlayOffset, chunk.text) } } - private fun createCodeWhispererInlays(states: InvocationContext, sessionContext: SessionContext, startOffset: Int, inlayText: String) { + private fun createCodeWhispererInlays(sessionContext: SessionContext, startOffset: Int, inlayText: String) { if (inlayText.isEmpty()) return - val editor = states.requestContext.editor + val editor = sessionContext.editor val firstNewlineIndex = inlayText.indexOf("\n") val firstLine: String val otherLines: String @@ -50,7 +47,7 @@ class CodeWhispererInlayManager { val inlineInlay = editor.inlayModel.addInlineElement(startOffset, true, firstLineRenderer) inlineInlay?.let { existingInlays.add(it) - Disposer.register(states, it) + Disposer.register(sessionContext, it) } } 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 d54d0a24ff3..82fc71ab62a 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,11 +3,18 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.model +import com.intellij.codeInsight.lookup.LookupManager import com.intellij.openapi.Disposable +import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.VisualPosition 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.vfs.VirtualFile +import com.intellij.openapi.wm.WindowManager +import com.intellij.ui.ComponentUtil +import com.intellij.ui.popup.AbstractPopup import com.intellij.util.concurrency.annotations.RequiresEdt import software.amazon.awssdk.services.codewhispererruntime.model.Completion import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsResponse @@ -16,10 +23,13 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionco import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererIntelliSenseOnHoverListener 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.service.RequestContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext +import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.AcceptedSuggestionEntry +import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants import software.aws.toolkits.jetbrains.services.codewhisperer.util.CrossFileStrategy import software.aws.toolkits.jetbrains.services.codewhisperer.util.SupplementalContextStrategy @@ -27,6 +37,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.UtgStrategy import software.aws.toolkits.telemetry.CodewhispererCompletionType import software.aws.toolkits.telemetry.CodewhispererTriggerType import software.aws.toolkits.telemetry.Result +import java.time.Duration +import java.time.Instant import java.util.concurrent.TimeUnit data class Chunk( @@ -90,8 +102,14 @@ data class RecommendationContext( val userInputSinceInvocation: String, val position: VisualPosition, val jobId: Int, -// var typeahead: String = "", - var typeaheadOriginal: String = "", + var typeahead: String = "", +) + +data class PreviewContext( + val jobId: Int, + val detail: DetailContext, + val userInput: String, + val typeahead: String, ) data class DetailContext( @@ -105,30 +123,61 @@ data class DetailContext( ) data class SessionContext( - var selectedIndex: Int = 0, + val project: Project, + val editor: Editor, + var popup: JBPopup? = null, + var selectedIndex: Int = -1, val seen: MutableSet = mutableSetOf(), var isFirstTimeShowingPopup: Boolean = true, var toBeRemovedHighlighter: RangeHighlighter? = null, var insertEndOffset: Int = -1, - var popupDisplayOffset: Int = -1 + var popupDisplayOffset: Int = -1, + val latencyContext: LatencyContext, + var hasAccepted: Boolean = false ) : Disposable { private var isDisposed = false + init { + project.messageBus.connect().subscribe( + CodeWhispererService.CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER, + object : CodeWhispererIntelliSenseOnHoverListener { + override fun onEnter() { + val popupWindow = (popup as AbstractPopup?)?.popupWindow ?: return + WindowManager.getInstance().setAlphaModeRatio(popupWindow, 1f) + + val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) + if (a != null) { + WindowManager.getInstance().setAlphaModeRatio(a, 0f) + } + } + } + ) + } @RequiresEdt override fun dispose() { println("disposing the session") - val jobIds = CodeWhispererService.getInstance().ongoingRequests.keys.toList() - jobIds.forEach { jobId -> - val job = CodeWhispererService.getInstance().ongoingRequests[jobId] ?: return@forEach - Disposer.dispose(job) + + CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll( + this, + hasAccepted, + CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) } + ) + + val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) + if (a != null) { + WindowManager.getInstance().setAlphaModeRatio(a, 0f) + } + + CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(false) + + if (hasAccepted) { + popup?.closeOk(null) + } else { + popup?.cancel() } - CodeWhispererService.getInstance().ongoingRequests.clear() - CodeWhispererService.getInstance().ongoingRequestsContext.clear() - CodeWhispererPopupManager.getInstance().cancelPopup() - CodeWhispererPopupManager.getInstance().sessionContext = null + popup?.let { Disposer.dispose(it) } + popup = null CodeWhispererInvocationStatus.getInstance().finishInvocation() - println("current ongoingRequestContext:") - CodeWhispererService.getInstance().ongoingRequestsContext.keys.forEach { print("$it ") } isDisposed = true } @@ -157,11 +206,6 @@ data class InvocationContext( @RequiresEdt override fun dispose() { - -// CodeWhispererPopupManager.getInstance().cancelPopup() - CodeWhispererService.getInstance().ongoingRequests.remove(recommendationContext.jobId) - CodeWhispererService.getInstance().ongoingRequestsContext.remove(recommendationContext.jobId) - // TODO: send userTriggerDecision telemetry println("state for jobId ${recommendationContext.jobId} is disposed") @@ -175,7 +219,6 @@ data class WorkerContext( val requestContext: RequestContext, val responseContext: ResponseContext, val response: GenerateCompletionsResponse, -// val popup: JBPopup ) data class CodeScanTelemetryEvent( diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt index 00e3400db86..ef981d90b8e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt @@ -5,34 +5,16 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup import com.intellij.openapi.ui.popup.JBPopupListener import com.intellij.openapi.ui.popup.LightweightWindowEvent -import com.intellij.openapi.util.Disposer -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext 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 java.time.Duration -import java.time.Instant -class CodeWhispererPopupListener(private val states: InvocationContext, private val sessionContext: SessionContext) : JBPopupListener { +class CodeWhispererPopupListener : JBPopupListener { override fun beforeShown(event: LightweightWindowEvent) { super.beforeShown(event) CodeWhispererInvocationStatus.getInstance().setPopupStartTimestamp() } override fun onClosed(event: LightweightWindowEvent) { super.onClosed(event) - val (requestContext, responseContext, recommendationContext) = states - - CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll( - requestContext, - responseContext, - recommendationContext, - sessionContext, - event.isOk, - CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) } - ) - - CodeWhispererInvocationStatus.getInstance().setPopupActive(false) - Disposer.dispose(sessionContext) + CodeWhispererService.getInstance().disposeDisplaySession(event.isOk) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index 3f6be398c4c..3aa1d7c47a6 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -3,11 +3,11 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup -import com.intellij.codeInsight.hint.ParameterInfoController import com.intellij.codeInsight.lookup.LookupManager import com.intellij.idea.AppMode import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_BACKSPACE import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ENTER +import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ESCAPE import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_LEFT import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TAB @@ -15,7 +15,6 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service -import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.actionSystem.EditorActionManager @@ -44,8 +43,6 @@ import com.intellij.ui.popup.PopupFactoryImpl import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.messages.Topic import com.intellij.util.ui.UIUtil -import com.jetbrains.rd.swing.awtMousePoint -import groovy.lang.Tuple3 import software.amazon.awssdk.services.codewhispererruntime.model.Import import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.aws.toolkits.core.utils.debug @@ -56,10 +53,12 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.inlineLabelConstraints import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererEditorActionHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupBackspaceHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEnterHandler +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEscHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupLeftArrowHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupRightArrowHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTabHandler @@ -71,7 +70,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.Co import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererScrollListener 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.CodeWhispererColorUtil.POPUP_DIM_HEX import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE @@ -91,9 +89,8 @@ class CodeWhispererPopupManager { var shouldListenerCancelPopup: Boolean = true private set - var sessionContext: SessionContext? = null - private var myPopup: JBPopup? = null +// private var myPopup: JBPopup? = null init { // Listen for global scheme changes @@ -121,255 +118,87 @@ class CodeWhispererPopupManager { } @RequiresEdt - fun changeStatesForNavigation(states: InvocationContext, indexChange: Int) { - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { - Triple(it, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } - var sessionContext = sessionContext ?: SessionContext() - this.sessionContext = sessionContext - - val isReverse = indexChange < 0 - val userInput = states.recommendationContext.userInputSinceInvocation - val validCount = getValidCount(emptyList(), userInput, "") - val validSelectedIndex = getValidSelectedIndex(emptyList(), userInput, sessionContext.selectedIndex, "") + fun changeStatesForNavigation(sessionContext: SessionContext, indexChange: Int) { + val validCount = getValidCount() + val validSelectedIndex = getValidSelectedIndex(sessionContext.selectedIndex) if ((validSelectedIndex == validCount - 1 && indexChange == 1) || (validSelectedIndex == 0 && indexChange == -1) ) { return } - val selectedIndex = findNewSelectedIndex( - isReverse, - sessionContext.selectedIndex + indexChange - ) - if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex].first, details[selectedIndex].second, details[selectedIndex].third)) { - LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } - Disposer.dispose(sessionContext) - return - } + val isReverse = indexChange < 0 + val selectedIndex = findNewSelectedIndex(isReverse, sessionContext.selectedIndex + indexChange) sessionContext.selectedIndex = selectedIndex sessionContext.isFirstTimeShowingPopup = false ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( - states, sessionContext ) } @RequiresEdt fun changeStatesForTypeahead( - states: InvocationContext, + sessionContext: SessionContext, typeaheadChange: String, typeaheadAdded: Boolean ) { - var sessionContext = sessionContext ?: SessionContext() - this.sessionContext = sessionContext - val recos = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull() - val userInput = states.recommendationContext.userInputSinceInvocation - - sessionContext.selectedIndex - recos.forEach { - val typeaheadOriginal = - if (typeaheadAdded) { - it.recommendationContext.typeaheadOriginal + typeaheadChange - } else { - if (typeaheadChange.length > it.recommendationContext.typeaheadOriginal.length) { - Disposer.dispose(sessionContext) - println("exit 7, ") - return - } - it.recommendationContext.typeaheadOriginal.substring( - 0, - it.recommendationContext.typeaheadOriginal.length - typeaheadChange.length - ) - } - it.recommendationContext.typeaheadOriginal = typeaheadOriginal - } - val isReverse = false - val selectedIndex = findNewSelectedIndex( - isReverse, - sessionContext.selectedIndex - ) - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - element.recommendationContext.details.map { - Triple(it, element.recommendationContext.userInputSinceInvocation, element.recommendationContext.typeaheadOriginal) - } - } - if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex].first, details[selectedIndex].second, details[selectedIndex].third)) { + CodeWhispererService.getInstance().updateTypeahead(typeaheadChange, typeaheadAdded, sessionContext) + val selectedIndex = findNewSelectedIndex(false, sessionContext.selectedIndex) + if (selectedIndex == -1) { LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } - Disposer.dispose(sessionContext) -// cancelPopup() + CodeWhispererService.getInstance().disposeDisplaySession(false) return } -// recos.forEach { -// it.recommendationContext.typeahead = resolveTypeahead(it, details, selectedIndex, it.recommendationContext.typeaheadOriginal) -// } -// sessionContext.typeahead = resolveTypeahead(states, selectedIndex, typeaheadOriginal) -// sessionContext.typeaheadOriginal = typeaheadOriginal sessionContext.selectedIndex = selectedIndex sessionContext.isFirstTimeShowingPopup = false ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( - states, sessionContext ) } @RequiresEdt - fun changeStatesForShowing( - states: InvocationContext, - recommendationAdded: Boolean = false - ) { - var sessionContext = sessionContext ?: SessionContext() - this.sessionContext = sessionContext + fun changeStatesForShowing(sessionContext: SessionContext, states: InvocationContext, recommendationAdded: Boolean = false) { if (recommendationAdded) { -// LOG.debug { -// "Add recommendations to the existing CodeWhisperer session, current number of recommendations: ${details.size}" -// } ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED) .recommendationAdded(states, sessionContext) return } - val isReverse = false - val selectedIndex = findNewSelectedIndex( - isReverse, - sessionContext.selectedIndex, - ) - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - element.recommendationContext.details.map { - Triple(it, element.recommendationContext.userInputSinceInvocation, element.recommendationContext.typeaheadOriginal) - } - } - if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex].first, details[selectedIndex].second, details[selectedIndex].third)) { + val selectedIndex = findNewSelectedIndex(false, sessionContext.selectedIndex) + if (selectedIndex == -1) { LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } - Disposer.dispose(sessionContext) -// cancelPopup() + CodeWhispererService.getInstance().disposeDisplaySession(false) return } sessionContext.selectedIndex = selectedIndex sessionContext.isFirstTimeShowingPopup = true if (sessionContext.popupDisplayOffset == -1) { - sessionContext.popupDisplayOffset = states.requestContext.editor.caretModel.offset + sessionContext.popupDisplayOffset = sessionContext.editor.caretModel.offset } ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( - states, sessionContext ) } -// fun changeStates( -// states: InvocationContext, -// indexChange: Int, -// typeaheadChange: String, -// typeaheadAdded: Boolean, -// recommendationAdded: Boolean = false -// ) { -// val (_, _, recommendationContext) = states -// val (details) = recommendationContext -// var sessionContext = sessionContext ?: SessionContext() -// if (recommendationAdded) { -// LOG.debug { -// "Add recommendations to the existing CodeWhisperer session, current number of recommendations: ${details.size}" -// } -// ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED) -// .recommendationAdded(states, sessionContext) -// return -// } -// val typeaheadOriginal = -// if (typeaheadAdded) { -// recommendationContext.typeaheadOriginal + typeaheadChange -// } else { -// if (typeaheadChange.length > recommendationContext.typeaheadOriginal.length) { -// Disposer.dispose(sessionContext) -// println("exit 7, ") -// return -// } -// sessionContext.typeaheadOriginal.substring( -// 0, -// sessionContext.typeaheadOriginal.length - typeaheadChange.length -// ) -// } -// val isReverse = indexChange < 0 -// val userInput = states.recommendationContext.userInputSinceInvocation -// val validCount = getValidCount(details, userInput, typeaheadOriginal) -// val validSelectedIndex = getValidSelectedIndex(details, userInput, sessionContext.selectedIndex, typeaheadOriginal) -// if ((validSelectedIndex == validCount - 1 && indexChange == 1) || -// (validSelectedIndex == 0 && indexChange == -1) -// ) { -// return -// } -// val selectedIndex = findNewSelectedIndex( -// isReverse, -// sessionContext.selectedIndex + indexChange -// ) -// if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex], userInput, typeaheadOriginal)) { -// LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } -// Disposer.dispose(sessionContext) -// return -// } -// -// sessionContext.typeahead = resolveTypeahead(states, selectedIndex, typeaheadOriginal) -// sessionContext.typeaheadOriginal = typeaheadOriginal -// sessionContext.selectedIndex = selectedIndex -// sessionContext.isFirstTimeShowingPopup = indexChange == 0 && typeaheadChange.isEmpty() -// -// ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( -// states, -// sessionContext -// ) -// this.sessionContext = sessionContext -// } - - private fun resolveTypeahead(states: InvocationContext, details: List>, selectedIndex: Int, typeahead: String): String { - val recommendation = details[selectedIndex].first.reformatted.content() - val userInput = states.recommendationContext.userInputSinceInvocation - var indexOfFirstNonWhiteSpace = typeahead.indexOfFirst { !it.isWhitespace() } - if (indexOfFirstNonWhiteSpace == -1) { - indexOfFirstNonWhiteSpace = typeahead.length - } - - for (i in 0..indexOfFirstNonWhiteSpace) { - val subTypeahead = typeahead.substring(i) - if (recommendation.startsWith(userInput + subTypeahead)) return subTypeahead - } - return typeahead - } - - fun updatePopupPanel(states: InvocationContext?, sessionContext: SessionContext?) { - if (states == null || sessionContext == null) return - val userInput = states.recommendationContext.userInputSinceInvocation -// val details = states.recommendationContext.details + fun updatePopupPanel(sessionContext: SessionContext?) { + if (sessionContext == null || sessionContext.selectedIndex == -1 || sessionContext.isDisposed()) return val selectedIndex = sessionContext.selectedIndex - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { - Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } - val typeaheadOriginal = details[selectedIndex].v3 - val validCount = getValidCount(emptyList(), userInput, typeaheadOriginal) - val validSelectedIndex = getValidSelectedIndex(emptyList(), userInput, selectedIndex, typeaheadOriginal) + val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() + val validCount = getValidCount() + val validSelectedIndex = getValidSelectedIndex(selectedIndex) updateSelectedRecommendationLabelText(validSelectedIndex, validCount) updateNavigationPanel(validSelectedIndex, validCount) - updateImportPanel(details[selectedIndex].v1.recommendation.mostRelevantMissingImports()) - updateCodeReferencePanel(states.requestContext.project, details[selectedIndex].v1.recommendation.references()) + updateImportPanel(previews[selectedIndex].detail.recommendation.mostRelevantMissingImports()) + updateCodeReferencePanel(sessionContext.project, previews[selectedIndex].detail.recommendation.references()) } - fun render( - states: InvocationContext, - sessionContext: SessionContext, - overlappingLinesCount: Int, - isRecommendationAdded: Boolean, - isScrolling: Boolean - ) { - updatePopupPanel(states, sessionContext) + fun render(sessionContext: SessionContext, isRecommendationAdded: Boolean, isScrolling: Boolean) { + updatePopupPanel(sessionContext) sessionContext.seen.add(sessionContext.selectedIndex) @@ -383,10 +212,10 @@ class CodeWhispererPopupManager { // 4. User navigating through the completions or typing as the completion shows. We should not update the latency // end time and should not emit any events in this case. if (!isRecommendationAdded) { - showPopup(states, sessionContext) + showPopup(sessionContext) if (!isScrolling) { - states.requestContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime() - states.requestContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime() + sessionContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime() + sessionContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime() } } if (isScrolling || @@ -395,7 +224,7 @@ class CodeWhispererPopupManager { ) { return } - CodeWhispererTelemetryService.getInstance().sendClientComponentLatencyEvent(states) +// CodeWhispererTelemetryService.getInstance().sendClientComponentLatencyEvent(sessionContext) } fun dontClosePopupAndRun(runnable: () -> Unit) { @@ -407,81 +236,47 @@ class CodeWhispererPopupManager { } } - fun resetSession() { - sessionContext?.let { - Disposer.dispose(it) - } - sessionContext = null - } - - fun cancelPopup() { - myPopup?.let { - it.cancel() - Disposer.dispose(it) - } - myPopup = null - } - - fun closePopup() { - myPopup?.let { - it.closeOk(null) - Disposer.dispose(it) - } - myPopup = null - } +// fun resetSession() { +// sessionContext?.let { +// Disposer.dispose(it) +// } +// sessionContext = null +// } fun showPopup( - states: InvocationContext, sessionContext: SessionContext, force: Boolean = false, ) { -// popup = initPopup() - val p = states.requestContext.editor.offsetToXY(sessionContext.popupDisplayOffset) - var popup: JBPopup? = null - if (myPopup == null) { + val p = sessionContext.editor.offsetToXY(sessionContext.popupDisplayOffset) + val popup: JBPopup? + if (sessionContext.popup == null) { popup = initPopup() - initPopupListener(states, sessionContext, popup) + sessionContext.popup = popup + initPopupListener(sessionContext, popup) } else { - popup = myPopup - } -// val popup = myPopup - if (popup == null) { - val a = 1 + popup = sessionContext.popup } - val editor = states.requestContext.editor -// val detailContexts = states.recommendationContext.details - val userInputOriginal = states.recommendationContext.userInputOriginal -// val userInput = states.recommendationContext.userInputSinceInvocation - val selectedIndex = sessionContext.selectedIndex - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { - Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } - val detail = details[selectedIndex].v1 -// val typeahead = details[selectedIndex].v4 + val editor = sessionContext.editor + val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() + val userInputOriginal = previews[sessionContext.selectedIndex].userInput val userInputLines = userInputOriginal.split("\n").size - 1 -// val lineCount = getReformattedRecommendation(detail, userInput).split("\n").size val popupSize = (popup as AbstractPopup).preferredContentSize -// val yBelowLastLine = p.y + (lineCount + additionalLines + userInputLines - overlappingLinesCount) * editor.lineHeight val yAboveFirstLine = p.y - popupSize.height + userInputLines * editor.lineHeight val popupRect = Rectangle(p.x, yAboveFirstLine, popupSize.width, popupSize.height) val editorRect = editor.scrollingModel.visibleArea var shouldHidePopup = false - CodeWhispererInvocationStatus.getInstance().setPopupActive(true) + CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(true) // Check if the current editor still has focus. If not, don't show the popup. val isSameEditorAsTrigger = if (!AppMode.isRemoteDevHost()) { editor.contentComponent.isFocusOwner } else { - FileEditorManager.getInstance(states.requestContext.project).selectedTextEditorWithRemotes.firstOrNull() == editor + FileEditorManager.getInstance(sessionContext.project).selectedTextEditorWithRemotes.firstOrNull() == editor } - if (!isSameEditorAsTrigger && false) { + if (!isSameEditorAsTrigger) { LOG.debug { "Current editor no longer has focus, not showing the popup" } - Disposer.dispose(sessionContext) -// cancelPopup() + CodeWhispererService.getInstance().disposeDisplaySession(false) return } @@ -526,13 +321,13 @@ class CodeWhispererPopupManager { editor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, popupPositionForRemote) popup.showInBestPositionFor(editor) } - val perceivedLatency = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged() - CodeWhispererTelemetryService.getInstance().sendPerceivedLatencyEvent( - detail.requestId, - states.requestContext, - states.responseContext, - perceivedLatency - ) +// val perceivedLatency = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged() +// CodeWhispererTelemetryService.getInstance().sendPerceivedLatencyEvent( +// detail.requestId, +// states.requestContext, +// states.responseContext, +// perceivedLatency +// ) } val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) @@ -551,48 +346,34 @@ class CodeWhispererPopupManager { popup.popupWindow?.let { WindowManager.getInstance().setAlphaModeRatio(it, 1f) } - } else { -// WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 1f) } } } } - fun hidePopup(editor: Editor) { - val popupWindow = (myPopup as AbstractPopup?)?.popupWindow ?: return - WindowManager.getInstance().setAlphaModeRatio(popupWindow, 1f) - - val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) - if (a != null) { - WindowManager.getInstance().setAlphaModeRatio(a, 0f) - } - } - fun initPopup(): JBPopup = JBPopupFactory.getInstance() .createComponentPopupBuilder(popupComponents.panel, null) .setAlpha(0.1F) .setCancelOnClickOutside(true) -// .setCancelOnOtherWindowOpen(true) - .setCancelKeyEnabled(true) + .setCancelOnOtherWindowOpen(true) +// .setCancelKeyEnabled(true) .setCancelOnWindowDeactivation(true) - .createPopup().also { - myPopup = it - } + .createPopup() fun getReformattedRecommendation(detailContext: DetailContext, userInput: String) = detailContext.reformatted.content().substring(userInput.length) - fun initPopupListener(states: InvocationContext, sessionContext: SessionContext, popup: JBPopup) { - addPopupListener(states, sessionContext, popup) - states.requestContext.editor.scrollingModel.addVisibleAreaListener(CodeWhispererScrollListener(states, sessionContext), sessionContext) - addButtonActionListeners(states, sessionContext) - addMessageSubscribers(states, sessionContext) - setPopupActionHandlers(states, sessionContext) - addComponentListeners(states, sessionContext) + private fun initPopupListener(sessionContext: SessionContext, popup: JBPopup) { + addPopupListener(popup) + sessionContext.editor.scrollingModel.addVisibleAreaListener(CodeWhispererScrollListener(sessionContext), sessionContext) + addButtonActionListeners(sessionContext) + addMessageSubscribers(sessionContext) + setPopupActionHandlers(sessionContext) + addComponentListeners(sessionContext) } - private fun addPopupListener(states: InvocationContext, sessionContext: SessionContext, popup: JBPopup) { - val listener = CodeWhispererPopupListener(states, sessionContext) + private fun addPopupListener(popup: JBPopup) { + val listener = CodeWhispererPopupListener() popup.addListener(listener) Disposer.register(popup) { println("listener is removed") @@ -600,54 +381,54 @@ class CodeWhispererPopupManager { } } - private fun addMessageSubscribers(states: InvocationContext, sessionContext: SessionContext) { + private fun addMessageSubscribers(sessionContext: SessionContext) { val connect = ApplicationManager.getApplication().messageBus.connect(sessionContext) connect.subscribe( CODEWHISPERER_USER_ACTION_PERFORMED, object : CodeWhispererUserActionListener { - override fun navigateNext(states: InvocationContext) { - changeStatesForNavigation(states, 1) + override fun navigateNext(sessionContext: SessionContext) { + changeStatesForNavigation(sessionContext, 1) } - override fun navigatePrevious(states: InvocationContext) { - changeStatesForNavigation(states, -1) + override fun navigatePrevious(sessionContext: SessionContext) { + changeStatesForNavigation(sessionContext, -1) } - override fun backspace(states: InvocationContext, diff: String) { - changeStatesForTypeahead(states, diff, false) + override fun backspace(sessionContext: SessionContext, diff: String) { + changeStatesForTypeahead(sessionContext, diff, false) } - override fun enter(states: InvocationContext, diff: String) { - changeStatesForTypeahead(states, diff, true) + override fun enter(sessionContext: SessionContext, diff: String) { + changeStatesForTypeahead(sessionContext, diff, true) } - override fun type(states: InvocationContext, diff: String) { + override fun type(sessionContext: SessionContext, diff: String) { // remove the character at primaryCaret if it's the same as the typed character - val caretOffset = states.requestContext.editor.caretModel.primaryCaret.offset - val document = states.requestContext.editor.document + val caretOffset = sessionContext.editor.caretModel.primaryCaret.offset + val document = sessionContext.editor.document val text = document.charsSequence if (caretOffset < text.length && diff == text[caretOffset].toString()) { - WriteCommandAction.runWriteCommandAction(states.requestContext.project) { + WriteCommandAction.runWriteCommandAction(sessionContext.project) { document.deleteString(caretOffset, caretOffset + 1) } } - changeStatesForTypeahead(states, diff, true) + changeStatesForTypeahead(sessionContext, diff, true) } - override fun beforeAccept(states: InvocationContext, sessionContext: SessionContext) { + override fun beforeAccept(sessionContext: SessionContext) { dontClosePopupAndRun { - CodeWhispererEditorManager.getInstance().updateEditorWithRecommendation(states, sessionContext) + CodeWhispererEditorManager.getInstance().updateEditorWithRecommendation(sessionContext) } - closePopup() + CodeWhispererService.getInstance().disposeDisplaySession(true) } } ) } - private fun addButtonActionListeners(states: InvocationContext, sessionContext: SessionContext) { - popupComponents.prevButton.addButtonActionListener(CodeWhispererPrevButtonActionListener(states), sessionContext) - popupComponents.nextButton.addButtonActionListener(CodeWhispererNextButtonActionListener(states), sessionContext) - popupComponents.acceptButton.addButtonActionListener(CodeWhispererAcceptButtonActionListener(states, sessionContext), sessionContext) + private fun addButtonActionListeners(sessionContext: SessionContext) { + popupComponents.prevButton.addButtonActionListener(CodeWhispererPrevButtonActionListener(sessionContext), sessionContext) + popupComponents.nextButton.addButtonActionListener(CodeWhispererNextButtonActionListener(sessionContext), sessionContext) + popupComponents.acceptButton.addButtonActionListener(CodeWhispererAcceptButtonActionListener(sessionContext), sessionContext) } private fun JButton.addButtonActionListener(listener: CodeWhispererActionListener, sessionContext: SessionContext) { @@ -655,20 +436,21 @@ class CodeWhispererPopupManager { Disposer.register(sessionContext) { this.removeActionListener(listener) } } - private fun setPopupActionHandlers(states: InvocationContext, sessionContext: SessionContext) { + private fun setPopupActionHandlers(sessionContext: SessionContext) { val actionManager = EditorActionManager.getInstance() - setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, states), sessionContext) - setPopupActionHandler(ACTION_EDITOR_TAB, CodeWhispererPopupTabHandler(states, sessionContext), sessionContext) - setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_LEFT, CodeWhispererPopupLeftArrowHandler(states), sessionContext) - setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_RIGHT, CodeWhispererPopupRightArrowHandler(states), sessionContext) + setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, sessionContext), sessionContext) + setPopupActionHandler(ACTION_EDITOR_TAB, CodeWhispererPopupTabHandler(sessionContext), sessionContext) + setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_LEFT, CodeWhispererPopupLeftArrowHandler(sessionContext), sessionContext) + setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_RIGHT, CodeWhispererPopupRightArrowHandler(sessionContext), sessionContext) + setPopupActionHandler(ACTION_EDITOR_ESCAPE, CodeWhispererPopupEscHandler(sessionContext), sessionContext) setPopupActionHandler( ACTION_EDITOR_ENTER, - CodeWhispererPopupEnterHandler(actionManager.getActionHandler(ACTION_EDITOR_ENTER), states), + CodeWhispererPopupEnterHandler(actionManager.getActionHandler(ACTION_EDITOR_ENTER), sessionContext), sessionContext ) setPopupActionHandler( ACTION_EDITOR_BACKSPACE, - CodeWhispererPopupBackspaceHandler(actionManager.getActionHandler(ACTION_EDITOR_BACKSPACE), states), + CodeWhispererPopupBackspaceHandler(actionManager.getActionHandler(ACTION_EDITOR_BACKSPACE), sessionContext), sessionContext ) } @@ -683,77 +465,77 @@ class CodeWhispererPopupManager { Disposer.register(sessionContext) { EditorActionManager.getInstance().setActionHandler(id, oldHandler) } } - private fun addComponentListeners(states: InvocationContext, sessionContext: SessionContext) { - val editor = states.requestContext.editor - val codewhispererSelectionListener: SelectionListener = object : SelectionListener { + private fun addComponentListeners(sessionContext: SessionContext) { + val editor = sessionContext.editor + val codeWhispererSelectionListener: SelectionListener = object : SelectionListener { override fun selectionChanged(event: SelectionEvent) { if (shouldListenerCancelPopup) { - cancelPopup() + CodeWhispererService.getInstance().disposeDisplaySession(false) } super.selectionChanged(event) } } - editor.selectionModel.addSelectionListener(codewhispererSelectionListener) - Disposer.register(sessionContext) { editor.selectionModel.removeSelectionListener(codewhispererSelectionListener) } + editor.selectionModel.addSelectionListener(codeWhispererSelectionListener) + Disposer.register(sessionContext) { editor.selectionModel.removeSelectionListener(codeWhispererSelectionListener) } - val codewhispererDocumentListener: DocumentListener = object : DocumentListener { + val codeWhispererDocumentListener: DocumentListener = object : DocumentListener { override fun documentChanged(event: DocumentEvent) { - val statesWithin = states if (shouldListenerCancelPopup) { // handle IntelliSense accept case + // TODO: handle bulk delete (delete word) case if (editor.document == event.document && editor.caretModel.offset == event.offset && event.newLength > event.oldLength) { dontClosePopupAndRun { super.documentChanged(event) editor.caretModel.moveCaretRelatively(event.newLength, 0, false, false, true) - changeStatesForTypeahead(states, event.newFragment.toString(), true) + changeStatesForTypeahead(sessionContext, event.newFragment.toString(), true) } return } else { - cancelPopup() + CodeWhispererService.getInstance().disposeDisplaySession(false) } } super.documentChanged(event) } } - editor.document.addDocumentListener(codewhispererDocumentListener, states) + editor.document.addDocumentListener(codeWhispererDocumentListener, sessionContext) - val codewhispererCaretListener: CaretListener = object : CaretListener { + val codeWhispererCaretListener: CaretListener = object : CaretListener { override fun caretPositionChanged(event: CaretEvent) { -// if (shouldListenerCancelPopup) { -// cancelPopup() -// } + if (shouldListenerCancelPopup) { + CodeWhispererService.getInstance().disposeDisplaySession(false) + } super.caretPositionChanged(event) } } - editor.caretModel.addCaretListener(codewhispererCaretListener) - Disposer.register(sessionContext) { editor.caretModel.removeCaretListener(codewhispererCaretListener) } + editor.caretModel.addCaretListener(codeWhispererCaretListener) + Disposer.register(sessionContext) { editor.caretModel.removeCaretListener(codeWhispererCaretListener) } val editorComponent = editor.contentComponent if (editorComponent.isShowing) { val window = ComponentUtil.getWindow(editorComponent) val windowListener: ComponentListener = object : ComponentAdapter() { override fun componentMoved(event: ComponentEvent) { - cancelPopup() + CodeWhispererService.getInstance().disposeDisplaySession(false) } override fun componentShown(e: ComponentEvent?) { - cancelPopup() + CodeWhispererService.getInstance().disposeDisplaySession(false) super.componentShown(e) } } window?.addComponentListener(windowListener) - Disposer.register(states) { window?.removeComponentListener(windowListener) } + Disposer.register(sessionContext) { window?.removeComponentListener(windowListener) } } val suggestionHoverEnterListener: EditorMouseMotionListener = object : EditorMouseMotionListener { override fun mouseMoved(e: EditorMouseEvent) { e.mouseEvent.component - println("current mouse offset : ${e.offset}, point: ${e.mouseEvent.point}") +// println("current mouse offset : ${e.offset}, point: ${e.mouseEvent.point}") val startOffset = editor.offsetToXY(editor.caretModel.offset) - println("caret x y: ${startOffset}") +// println("caret x y: ${startOffset}") val point = e.mouseEvent.point val right = startOffset.x + (e.inlay?.widthInPixels ?: 0) val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) @@ -765,14 +547,16 @@ class CodeWhispererPopupManager { } if (e.inlay != null) { - showPopup(states, sessionContext, force = true) + showPopup(sessionContext, force = true) } else { - hidePopup(editor) + sessionContext.project.messageBus.syncPublisher( + CodeWhispererService.CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER, + ).onEnter() } super.mouseMoved(e) } } - editor.addEditorMouseMotionListener(suggestionHoverEnterListener, states) + editor.addEditorMouseMotionListener(suggestionHoverEnterListener, sessionContext) } private fun updateSelectedRecommendationLabelText(validSelectedIndex: Int, validCount: Int) { @@ -849,24 +633,10 @@ class CodeWhispererPopupManager { } } - fun hasConflictingPopups(editor: Editor): Boolean = - (ParameterInfoController.existsWithVisibleHintForEditor(editor, true) || - LookupManager.getActiveLookup(editor) != null) && false - - fun findNewSelectedIndex( - isReverse: Boolean, - start: Int - ): Int { - val recos = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - element.recommendationContext.details.map { - Triple(it, element.recommendationContext.userInputSinceInvocation, element.recommendationContext.typeaheadOriginal) - } - } - - - -// .flatMap { Triple(it.recommendationContext.details, it.recommendationContext. } - val count = recos.size + fun findNewSelectedIndex(isReverse: Boolean, selectedIndex: Int): Int { + val start = if (selectedIndex == -1) 0 else selectedIndex + val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() + val count = previews.size val unit = if (isReverse) -1 else 1 var currIndex: Int for (i in 0 until count) { @@ -874,58 +644,25 @@ class CodeWhispererPopupManager { if (currIndex < 0) { currIndex += count } - val triple = recos[currIndex] - if (isValidRecommendation(triple.first, triple.second, triple.third)) { + if (isValidRecommendation(previews[currIndex])) { return currIndex } } - -// val count = detailContexts.size -// val unit = if (isReverse) -1 else 1 -// var currIndex: Int -// for (i in 0 until count) { -// currIndex = (start + i * unit) % count -// if (currIndex < 0) { -// currIndex += count -// } -// if (isValidRecommendation(detailContexts[currIndex], userInput, typeahead)) { -// return currIndex -// } -// } return -1 } - private fun getValidCount(detailContexts: List, userInput: String, typeahead: String): Int { - var count = 0 - CodeWhispererService.getInstance().ongoingRequests.forEach { t, u -> - if (u == null) return@forEach - count += u.recommendationContext.details.filter { - isValidRecommendation(it, u.recommendationContext.userInputSinceInvocation, u.recommendationContext.typeaheadOriginal) - }.size - } - return count -// detailContexts.filter { isValidRecommendation(it, userInput, typeahead) }.size - } + private fun getValidCount(): Int = + CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo().filter { isValidRecommendation(it) }.size - private fun getValidSelectedIndex( - detailContexts: List, - userInput: String, - selectedIndex: Int, - typeahead: String - ): Int { + private fun getValidSelectedIndex(selectedIndex: Int): Int { var curr = 0 - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { - Triple(it, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } - details.forEachIndexed { index, triple -> + val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() + previews.forEachIndexed { index, triple -> if (index == selectedIndex) { return curr } - if (isValidRecommendation(triple.first, triple.second, triple.third)) { + if (isValidRecommendation(triple)) { curr++ } @@ -933,10 +670,9 @@ class CodeWhispererPopupManager { return -1 } - private fun isValidRecommendation(detailContext: DetailContext, userInput: String, typeahead: String): Boolean { - if (detailContext.isDiscarded) return false - if (detailContext.recommendation.content().isEmpty()) return false - return detailContext.recommendation.content().startsWith(userInput + typeahead) + private fun isValidRecommendation(preview: PreviewContext): Boolean { + if (preview.detail.isDiscarded) return false + return preview.detail.recommendation.content().startsWith(preview.userInput + preview.typeahead) } companion object { @@ -954,17 +690,17 @@ class CodeWhispererPopupManager { } interface CodeWhispererPopupStateChangeListener { - fun stateChanged(states: InvocationContext, sessionContext: SessionContext) {} - fun scrolled(states: InvocationContext, sessionContext: SessionContext) {} + fun stateChanged(sessionContext: SessionContext) {} + fun scrolled(sessionContext: SessionContext) {} fun recommendationAdded(states: InvocationContext, sessionContext: SessionContext) {} } interface CodeWhispererUserActionListener { - fun backspace(states: InvocationContext, diff: String) {} - fun enter(states: InvocationContext, diff: String) {} - fun type(states: InvocationContext, diff: String) {} - fun navigatePrevious(states: InvocationContext) {} - fun navigateNext(states: InvocationContext) {} - fun beforeAccept(states: InvocationContext, sessionContext: SessionContext) {} - fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) {} + fun backspace(sessionContext: SessionContext, diff: String) {} + fun enter(sessionContext: SessionContext, diff: String) {} + fun type(sessionContext: SessionContext, diff: String) {} + fun navigatePrevious(sessionContext: SessionContext) {} + fun navigateNext(sessionContext: SessionContext) {} + fun beforeAccept(sessionContext: SessionContext) {} + fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) {} } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt index eac1547b86b..248ef1d1596 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt @@ -9,8 +9,6 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.util.Disposer import com.intellij.xdebugger.ui.DebuggerColors -import groovy.lang.Tuple3 -import groovy.lang.Tuple4 import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager import software.aws.toolkits.jetbrains.services.codewhisperer.inlay.CodeWhispererInlayManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext @@ -20,27 +18,22 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { - override fun stateChanged(states: InvocationContext, sessionContext: SessionContext) { - val editor = states.requestContext.editor + override fun stateChanged(sessionContext: SessionContext) { + val editor = sessionContext.editor val editorManager = CodeWhispererEditorManager.getInstance() - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { - Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } + val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() val selectedIndex = sessionContext.selectedIndex - val typeaheadOriginal = details[selectedIndex].v3 - val detail = details[selectedIndex].v1 + val typeahead = previews[selectedIndex].typeahead + val detail = previews[selectedIndex].detail val caretOffset = editor.caretModel.primaryCaret.offset val document = editor.document val lineEndOffset = document.getLineEndOffset(document.getLineNumber(caretOffset)) // get matching brackets from recommendations to the brackets after caret position val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation( - details[selectedIndex].v1, - details[selectedIndex].v2, - ).substring(typeaheadOriginal.length) + detail, + previews[selectedIndex].userInput, + ).substring(typeahead.length) val remainingLines = remaining.split("\n") val firstLineOfRemaining = remainingLines.first() @@ -70,7 +63,7 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { }, HighlighterTargetArea.EXACT_RANGE ) - Disposer.register(states) { + Disposer.register(sessionContext) { editor.markupModel.removeHighlighter(rangeHighlighter) } sessionContext.toBeRemovedHighlighter = rangeHighlighter @@ -96,63 +89,19 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { // inlay chunks are chunks from first line(chunks) and an additional chunk from other lines val inlayChunks = chunks + listOf(RecommendationChunk(otherLinesInlayText, 0, chunks.last().inlayOffset)) - CodeWhispererInlayManager.getInstance().updateInlays(states, sessionContext, inlayChunks) + CodeWhispererInlayManager.getInstance().updateInlays(sessionContext, inlayChunks) CodeWhispererPopupManager.getInstance().render( - states, sessionContext, - overlappingLinesCount, isRecommendationAdded = false, isScrolling = false ) } - override fun scrolled(states: InvocationContext, sessionContext: SessionContext) { - if (states.isDisposed()) return - val editor = states.requestContext.editor - val editorManager = CodeWhispererEditorManager.getInstance() - val selectedIndex = sessionContext.selectedIndex - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { - Tuple3(it, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } - val typeaheadOriginal = details[selectedIndex].v3 - val detail = details[selectedIndex].v1 - - // get matching brackets from recommendations to the brackets after caret position - val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation( - detail, - states.recommendationContext.userInputSinceInvocation - ).substring(typeaheadOriginal.length) - - val remainingLines = remaining.split("\n") - val otherLinesOfRemaining = remainingLines.drop(1) - - // process other lines inlays, where we do tail-head matching as much as possible - val overlappingLinesCount = editorManager.findOverLappingLines( - editor, - otherLinesOfRemaining, - detail.isTruncatedOnRight, - sessionContext - ) - - CodeWhispererPopupManager.getInstance().render( - states, - sessionContext, - overlappingLinesCount, - isRecommendationAdded = false, - isScrolling = true - ) + override fun scrolled(sessionContext: SessionContext) { + CodeWhispererPopupManager.getInstance().render(sessionContext, isRecommendationAdded = false, isScrolling = true) } override fun recommendationAdded(states: InvocationContext, sessionContext: SessionContext) { - CodeWhispererPopupManager.getInstance().render( - states, - sessionContext, - 0, - isRecommendationAdded = true, - isScrolling = false - ) + CodeWhispererPopupManager.getInstance().render(sessionContext, isRecommendationAdded = true, isScrolling = false) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt index 0e02fb260bf..90b0cbf244f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt @@ -5,5 +5,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers import com.intellij.openapi.editor.actionSystem.EditorActionHandler import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext -abstract class CodeWhispererEditorActionHandler(val states: InvocationContext) : EditorActionHandler() +abstract class CodeWhispererEditorActionHandler(val sessionContext: SessionContext) : EditorActionHandler() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt index 8f32eceac34..8ed6ded8933 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt @@ -9,12 +9,13 @@ 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.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager class CodeWhispererPopupBackspaceHandler( private val defaultHandler: EditorActionHandler, - states: InvocationContext -) : CodeWhispererEditorActionHandler(states) { + sessionContext: SessionContext +) : CodeWhispererEditorActionHandler(sessionContext) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { val popupManager = CodeWhispererPopupManager.getInstance() popupManager.dontClosePopupAndRun { @@ -24,7 +25,7 @@ class CodeWhispererPopupBackspaceHandler( val newText = "a".repeat(oldOffset - newOffset) ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).backspace(states, newText) + ).backspace(sessionContext, newText) } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt index 1c71f0675d6..e6c0156d756 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt @@ -10,12 +10,13 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.EditorActionHandler import com.intellij.openapi.util.TextRange import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager class CodeWhispererPopupEnterHandler( private val defaultHandler: EditorActionHandler, - states: InvocationContext -) : CodeWhispererEditorActionHandler(states) { + sessionContext: SessionContext +) : CodeWhispererEditorActionHandler(sessionContext) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { val popupManager = CodeWhispererPopupManager.getInstance() popupManager.dontClosePopupAndRun { @@ -25,7 +26,7 @@ class CodeWhispererPopupEnterHandler( val newText = editor.document.getText(TextRange.create(oldOffset, newOffset)) ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).enter(states, newText) + ).enter(sessionContext, newText) } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt new file mode 100644 index 00000000000..eaec22d90d7 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt @@ -0,0 +1,18 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers + +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Caret +import com.intellij.openapi.editor.Editor +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService + +class CodeWhispererPopupEscHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { + override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { + CodeWhispererService.getInstance().disposeDisplaySession(false) + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt index 020e1434808..73854ef1cc5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt @@ -8,12 +8,13 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager -class CodeWhispererPopupLeftArrowHandler(states: InvocationContext) : CodeWhispererEditorActionHandler(states) { +class CodeWhispererPopupLeftArrowHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigatePrevious(states) + ).navigatePrevious(sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt index 9efba65a80b..0fdd1a27e61 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt @@ -8,12 +8,13 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager -class CodeWhispererPopupRightArrowHandler(states: InvocationContext) : CodeWhispererEditorActionHandler(states) { +class CodeWhispererPopupRightArrowHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigateNext(states) + ).navigateNext(sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt index da004835c7d..c361ee60aaa 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt @@ -20,10 +20,10 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationCo import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager -class CodeWhispererPopupTabHandler(states: InvocationContext, private val sessionContext: SessionContext) : CodeWhispererEditorActionHandler(states) { +class CodeWhispererPopupTabHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).beforeAccept(states, sessionContext) + ).beforeAccept(sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt index 7e18feaf3e0..47b53e99046 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt @@ -8,18 +8,19 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.TypedActionHandler import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager class CodeWhispererPopupTypedHandler( private val defaultHandler: TypedActionHandler, - val states: InvocationContext, + val sessionContext: SessionContext ) : TypedActionHandler { override fun execute(editor: Editor, charTyped: Char, dataContext: DataContext) { CodeWhispererPopupManager.getInstance().dontClosePopupAndRun { defaultHandler.execute(editor, charTyped, dataContext) ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).type(states, charTyped.toString()) + ).type(sessionContext, charTyped.toString()) } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt index d64d745c6bc..1f528c8ba5b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt @@ -9,10 +9,10 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionConte import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import java.awt.event.ActionEvent -class CodeWhispererAcceptButtonActionListener(states: InvocationContext, private val sessionContext: SessionContext) : CodeWhispererActionListener(states) { +class CodeWhispererAcceptButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) { override fun actionPerformed(e: ActionEvent?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).beforeAccept(states, sessionContext) + ).beforeAccept(sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt index c04f8cc444c..e6f6838b2a7 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt @@ -4,6 +4,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import java.awt.event.ActionListener -abstract class CodeWhispererActionListener(val states: InvocationContext) : ActionListener +abstract class CodeWhispererActionListener(val sessionContext: SessionContext) : ActionListener diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt index ce1d34432ee..0a15257eaae 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt @@ -5,13 +5,14 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import com.intellij.openapi.application.ApplicationManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import java.awt.event.ActionEvent -class CodeWhispererNextButtonActionListener(states: InvocationContext) : CodeWhispererActionListener(states) { +class CodeWhispererNextButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) { override fun actionPerformed(e: ActionEvent?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigateNext(states) + ).navigateNext(sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt index e77fdf469b5..04500415c1f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt @@ -5,13 +5,14 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import com.intellij.openapi.application.ApplicationManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import java.awt.event.ActionEvent -class CodeWhispererPrevButtonActionListener(states: InvocationContext) : CodeWhispererActionListener(states) { +class CodeWhispererPrevButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) { override fun actionPerformed(e: ActionEvent?) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigatePrevious(states) + ).navigatePrevious(sessionContext) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt index 4a17ac5a9c0..a4289e6031c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt @@ -11,16 +11,16 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionConte import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus -class CodeWhispererScrollListener(private val states: InvocationContext, private val sessionContext: SessionContext) : VisibleAreaListener { +class CodeWhispererScrollListener(private val sessionContext: SessionContext) : VisibleAreaListener { override fun visibleAreaChanged(e: VisibleAreaEvent) { val oldRect = e.oldRectangle val newRect = e.newRectangle - if (CodeWhispererInvocationStatus.getInstance().isPopupActive() && + if (CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() && (oldRect.x != newRect.x || oldRect.y != newRect.y) ) { ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_POPUP_STATE_CHANGED - ).scrolled(states, sessionContext) + ).scrolled(sessionContext) } } } 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 3ea0367f20b..1f5c09d0ce1 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 @@ -3,9 +3,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service -import com.intellij.codeWithMe.clientId import com.intellij.openapi.Disposable -import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service @@ -22,16 +20,15 @@ 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.codewhisperer.editor.CodeWhispererEditorUtil -import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.extractCaretContext import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants +import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.INVOCATION_DELAY import software.aws.toolkits.telemetry.CodewhispererAutomatedTriggerType import software.aws.toolkits.telemetry.CodewhispererPreviousSuggestionState import software.aws.toolkits.telemetry.CodewhispererTriggerType -import java.lang.Thread.sleep import java.time.Duration import java.time.Instant import kotlin.math.exp @@ -86,8 +83,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa if (!CodeWhispererService.getInstance().canDoInvocation(editor, CodewhispererTriggerType.AutoTrigger)) { return null } - if (lastCharTypedTime != null && lastCharTypedTime?.plusMillis(0)?.isAfter(Instant.now()) == true) { -// println("cancelling last trigger job ${lastTrigger.hashCode()} since user has immediately typed since then") + if (hasEnoughDelaySinceLastTrigger()) { lastTrigger?.cancel() } @@ -102,44 +98,22 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa val coroutineScope = applicationCoroutineScope() - return when (triggerType) { - is CodeWhispererAutomatedTriggerType.IdleTime -> run { - coroutineScope.launch { - // TODO: potential race condition between hasExistingInvocation and entering edt - // but in that case we will just return in performAutomatedTriggerAction - while (!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToInvokeCodeWhisperer() || - CodeWhispererInvocationStatus.getInstance().hasExistingInvocation() - ) { - if (!isActive) return@launch - delay(CodeWhispererConstants.IDLE_TIME_CHECK_INTERVAL) - } - runInEdt { - if (CodeWhispererInvocationStatus.getInstance().isPopupActive()) return@runInEdt - performAutomatedTriggerAction(editor, CodeWhispererAutomatedTriggerType.IdleTime(), latencyContext) - } - }.also { - lastTrigger = it - } - } - - else -> run { -// if (lastCharTypedTime != null && lastCharTypedTime?.plusMillis(100)?.isAfter(Instant.now()) != false) return null + return run { coroutineScope.launch(EDT) { - while (lastCharTypedTime != null && lastCharTypedTime?.plusMillis(0)?.isAfter(Instant.now()) == true) { + while (!hasEnoughDelaySinceLastTrigger()) { if (!isActive) return@launch delay(CodeWhispererConstants.IDLE_TIME_CHECK_INTERVAL) } -// println("time now: ${System.currentTimeMillis()}") performAutomatedTriggerAction(editor, triggerType, latencyContext) }.also { lastTrigger = it -// println("create trigger job ${lastTrigger.hashCode()}, time: ${System.currentTimeMillis()}") } } - } } + private fun hasEnoughDelaySinceLastTrigger(): Boolean = lastCharTypedTime == null || lastCharTypedTime?.plusMillis(INVOCATION_DELAY)?.isBefore(Instant.now()) == true + private fun scheduleReset() { if (!alarm.isDisposed) { alarm.addRequest({ resetPreviousStates() }, Duration.ofSeconds(120).toMillis()) 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 b537522dcb8..b4abd7a9ce0 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 @@ -18,7 +18,6 @@ import java.util.concurrent.atomic.AtomicBoolean class CodeWhispererInvocationStatus { private val isInvokingCodeWhisperer: AtomicBoolean = AtomicBoolean(false) private var invokingSessionId: String? = null - private var timeAtLastInvocationComplete: Instant? = null var timeAtLastDocumentChanged: Instant = Instant.now() private set private var isPopupActive: Boolean = false @@ -46,10 +45,6 @@ class CodeWhispererInvocationStatus { } } - fun setInvocationComplete() { - timeAtLastInvocationComplete = Instant.now() - } - fun documentChanged() { timeAtLastDocumentChanged = Instant.now() } @@ -69,11 +64,11 @@ class CodeWhispererInvocationStatus { return timeCanShowCodeWhisperer.isBefore(Instant.now()) } - fun isPopupActive(): Boolean = isPopupActive + fun isDisplaySessionActive(): Boolean = isPopupActive - fun setPopupActive(value: Boolean) { + fun setDisplaySessionActive(value: Boolean) { isPopupActive = value - println("set popup active to $value") +// println("set popup active to $value") } fun setInvocationStart() { @@ -85,11 +80,6 @@ class CodeWhispererInvocationStatus { invokingSessionId = sessionId } - fun hasEnoughDelayToInvokeCodeWhisperer(): Boolean { - val timeCanShowCodeWhisperer = timeAtLastInvocationStart?.plusMillis(CodeWhispererConstants.INVOCATION_INTERVAL) ?: return true - return timeCanShowCodeWhisperer.isBefore(Instant.now()) - } - companion object { private val LOG = getLogger() fun getInstance(): CodeWhispererInvocationStatus = service() 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 8467b1dcecc..947d300517f 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 @@ -25,7 +25,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import io.ktor.utils.io.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -47,7 +46,6 @@ import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.coroutines.disposableCoroutineScope import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager @@ -59,12 +57,12 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getCaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled -import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo @@ -96,9 +94,10 @@ import java.util.concurrent.TimeUnit class CodeWhispererService(private val cs: CoroutineScope) : Disposable { private val codeInsightSettingsFacade = CodeInsightsSettingsFacade() private var refreshFailure: Int = 0 - val ongoingRequests = mutableMapOf() + private val ongoingRequests = mutableMapOf() val ongoingRequestsContext = mutableMapOf() private var jobId = 0 + private var sessionContext: SessionContext? = null init { Disposer.register(this, codeInsightSettingsFacade) @@ -146,8 +145,9 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val isInjectedFile = runReadAction { psiFile.isInjectedText() } if (isInjectedFile) return + val currentJobId = jobId++ val requestContext = try { - getRequestContext(triggerTypeInfo, editor, project, psiFile, latencyContext) + getRequestContext(currentJobId, triggerTypeInfo, editor, project, psiFile) } catch (e: Exception) { LOG.debug { e.message.toString() } CodeWhispererTelemetryService.getInstance().sendFailedServiceInvocationEvent(project, e::class.simpleName) @@ -157,7 +157,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { ongoingRequestsContext.forEach { (k, v) -> val vCaretContext = v.fileContextInfo.caretContext if (vCaretContext == caretContext) { - println("same caretContext found from job: $k, left context ${vCaretContext.leftContextOnCurrentLine}") + LOG.debug { "same caretContext found from job: $k, left context ${vCaretContext.leftContextOnCurrentLine}, jobId: $currentJobId" } return } } @@ -165,7 +165,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val language = requestContext.fileContextInfo.programmingLanguage val leftContext = requestContext.fileContextInfo.caretContext.leftFileContext if (!language.isCodeCompletionSupported() || (checkLeftContextKeywordsForJsonAndYaml(leftContext, language.languageId))) { - LOG.debug { "Programming language $language is not supported by CodeWhisperer" } + LOG.debug { "Programming language $language is not supported by CodeWhisperer, jobId: $currentJobId" } if (triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) { showCodeWhispererInfoHint( requestContext.editor, @@ -176,7 +176,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } LOG.debug { - "Calling CodeWhisperer service, trigger type: ${triggerTypeInfo.triggerType}" + + "Calling CodeWhisperer service, jobId: $currentJobId, trigger type: ${triggerTypeInfo.triggerType}" + if (triggerTypeInfo.triggerType == CodewhispererTriggerType.AutoTrigger) { ", auto-trigger type: ${triggerTypeInfo.automatedTriggerType}" } else { @@ -189,15 +189,22 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { return } - invokeCodeWhispererInBackground(requestContext) + invokeCodeWhispererInBackground(requestContext, currentJobId, latencyContext) } - internal fun invokeCodeWhispererInBackground(requestContext: RequestContext): Job { -// val popup = CodeWhispererPopupManager.getInstance().initPopup() - val currentJobId = jobId++ - // a placeholder to indicate a session has started - ongoingRequests[currentJobId] = null + internal fun invokeCodeWhispererInBackground(requestContext: RequestContext, currentJobId: Int, latencyContext: LatencyContext): Job? { + // a placeholder value to indicate a session has started +// ongoingRequests[currentJobId] = null ongoingRequestsContext[currentJobId] = requestContext + val sessionContext = sessionContext ?: SessionContext(requestContext.project, requestContext.editor, latencyContext = latencyContext) + + // In rare cases when there's an ongoing session and subsequent triggers are from a different project or editor -- + // we will cancel the existing session(since we've already moved to a different project or editor simply return. + if (requestContext.project != sessionContext.project || requestContext.editor != sessionContext.editor) { + disposeDisplaySession(false) + return null + } + this.sessionContext = sessionContext val workerContexts = mutableListOf() // When popup is disposed we will cancel this coroutine. The only places popup can get disposed should be @@ -221,8 +228,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { ) var startTime = System.nanoTime() - requestContext.latencyContext.codewhispererPreprocessingEnd = System.nanoTime() - requestContext.latencyContext.paginationAllCompletionsStart = System.nanoTime() + latencyContext.codewhispererPreprocessingEnd = System.nanoTime() + latencyContext.paginationAllCompletionsStart = System.nanoTime() CodeWhispererInvocationStatus.getInstance().setInvocationStart() var requestCount = 0 for (response in responseIterable) { @@ -233,13 +240,13 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val requestId = response.responseMetadata().requestId() val sessionId = response.sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0] if (requestCount == 1) { - requestContext.latencyContext.codewhispererPostprocessingStart = System.nanoTime() - requestContext.latencyContext.paginationFirstCompletionTime = latency - requestContext.latencyContext.firstRequestId = requestId + latencyContext.codewhispererPostprocessingStart = System.nanoTime() + latencyContext.paginationFirstCompletionTime = latency + latencyContext.firstRequestId = requestId CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId) } if (response.nextToken().isEmpty()) { - requestContext.latencyContext.paginationAllCompletionsEnd = System.nanoTime() + latencyContext.paginationAllCompletionsEnd = System.nanoTime() } val responseContext = ResponseContext(sessionId) logServiceInvocation(requestId, requestContext, responseContext, response.completions(), latency, null) @@ -272,23 +279,24 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { if (workerContexts.isNotEmpty()) { workerContexts.add(workerContext) } else { - if (ongoingRequests.isEmpty() && + if (ongoingRequests.values.filterNotNull().isEmpty() && !CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer() ) { // It's the first response, and no enough delay before showing projectCoroutineScope(requestContext.project).launch { - while (!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer() && false) { + while (!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer()) { delay(CodeWhispererConstants.POPUP_DELAY_CHECK_INTERVAL) } runInEdt { workerContexts.forEach { - ongoingRequests[currentJobId] = processCodeWhispererUI( + processCodeWhispererUI( + sessionContext, it, ongoingRequests[currentJobId], coroutineScope, currentJobId ) - if (ongoingRequests[currentJobId] == null) { + if (!ongoingRequests.contains(currentJobId)) { coroutineScope.coroutineContext.cancel() } } @@ -298,20 +306,21 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { workerContexts.add(workerContext) } else { // Have enough delay before showing for the first response, or it's subsequent responses - ongoingRequests[currentJobId] = processCodeWhispererUI( + processCodeWhispererUI( + sessionContext, workerContext, ongoingRequests[currentJobId], coroutineScope, currentJobId ) +// if (!ongoingRequests.contains(currentJobId)) { +// LOG.debug { "Skipping sending remaining requests on jobId removed" } +// println("exit 1 , jobId: $currentJobId") +// break +// } } } } - if (!ongoingRequests.contains(currentJobId)) { - LOG.debug { "Skipping sending remaining requests on jobId removed" } - println("exit 1 , jobId: $currentJobId") - break - } if (!isActive) { // If job is cancelled before we do another request, don't bother making // another API call to save resources @@ -319,6 +328,11 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { println("exit 2 , jobId: $currentJobId") break } + if (requestCount >= 1) { + println("We only need one pagination request at the moment") + CodeWhispererInvocationStatus.getInstance().finishInvocation() + break + } } } catch (e: Exception) { val requestId: String @@ -366,7 +380,6 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { ) ) CodeWhispererInvocationStatus.getInstance().finishInvocation() - CodeWhispererInvocationStatus.getInstance().setInvocationComplete() requestContext.customizationArn?.let { CodeWhispererModelConfigurator.getInstance().invalidateCustomization(it) } @@ -374,7 +387,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { showRecommendationsInPopup( requestContext.editor, requestContext.triggerTypeInfo, - requestContext.latencyContext + latencyContext ) } return@launch @@ -382,7 +395,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { requestId = e.requestId() ?: "" sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0] displayMessage = e.awsErrorDetails().errorMessage() ?: message("codewhisperer.trigger.error.server_side") - } else if (e is software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException) { + } else if (e is CodeWhispererRuntimeException) { requestId = e.requestId() ?: "" sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0] displayMessage = e.awsErrorDetails().errorMessage() ?: message("codewhisperer.trigger.error.server_side") @@ -426,7 +439,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // We should only show error hint when CodeWhisperer popup is not visible, // and make it silent if CodeWhisperer popup is showing. runInEdt { - if (!CodeWhispererInvocationStatus.getInstance().isPopupActive()) { + if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) { showCodeWhispererErrorHint(requestContext.editor, displayMessage) } } @@ -434,13 +447,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } CodeWhispererInvocationStatus.getInstance().finishInvocation() runInEdt { - CodeWhispererPopupManager.getInstance().updatePopupPanel( - ongoingRequests[currentJobId], - CodeWhispererPopupManager.getInstance().sessionContext - ) + CodeWhispererPopupManager.getInstance().updatePopupPanel(sessionContext) } - } finally { - CodeWhispererInvocationStatus.getInstance().setInvocationComplete() } } @@ -449,33 +457,32 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { @RequiresEdt private fun processCodeWhispererUI( + sessionContext: SessionContext, workerContext: WorkerContext, currStates: InvocationContext?, coroutine: CoroutineScope, jobId: Int - ): InvocationContext? { + ) { val requestContext = workerContext.requestContext val responseContext = workerContext.responseContext val response = workerContext.response -// val popup = workerContext.popup val requestId = response.responseMetadata().requestId() // At this point when we are in EDT, the state of the popup will be thread-safe // across this thread execution, so if popup is disposed, we will stop here. // This extra check is needed because there's a time between when we get the response and // when we enter the EDT. - if (!ongoingRequests.contains(jobId)) { + if (!coroutine.isActive || sessionContext.isDisposed()) { LOG.debug { "Stop showing CodeWhisperer recommendations on CodeWhisperer session exit. RequestId: $requestId" } println("exit 3 , jobId: $jobId") - return null + return } if (requestContext.editor.isDisposed) { LOG.debug { "Stop showing all CodeWhisperer recommendations since editor is disposed. RequestId: $requestId" } - CodeWhispererPopupManager.getInstance().sessionContext?.let { Disposer.dispose(it) } -// CodeWhispererPopupManager.getInstance().cancelPopup(popup) + disposeDisplaySession(false) println("exit 4 , jobId: $jobId") - return null + return } if (response.nextToken().isEmpty()) { @@ -486,20 +493,19 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { requestContext.editor, requestContext.caretPosition ) - val isPopupShowing = checkRecommendationsValidity(currStates, false, jobId) + val isPopupShowing = checkRecommendationsValidity(jobId, currStates, false) val nextStates: InvocationContext? if (currStates == null) { // first response for the jobId - nextStates = initStates(requestContext, responseContext, response, caretMovement, coroutine, jobId) + nextStates = initStates(jobId, sessionContext, requestContext, responseContext, response, caretMovement, coroutine) // receiving a null state means caret has moved backward or there's a conflict with // Intellisense popup, so we are going to cancel the current job if (nextStates == null) { LOG.debug { "Exiting CodeWhisperer session. RequestId: $requestId" } - ongoingRequests[jobId]?.let { Disposer.dispose(it) } -// CodeWhispererPopupManager.getInstance().cancelPopup(popup) + disposeJob(jobId) println("exit 5 , jobId: $jobId") - return null + return } } else { // subsequent responses for the jobId @@ -508,18 +514,10 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { println("adding ${response.completions().size} completions from job ${jobId}") ongoingRequests[jobId] = nextStates - val hasAtLeastOneValid = checkRecommendationsValidity(nextStates, response.nextToken().isEmpty(), jobId) -// println("details for $jobId:") -// println("current states have ${nextStates.recommendationContext.details.size} total suggestions, recently added by ${jobId}") + val hasAtLeastOneValid = checkRecommendationsValidity(jobId, nextStates, response.nextToken().isEmpty()) val allSuggestions = ongoingRequests.values.filterNotNull().flatMap { it.recommendationContext.details } val valid = allSuggestions.filter { !it.isDiscarded }.size println("total: $valid valid, ${allSuggestions.size - valid} discarded") -// nextStates.recommendationContext.details.forEach { -// println(it.recommendation.content()) -// } - - - // If there are no recommendations at all in this session, we need to manually send the user decision event here // since it won't be sent automatically later @@ -544,43 +542,35 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } if (!hasAtLeastOneValid) { if (response.nextToken().isEmpty()) { - LOG.debug { "None of the recommendations are valid, exiting CodeWhisperer session" } + LOG.debug { "None of the recommendations are valid, exiting current CodeWhisperer pagination session" } // TODO: decide whether or not to dispose what here -// CodeWhispererPopupManager.getInstance().cancelPopup(popup) - ongoingRequests[jobId]?.let { Disposer.dispose(it) } - CodeWhispererPopupManager.getInstance().sessionContext?.let { - it.selectedIndex = CodeWhispererPopupManager.getInstance().findNewSelectedIndex(true, it.selectedIndex) - } + disposeJob(jobId) + sessionContext.selectedIndex = CodeWhispererPopupManager.getInstance().findNewSelectedIndex(true, sessionContext.selectedIndex) println("exit 6 , jobId: $jobId") - return null + return } } else { - updateCodeWhisperer(nextStates, isPopupShowing) + updateCodeWhisperer(sessionContext, nextStates, isPopupShowing) } - return nextStates +// return nextStates } private fun initStates( + jobId: Int, + sessionContext: SessionContext, requestContext: RequestContext, responseContext: ResponseContext, response: GenerateCompletionsResponse, caretMovement: CaretMovement, -// popup: JBPopup, coroutine: CoroutineScope, - jobId: Int ): InvocationContext? { val requestId = response.responseMetadata().requestId() val recommendations = response.completions() val visualPosition = requestContext.editor.caretModel.visualPosition - if (CodeWhispererPopupManager.getInstance().hasConflictingPopups(requestContext.editor)) { - LOG.debug { "Detect conflicting popup window with CodeWhisperer popup, not showing CodeWhisperer popup" } - sendDiscardedUserDecisionEventForAll(requestContext, responseContext, recommendations) - return null - } if (caretMovement == CaretMovement.MOVE_BACKWARD) { LOG.debug { "Caret moved backward, discarding all of the recommendations. Request ID: $requestId" } - sendDiscardedUserDecisionEventForAll(requestContext, responseContext, recommendations) + sendDiscardedUserDecisionEventForAll(jobId, sessionContext, requestContext, responseContext, recommendations, coroutine) return null } val userInputOriginal = CodeWhispererEditorManager.getInstance().getUserInputSinceInvocation( @@ -615,27 +605,18 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { response: GenerateCompletionsResponse ): InvocationContext { val recommendationContext = states.recommendationContext -// val details = recommendationContext.details val newDetailContexts = CodeWhispererRecommendationManager.getInstance().buildDetailContext( states.requestContext, recommendationContext.userInputSinceInvocation, response.completions(), response.responseMetadata().requestId() ) -// Disposer.dispose(states) recommendationContext.details.addAll(newDetailContexts) - -// Disposer.dispose(states) -// val updatedStates = states.copy( -// recommendationContext = recommendationContext.copy(details = details + newDetailContexts) -// ) -// Disposer.register(states.popup, updatedStates) -// CodeWhispererPopupManager.getInstance().initPopupListener(updatedStates) return states } - private fun checkRecommendationsValidity(states: InvocationContext?, showHint: Boolean, jobId: Int): Boolean { + private fun checkRecommendationsValidity(jobId: Int, states: InvocationContext?, showHint: Boolean): Boolean { if (states == null) return false val details = states.recommendationContext.details @@ -651,35 +632,91 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { return hasAtLeastOneValid } - private fun updateCodeWhisperer(states: InvocationContext, recommendationAdded: Boolean) { - CodeWhispererPopupManager.getInstance().changeStatesForShowing(states, recommendationAdded) + private fun updateCodeWhisperer(sessionContext: SessionContext, states: InvocationContext, recommendationAdded: Boolean) { + CodeWhispererPopupManager.getInstance().changeStatesForShowing(sessionContext, states, recommendationAdded) } private fun sendDiscardedUserDecisionEventForAll( + jobId: Int, + sessionContext: SessionContext, requestContext: RequestContext, responseContext: ResponseContext, - recommendations: List + recommendations: List, + coroutine: CoroutineScope ) { val detailContexts = recommendations.map { DetailContext("", it, it, true, false, "", getCompletionType(it)) }.toMutableList() - val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0), -1) + val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0), jobId) + ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext, coroutine) - CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll( - requestContext, - responseContext, - recommendationContext, - SessionContext(), - false - ) + CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(sessionContext, false) + } + + @RequiresEdt + private fun disposeJob(jobId: Int) { + ongoingRequests[jobId]?.let { Disposer.dispose(it) } + ongoingRequests.remove(jobId) + ongoingRequestsContext.remove(jobId) + } + + @RequiresEdt + fun disposeDisplaySession(accept: Boolean) { + // avoid duplicate session disposal logic + if (sessionContext == null || sessionContext?.isDisposed() == true) return + + val jobIds = ongoingRequests.keys.toList() + jobIds.forEach { jobId -> disposeJob(jobId) } + ongoingRequests.clear() + ongoingRequestsContext.clear() + sessionContext?.let { + it.hasAccepted = accept + Disposer.dispose(it) + } + sessionContext = null + } + + fun getAllSuggestionsPreviewInfo() = +// ongoingRequests.filter { it.second != null }.map { element -> +// val context = element.second?.recommendationContext ?: return@map +// context.details.map { PreviewContext(context.jobId, it, context.userInputSinceInvocation, context.typeahead) } +// } + ongoingRequests.values.filterNotNull().flatMap { element -> + val context = element.recommendationContext + context.details.map { + PreviewContext(context.jobId, it, context.userInputSinceInvocation, context.typeahead) + } + } + + fun getAllPaginationSessions() = ongoingRequests + + fun updateTypeahead(typeaheadChange: String, typeaheadAdded: Boolean, sessionContext: SessionContext) { + val recommendations = ongoingRequests.values.filterNotNull() + recommendations.forEach { + val typeaheadOriginal = + if (typeaheadAdded) { + it.recommendationContext.typeahead + typeaheadChange + } else { + if (typeaheadChange.length > it.recommendationContext.typeahead.length) { + disposeDisplaySession(false) + println("exit 7, ") + return + } + it.recommendationContext.typeahead.substring( + 0, + it.recommendationContext.typeahead.length - typeaheadChange.length + ) + } + it.recommendationContext.typeahead = typeaheadOriginal + } } fun getRequestContext( + jobId: Int, triggerTypeInfo: TriggerTypeInfo, editor: Editor, project: Project, - psiFile: PsiFile, - latencyContext: LatencyContext + psiFile: PsiFile ): RequestContext { // 1. file context val fileContext: FileContextInfo = runReadAction { FileContextProvider.getInstance(project).extractFileContext(editor, psiFile) } @@ -704,7 +741,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // 5. customization val customizationArn = CodeWhispererModelConfigurator.getInstance().activeCustomization(project)?.arn - return RequestContext(project, editor, triggerTypeInfo, caretPosition, fileContext, supplementalContext, connection, latencyContext, customizationArn) + return RequestContext(project, editor, triggerTypeInfo, caretPosition, fileContext, supplementalContext, connection, customizationArn) } fun validateResponse(response: GenerateCompletionsResponse): GenerateCompletionsResponse { @@ -731,7 +768,6 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { responseContext: ResponseContext, recommendationContext: RecommendationContext, coroutine: CoroutineScope -// popup: JBPopup ): InvocationContext { // Creating a disposable for managing all listeners lifecycle attached to the popup. // previously(before pagination) we use popup as the parent disposable. @@ -739,32 +775,12 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // so disposable chain becomes popup -> disposable -> listeners updates, and disposable gets replaced on every // state update. val states = InvocationContext(requestContext, responseContext, recommendationContext) -// addSessionDisposables(states) -// if (CodeWhispererPopupManager.getInstance().sessionContext == null) { -// CodeWhispererPopupManager.getInstance().sessionContext = SessionContext() -// } -// val sessionContext = CodeWhispererPopupManager.getInstance().sessionContext ?: SessionContext() -// CodeWhispererPopupManager.getInstance().sessionContext = sessionContext -// Disposer.register(sessionContext) { -// CodeWhispererInvocationStatus.getInstance().finishInvocation() -// } Disposer.register(states) { coroutine.cancel(CancellationException("hahahah")) } -// Disposer.register(states, popup) -// Disposer.register(popup, states) -// CodeWhispererRecommendationManager.getInstance().states = states return states } - private fun addSessionDisposables(disposable: Disposable) { -// codeInsightSettingsFacade.disableCodeInsightUntil(disposable) - - Disposer.register(disposable) { - CodeWhispererPopupManager.getInstance().resetSession() - } - } - private fun logServiceInvocation( requestId: String, requestContext: RequestContext, @@ -802,13 +818,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { return false } - if (CodeWhispererPopupManager.getInstance().hasConflictingPopups(editor)) { - LOG.debug { "Find other active popup windows before triggering CodeWhisperer, not invoking service" } - return false - } - - if (CodeWhispererInvocationStatus.getInstance().isPopupActive()) { - LOG.debug { "Find an existing CodeWhisperer popup window before triggering CodeWhisperer, not invoking service" } + if (CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) { + LOG.debug { "Find an existing CodeWhisperer session before triggering CodeWhisperer, not invoking service" } return false } return true @@ -832,6 +843,10 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { "CodeWhisperer code completion service invoked", CodeWhispererCodeCompletionServiceListener::class.java ) + val CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER: Topic = Topic.create( + "CodeWhisperer intelliSense popup on hover", + CodeWhispererIntelliSenseOnHoverListener::class.java + ) fun getInstance(): CodeWhispererService = service() const val KET_SESSION_ID = "x-amzn-SessionId" @@ -888,7 +903,6 @@ data class RequestContext( val fileContextInfo: FileContextInfo, private val supplementalContextDeferred: Deferred, val connection: ToolkitConnection?, - val latencyContext: LatencyContext, val customizationArn: String? ) { // TODO: should make the entire getRequestContext() suspend function instead of making supplemental context only @@ -902,7 +916,6 @@ data class RequestContext( null } } - else -> field } @@ -919,3 +932,7 @@ data class ResponseContext( interface CodeWhispererCodeCompletionServiceListener { fun onSuccess(fileContextInfo: FileContextInfo) {} } + +interface CodeWhispererIntelliSenseOnHoverListener { + fun onEnter() {} +} 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 6bc7008cf1e..e0d2ad81911 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 @@ -14,6 +14,7 @@ import com.intellij.ui.hover.HoverListener import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager 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.service.CodeWhispererService import java.awt.Component object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener { @@ -45,7 +46,9 @@ object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener { newLookup, object : HoverListener() { override fun mouseEntered(component: Component, x: Int, y: Int) { - runReadAction { CodeWhispererPopupManager.getInstance().hidePopup(newLookup.editor) } + runReadAction { newLookup.project.messageBus.syncPublisher( + CodeWhispererService.CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER, + ).onEnter() } } override fun mouseMoved(component: Component, x: Int, y: Int) {} override fun mouseExited(component: Component) {} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt index c5f0f54999e..dbf4f93a226 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt @@ -14,8 +14,6 @@ import com.intellij.openapi.util.Key import com.intellij.refactoring.suggested.range import com.intellij.util.Alarm import com.intellij.util.AlarmFactory -import groovy.lang.Tuple3 -import groovy.lang.Tuple4 import info.debatty.java.stringsimilarity.Levenshtein import org.jetbrains.annotations.TestOnly import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException @@ -24,10 +22,7 @@ import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage -import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.* import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererCodeCompletionServiceListener @@ -84,7 +79,7 @@ abstract class CodeWhispererCodeCoverageTracker( conn.subscribe( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED, object : CodeWhispererUserActionListener { - override fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) { + override fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) { if (states.requestContext.fileContextInfo.programmingLanguage != language) return rangeMarkers.add(rangeMarker) val originalRecommendation = extractRangeMarkerString(rangeMarker) ?: return 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 76438adb8ef..a5e783e0346 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 @@ -10,12 +10,11 @@ import com.intellij.openapi.components.service import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import groovy.lang.Tuple3 -import groovy.lang.Tuple4 import kotlinx.coroutines.launch import org.apache.commons.collections4.queue.CircularFifoQueue import org.jetbrains.annotations.TestOnly import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException +import software.amazon.awssdk.services.codewhispererruntime.model.Completion import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope @@ -193,6 +192,7 @@ class CodeWhispererTelemetryService { } fun sendUserTriggerDecisionEvent( + sessionContext: SessionContext, requestContext: RequestContext, responseContext: ResponseContext, recommendationContext: RecommendationContext, @@ -229,6 +229,7 @@ class CodeWhispererTelemetryService { try { val response = CodeWhispererClientAdaptor.getInstance(project) .sendUserTriggerDecisionTelemetry( + sessionContext, requestContext, responseContext, completionType, @@ -252,7 +253,7 @@ class CodeWhispererTelemetryService { CodewhispererTelemetry.userTriggerDecision( project = project, codewhispererSessionId = responseContext.sessionId, - codewhispererFirstRequestId = requestContext.latencyContext.firstRequestId, + codewhispererFirstRequestId = sessionContext.latencyContext.firstRequestId, credentialStartUrl = getConnectionStartUrl(requestContext.connection), codewhispererIsPartialAcceptance = null, codewhispererPartialAcceptanceCount = null, @@ -271,7 +272,7 @@ class CodeWhispererTelemetryService { codewhispererTypeaheadLength = recommendationContext.userInputSinceInvocation.length, codewhispererTimeSinceLastDocumentChange = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged(), codewhispererTimeSinceLastUserDecision = codewhispererTimeSinceLastUserDecision, - codewhispererTimeToFirstRecommendation = requestContext.latencyContext.paginationFirstCompletionTime, + codewhispererTimeToFirstRecommendation = sessionContext.latencyContext.paginationFirstCompletionTime, codewhispererPreviousSuggestionState = previousUserTriggerDecision, codewhispererSuggestionState = suggestionState, codewhispererClassifierResult = classifierResult, @@ -393,17 +394,16 @@ class CodeWhispererTelemetryService { } fun sendUserDecisionEventForAll( - requestContext: RequestContext, - responseContext: ResponseContext, - recommendationContext: RecommendationContext, sessionContext: SessionContext, hasUserAccepted: Boolean, popupShownTime: Duration? = null ) { val decisions = mutableListOf() - CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().forEach { - val detailContexts = it.recommendationContext.details + + CodeWhispererService.getInstance().getAllPaginationSessions().forEach { (jobId, state) -> + if (state == null) return@forEach + val detailContexts = state.recommendationContext.details detailContexts.forEachIndexed { index, detailContext -> val suggestionState = recordSuggestionState( @@ -414,7 +414,7 @@ class CodeWhispererTelemetryService { detailContext.isDiscarded, detailContext.recommendation.content().isEmpty() ) - sendUserDecisionEvent(it.requestContext, it.responseContext, detailContext, index, suggestionState, detailContexts.size) + sendUserDecisionEvent(state.requestContext, state.responseContext, detailContext, index, suggestionState, detailContexts.size) decisions.add(suggestionState) } @@ -424,25 +424,22 @@ class CodeWhispererTelemetryService { // step 1, send out current decision previousUserTriggerDecisionTimestamp = Instant.now() - val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> - val context = element.recommendationContext - context.details.map { detail -> - Tuple3(detail, context.userInputSinceInvocation, context.typeaheadOriginal) - } - } - val referenceCount = if (hasUserAccepted && details[sessionContext.selectedIndex].v1.recommendation.hasReferences()) 1 else 0 - val acceptedContent = + val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() + val recommendation = if (hasUserAccepted) { - details[sessionContext.selectedIndex].v1.recommendation.content() + previews[sessionContext.selectedIndex].detail.recommendation } else { - "" + Completion.builder().content("").references(emptyList()).build() } + val referenceCount = if (hasUserAccepted && recommendation.hasReferences()) 1 else 0 + val acceptedContent = if (hasUserAccepted) recommendation.content() else "" val generatedLineCount = if (acceptedContent.isEmpty()) 0 else acceptedContent.split("\n").size val acceptedCharCount = acceptedContent.length sendUserTriggerDecisionEvent( - requestContext, - responseContext, - recommendationContext, + sessionContext, + state.requestContext, + state.responseContext, + state.recommendationContext, CodewhispererSuggestionState.from(this.toString()), popupShownTime, referenceCount, @@ -488,53 +485,55 @@ class CodeWhispererTelemetryService { } } - fun sendPerceivedLatencyEvent( - requestId: String, - requestContext: RequestContext, - responseContext: ResponseContext, - latency: Double, - ) { - val (project, _, triggerTypeInfo) = requestContext - val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType() - val startUrl = getConnectionStartUrl(requestContext.connection) - CodewhispererTelemetry.perceivedLatency( - project = project, - codewhispererCompletionType = CodewhispererCompletionType.Line, - codewhispererLanguage = codewhispererLanguage, - codewhispererRequestId = requestId, - codewhispererSessionId = responseContext.sessionId, - codewhispererTriggerType = triggerTypeInfo.triggerType, - duration = latency, - passive = true, - credentialStartUrl = startUrl, - codewhispererUserGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup().name, - codewhispererCustomizationArn = requestContext.customizationArn, - ) - } - - fun sendClientComponentLatencyEvent(states: InvocationContext) { - val requestContext = states.requestContext - val responseContext = states.responseContext - val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType() - val startUrl = getConnectionStartUrl(requestContext.connection) - CodewhispererTelemetry.clientComponentLatency( - project = requestContext.project, - codewhispererSessionId = responseContext.sessionId, - codewhispererRequestId = requestContext.latencyContext.firstRequestId, - codewhispererFirstCompletionLatency = requestContext.latencyContext.paginationFirstCompletionTime, - codewhispererPreprocessingLatency = requestContext.latencyContext.getCodeWhispererPreprocessingLatency(), - codewhispererEndToEndLatency = requestContext.latencyContext.getCodeWhispererEndToEndLatency(), - codewhispererAllCompletionsLatency = requestContext.latencyContext.getCodeWhispererAllCompletionsLatency(), - codewhispererPostprocessingLatency = requestContext.latencyContext.getCodeWhispererPostprocessingLatency(), - codewhispererCredentialFetchingLatency = requestContext.latencyContext.getCodeWhispererCredentialFetchingLatency(), - codewhispererTriggerType = requestContext.triggerTypeInfo.triggerType, - codewhispererCompletionType = CodewhispererCompletionType.Line, - codewhispererLanguage = codewhispererLanguage, - credentialStartUrl = startUrl, - codewhispererUserGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup().name, - codewhispererCustomizationArn = requestContext.customizationArn, - ) - } + // TODO: Decide the scope of this telemetry +// fun sendPerceivedLatencyEvent( +// requestId: String, +// requestContext: RequestContext, +// responseContext: ResponseContext, +// latency: Double, +// ) { +// val (project, _, triggerTypeInfo) = requestContext +// val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType() +// val startUrl = getConnectionStartUrl(requestContext.connection) +// CodewhispererTelemetry.perceivedLatency( +// project = project, +// codewhispererCompletionType = CodewhispererCompletionType.Line, +// codewhispererLanguage = codewhispererLanguage, +// codewhispererRequestId = requestId, +// codewhispererSessionId = responseContext.sessionId, +// codewhispererTriggerType = triggerTypeInfo.triggerType, +// duration = latency, +// passive = true, +// credentialStartUrl = startUrl, +// codewhispererUserGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup().name, +// codewhispererCustomizationArn = requestContext.customizationArn, +// ) +// } + + // TODO: decide the scope of this telemetry +// fun sendClientComponentLatencyEvent(states: InvocationContext, sessionContext: SessionContext) { +// val requestContext = states.requestContext +// val responseContext = states.responseContext +// val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType() +// val startUrl = getConnectionStartUrl(requestContext.connection) +// CodewhispererTelemetry.clientComponentLatency( +// project = sessionContext.project, +// codewhispererSessionId = responseContext.sessionId, +// codewhispererRequestId = sessionContext.latencyContext.firstRequestId, +// codewhispererFirstCompletionLatency = sessionContext.latencyContext.paginationFirstCompletionTime, +// codewhispererPreprocessingLatency = sessionContext.latencyContext.getCodeWhispererPreprocessingLatency(), +// codewhispererEndToEndLatency = sessionContext.latencyContext.getCodeWhispererEndToEndLatency(), +// codewhispererAllCompletionsLatency = sessionContext.latencyContext.getCodeWhispererAllCompletionsLatency(), +// codewhispererPostprocessingLatency = sessionContext.latencyContext.getCodeWhispererPostprocessingLatency(), +// codewhispererCredentialFetchingLatency = sessionContext.latencyContext.getCodeWhispererCredentialFetchingLatency(), +// codewhispererTriggerType = requestContext.triggerTypeInfo.triggerType, +// codewhispererCompletionType = CodewhispererCompletionType.Line, +// codewhispererLanguage = codewhispererLanguage, +// credentialStartUrl = startUrl, +// codewhispererUserGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup().name, +// codewhispererCustomizationArn = requestContext.customizationArn, +// ) +// } fun sendOnboardingClickEvent(language: CodeWhispererProgrammingLanguage, taskType: CodewhispererGettingStartedTask) { // Project instance is not needed. We look at these metrics for each clientId. diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt index 4c23d0d7355..daae72c39bb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt @@ -4,18 +4,16 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow import com.intellij.openapi.editor.RangeMarker -import groovy.lang.Tuple3 -import groovy.lang.Tuple4 -import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener class CodeWhispererCodeReferenceActionListener : CodeWhispererUserActionListener { - override fun afterAccept(states: InvocationContext, details: List>, sessionContext: SessionContext, rangeMarker: RangeMarker) { + override fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) { val (project, editor) = states.requestContext val manager = CodeWhispererCodeReferenceManager.getInstance(project) - manager.insertCodeReference(states, details, sessionContext.selectedIndex) + manager.insertCodeReference(states, previews, sessionContext.selectedIndex) manager.addListeners(editor) } } 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 0f3ba23fdca..4d4df9b43bf 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,8 +23,6 @@ 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 groovy.lang.Tuple3 -import groovy.lang.Tuple4 import software.amazon.awssdk.services.codewhispererruntime.model.Completion import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.amazon.awssdk.services.codewhispererruntime.model.Span @@ -32,9 +30,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getRelativePathToContentRoot import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.horizontalPanelConstraints import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition -import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService +import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.EDITOR_CODE_REFERENCE_HOVER import software.aws.toolkits.resources.message import javax.swing.JLabel @@ -113,18 +110,15 @@ class CodeWhispererCodeReferenceManager(private val project: Project) { } } - fun insertCodeReference(states: InvocationContext, details: List>, selectedIndex: Int) { - val (requestContext, _, recommendationContext) = states - val (_, editor, _, caretPosition) = requestContext -// val (_, detail, reformattedDetail) = recommendationContext.details[selectedIndex] -// val details = CodeWhispererService.getInstance().ongoingRequests.values.filterNotNull().flatMap { element -> -// val context = element.recommendationContext -// context.details.map { -// Tuple4(it, context.userInputSinceInvocation, context.typeaheadOriginal, context.typeahead) -// } -// } - val detail = details[selectedIndex].v1 - insertCodeReference(details[selectedIndex].v1.recommendation.content(), detail.reformatted.references(), editor, caretPosition, detail.recommendation) + fun insertCodeReference(states: InvocationContext, previews: List, selectedIndex: Int) { + val detail = previews[selectedIndex].detail + insertCodeReference( + detail.recommendation.content(), + detail.reformatted.references(), + states.requestContext.editor, + states.requestContext.caretPosition, + detail.recommendation + ) } fun getReferenceLineNums(editor: Editor, start: Int, end: Int): String { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt index f8ac8f4cfe4..15279e2230d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt @@ -27,7 +27,7 @@ object CodeWhispererConstants { const val LEFT_CONTEXT_ON_CURRENT_LINE = 50 const val POPUP_INFO_TEXT_SIZE = 11f const val POPUP_BUTTON_TEXT_SIZE = 12f - const val POPUP_DELAY: Long = 250 + const val POPUP_DELAY: Long = 50 const val POPUP_DELAY_CHECK_INTERVAL: Long = 25 const val IDLE_TIME_CHECK_INTERVAL: Long = 25 const val SUPPLEMENTAL_CONTEXT_TIMEOUT = 50L @@ -36,9 +36,9 @@ object CodeWhispererConstants { val AWSTemplateKeyWordsRegex = Regex("(AWSTemplateFormatVersion|Resources|AWS::|Description)") val AWSTemplateCaseInsensitiveKeyWordsRegex = Regex("(cloudformation|cfn|template|description)") - // TODO: this is currently set to 2050 to account for the server side 0.5 TPS and and extra 50 ms buffer to - // avoid ThrottlingException as much as possible. - const val INVOCATION_INTERVAL: Long = 100 + // TODO: this is currently set to 0 to trigger with 0 delays and rely on ML trigger to determine which characters to trigger at + // We will monitor service side resource utilization and throttling. + const val INVOCATION_DELAY: Long = 0 const val Q_CUSTOM_LEARN_MORE_URI = "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/customizations.html" const val Q_SUPPORTED_LANG_URI = "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-language-ide-support.html" diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt index 9a05ffe781f..27f402128c3 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt @@ -311,7 +311,11 @@ class CodeWhispererTelemetryServiceTest { val hasUserAccept = decisions.any { it == CodewhispererSuggestionState.Accept } val popupShownDuration = Duration.ofSeconds(Random.nextLong(0, 30)) - sut.sendUserDecisionEventForAll(requestContext, responseContext, recommendationContext, sessionContext, hasUserAccept, popupShownDuration) + sut.sendUserDecisionEventForAll( + sessionContext, + hasUserAccept, + popupShownDuration + ) argumentCaptor().apply { verify(batcher, atLeastOnce()).enqueue(capture()) @@ -376,7 +380,11 @@ class CodeWhispererTelemetryServiceTest { val hasUserAccept = decisions.any { it == CodewhispererSuggestionState.Accept } val popupShownDuration = Duration.ofSeconds(Random.nextLong(0, 30)) - sut.sendUserDecisionEventForAll(requestContext, responseContext, recommendationContext, sessionContext, hasUserAccept, popupShownDuration) + sut.sendUserDecisionEventForAll( + sessionContext, + hasUserAccept, + popupShownDuration + ) argumentCaptor().apply { verify(batcher, atLeastOnce()).enqueue(capture()) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt index 1c803c33377..6a30ae69e56 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt @@ -416,7 +416,7 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { val userGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup() val statesCaptor = argumentCaptor() withCodeWhispererServiceInvokedAndWait {} - verify(popupManagerSpy, timeout(5000).atLeastOnce()).render(statesCaptor.capture(), any(), any(), any(), any()) + verify(popupManagerSpy, timeout(5000).atLeastOnce()).render(statesCaptor.capture(), any(), any(), any()) val states = statesCaptor.lastValue val metricCaptor = argumentCaptor() verify(batcher, atLeastOnce()).enqueue(metricCaptor.capture()) @@ -430,40 +430,6 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { ) } - @Test - fun `test showing IntelliSense after triggering CodeWhisperer will send userDecision events of state Discard`() { - val userGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup() - val codewhispererServiceSpy = spy(codewhispererService) - codewhispererServiceSpy.stub { - onGeneric { - canDoInvocation(any(), any()) - } doAnswer { - true - } - } - ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererServiceSpy, disposableRule.disposable) - popupManagerSpy.stub { - onGeneric { - hasConflictingPopups(any()) - } doAnswer { - true - } - } - invokeCodeWhispererService() - - runInEdtAndWait { - val metricCaptor = argumentCaptor() - verify(batcher, atLeastOnce()).enqueue(metricCaptor.capture()) - assertEventsContainsFieldsAndCount( - metricCaptor.allValues, - userDecision, - pythonResponse.completions().size, - codewhispererSuggestionState to CodewhispererSuggestionState.Discard.toString(), - "codewhispererUserGroup" to userGroup.name, - ) - } - } - @Test fun `test codePercentage tracker will not be activated if CWSPR terms of service is not accepted`() { val exploreManagerMock = mock { 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 eecebf46ae1..00c581709de 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 @@ -138,7 +138,7 @@ class CodeWhispererUserActionsTest : CodeWhispererTestBase() { on { this.newRectangle } doReturn newRect } withCodeWhispererServiceInvokedAndWait { states -> - CodeWhispererInvocationStatus.getInstance().setPopupActive(true) + CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(true) val listener = CodeWhispererScrollListener(states) listener.visibleAreaChanged(event) verify(popupManagerSpy, times(2)).showPopup(any(), any(), any(), any(), any()) From 989c77fe372d6f0c879e2f0010d89cc7cc2bf71e Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Tue, 3 Sep 2024 05:11:30 -0700 Subject: [PATCH 03/11] Q and intelliSense co-exist --- .../aws/toolkits/gradle/BuildScriptUtils.kt | 3 +- .../META-INF/plugin-codewhisperer.xml | 22 +++++++ .../actions/CodeWhispererAcceptAction.kt | 33 +++++++++++ .../actions/CodeWhispererActionPromoter.kt | 58 +++++++++++++------ .../actions/CodeWhispererForceAcceptAction.kt | 7 +++ .../CodeWhispererNavigateNextAction.kt | 33 +++++++++++ .../CodeWhispererNavigatePrevAction.kt | 33 +++++++++++ .../popup/CodeWhispererPopupComponents.kt | 21 ++++++- .../popup/CodeWhispererPopupManager.kt | 46 +++++++++------ .../service/CodeWhispererService.kt | 15 +++-- .../settings/CodeWhispererConfigurable.kt | 34 +++++++++-- .../settings/CodeWhispererSettings.kt | 20 ++++++- .../resources/MessagesBundle.properties | 11 +++- 13 files changed, 284 insertions(+), 52 deletions(-) create mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererAcceptAction.kt create mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt create mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt create mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigatePrevAction.kt diff --git a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt index 2bccc07914e..5b78b2f9d42 100644 --- a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt +++ b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt @@ -59,5 +59,6 @@ fun Project.buildMetadata() = } catch(e: Exception) { logger.warn("Could not determine current commit", e) - "unknownCommit" + "beta-auto-trigger-20240903" +// "unknownCommit" } 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 f1df792e004..a9d814bc350 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 @@ -84,6 +84,28 @@ text="Show Code Suggestions"> + + + + + + + + + + + + + + , context: DataContext): MutableList { val results = actions.toMutableList() -// results.sortWith { a, b -> -// if (isCodeWhispererPopupAction(a)) { -// return@sortWith -1 -// } else if (isCodeWhispererPopupAction(b)) { -// return@sortWith 1 -// } else { -// 0 -// } -// } + if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return results + if (!CodeWhispererSettings.getInstance().isQSuggestionPrioritized() && + !results.any { isCodeWhispererForceAction(it) }) { + return results + } + results.sortWith { a, b -> + if (isCodeWhispererForceAction(a) || isCodeWhispererAcceptActionPrioritized(a)) { + return@sortWith -1 + } else if (isCodeWhispererForceAction(b) || isCodeWhispererAcceptActionPrioritized(b)) { + return@sortWith 1 + } + + if (a is ChooseItemAction) { + return@sortWith -1 + } else if (b is ChooseItemAction) { + return@sortWith 1 + } + + if (isCodeWhispererAcceptAction(a)) { + return@sortWith -1 + } else if (isCodeWhispererAcceptAction(b)) { + return@sortWith 1 + } + + 0 + } return results } private fun isCodeWhispererAcceptAction(action: AnAction): Boolean = - action is EditorAction && action.handler is CodeWhispererPopupTabHandler + action is CodeWhispererAcceptAction + + private fun isCodeWhispererForceAcceptAction(action: AnAction): Boolean = + action is CodeWhispererForceAcceptAction private fun isCodeWhispererNavigateAction(action: AnAction): Boolean = - action is EditorAction && ( - action.handler is CodeWhispererPopupRightArrowHandler || - action.handler is CodeWhispererPopupLeftArrowHandler - ) + action is CodeWhispererNavigateNextAction || action is CodeWhispererNavigatePrevAction + + private fun isCodeWhispererForceAction(action: AnAction): Boolean = + isCodeWhispererForceAcceptAction(action) || isCodeWhispererNavigateAction(action) + + private fun isCodeWhispererAcceptActionPrioritized(action: AnAction): Boolean = + action is CodeWhispererAcceptAction && CodeWhispererSettings.getInstance().isQSuggestionPrioritized() private fun isCodeWhispererPopupAction(action: AnAction): Boolean = isCodeWhispererAcceptAction(action) || isCodeWhispererNavigateAction(action) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt new file mode 100644 index 00000000000..52b723bc7b7 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt @@ -0,0 +1,7 @@ +// 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 + +// A same action but different key shortcut and different logic +class CodeWhispererForceAcceptAction: CodeWhispererAcceptAction() 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 new file mode 100644 index 00000000000..6e6c6455c5e --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt @@ -0,0 +1,33 @@ +// 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.model.SessionContext +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 { + var sessionContext: SessionContext? = null + + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null + } + + override fun actionPerformed(e: AnActionEvent) { + val sessionContext = sessionContext ?: return + if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return + ApplicationManager.getApplication().messageBus.syncPublisher( + CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED + ).navigateNext(sessionContext) + } +} 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 new file mode 100644 index 00000000000..b831de3de88 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigatePrevAction.kt @@ -0,0 +1,33 @@ +// 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.model.SessionContext +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 { + var sessionContext: SessionContext? = null + + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null + } + + override fun actionPerformed(e: AnActionEvent) { + val sessionContext = sessionContext ?: return + if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return + ApplicationManager.getApplication().messageBus.syncPublisher( + CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED + ).navigatePrevious(sessionContext) + } +} 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 fcf2279f478..85d083e5eb9 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 @@ -6,11 +6,16 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.idea.AppMode +import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionToolbar import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.actionSystem.KeyboardShortcut import com.intellij.openapi.actionSystem.Presentation import com.intellij.openapi.actionSystem.impl.ActionButton +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.keymap.MacKeymapUtil +import com.intellij.openapi.util.SystemInfo import com.intellij.ui.IdeBorderFactory import com.intellij.ui.components.ActionLink import com.intellij.util.ui.UIUtil @@ -43,10 +48,22 @@ import javax.swing.JPanel class CodeWhispererPopupComponents { val prevButton = createNavigationButton( - message("codewhisperer.popup.button.prev", POPUP_DIM_HEX) + message("codewhisperer.popup.button.prev", POPUP_DIM_HEX, run { + val shortcut = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous") + .shortcutSet.shortcuts.first() + val keyStroke = (shortcut as KeyboardShortcut).firstKeyStroke + if (SystemInfo.isMac) MacKeymapUtil.getKeyStrokeText(keyStroke, " ", true) + else KeymapUtil.getKeystrokeText(keyStroke) + }) ) val nextButton = createNavigationButton( - message("codewhisperer.popup.button.next", POPUP_DIM_HEX) + message("codewhisperer.popup.button.next", POPUP_DIM_HEX, run { + val shortcut = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next") + .shortcutSet.shortcuts.first() + val keyStroke = (shortcut as KeyboardShortcut).firstKeyStroke + if (SystemInfo.isMac) MacKeymapUtil.getKeyStrokeText(keyStroke, " ", true) + else KeymapUtil.getKeystrokeText(keyStroke) + }) ).apply { preferredSize = prevButton.preferredSize } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index 3aa1d7c47a6..66149eca668 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -5,11 +5,10 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup import com.intellij.codeInsight.lookup.LookupManager import com.intellij.idea.AppMode +import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_BACKSPACE import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ENTER import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ESCAPE -import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_LEFT -import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TAB import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.WriteCommandAction @@ -37,6 +36,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.Disposer import com.intellij.openapi.wm.WindowManager import com.intellij.ui.ComponentUtil +import com.intellij.ui.EditorNotificationPanel.ActionHandler import com.intellij.ui.awt.RelativePoint import com.intellij.ui.popup.AbstractPopup import com.intellij.ui.popup.PopupFactoryImpl @@ -47,6 +47,10 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Import import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger +import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererAcceptAction +import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererForceAcceptAction +import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererNavigateNextAction +import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererNavigatePrevAction import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.addHorizontalGlue import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.horizontalPanelConstraints @@ -59,8 +63,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.Cod import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupBackspaceHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEnterHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEscHandler -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupLeftArrowHandler -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupRightArrowHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTabHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTypedHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererAcceptButtonActionListener @@ -189,6 +191,7 @@ class CodeWhispererPopupManager { if (sessionContext == null || sessionContext.selectedIndex == -1 || sessionContext.isDisposed()) return val selectedIndex = sessionContext.selectedIndex val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() + if (selectedIndex >= previews.size) return val validCount = getValidCount() val validSelectedIndex = getValidSelectedIndex(selectedIndex) updateSelectedRecommendationLabelText(validSelectedIndex, validCount) @@ -274,20 +277,21 @@ class CodeWhispererPopupManager { } else { FileEditorManager.getInstance(sessionContext.project).selectedTextEditorWithRemotes.firstOrNull() == editor } - if (!isSameEditorAsTrigger) { - LOG.debug { "Current editor no longer has focus, not showing the popup" } - CodeWhispererService.getInstance().disposeDisplaySession(false) - return - } +// if (!isSameEditorAsTrigger) { +// LOG.debug { "Current editor no longer has focus, not showing the popup" } +// CodeWhispererService.getInstance().disposeDisplaySession(false) +// return +// } if (!editorRect.contains(popupRect)) { // popup location above first line don't work, so don't show the popup shouldHidePopup = true } else { - LOG.debug { - "Show popup above the first line of recommendation. " + - "Editor position: $editorRect, popup position: $popupRect" - } +// LOG.debug { +// "Show popup above the first line of recommendation. " + +// "Editor position: $editorRect, popup position: $popupRect" +// } + shouldHidePopup } val popupLocation = Point(p.x, yAboveFirstLine) @@ -355,7 +359,7 @@ class CodeWhispererPopupManager { .createComponentPopupBuilder(popupComponents.panel, null) .setAlpha(0.1F) .setCancelOnClickOutside(true) - .setCancelOnOtherWindowOpen(true) +// .setCancelOnOtherWindowOpen(true) // .setCancelKeyEnabled(true) .setCancelOnWindowDeactivation(true) .createPopup() @@ -438,10 +442,15 @@ class CodeWhispererPopupManager { private fun setPopupActionHandlers(sessionContext: SessionContext) { val actionManager = EditorActionManager.getInstance() + val prevAction = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous") as CodeWhispererNavigatePrevAction + prevAction.sessionContext = sessionContext + val nextAction = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next") as CodeWhispererNavigateNextAction + nextAction.sessionContext = sessionContext + val acceptAction = ActionManager.getInstance().getAction("codewhisperer.inline.accept") as CodeWhispererAcceptAction + acceptAction.sessionContext = sessionContext + val forceAcceptAction = ActionManager.getInstance().getAction("codewhisperer.inline.force.accept") as CodeWhispererForceAcceptAction + forceAcceptAction.sessionContext = sessionContext setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, sessionContext), sessionContext) - setPopupActionHandler(ACTION_EDITOR_TAB, CodeWhispererPopupTabHandler(sessionContext), sessionContext) - setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_LEFT, CodeWhispererPopupLeftArrowHandler(sessionContext), sessionContext) - setPopupActionHandler(ACTION_EDITOR_MOVE_CARET_RIGHT, CodeWhispererPopupRightArrowHandler(sessionContext), sessionContext) setPopupActionHandler(ACTION_EDITOR_ESCAPE, CodeWhispererPopupEscHandler(sessionContext), sessionContext) setPopupActionHandler( ACTION_EDITOR_ENTER, @@ -516,8 +525,9 @@ class CodeWhispererPopupManager { if (editorComponent.isShowing) { val window = ComponentUtil.getWindow(editorComponent) val windowListener: ComponentListener = object : ComponentAdapter() { - override fun componentMoved(event: ComponentEvent) { + override fun componentMoved(e: ComponentEvent) { CodeWhispererService.getInstance().disposeDisplaySession(false) + super.componentMoved(e) } override fun componentShown(e: ComponentEvent?) { 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 947d300517f..61244423c9c 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 @@ -6,6 +6,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service import com.intellij.codeInsight.hint.HintManager import com.intellij.notification.NotificationAction import com.intellij.openapi.Disposable +import com.intellij.openapi.actionSystem.DataKey import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runInEdt @@ -20,11 +21,11 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.messages.Topic +import io.ktor.utils.io.CancellationException +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job import kotlinx.coroutines.async -import io.ktor.utils.io.CancellationException -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -573,6 +574,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { sendDiscardedUserDecisionEventForAll(jobId, sessionContext, requestContext, responseContext, recommendations, coroutine) return null } + val userInputOriginal = CodeWhispererEditorManager.getInstance().getUserInputSinceInvocation( requestContext.editor, requestContext.caretPosition.offset @@ -665,15 +667,15 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // avoid duplicate session disposal logic if (sessionContext == null || sessionContext?.isDisposed() == true) return - val jobIds = ongoingRequests.keys.toList() - jobIds.forEach { jobId -> disposeJob(jobId) } - ongoingRequests.clear() - ongoingRequestsContext.clear() sessionContext?.let { it.hasAccepted = accept Disposer.dispose(it) } sessionContext = null + val jobIds = ongoingRequests.keys.toList() + jobIds.forEach { jobId -> disposeJob(jobId) } + ongoingRequests.clear() + ongoingRequestsContext.clear() } fun getAllSuggestionsPreviewInfo() = @@ -847,6 +849,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { "CodeWhisperer intelliSense popup on hover", CodeWhispererIntelliSenseOnHoverListener::class.java ) + val DATA_KEY_SESSION = DataKey.create("codewhisperer.session") fun getInstance(): CodeWhispererService = service() const val KET_SESSION_ID = "x-amzn-SessionId" 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 9db80018184..0e5e3a04fa0 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 @@ -7,10 +7,14 @@ import com.intellij.icons.AllIcons import com.intellij.openapi.options.BoundConfigurable import com.intellij.openapi.options.SearchableConfigurable import com.intellij.openapi.project.Project -import com.intellij.ui.dsl.builder.AlignX +import com.intellij.openapi.ui.popup.ListSeparator +import com.intellij.ui.GroupedComboBoxRenderer +import com.intellij.ui.dsl.builder.Align import com.intellij.ui.dsl.builder.bindIntText +import com.intellij.ui.dsl.builder.bindItem import com.intellij.ui.dsl.builder.bindSelected import com.intellij.ui.dsl.builder.panel +import org.jetbrains.annotations.Nls import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType @@ -18,6 +22,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhisp import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled import software.aws.toolkits.resources.message import java.awt.Font +import javax.swing.ListCellRenderer // As the connection is project-level, we need to make this project-level too (we have different config for Sono vs SSO users) class CodeWhispererConfigurable(private val project: Project) : @@ -87,6 +92,27 @@ class CodeWhispererConfigurable(private val project: Project) : bindSelected(codeWhispererSettings::isImportAdderEnabled, codeWhispererSettings::toggleImportAdder) }.comment(message("aws.settings.codewhisperer.automatic_import_adder.tooltip")) } + + row(message("aws.settings.codewhisperer.inline.suggestion_priority.text")) { + comboBox( + listOf( + message("aws.settings.codewhisperer.inline.suggestion_priority.intellisense.text"), + message("aws.settings.codewhisperer.inline.suggestion_priority.q.text") + ), + object : GroupedComboBoxRenderer() { + override fun getText(item: String?): String = item ?: "" + override fun getSecondaryText(item: String?): String = + if (item == message("aws.settings.codewhisperer.inline.suggestion_priority.intellisense.text")) { + " default" + } else { + "" + } + override fun separatorFor(value: String?): ListSeparator? = null + } + ).apply { + bindItem(codeWhispererSettings::getPrioritizedSuggestionString, codeWhispererSettings::setIsQSuggestionPrioritized) + }.comment(message("aws.settings.codewhisperer.inline.suggestion_priority.tooltip"), maxLineLength = 38) + } } group(message("aws.settings.codewhisperer.group.q_chat")) { @@ -109,7 +135,7 @@ class CodeWhispererConfigurable(private val project: Project) : intTextField( range = IntRange(0, 50) ).bindIntText(codeWhispererSettings::getProjectContextIndexThreadCount, codeWhispererSettings::setProjectContextIndexThreadCount) - .align(AlignX.FILL).apply { + .apply { connect.subscribe( ToolkitConnectionManagerListener.TOPIC, object : ToolkitConnectionManagerListener { @@ -119,14 +145,14 @@ class CodeWhispererConfigurable(private val project: Project) : } ) enabled(invoke) - }.comment(message("aws.settings.codewhisperer.project_context_index_thread.tooltip")) + }.comment(message("aws.settings.codewhisperer.project_context_index_thread.tooltip"), maxLineLength = 47) } row(message("aws.settings.codewhisperer.project_context_index_max_size")) { intTextField( range = IntRange(1, 250) ).bindIntText(codeWhispererSettings::getProjectContextIndexMaxSize, codeWhispererSettings::setProjectContextIndexMaxSize) - .align(AlignX.FILL).apply { + .apply { connect.subscribe( ToolkitConnectionManagerListener.TOPIC, object : ToolkitConnectionManagerListener { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt index 749e5e273d0..40c37047e44 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import com.intellij.util.xmlb.annotations.Property import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererFeatureConfigService +import software.aws.toolkits.resources.message @Service @State(name = "codewhispererSettings", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)]) @@ -94,6 +95,22 @@ class CodeWhispererSettings : PersistentStateComponentLearn more +aws.settings.codewhisperer.inline.suggestion_priority.text=Tab to prioritize accepting suggestions from +aws.settings.codewhisperer.inline.suggestion_priority.intellisense.text=JetBrains +aws.settings.codewhisperer.inline.suggestion_priority.q.text=Amazon Q +aws.settings.codewhisperer.inline.suggestion_priority.tooltip=Tip: Alternatively, use \u2325 \u21E5 to accept Amazon Q suggestions and \u23CE to accept JetBrains suggestions. aws.settings.codewhisperer.project_context=Workspace index aws.settings.codewhisperer.project_context.tooltip=This feature is in BETA. When you add @workspace to your questions in Amazon Q chat, Amazon Q will index your open workspace files locally to use as context for its response. Extra CPU usage is expected while indexing a workspace. This will not impact Amazon Q features or your IDE, but you may manage CPU usage by setting the number of local threads below. aws.settings.codewhisperer.project_context_gpu=Workspace index uses GPU @@ -845,6 +849,9 @@ codewhisperer.gettingstarted.panel.learn_more=Learn more codewhisperer.gettingstarted.panel.learn_more.with.q=Learn more about Amazon Q and Codewhisperer codewhisperer.gettingstarted.panel.licence_comment=Already have a license? codewhisperer.gettingstarted.panel.login_button=Use for free, no AWS account required +codewhisperer.inline.navigate.next=Navigate to Next Suggestion +codewhisperer.inline.accept=Accept Suggestion +codewhisperer.inline.navigate.previous=Navigate to Previous Suggestion codewhisperer.language.error={0} is currently not supported by Amazon Q codewhisperer.learn_page.banner.dismiss=Dismiss codewhisperer.learn_page.banner.message.new_user=You can always return to this page by clicking "Learn" in the Amazon Q status bar menu. @@ -872,8 +879,8 @@ codewhisperer.notification.remote.ide_unsupported.title=Amazon Q inline suggesti codewhisperer.notification.usage_limit.codescan.warn.content=Amazon Q: You have reached the monthly limit for project scans. codewhisperer.notification.usage_limit.codesuggestion.warn.content=You have reached the monthly fair use limit of code recommendations. codewhisperer.popup.button.accept=
 Insert Code 
\u21E5
-codewhisperer.popup.button.next=
Next
-codewhisperer.popup.button.prev=
Previous
+codewhisperer.popup.button.next=
Next
{1}
+codewhisperer.popup.button.prev=
Previous
{1}
codewhisperer.popup.import_info=

If you insert code, "{0}"{2, choice, 0#|1# and {1} other import|2# and {1} other imports} will also be added.

codewhisperer.popup.no_recommendations=No Amazon Q recommendations available at this point codewhisperer.popup.pagination_info=Loading additional Amazon Q suggestions... From 946d5b7d4068bde86515f5bfb4f8a89b65c456df Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 5 Sep 2024 23:20:18 -0700 Subject: [PATCH 04/11] fix version update logistic and q-intellij conflict options --- .../aws/toolkits/gradle/BuildScriptUtils.kt | 2 +- gradle.properties | 2 +- .../META-INF/plugin-codewhisperer.xml | 15 ++--- .../actions/CodeWhispererAcceptAction.kt | 3 +- .../actions/CodeWhispererActionPromoter.kt | 11 ++-- .../actions/CodeWhispererForceAcceptAction.kt | 6 +- .../CodeWhispererNavigateNextAction.kt | 1 + .../CodeWhispererNavigatePrevAction.kt | 2 + .../explorer/QStatusBarLoggedInActionGroup.kt | 2 + .../CodeWhispererPopupBackspaceHandler.kt | 1 + .../settings/CodeWhispererConfigurable.kt | 38 +++++------- .../settings/CodeWhispererSettings.kt | 34 ++++++----- .../CodeWhispererProjectStartupActivity.kt | 59 +++++++++++++++++++ .../status/CodeWhispererStatusBarWidget.kt | 11 ++-- .../CodeWhispererUserModificationTracker.kt | 2 +- .../core/plugin/PluginUpdateManager.kt | 2 + .../resources/MessagesBundle.properties | 8 +-- 17 files changed, 137 insertions(+), 62 deletions(-) diff --git a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt index 5b78b2f9d42..c7809253f67 100644 --- a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt +++ b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt @@ -59,6 +59,6 @@ fun Project.buildMetadata() = } catch(e: Exception) { logger.warn("Could not determine current commit", e) - "beta-auto-trigger-20240903" + "beta.20240906" // "unknownCommit" } diff --git a/gradle.properties b/gradle.properties index e2ed3537258..fc2024b98a9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # Toolkit Version -toolkitVersion=3.29-SNAPSHOT +toolkitVersion=99.99.3 # Publish Settings publishToken= 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 a9d814bc350..83a827cbd01 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 @@ -81,28 +81,29 @@
+ text="Invoke Amazon Q Inline Suggestions"> - + text="Navigate to Previous Inline Suggestion" description="Navigate to previous inline suggestion"> + - + text="Navigate to Next Inline Suggestion" description="Navigate to next inline suggestion"> + + text="Accept the Current Inline Suggestion" description="Accept the current inline suggestions"> + text="Force Accept the Current Inline Suggestion" description="Force accept the current inline suggestion"> + 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 index 03d56fe387c..3f6c87ea3f7 100644 --- 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 @@ -14,13 +14,14 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispere import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus import software.aws.toolkits.resources.message -open class CodeWhispererAcceptAction : AnAction(message("codewhisperer.inline.accept")), DumbAware { +open class CodeWhispererAcceptAction(title: String = message("codewhisperer.inline.accept")) : AnAction(title), DumbAware { var sessionContext: SessionContext? = null 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) { 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 index 1ea475db399..0b87847fb40 100644 --- 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 @@ -14,10 +14,11 @@ class CodeWhispererActionPromoter : ActionPromoter { override fun promote(actions: MutableList, context: DataContext): MutableList { val results = actions.toMutableList() if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return results - if (!CodeWhispererSettings.getInstance().isQSuggestionPrioritized() && - !results.any { isCodeWhispererForceAction(it) }) { - return results - } + +// if (!CodeWhispererSettings.getInstance().isQSuggestionPrioritized() && +// !results.any { isCodeWhispererForceAction(it) }) { +// return results +// } results.sortWith { a, b -> if (isCodeWhispererForceAction(a) || isCodeWhispererAcceptActionPrioritized(a)) { return@sortWith -1 @@ -55,7 +56,7 @@ class CodeWhispererActionPromoter : ActionPromoter { isCodeWhispererForceAcceptAction(action) || isCodeWhispererNavigateAction(action) private fun isCodeWhispererAcceptActionPrioritized(action: AnAction): Boolean = - action is CodeWhispererAcceptAction && CodeWhispererSettings.getInstance().isQSuggestionPrioritized() + action is CodeWhispererAcceptAction && false private fun isCodeWhispererPopupAction(action: AnAction): Boolean = isCodeWhispererAcceptAction(action) || isCodeWhispererNavigateAction(action) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt index 52b723bc7b7..dd67f93e3c6 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt @@ -3,5 +3,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.actions -// A same action but different key shortcut and different logic -class CodeWhispererForceAcceptAction: CodeWhispererAcceptAction() +import software.aws.toolkits.resources.message + +// A same 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/CodeWhispererNavigateNextAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt index 6e6c6455c5e..36bead59177 100644 --- 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 @@ -21,6 +21,7 @@ class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.n override fun update(e: AnActionEvent) { e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null + && CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() } override fun actionPerformed(e: AnActionEvent) { 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 index b831de3de88..b8578fb0151 100644 --- 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 @@ -20,7 +20,9 @@ class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.n 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) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt index e03b7497260..c11b88759d4 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt @@ -45,6 +45,8 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { override val sendFeedback = CodeWhispererProvideFeedbackAction() override val connectOnGithub = CodeWhispererConnectOnGithubAction() override val documentation = CodeWhispererLearnMoreAction() + // TODO: add the actions to switch between beta/marketplace plugins + val switchToMarketplacePlugin = null } override fun getChildren(e: AnActionEvent?) = e?.project?.let { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt index 8ed6ded8933..1d3fe063c49 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt @@ -18,6 +18,7 @@ class CodeWhispererPopupBackspaceHandler( ) : CodeWhispererEditorActionHandler(sessionContext) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { val popupManager = CodeWhispererPopupManager.getInstance() + popupManager.dontClosePopupAndRun { val oldOffset = editor.caretModel.offset defaultHandler.execute(editor, caret, dataContext) 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 0e5e3a04fa0..c2d62feae60 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 @@ -4,17 +4,20 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.settings import com.intellij.icons.AllIcons +import com.intellij.ide.DataManager import com.intellij.openapi.options.BoundConfigurable +import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.SearchableConfigurable +import com.intellij.openapi.options.ex.Settings import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.ListSeparator import com.intellij.ui.GroupedComboBoxRenderer -import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.components.ActionLink import com.intellij.ui.dsl.builder.bindIntText import com.intellij.ui.dsl.builder.bindItem import com.intellij.ui.dsl.builder.bindSelected import com.intellij.ui.dsl.builder.panel -import org.jetbrains.annotations.Nls +import com.intellij.util.concurrency.EdtExecutorService import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType @@ -22,7 +25,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhisp import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled import software.aws.toolkits.resources.message import java.awt.Font -import javax.swing.ListCellRenderer +import java.util.concurrent.TimeUnit // As the connection is project-level, we need to make this project-level too (we have different config for Sono vs SSO users) class CodeWhispererConfigurable(private val project: Project) : @@ -93,25 +96,16 @@ class CodeWhispererConfigurable(private val project: Project) : }.comment(message("aws.settings.codewhisperer.automatic_import_adder.tooltip")) } - row(message("aws.settings.codewhisperer.inline.suggestion_priority.text")) { - comboBox( - listOf( - message("aws.settings.codewhisperer.inline.suggestion_priority.intellisense.text"), - message("aws.settings.codewhisperer.inline.suggestion_priority.q.text") - ), - object : GroupedComboBoxRenderer() { - override fun getText(item: String?): String = item ?: "" - override fun getSecondaryText(item: String?): String = - if (item == message("aws.settings.codewhisperer.inline.suggestion_priority.intellisense.text")) { - " default" - } else { - "" - } - override fun separatorFor(value: String?): ListSeparator? = null - } - ).apply { - bindItem(codeWhispererSettings::getPrioritizedSuggestionString, codeWhispererSettings::setIsQSuggestionPrioritized) - }.comment(message("aws.settings.codewhisperer.inline.suggestion_priority.tooltip"), maxLineLength = 38) + row() { + link("Configure inline suggestion keybindings") { e -> + val settings = DataManager.getInstance().getDataContext(e.source as ActionLink).getData(Settings.KEY) ?: return@link + val configurable: Configurable = settings.find("preferences.keymap") ?: return@link + // workaround for sometimes the string is not input there + settings.select(configurable, "inline suggestion") + EdtExecutorService.getScheduledExecutorInstance().schedule({ + settings.select(configurable, "inline suggestion") + }, 500, TimeUnit.MILLISECONDS) + } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt index 40c37047e44..c45f38e8785 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt @@ -12,7 +12,6 @@ import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import com.intellij.util.xmlb.annotations.Property import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererFeatureConfigService -import software.aws.toolkits.resources.message @Service @State(name = "codewhispererSettings", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)]) @@ -95,20 +94,27 @@ class CodeWhispererSettings : PersistentStateComponentLearn more -aws.settings.codewhisperer.inline.suggestion_priority.text=Tab to prioritize accepting suggestions from -aws.settings.codewhisperer.inline.suggestion_priority.intellisense.text=JetBrains -aws.settings.codewhisperer.inline.suggestion_priority.q.text=Amazon Q -aws.settings.codewhisperer.inline.suggestion_priority.tooltip=Tip: Alternatively, use \u2325 \u21E5 to accept Amazon Q suggestions and \u23CE to accept JetBrains suggestions. aws.settings.codewhisperer.project_context=Workspace index aws.settings.codewhisperer.project_context.tooltip=This feature is in BETA. When you add @workspace to your questions in Amazon Q chat, Amazon Q will index your open workspace files locally to use as context for its response. Extra CPU usage is expected while indexing a workspace. This will not impact Amazon Q features or your IDE, but you may manage CPU usage by setting the number of local threads below. aws.settings.codewhisperer.project_context_gpu=Workspace index uses GPU @@ -851,6 +847,7 @@ codewhisperer.gettingstarted.panel.licence_comment=Already have a license? codewhisperer.gettingstarted.panel.login_button=Use for free, no AWS account required codewhisperer.inline.navigate.next=Navigate to Next Suggestion codewhisperer.inline.accept=Accept Suggestion +codewhisperer.inline.force.accept=Force Accept Suggestion codewhisperer.inline.navigate.previous=Navigate to Previous Suggestion codewhisperer.language.error={0} is currently not supported by Amazon Q codewhisperer.learn_page.banner.dismiss=Dismiss @@ -874,6 +871,9 @@ codewhisperer.notification.custom.not_available=Selected Amazon Q customization codewhisperer.notification.custom.simple.button.got_it=Got it codewhisperer.notification.custom.simple.button.select_another_customization=Select another customization codewhisperer.notification.custom.simple.button.select_customization=Select customization +codewhisperer.notification.inline.shortcut_config.content=Check out the default and customize in the keymap settings. +codewhisperer.notification.inline.shortcut_config.title=Configurable Amazon Q suggestion shortcuts +codewhisperer.notification.inline.shortcut_config.open_setting=Open keymap settings codewhisperer.notification.remote.ide_unsupported.message=Please update your IDE backend to a 2023.3 or later version to continue using Amazon Q inline suggestions. codewhisperer.notification.remote.ide_unsupported.title=Amazon Q inline suggestion not supported in this IDE version codewhisperer.notification.usage_limit.codescan.warn.content=Amazon Q: You have reached the monthly limit for project scans. From 8bc1a6da38b7dca979cefbc7b69ef72649c22100 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 9 Sep 2024 13:44:21 -0700 Subject: [PATCH 05/11] Add auto-update logistic and switch to marketplace and beta expiry rules --- .../META-INF/plugin-codewhisperer.xml | 2 + .../actions/CodeWhispererActionPromoter.kt | 14 +-- .../ConnectWithAwsToContinueActionWarn.kt | 32 ------ .../QSwitchToMarketplaceVersionAction.kt | 103 ++++++++++++++++++ .../editor/CodeWhispererEditorManager.kt | 2 + .../explorer/QStatusBarLoggedInActionGroup.kt | 11 +- .../explorer/actions/ActionFactory.kt | 6 + .../codewhisperer/model/CodeWhispererModel.kt | 2 + .../plugin/QBetaPluginManagementPolicy.kt | 30 +++++ .../popup/CodeWhispererPopupListener.kt | 1 - .../popup/CodeWhispererPopupManager.kt | 7 +- .../popup/CodeWhispererUIChangeListener.kt | 4 + .../service/CodeWhispererService.kt | 13 ++- .../CodeWhispererProjectStartupActivity.kt | 60 ++++++---- .../status/CodeWhispererStatusBarWidget.kt | 11 +- .../CodeWhispererTelemetryService.kt | 91 ++++++++++------ .../util/CodeWhispererConstants.kt | 2 +- .../core/plugin/PluginAutoUpdater.kt | 2 +- 18 files changed, 279 insertions(+), 114 deletions(-) delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ConnectWithAwsToContinueActionWarn.kt create mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt create mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt 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 83a827cbd01..2ba71479540 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 @@ -27,6 +27,8 @@ + , context: DataContext): MutableList { val results = actions.toMutableList() if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return results -// if (!CodeWhispererSettings.getInstance().isQSuggestionPrioritized() && -// !results.any { isCodeWhispererForceAction(it) }) { -// return results -// } results.sortWith { a, b -> - if (isCodeWhispererForceAction(a) || isCodeWhispererAcceptActionPrioritized(a)) { + if (isCodeWhispererForceAction(a)) { return@sortWith -1 - } else if (isCodeWhispererForceAction(b) || isCodeWhispererAcceptActionPrioritized(b)) { + } else if (isCodeWhispererForceAction(b)) { return@sortWith 1 } @@ -55,9 +50,4 @@ class CodeWhispererActionPromoter : ActionPromoter { private fun isCodeWhispererForceAction(action: AnAction): Boolean = isCodeWhispererForceAcceptAction(action) || isCodeWhispererNavigateAction(action) - private fun isCodeWhispererAcceptActionPrioritized(action: AnAction): Boolean = - action is CodeWhispererAcceptAction && false - - private fun isCodeWhispererPopupAction(action: AnAction): Boolean = - isCodeWhispererAcceptAction(action) || isCodeWhispererNavigateAction(action) } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ConnectWithAwsToContinueActionWarn.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ConnectWithAwsToContinueActionWarn.kt deleted file mode 100644 index 0c19cfcff97..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ConnectWithAwsToContinueActionWarn.kt +++ /dev/null @@ -1,32 +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.AnActionEvent -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.DumbAwareAction -import software.aws.toolkits.jetbrains.core.gettingstarted.requestCredentialsForCodeWhisperer -import software.aws.toolkits.resources.message - -/** - * Action prompting users to switch to SSO based credential, will nullify accountless credential (delete) - */ -class ConnectWithAwsToContinueActionWarn : DumbAwareAction(message("codewhisperer.notification.accountless.warn.action.connect")) { - override fun actionPerformed(e: AnActionEvent) { - e.project?.let { - runInEdt { - requestCredentialsForCodeWhisperer(it) - } - } - } -} -class ConnectWithAwsToContinueActionError : DumbAwareAction(message("codewhisperer.notification.accountless.error.action.connect")) { - override fun actionPerformed(e: AnActionEvent) { - e.project?.let { - runInEdt { - requestCredentialsForCodeWhisperer(it) - } - } - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt new file mode 100644 index 00000000000..75c0d0304b2 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt @@ -0,0 +1,103 @@ +// 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.icons.AllIcons +import com.intellij.ide.plugins.CustomPluginRepositoryService +import com.intellij.ide.plugins.PluginManagementPolicy +import com.intellij.ide.plugins.marketplace.MarketplaceRequests +import com.intellij.notification.NotificationAction +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.updateSettings.impl.PluginDownloader +import software.aws.toolkits.jetbrains.AwsPlugin +import software.aws.toolkits.jetbrains.AwsToolkit +import software.aws.toolkits.jetbrains.core.plugin.PluginUpdateManager +import software.aws.toolkits.jetbrains.utils.notifyInfo +import software.aws.toolkits.resources.AwsCoreBundle +import software.aws.toolkits.resources.message + +class QSwitchToMarketplaceVersionAction: + AnAction( + "Switch Back to Marketplace", + null, + AllIcons.Actions.Refresh + ), + DumbAware { + + override fun getActionUpdateThread() = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + e.project?.let { + e.presentation.isEnabledAndVisible = PluginUpdateManager.getInstance().isBeta() + } + } + + override fun actionPerformed(e: AnActionEvent) { + // remove all custom channel for amazon q and aws core + val qId = PluginId.getId(AwsToolkit.Q_PLUGIN_ID) + val coreId = PluginId.getId(AwsToolkit.CORE_PLUGIN_ID) + val customPlugins = CustomPluginRepositoryService.getInstance().customRepositoryPluginMap + val result = customPlugins.filter { + it.value.any { node -> + listOf(qId, coreId).contains(node.pluginId) + } + } + result.keys.forEach { + customPlugins.remove(it) + } + + runInEdt { + ProgressManager.getInstance().run(object : Task.Backgroundable( + null, + "Switching to marketplace version", + true + ) { + override fun run(indicator: ProgressIndicator) { + installMarketplaceAwsPlugins(PluginId.getId(AwsToolkit.CORE_PLUGIN_ID), indicator) + installMarketplaceAwsPlugins(qId, indicator) + } + }) + } + + } + + private fun installMarketplaceAwsPlugins(pluginId: PluginId, indicator: ProgressIndicator) { + // force update to marketplace version + try { + // MarketplaceRequest class is marked as @ApiStatus.Internal + val descriptor = MarketplaceRequests.loadLastCompatiblePluginDescriptors(setOf(pluginId)) + .find { it.pluginId == pluginId } ?: return + + val downloader = PluginDownloader.createDownloader(descriptor) + if (!downloader.prepareToInstall(indicator)) return + downloader.install() + + if (pluginId == PluginId.getId(AwsToolkit.CORE_PLUGIN_ID)) return + notifyInfo( + title = AwsCoreBundle.message("aws.notification.auto_update.title", "Amazon Q"), + content = AwsCoreBundle.message("aws.settings.auto_update.notification.message"), + project = null, + notificationActions = listOf( + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("aws.settings.auto_update.notification.yes")) { + ApplicationManager.getApplication().restart() + }, + NotificationAction.createSimpleExpiring(AwsCoreBundle.message("aws.settings.auto_update.notification.no")) { + } + ) + ) + } catch (e: Exception) { + return + } + return + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt index 742d6ad782e..585b24c3b50 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt @@ -48,6 +48,8 @@ class CodeWhispererEditorManager { val insertEndOffset = sessionContext.insertEndOffset val endOffsetToReplace = if (insertEndOffset != -1) insertEndOffset else primaryCaret.offset + preview.detail.isAccepted = true + WriteCommandAction.runWriteCommandAction(project) { document.replaceString(originalOffset, endOffsetToReplace, reformatted) PsiDocumentManager.getInstance(project).commitDocument(document) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt index c11b88759d4..93bec84b131 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt @@ -12,10 +12,12 @@ import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.actions.SsoLogoutAction import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection +import software.aws.toolkits.jetbrains.core.plugin.PluginUpdateManager import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererConnectOnGithubAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererLearnMoreAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererProvideFeedbackAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererShowSettingsAction +import software.aws.toolkits.jetbrains.services.codewhisperer.actions.QSwitchToMarketplaceVersionAction import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.ActionProvider import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Customize import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Learn @@ -24,6 +26,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.P import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.PauseCodeScans import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Resume import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.ResumeCodeScans +import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.buildActionListForBeta import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.buildActionListForCodeScan import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.buildActionListForConnectHelp import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.buildActionListForInlineSuggestions @@ -46,7 +49,7 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { override val connectOnGithub = CodeWhispererConnectOnGithubAction() override val documentation = CodeWhispererLearnMoreAction() // TODO: add the actions to switch between beta/marketplace plugins - val switchToMarketplacePlugin = null + override val switchToMarketplace = QSwitchToMarketplaceVersionAction() } override fun getChildren(e: AnActionEvent?) = e?.project?.let { @@ -67,6 +70,12 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { add(Separator.create(message("codewhisperer.statusbar.sub_menu.connect_help.title"))) addAll(buildActionListForConnectHelp(actionProvider)) + if (PluginUpdateManager.getInstance().isBeta()) { + add(Separator.create()) + add(Separator.create("Beta")) + addAll(buildActionListForBeta(actionProvider)) + } + add(Separator.create()) add(CodeWhispererShowSettingsAction()) ToolkitConnectionManager.getInstance(it).activeConnectionForFeature(CodeWhispererConnection.getInstance())?.let { c -> diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt index 3b4979572cb..a7dff2e1704 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/actions/ActionFactory.kt @@ -27,6 +27,7 @@ interface ActionProvider { val sendFeedback: T val connectOnGithub: T val documentation: T + val switchToMarketplace: T } @SuppressWarnings("UnusedParameter") @@ -84,3 +85,8 @@ fun buildActionListForConnectHelp(actionProvider: ActionProvider): List buildActionListForBeta(actionProvider: ActionProvider): List = + buildList { + add(actionProvider.switchToMarketplace) + } 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 82fc71ab62a..49180b37539 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 @@ -120,6 +120,8 @@ data class DetailContext( val isTruncatedOnRight: Boolean, val rightOverlap: String = "", val completionType: CodewhispererCompletionType, + var hasSeen: Boolean = false, + var isAccepted: Boolean = false ) data class SessionContext( diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt new file mode 100644 index 00000000000..c879b9542e7 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt @@ -0,0 +1,30 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.plugin + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.ide.plugins.PluginManagementPolicy +import com.intellij.ide.plugins.org.PluginManagerFilters + +class QBetaPluginManagementPolicy: PluginManagementPolicy { + override fun canEnablePlugin(descriptor: IdeaPluginDescriptor?): Boolean { + return descriptor?.let { PluginManagerFilters.getInstance().allowInstallingPlugin(it) } ?: true + } + + override fun canInstallPlugin(descriptor: IdeaPluginDescriptor?): Boolean { + return canEnablePlugin(descriptor) + } + + override fun isDowngradeAllowed(localDescriptor: IdeaPluginDescriptor?, remoteDescriptor: IdeaPluginDescriptor?): Boolean { + return true + } + + override fun isInstallFromDiskAllowed(): Boolean { + return PluginManagerFilters.getInstance().allowInstallFromDisk() + } + + override fun isUpgradeAllowed(localDescriptor: IdeaPluginDescriptor?, remoteDescriptor: IdeaPluginDescriptor?): Boolean { + return true + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt index ef981d90b8e..e41ba213228 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt @@ -11,7 +11,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe class CodeWhispererPopupListener : JBPopupListener { override fun beforeShown(event: LightweightWindowEvent) { super.beforeShown(event) - CodeWhispererInvocationStatus.getInstance().setPopupStartTimestamp() } override fun onClosed(event: LightweightWindowEvent) { super.onClosed(event) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index 66149eca668..ec5806440ae 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -163,6 +163,7 @@ class CodeWhispererPopupManager { @RequiresEdt fun changeStatesForShowing(sessionContext: SessionContext, states: InvocationContext, recommendationAdded: Boolean = false) { + sessionContext.isFirstTimeShowingPopup = !recommendationAdded if (recommendationAdded) { ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED) .recommendationAdded(states, sessionContext) @@ -177,7 +178,7 @@ class CodeWhispererPopupManager { } sessionContext.selectedIndex = selectedIndex - sessionContext.isFirstTimeShowingPopup = true +// sessionContext.isFirstTimeShowingPopup = true if (sessionContext.popupDisplayOffset == -1) { sessionContext.popupDisplayOffset = sessionContext.editor.caretModel.offset } @@ -203,8 +204,6 @@ class CodeWhispererPopupManager { fun render(sessionContext: SessionContext, isRecommendationAdded: Boolean, isScrolling: Boolean) { updatePopupPanel(sessionContext) - sessionContext.seen.add(sessionContext.selectedIndex) - // There are four cases that render() is called: // 1. Popup showing for the first time, both booleans are false, we should show the popup and update the latency // end time, and emit the event if it's at the pagination end. @@ -255,6 +254,8 @@ class CodeWhispererPopupManager { if (sessionContext.popup == null) { popup = initPopup() sessionContext.popup = popup + CodeWhispererInvocationStatus.getInstance().setPopupStartTimestamp() + println("set suggestion display time starting now: ${System.currentTimeMillis()}") initPopupListener(sessionContext, popup) } else { popup = sessionContext.popup diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt index 248ef1d1596..e5dce240995 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt @@ -29,6 +29,8 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { val document = editor.document val lineEndOffset = document.getLineEndOffset(document.getLineNumber(caretOffset)) + detail.hasSeen = true + // get matching brackets from recommendations to the brackets after caret position val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation( detail, @@ -98,10 +100,12 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener { } override fun scrolled(sessionContext: SessionContext) { + sessionContext.isFirstTimeShowingPopup = false CodeWhispererPopupManager.getInstance().render(sessionContext, isRecommendationAdded = false, isScrolling = true) } override fun recommendationAdded(states: InvocationContext, sessionContext: SessionContext) { + sessionContext.isFirstTimeShowingPopup = false CodeWhispererPopupManager.getInstance().render(sessionContext, isRecommendationAdded = true, isScrolling = false) } } 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 61244423c9c..a63d2b4a57e 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 @@ -99,6 +99,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val ongoingRequestsContext = mutableMapOf() private var jobId = 0 private var sessionContext: SessionContext? = null + var isBetaExpired: Boolean = false init { Disposer.register(this, codeInsightSettingsFacade) @@ -133,6 +134,11 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } } + if (isBetaExpired && triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) { + showCodeWhispererInfoHint(editor, "Current beta period ended, please switch to the marketplace version") + return + } + latencyContext.credentialFetchingEnd = System.nanoTime() val psiFile = runReadAction { PsiDocumentManager.getInstance(project).getPsiFile(editor.document) } @@ -255,6 +261,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_CODE_COMPLETION_PERFORMED) .onSuccess(requestContext.fileContextInfo) CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent( + currentJobId, requestId, requestContext, responseContext, @@ -352,6 +359,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val responseContext = ResponseContext(sessionId) CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent( + currentJobId, requestId, requestContext, responseContext, @@ -419,6 +427,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId) logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType) CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent( + currentJobId, requestId, requestContext, responseContext, @@ -679,10 +688,6 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } fun getAllSuggestionsPreviewInfo() = -// ongoingRequests.filter { it.second != null }.map { element -> -// val context = element.second?.recommendationContext ?: return@map -// context.details.map { PreviewContext(context.jobId, it, context.userInputSinceInvocation, context.typeahead) } -// } ongoingRequests.values.filterNotNull().flatMap { element -> val context = element.recommendationContext context.details.map { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt index 3b0ba53707d..15485a72b5d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererProjectStartupActivity.kt @@ -8,6 +8,7 @@ import com.intellij.ide.DataManager import com.intellij.notification.Notification import com.intellij.notification.NotificationAction import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.keymap.KeymapManager import com.intellij.openapi.keymap.impl.ui.KeymapPanel import com.intellij.openapi.options.ShowSettingsUtil @@ -21,13 +22,16 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope +import software.aws.toolkits.jetbrains.core.plugin.PluginUpdateManager import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager +import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomizationListener.Companion.TOPIC import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isUserBuilderId import software.aws.toolkits.jetbrains.services.codewhisperer.importadder.CodeWhispererImportAdderListener import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager.Companion.CODEWHISPERER_USER_ACTION_PERFORMED import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererFeatureConfigService +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererSettings import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants @@ -41,7 +45,7 @@ import software.aws.toolkits.jetbrains.utils.notifyInfo import software.aws.toolkits.jetbrains.utils.notifyWarn import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread import software.aws.toolkits.resources.message -import java.util.concurrent.TimeUnit +import java.time.LocalDate // TODO: add logics to check if we want to remove recommendation suspension date when user open the IDE class CodeWhispererProjectStartupActivity : StartupActivity.DumbAware { @@ -67,11 +71,16 @@ class CodeWhispererProjectStartupActivity : StartupActivity.DumbAware { scanManager.createDebouncedRunCodeScan(CodeWhispererConstants.CodeAnalysisScope.FILE, isPluginStarting = true) } - if (!CodeWhispererSettings.getInstance().isInlineShortcutFeatureNotificationDisplayed() || true) { + if (!CodeWhispererSettings.getInstance().isInlineShortcutFeatureNotificationDisplayed()) { CodeWhispererSettings.getInstance().setInlineShortcutFeatureNotificationDisplayed(true) showInlineShortcutFeatureNotification(project) } + if (PluginUpdateManager.getInstance().isBeta()) { + postWelcomeToBetaMessage() + checkBetaExpiryInfo() + } + // ---- Everything below will be triggered once after startup ---- @@ -115,30 +124,35 @@ class CodeWhispererProjectStartupActivity : StartupActivity.DumbAware { listOf( object: NotificationAction(message("codewhisperer.notification.inline.shortcut_config.open_setting")) { override fun actionPerformed(e: AnActionEvent, notification: Notification) { -// notification.expire() ShowSettingsUtil.getInstance().showSettingsDialog(project, CodeWhispererConfigurable::class.java) -// e.inputEvent?.source -// EdtExecutorService.getScheduledExecutorInstance().schedule({ -// KeymapManager.getInstance().activeKeymap -// it.showOption("inline suggestion") -// }, 100, TimeUnit.MILLISECONDS) -// val settings = DataManager.getInstance().getDataContext(e.inputEvent?.source as ActionLink?).getData(Settings.KEY) -// if (settings == null) return@showSettingsDialog -// -// it.showOption("") -// settings.select(it, "Terminal").doWhenDone(Runnable { -// Remove once https://youtrack.jetbrains.com/issue/IDEA-212247 is fixed -// EdtExecutorService.getScheduledExecutorInstance().schedule({ -// settings.select(it, "Terminal") -// }, 100, TimeUnit.MILLISECONDS) -// }) - } - -// val keymapPanel: KeymapPanel = Settings.KEY.getData(e.dataContext)?.find(KeymapPanel::class.java) ?: return -// keymapPanel.showOption("inline suggestion") -// } + } } ) ) } + + private fun postWelcomeToBetaMessage() { + notifyInfo( + title = "Welcome to Amazon Q Plugin Beta", + content = "Thank you for participating in Amazon Q beta plugin testing. Plugin auto-update is always turned on to ensure the best beta experience.", + project = null + ) + } + + private fun checkBetaExpiryInfo() { + // hard 2024/10/1 stop + val expiryDate = LocalDate.of(2024, 10, 1) + + // Get the current date + val currentDate = LocalDate.now() + if (currentDate.isAfter(expiryDate)) { + notifyInfo( + title = "Amazon Q current beta period ended", + content = "The current beta period has ended on $expiryDate, please switch to the marketplace version to continue using Amazon Q.", + project = null + ) + CodeWhispererService.getInstance().isBetaExpired = true + ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).refreshUi() + } + } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt index bbe106054c2..fba6cce753b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt @@ -27,6 +27,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.customization.Code import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.QStatusBarLoggedInActionGroup import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStateChangeListener 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.util.CodeWhispererUtil.reconnectCodeWhisperer import software.aws.toolkits.jetbrains.utils.isQConnected import software.aws.toolkits.jetbrains.utils.isQExpired @@ -129,7 +130,15 @@ class CodeWhispererStatusBarWidget(project: Project) : AllIcons.Debugger.ThreadStates.Idle } - private fun pluginName() = if (PluginUpdateManager.getInstance().isBeta()) "Amazon Q (Beta)" else "Amazon Q" + private fun pluginName() = if (PluginUpdateManager.getInstance().isBeta()) { + if (CodeWhispererService.getInstance().isBetaExpired) { + "Amazon Q (Beta) (Update required)" + } else { + "Amazon Q (Beta)" + } + } else { + "Amazon Q" + } companion object { const val ID = "aws.codewhisperer.statusWidget" 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 a5e783e0346..b495adc55a2 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 @@ -23,7 +23,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh 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.DetailContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService @@ -94,6 +93,7 @@ class CodeWhispererTelemetryService { } fun sendServiceInvocationEvent( + jobId: Int, requestId: String, requestContext: RequestContext, responseContext: ResponseContext, @@ -102,6 +102,7 @@ class CodeWhispererTelemetryService { latency: Double, exceptionType: String? ) { + println("jobId: $jobId, serviceInvocation: $requestId") val (triggerType, automatedTriggerType) = requestContext.triggerTypeInfo val (offset, line) = requestContext.caretPosition @@ -398,30 +399,32 @@ class CodeWhispererTelemetryService { hasUserAccepted: Boolean, popupShownTime: Duration? = null ) { - val decisions = mutableListOf() - + if (hasUserAccepted) { + // find the accepted session, other trigger session will be ignore or unseen + } else { + // user didn't accept any of the suggestion in this display session, for all the trigger session that they have seen + // mark them as reject, otherwise mark them as unseen + } CodeWhispererService.getInstance().getAllPaginationSessions().forEach { (jobId, state) -> if (state == null) return@forEach - val detailContexts = state.recommendationContext.details - - detailContexts.forEachIndexed { index, detailContext -> - val suggestionState = recordSuggestionState( - index, - sessionContext.selectedIndex, - sessionContext.seen.contains(index), - hasUserAccepted, - detailContext.isDiscarded, - detailContext.recommendation.content().isEmpty() - ) - sendUserDecisionEvent(state.requestContext, state.responseContext, detailContext, index, suggestionState, detailContexts.size) + val decisions = mutableListOf() + val details = state.recommendationContext.details + + details.forEachIndexed { index, detail -> + val suggestionState = recordSuggestionState(detail, hasUserAccepted) + sendUserDecisionEvent(state.requestContext, state.responseContext, detail, index, suggestionState, details.size) decisions.add(suggestionState) } + print("jobId: $jobId, userDecisions: [") + decisions.forEach { print("$it, ") } + println("]") - with(aggregateUserDecision(decisions)) { + with(aggregateUserDecision(decisions, hasUserAccepted)) { // the order of the following matters // step 1, send out current decision + println("jobId: $jobId, userTriggerDecision: $this") previousUserTriggerDecisionTimestamp = Instant.now() val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() @@ -440,7 +443,7 @@ class CodeWhispererTelemetryService { state.requestContext, state.responseContext, state.recommendationContext, - CodewhispererSuggestionState.from(this.toString()), + this, popupShownTime, referenceCount, generatedLineCount, @@ -448,9 +451,12 @@ class CodeWhispererTelemetryService { ) // step 2, put current decision into queue for later reference - previousUserTriggerDecisions.add(this) - // we need this as well because AutoTriggerService will reset the queue periodically - CodeWhispererAutoTriggerService.getInstance().addPreviousDecision(this) + if (this != CodewhispererSuggestionState.Ignore && this != CodewhispererSuggestionState.Unseen) { + val previousState = CodewhispererPreviousSuggestionState.from(this.toString()) + // we need this as well because AutoTriggerService will reset the queue periodically + previousUserTriggerDecisions.add(previousState) + CodeWhispererAutoTriggerService.getInstance().addPreviousDecision(previousState) + } } } @@ -465,23 +471,42 @@ class CodeWhispererTelemetryService { * - Record the accepted suggestion index * - Discard otherwise */ - fun aggregateUserDecision(decisions: List): CodewhispererPreviousSuggestionState { + fun aggregateUserDecision(decisions: List, hasUserAccepted: Boolean): CodewhispererSuggestionState { var isEmpty = true + var isUnseen = true + var isDiscard = true for (decision in decisions) { if (decision == CodewhispererSuggestionState.Accept) { - return CodewhispererPreviousSuggestionState.Accept + return CodewhispererSuggestionState.Accept } else if (decision == CodewhispererSuggestionState.Reject) { - return CodewhispererPreviousSuggestionState.Reject - } else if (decision != CodewhispererSuggestionState.Empty) { + return CodewhispererSuggestionState.Reject + } else if (decision == CodewhispererSuggestionState.Unseen) { + isEmpty = false + isDiscard = false + } else if (decision == CodewhispererSuggestionState.Ignore) { + isUnseen = false + isEmpty = false + isDiscard = false + } else if (decision == CodewhispererSuggestionState.Empty) { + isDiscard = false + } else if (decision == CodewhispererSuggestionState.Discard) { isEmpty = false } } return if (isEmpty) { - CodewhispererPreviousSuggestionState.Empty + CodewhispererSuggestionState.Empty + } else if (isDiscard) { + CodewhispererSuggestionState.Discard + } else if (isUnseen) { + CodewhispererSuggestionState.Unseen } else { - CodewhispererPreviousSuggestionState.Discard + CodewhispererSuggestionState.Ignore +// if (hasUserAccepted) { +// +// } +// CodewhispererSuggestionState.Discard } } @@ -541,21 +566,17 @@ class CodeWhispererTelemetryService { } fun recordSuggestionState( - index: Int, - selectedIndex: Int, - hasSeen: Boolean, + detail: DetailContext, hasUserAccepted: Boolean, - isDiscarded: Boolean, - isEmpty: Boolean ): CodewhispererSuggestionState = - if (isEmpty) { + if (detail.recommendation.content().isEmpty()) { CodewhispererSuggestionState.Empty - } else if (isDiscarded) { + } else if (detail.isDiscarded) { CodewhispererSuggestionState.Discard - } else if (!hasSeen) { + } else if (!detail.hasSeen) { CodewhispererSuggestionState.Unseen } else if (hasUserAccepted) { - if (selectedIndex == index) { + if (detail.isAccepted) { CodewhispererSuggestionState.Accept } else { CodewhispererSuggestionState.Ignore diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt index 15279e2230d..988d0ed6e2b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt @@ -105,7 +105,7 @@ object CodeWhispererConstants { } object Config { - const val CODEWHISPERER_ENDPOINT = "https://rts.gamma-us-east-1.codewhisperer.ai.aws.dev/" // PROD + const val CODEWHISPERER_ENDPOINT = "https://codewhisperer.us-east-1.amazonaws.com/" // PROD const val CODEWHISPERER_IDPOOL_ID = "us-east-1:70717e99-906f-4add-908c-bd9074a2f5b9" val Sigv4ClientRegion = Region.US_EAST_1 val BearerClientRegion = Region.US_EAST_1 diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt index 94ab3ec1a95..3abb32eb60b 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt @@ -13,7 +13,7 @@ class PluginAutoUpdater : ProjectActivity { override suspend fun execute(project: Project) { // We want the auto-update feature to be triggered only once per running application - if (!autoUpdateRunOnce.getAndSet(true)) { + if (!autoUpdateRunOnce.getAndSet(true) || PluginUpdateManager.getInstance().isBeta()) { PluginUpdateManager.getInstance().scheduleAutoUpdate() if (!AwsSettings.getInstance().isAutoUpdateFeatureNotificationShownOnce) { PluginUpdateManager.getInstance().notifyAutoUpdateFeature(project) From 45e0632899b2673cb146345104df22c6e1b356b3 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 9 Sep 2024 14:55:09 -0700 Subject: [PATCH 06/11] Empty telemetry emit fix --- .../aws/toolkits/gradle/BuildScriptUtils.kt | 2 +- .../service/CodeWhispererService.kt | 45 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt index c7809253f67..2e7f17888cd 100644 --- a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt +++ b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt @@ -59,6 +59,6 @@ fun Project.buildMetadata() = } catch(e: Exception) { logger.warn("Could not determine current commit", e) - "beta.20240906" + "beta.20240909" // "unknownCommit" } 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 a63d2b4a57e..50fbdb24303 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 @@ -495,9 +495,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { return } - if (response.nextToken().isEmpty()) { - CodeWhispererInvocationStatus.getInstance().finishInvocation() - } + CodeWhispererInvocationStatus.getInstance().finishInvocation() val caretMovement = CodeWhispererEditorManager.getInstance().getCaretMovement( requestContext.editor, @@ -509,11 +507,12 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // first response for the jobId nextStates = initStates(jobId, sessionContext, requestContext, responseContext, response, caretMovement, coroutine) - // receiving a null state means caret has moved backward or there's a conflict with - // Intellisense popup, so we are going to cancel the current job + // receiving a null state means caret has moved backward, + // so we are going to cancel the current job if (nextStates == null) { LOG.debug { "Exiting CodeWhisperer session. RequestId: $requestId" } - disposeJob(jobId) +// buildInvalidInvocationContextForUTD(jobId, sessionContext, requestContext, responseContext, response.completions(), coroutine) +// disposeDisplaySession(false) println("exit 5 , jobId: $jobId") return } @@ -522,15 +521,17 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { nextStates = updateStates(currStates, response) } println("adding ${response.completions().size} completions from job ${jobId}") - ongoingRequests[jobId] = nextStates +// ongoingRequests[jobId] = nextStates - val hasAtLeastOneValid = checkRecommendationsValidity(jobId, nextStates, response.nextToken().isEmpty()) + // TODO: may have bug when it's a mix of auto-trigger + manual trigger + val hasAtLeastOneValid = checkRecommendationsValidity(jobId, nextStates, true) val allSuggestions = ongoingRequests.values.filterNotNull().flatMap { it.recommendationContext.details } val valid = allSuggestions.filter { !it.isDiscarded }.size println("total: $valid valid, ${allSuggestions.size - valid} discarded") // If there are no recommendations at all in this session, we need to manually send the user decision event here // since it won't be sent automatically later + // TODO: may have bug; visit later if (nextStates.recommendationContext.details.isEmpty() && response.nextToken().isEmpty()) { LOG.debug { "Received just an empty list from this session, requestId: $requestId" } CodeWhispererTelemetryService.getInstance().sendUserDecisionEvent( @@ -551,14 +552,20 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { ) } if (!hasAtLeastOneValid) { - if (response.nextToken().isEmpty()) { +// if (response.nextToken().isEmpty()) { LOG.debug { "None of the recommendations are valid, exiting current CodeWhisperer pagination session" } // TODO: decide whether or not to dispose what here - disposeJob(jobId) - sessionContext.selectedIndex = CodeWhispererPopupManager.getInstance().findNewSelectedIndex(true, sessionContext.selectedIndex) + // only key here, after disposing this, the whole session will also end + if (ongoingRequests.keys.size == 1) { +// buildInvalidInvocationContextForUTD(jobId, sessionContext, requestContext, responseContext, response.completions(), coroutine) + disposeDisplaySession(false) + } else { + disposeJob(jobId) + sessionContext.selectedIndex = CodeWhispererPopupManager.getInstance().findNewSelectedIndex(true, sessionContext.selectedIndex) + } println("exit 6 , jobId: $jobId") return - } +// } } else { updateCodeWhisperer(sessionContext, nextStates, isPopupShowing) } @@ -580,7 +587,12 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { if (caretMovement == CaretMovement.MOVE_BACKWARD) { LOG.debug { "Caret moved backward, discarding all of the recommendations. Request ID: $requestId" } - sendDiscardedUserDecisionEventForAll(jobId, sessionContext, requestContext, responseContext, recommendations, coroutine) + val detailContexts = recommendations.map { + DetailContext("", it, it, true, false, "", getCompletionType(it)) + }.toMutableList() + val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0), jobId) + ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext, coroutine) + disposeDisplaySession(false) return null } @@ -608,7 +620,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { requestId ) val recommendationContext = RecommendationContext(detailContexts, userInputOriginal, userInput, visualPosition, jobId) - return buildInvocationContext(requestContext, responseContext, recommendationContext, coroutine) + ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext, coroutine) + return ongoingRequests[jobId] } private fun updateStates( @@ -647,7 +660,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { CodeWhispererPopupManager.getInstance().changeStatesForShowing(sessionContext, states, recommendationAdded) } - private fun sendDiscardedUserDecisionEventForAll( + private fun buildInvalidInvocationContextForUTD( jobId: Int, sessionContext: SessionContext, requestContext: RequestContext, @@ -661,7 +674,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0), jobId) ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext, coroutine) - CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(sessionContext, false) +// CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(sessionContext, false) } @RequiresEdt From fe64f8a9f0f01c7d9b4f343abea60d16c15a118c Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 12 Sep 2024 17:24:39 -0700 Subject: [PATCH 07/11] actually removing the custom url from the persistent store --- .../aws/toolkits/gradle/BuildScriptUtils.kt | 2 +- gradle.properties | 2 +- .../QSwitchToMarketplaceVersionAction.kt | 17 ++++------------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt index 2e7f17888cd..7c64da3fb70 100644 --- a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt +++ b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt @@ -59,6 +59,6 @@ fun Project.buildMetadata() = } catch(e: Exception) { logger.warn("Could not determine current commit", e) - "beta.20240909" + "beta.20240910" // "unknownCommit" } diff --git a/gradle.properties b/gradle.properties index fc2024b98a9..51419b48f45 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # Toolkit Version -toolkitVersion=99.99.3 +toolkitVersion=99.99.2 # Publish Settings publishToken= diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt index 75c0d0304b2..349cb02b863 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt @@ -19,6 +19,7 @@ import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.project.DumbAware import com.intellij.openapi.updateSettings.impl.PluginDownloader +import com.intellij.openapi.updateSettings.impl.UpdateSettings import software.aws.toolkits.jetbrains.AwsPlugin import software.aws.toolkits.jetbrains.AwsToolkit import software.aws.toolkits.jetbrains.core.plugin.PluginUpdateManager @@ -43,18 +44,8 @@ class QSwitchToMarketplaceVersionAction: } override fun actionPerformed(e: AnActionEvent) { - // remove all custom channel for amazon q and aws core - val qId = PluginId.getId(AwsToolkit.Q_PLUGIN_ID) - val coreId = PluginId.getId(AwsToolkit.CORE_PLUGIN_ID) - val customPlugins = CustomPluginRepositoryService.getInstance().customRepositoryPluginMap - val result = customPlugins.filter { - it.value.any { node -> - listOf(qId, coreId).contains(node.pluginId) - } - } - result.keys.forEach { - customPlugins.remove(it) - } + val url = "https://d244q0w8umigth.cloudfront.net/" + UpdateSettings.getInstance().storedPluginHosts.remove(url) runInEdt { ProgressManager.getInstance().run(object : Task.Backgroundable( @@ -64,7 +55,7 @@ class QSwitchToMarketplaceVersionAction: ) { override fun run(indicator: ProgressIndicator) { installMarketplaceAwsPlugins(PluginId.getId(AwsToolkit.CORE_PLUGIN_ID), indicator) - installMarketplaceAwsPlugins(qId, indicator) + installMarketplaceAwsPlugins(PluginId.getId(AwsToolkit.Q_PLUGIN_ID), indicator) } }) } From abb78133cb85bf429899bb6a176c89fee053883f Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Fri, 13 Sep 2024 16:28:13 -0700 Subject: [PATCH 08/11] clean up for PR --- .../aws/toolkits/gradle/BuildScriptUtils.kt | 1 - .../actions/CodeWhispererAcceptAction.kt | 4 +- .../actions/CodeWhispererActionPromoter.kt | 3 +- .../actions/CodeWhispererForceAcceptAction.kt | 4 +- .../CodeWhispererNavigateNextAction.kt | 4 +- .../CodeWhispererNavigatePrevAction.kt | 5 +- .../QSwitchToMarketplaceVersionAction.kt | 18 +-- .../editor/CodeWhispererEditorManager.kt | 1 - .../editor/CodeWhispererTypedHandler.kt | 2 - .../explorer/QStatusBarLoggedInActionGroup.kt | 1 - .../importadder/CodeWhispererImportAdder.kt | 2 - .../codewhisperer/model/CodeWhispererModel.kt | 23 +-- .../plugin/QBetaPluginManagementPolicy.kt | 24 +-- .../popup/CodeWhispererPopupComponents.kt | 44 ++++-- .../popup/CodeWhispererPopupListener.kt | 4 - .../popup/CodeWhispererPopupManager.kt | 149 +++++------------- .../CodeWhispererEditorActionHandler.kt | 1 - .../CodeWhispererPopupBackspaceHandler.kt | 1 - .../CodeWhispererPopupEnterHandler.kt | 1 - .../handlers/CodeWhispererPopupEscHandler.kt | 2 - .../CodeWhispererPopupLeftArrowHandler.kt | 20 --- .../CodeWhispererPopupRightArrowHandler.kt | 20 --- .../handlers/CodeWhispererPopupTabHandler.kt | 29 ---- .../CodeWhispererPopupTypedHandler.kt | 1 - ...CodeWhispererAcceptButtonActionListener.kt | 1 - .../listeners/CodeWhispererActionListener.kt | 1 - .../CodeWhispererNextButtonActionListener.kt | 1 - .../CodeWhispererPrevButtonActionListener.kt | 1 - .../listeners/CodeWhispererScrollListener.kt | 1 - .../CodeWhispererAutoTriggerService.kt | 21 +-- .../service/CodeWhispererInvocationStatus.kt | 20 +-- .../service/CodeWhispererService.kt | 105 ++++-------- .../settings/CodeWhispererConfigurable.kt | 17 +- .../settings/CodeWhispererSettings.kt | 16 -- ...hispererIntelliSenseAutoTriggerListener.kt | 9 +- .../CodeWhispererProjectStartupActivity.kt | 21 +-- .../status/CodeWhispererStatusBarWidget.kt | 17 +- .../CodeWhispererCodeCoverageTracker.kt | 5 +- .../CodeWhispererTelemetryService.kt | 27 +--- .../CodeWhispererUserModificationTracker.kt | 2 +- ...odeWhispererCodeReferenceActionListener.kt | 5 +- .../util/CodeWhispererConstants.kt | 2 +- .../codewhisperer/util/CodeWhispererUtil.kt | 9 ++ .../CodeWhispererClientAdaptorTest.kt | 4 - .../codewhisperer/CodeWhispererServiceTest.kt | 2 - .../CodeWhispererTelemetryTest.kt | 2 +- .../codewhisperer/CodeWhispererTestBase.kt | 2 +- .../CodeWhispererUserInputTest.kt | 6 +- .../core/plugin/PluginAutoUpdater.kt | 2 +- .../core/plugin/PluginUpdateManager.kt | 4 +- .../resources/MessagesBundle.properties | 6 + 51 files changed, 211 insertions(+), 462 deletions(-) delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt diff --git a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt index 7c64da3fb70..c81e78dfe37 100644 --- a/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt +++ b/buildSrc/src/main/kotlin/software/aws/toolkits/gradle/BuildScriptUtils.kt @@ -60,5 +60,4 @@ fun Project.buildMetadata() = logger.warn("Could not determine current commit", e) "beta.20240910" -// "unknownCommit" } 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 index 3f6c87ea3f7..6b31b048b53 100644 --- 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 @@ -20,8 +20,8 @@ open class CodeWhispererAcceptAction(title: String = message("codewhisperer.inli 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() + e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null && + CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() } override fun actionPerformed(e: AnActionEvent) { 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 index a38c62f7c58..59ee7dcd7c0 100644 --- 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 @@ -26,7 +26,7 @@ class CodeWhispererActionPromoter : ActionPromoter { } else if (b is ChooseItemAction) { return@sortWith 1 } - + if (isCodeWhispererAcceptAction(a)) { return@sortWith -1 } else if (isCodeWhispererAcceptAction(b)) { @@ -49,5 +49,4 @@ class CodeWhispererActionPromoter : ActionPromoter { 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/CodeWhispererForceAcceptAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt index dd67f93e3c6..fbea151598b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt @@ -5,5 +5,5 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.actions import software.aws.toolkits.resources.message -// A same action but different key shortcut and different promoter logic -class CodeWhispererForceAcceptAction(title: String = message("codewhisperer.inline.force.accept")): CodeWhispererAcceptAction(title) +// 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/CodeWhispererNavigateNextAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt index 36bead59177..de7a0cbd382 100644 --- 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 @@ -20,8 +20,8 @@ class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.n 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() + e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null && + CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() } override fun actionPerformed(e: AnActionEvent) { 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 index b8578fb0151..bc3c10a9948 100644 --- 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 @@ -20,9 +20,8 @@ class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.n 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() + e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null && + CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() } override fun actionPerformed(e: AnActionEvent) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt index 349cb02b863..52def16c590 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/QSwitchToMarketplaceVersionAction.kt @@ -4,8 +4,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.actions import com.intellij.icons.AllIcons -import com.intellij.ide.plugins.CustomPluginRepositoryService -import com.intellij.ide.plugins.PluginManagementPolicy import com.intellij.ide.plugins.marketplace.MarketplaceRequests import com.intellij.notification.NotificationAction import com.intellij.openapi.actionSystem.ActionUpdateThread @@ -20,16 +18,15 @@ import com.intellij.openapi.progress.Task import com.intellij.openapi.project.DumbAware import com.intellij.openapi.updateSettings.impl.PluginDownloader import com.intellij.openapi.updateSettings.impl.UpdateSettings -import software.aws.toolkits.jetbrains.AwsPlugin import software.aws.toolkits.jetbrains.AwsToolkit import software.aws.toolkits.jetbrains.core.plugin.PluginUpdateManager import software.aws.toolkits.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.AwsCoreBundle import software.aws.toolkits.resources.message -class QSwitchToMarketplaceVersionAction: +class QSwitchToMarketplaceVersionAction : AnAction( - "Switch Back to Marketplace", + message("codewhisperer.actions.switch_to_marketplace.title"), null, AllIcons.Actions.Refresh ), @@ -44,13 +41,13 @@ class QSwitchToMarketplaceVersionAction: } override fun actionPerformed(e: AnActionEvent) { - val url = "https://d244q0w8umigth.cloudfront.net/" - UpdateSettings.getInstance().storedPluginHosts.remove(url) + UpdateSettings.getInstance().storedPluginHosts.remove(CUSTOM_PLUGIN_URL) + UpdateSettings.getInstance().storedPluginHosts.remove("$CUSTOM_PLUGIN_URL/") runInEdt { ProgressManager.getInstance().run(object : Task.Backgroundable( null, - "Switching to marketplace version", + message("codewhisperer.actions.switch_to_marketplace.progress.title"), true ) { override fun run(indicator: ProgressIndicator) { @@ -59,7 +56,6 @@ class QSwitchToMarketplaceVersionAction: } }) } - } private fun installMarketplaceAwsPlugins(pluginId: PluginId, indicator: ProgressIndicator) { @@ -91,4 +87,8 @@ class QSwitchToMarketplaceVersionAction: } return } + + companion object { + private const val CUSTOM_PLUGIN_URL = "https://d244q0w8umigth.cloudfront.net" + } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt index 585b24c3b50..e8db820857c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt @@ -11,7 +11,6 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService 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 index bcc48644992..0cc5d929501 100644 --- 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 @@ -7,7 +7,6 @@ import com.intellij.codeInsight.editorActions.TypedHandlerDelegate import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile -import kotlinx.coroutines.Job import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.shouldSkipInvokingBasedOnRightContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType @@ -15,7 +14,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer class CodeWhispererTypedHandler : TypedHandlerDelegate() { override fun charTyped(c: Char, project: Project, editor: Editor, psiFiles: PsiFile): Result { -// println("try triggering at character ${c}") if (shouldSkipInvokingBasedOnRightContext(editor)) { return Result.CONTINUE } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt index 93bec84b131..b9362e41ef9 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt @@ -48,7 +48,6 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { override val sendFeedback = CodeWhispererProvideFeedbackAction() override val connectOnGithub = CodeWhispererConnectOnGithubAction() override val documentation = CodeWhispererLearnMoreAction() - // TODO: add the actions to switch between beta/marketplace plugins override val switchToMarketplace = QSwitchToMarketplaceVersionAction() } 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 a83dd75b812..6c3bd789c2e 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 @@ -8,13 +8,11 @@ import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import groovy.lang.Tuple3 import software.amazon.awssdk.services.codewhispererruntime.model.Import import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage -import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext 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 49180b37539..bffe7df31ce 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,7 +3,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.model -import com.intellij.codeInsight.lookup.LookupManager import com.intellij.openapi.Disposable import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.VisualPosition @@ -12,9 +11,6 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.wm.WindowManager -import com.intellij.ui.ComponentUtil -import com.intellij.ui.popup.AbstractPopup import com.intellij.util.concurrency.annotations.RequiresEdt import software.amazon.awssdk.services.codewhispererruntime.model.Completion import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsResponse @@ -28,9 +24,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext -import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.AcceptedSuggestionEntry import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants +import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.setIntelliSensePopupAlpha import software.aws.toolkits.jetbrains.services.codewhisperer.util.CrossFileStrategy import software.aws.toolkits.jetbrains.services.codewhisperer.util.SupplementalContextStrategy import software.aws.toolkits.jetbrains.services.codewhisperer.util.UtgStrategy @@ -143,13 +139,7 @@ data class SessionContext( CodeWhispererService.CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER, object : CodeWhispererIntelliSenseOnHoverListener { override fun onEnter() { - val popupWindow = (popup as AbstractPopup?)?.popupWindow ?: return - WindowManager.getInstance().setAlphaModeRatio(popupWindow, 1f) - - val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) - if (a != null) { - WindowManager.getInstance().setAlphaModeRatio(a, 0f) - } + CodeWhispererPopupManager.getInstance().bringSuggestionInlayToFront(editor, popup, opposite = true) } } ) @@ -157,19 +147,12 @@ data class SessionContext( @RequiresEdt override fun dispose() { - println("disposing the session") - CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll( this, hasAccepted, CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) } ) - - val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) - if (a != null) { - WindowManager.getInstance().setAlphaModeRatio(a, 0f) - } - + setIntelliSensePopupAlpha(editor, 0f) CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(false) if (hasAccepted) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt index c879b9542e7..3a46375e250 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/plugin/QBetaPluginManagementPolicy.kt @@ -7,24 +7,16 @@ import com.intellij.ide.plugins.IdeaPluginDescriptor import com.intellij.ide.plugins.PluginManagementPolicy import com.intellij.ide.plugins.org.PluginManagerFilters -class QBetaPluginManagementPolicy: PluginManagementPolicy { - override fun canEnablePlugin(descriptor: IdeaPluginDescriptor?): Boolean { - return descriptor?.let { PluginManagerFilters.getInstance().allowInstallingPlugin(it) } ?: true - } +// Specifically for "Switch Back to Marketplace" action because the default one doesn't support downgrade +class QBetaPluginManagementPolicy : PluginManagementPolicy { + override fun canEnablePlugin(descriptor: IdeaPluginDescriptor?): Boolean = + descriptor?.let { PluginManagerFilters.getInstance().allowInstallingPlugin(it) } ?: true - override fun canInstallPlugin(descriptor: IdeaPluginDescriptor?): Boolean { - return canEnablePlugin(descriptor) - } + override fun canInstallPlugin(descriptor: IdeaPluginDescriptor?): Boolean = canEnablePlugin(descriptor) - override fun isDowngradeAllowed(localDescriptor: IdeaPluginDescriptor?, remoteDescriptor: IdeaPluginDescriptor?): Boolean { - return true - } + override fun isDowngradeAllowed(localDescriptor: IdeaPluginDescriptor?, remoteDescriptor: IdeaPluginDescriptor?): Boolean = true - override fun isInstallFromDiskAllowed(): Boolean { - return PluginManagerFilters.getInstance().allowInstallFromDisk() - } + override fun isInstallFromDiskAllowed(): Boolean = PluginManagerFilters.getInstance().allowInstallFromDisk() - override fun isUpgradeAllowed(localDescriptor: IdeaPluginDescriptor?, remoteDescriptor: IdeaPluginDescriptor?): Boolean { - return true - } + override fun isUpgradeAllowed(localDescriptor: IdeaPluginDescriptor?, remoteDescriptor: IdeaPluginDescriptor?): Boolean = true } 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 85d083e5eb9..8faf681c9dd 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 @@ -48,22 +48,38 @@ import javax.swing.JPanel class CodeWhispererPopupComponents { val prevButton = createNavigationButton( - message("codewhisperer.popup.button.prev", POPUP_DIM_HEX, run { - val shortcut = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous") - .shortcutSet.shortcuts.first() - val keyStroke = (shortcut as KeyboardShortcut).firstKeyStroke - if (SystemInfo.isMac) MacKeymapUtil.getKeyStrokeText(keyStroke, " ", true) - else KeymapUtil.getKeystrokeText(keyStroke) - }) + message( + "codewhisperer.popup.button.prev", + POPUP_DIM_HEX, + run { + // TODO: Doesn't reflect dynamically if users change but didn't restart IDE + val shortcut = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous") + .shortcutSet.shortcuts.first() + val keyStroke = (shortcut as KeyboardShortcut).firstKeyStroke + if (SystemInfo.isMac) { + MacKeymapUtil.getKeyStrokeText(keyStroke, " ", true) + } else { + KeymapUtil.getKeystrokeText(keyStroke) + } + } + ) ) val nextButton = createNavigationButton( - message("codewhisperer.popup.button.next", POPUP_DIM_HEX, run { - val shortcut = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next") - .shortcutSet.shortcuts.first() - val keyStroke = (shortcut as KeyboardShortcut).firstKeyStroke - if (SystemInfo.isMac) MacKeymapUtil.getKeyStrokeText(keyStroke, " ", true) - else KeymapUtil.getKeystrokeText(keyStroke) - }) + message( + "codewhisperer.popup.button.next", + POPUP_DIM_HEX, + run { + // TODO: Doesn't reflect dynamically if users change but didn't restart IDE + val shortcut = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next") + .shortcutSet.shortcuts.first() + val keyStroke = (shortcut as KeyboardShortcut).firstKeyStroke + if (SystemInfo.isMac) { + MacKeymapUtil.getKeyStrokeText(keyStroke, " ", true) + } else { + KeymapUtil.getKeystrokeText(keyStroke) + } + } + ) ).apply { preferredSize = prevButton.preferredSize } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt index e41ba213228..8d4e288b5f7 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt @@ -5,13 +5,9 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup import com.intellij.openapi.ui.popup.JBPopupListener import com.intellij.openapi.ui.popup.LightweightWindowEvent -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService class CodeWhispererPopupListener : JBPopupListener { - override fun beforeShown(event: LightweightWindowEvent) { - super.beforeShown(event) - } override fun onClosed(event: LightweightWindowEvent) { super.onClosed(event) CodeWhispererService.getInstance().disposeDisplaySession(event.isOk) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index ec5806440ae..444804b0077 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -9,11 +9,11 @@ import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_BACKSPACE import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ENTER import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ESCAPE -import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TAB import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service +import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.actionSystem.EditorActionManager @@ -29,14 +29,12 @@ import com.intellij.openapi.editor.event.EditorMouseEvent import com.intellij.openapi.editor.event.EditorMouseMotionListener import com.intellij.openapi.editor.event.SelectionEvent import com.intellij.openapi.editor.event.SelectionListener -import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.Disposer import com.intellij.openapi.wm.WindowManager import com.intellij.ui.ComponentUtil -import com.intellij.ui.EditorNotificationPanel.ActionHandler import com.intellij.ui.awt.RelativePoint import com.intellij.ui.popup.AbstractPopup import com.intellij.ui.popup.PopupFactoryImpl @@ -63,7 +61,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.Cod import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupBackspaceHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEnterHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEscHandler -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTabHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTypedHandler import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererAcceptButtonActionListener import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererActionListener @@ -92,8 +89,6 @@ class CodeWhispererPopupManager { var shouldListenerCancelPopup: Boolean = true private set -// private var myPopup: JBPopup? = null - init { // Listen for global scheme changes ApplicationManager.getApplication().messageBus.connect().subscribe( @@ -145,15 +140,8 @@ class CodeWhispererPopupManager { typeaheadChange: String, typeaheadAdded: Boolean ) { - CodeWhispererService.getInstance().updateTypeahead(typeaheadChange, typeaheadAdded, sessionContext) - val selectedIndex = findNewSelectedIndex(false, sessionContext.selectedIndex) - if (selectedIndex == -1) { - LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } - CodeWhispererService.getInstance().disposeDisplaySession(false) - return - } - - sessionContext.selectedIndex = selectedIndex + CodeWhispererService.getInstance().updateTypeahead(typeaheadChange, typeaheadAdded) + updateSessionSelectedIndex(sessionContext) sessionContext.isFirstTimeShowingPopup = false ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( @@ -170,6 +158,17 @@ class CodeWhispererPopupManager { return } + updateSessionSelectedIndex(sessionContext) + if (sessionContext.popupDisplayOffset == -1) { + sessionContext.popupDisplayOffset = sessionContext.editor.caretModel.offset + } + + ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( + sessionContext + ) + } + + private fun updateSessionSelectedIndex(sessionContext: SessionContext) { val selectedIndex = findNewSelectedIndex(false, sessionContext.selectedIndex) if (selectedIndex == -1) { LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" } @@ -178,14 +177,6 @@ class CodeWhispererPopupManager { } sessionContext.selectedIndex = selectedIndex -// sessionContext.isFirstTimeShowingPopup = true - if (sessionContext.popupDisplayOffset == -1) { - sessionContext.popupDisplayOffset = sessionContext.editor.caretModel.offset - } - - ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged( - sessionContext - ) } fun updatePopupPanel(sessionContext: SessionContext?) { @@ -213,20 +204,11 @@ class CodeWhispererPopupManager { // emit any events. // 4. User navigating through the completions or typing as the completion shows. We should not update the latency // end time and should not emit any events in this case. - if (!isRecommendationAdded) { - showPopup(sessionContext) - if (!isScrolling) { - sessionContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime() - sessionContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime() - } - } - if (isScrolling || - CodeWhispererInvocationStatus.getInstance().hasExistingInvocation() || - !sessionContext.isFirstTimeShowingPopup - ) { - return - } -// CodeWhispererTelemetryService.getInstance().sendClientComponentLatencyEvent(sessionContext) + if (isRecommendationAdded) return + showPopup(sessionContext) + if (isScrolling) return + sessionContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime() + sessionContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime() } fun dontClosePopupAndRun(runnable: () -> Unit) { @@ -238,24 +220,13 @@ class CodeWhispererPopupManager { } } -// fun resetSession() { -// sessionContext?.let { -// Disposer.dispose(it) -// } -// sessionContext = null -// } - - fun showPopup( - sessionContext: SessionContext, - force: Boolean = false, - ) { + fun showPopup(sessionContext: SessionContext, force: Boolean = false) { val p = sessionContext.editor.offsetToXY(sessionContext.popupDisplayOffset) val popup: JBPopup? if (sessionContext.popup == null) { popup = initPopup() sessionContext.popup = popup CodeWhispererInvocationStatus.getInstance().setPopupStartTimestamp() - println("set suggestion display time starting now: ${System.currentTimeMillis()}") initPopupListener(sessionContext, popup) } else { popup = sessionContext.popup @@ -272,29 +243,12 @@ class CodeWhispererPopupManager { CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(true) - // Check if the current editor still has focus. If not, don't show the popup. - val isSameEditorAsTrigger = if (!AppMode.isRemoteDevHost()) { - editor.contentComponent.isFocusOwner - } else { - FileEditorManager.getInstance(sessionContext.project).selectedTextEditorWithRemotes.firstOrNull() == editor - } -// if (!isSameEditorAsTrigger) { -// LOG.debug { "Current editor no longer has focus, not showing the popup" } -// CodeWhispererService.getInstance().disposeDisplaySession(false) -// return -// } - if (!editorRect.contains(popupRect)) { // popup location above first line don't work, so don't show the popup shouldHidePopup = true - } else { -// LOG.debug { -// "Show popup above the first line of recommendation. " + -// "Editor position: $editorRect, popup position: $popupRect" -// } - shouldHidePopup } + // popup to always display above the current editing line val popupLocation = Point(p.x, yAboveFirstLine) val relativePopupLocationToEditor = RelativePoint(editor.contentComponent, popupLocation) @@ -312,6 +266,7 @@ class CodeWhispererPopupManager { popup.show(relativePopupLocationToEditor) } } else { + // TODO: Fix in remote case the popup should display above the current editing line // TODO: For now, the popup will always display below the suggestions, without checking // if the location the popup is about to show at stays in the editor window or not, due to // the limitation of BackendBeAbstractPopup @@ -326,33 +281,20 @@ class CodeWhispererPopupManager { editor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, popupPositionForRemote) popup.showInBestPositionFor(editor) } -// val perceivedLatency = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged() -// CodeWhispererTelemetryService.getInstance().sendPerceivedLatencyEvent( -// detail.requestId, -// states.requestContext, -// states.responseContext, -// perceivedLatency -// ) } - val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) + bringSuggestionInlayToFront(editor, popup, !force) + } - if (a != null) { - val alpha = if (force) 0.8f else 0f - WindowManager.getInstance().setAlphaModeRatio(a, alpha) - } + fun bringSuggestionInlayToFront(editor: Editor, popup: JBPopup?, opposite: Boolean = false) { + val qInlinePopupAlpha = if (opposite) 1f else 0.1f + val intelliSensePopupAlpha = if (opposite) 0f else 0.8f - // popup.popupWindow is null in remote host - if (!AppMode.isRemoteDevHost()) { - if (force) { - WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 0.1f) - } else { - if (shouldHidePopup) { - popup.popupWindow?.let { - WindowManager.getInstance().setAlphaModeRatio(it, 1f) - } - } - } + (popup as AbstractPopup?)?.popupWindow?.let { + WindowManager.getInstance().setAlphaModeRatio(it, qInlinePopupAlpha) + } + ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component)?.let { + WindowManager.getInstance().setAlphaModeRatio(it, intelliSensePopupAlpha) } } @@ -360,8 +302,6 @@ class CodeWhispererPopupManager { .createComponentPopupBuilder(popupComponents.panel, null) .setAlpha(0.1F) .setCancelOnClickOutside(true) -// .setCancelOnOtherWindowOpen(true) -// .setCancelKeyEnabled(true) .setCancelOnWindowDeactivation(true) .createPopup() @@ -381,7 +321,6 @@ class CodeWhispererPopupManager { val listener = CodeWhispererPopupListener() popup.addListener(listener) Disposer.register(popup) { - println("listener is removed") popup.removeListener(listener) } } @@ -443,6 +382,8 @@ class CodeWhispererPopupManager { private fun setPopupActionHandlers(sessionContext: SessionContext) { val actionManager = EditorActionManager.getInstance() + + // TODO: find a better way to pass in the local sessionContext for the handler to know the session state val prevAction = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous") as CodeWhispererNavigatePrevAction prevAction.sessionContext = sessionContext val nextAction = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next") as CodeWhispererNavigateNextAction @@ -451,6 +392,7 @@ class CodeWhispererPopupManager { acceptAction.sessionContext = sessionContext val forceAcceptAction = ActionManager.getInstance().getAction("codewhisperer.inline.force.accept") as CodeWhispererForceAcceptAction forceAcceptAction.sessionContext = sessionContext + setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, sessionContext), sessionContext) setPopupActionHandler(ACTION_EDITOR_ESCAPE, CodeWhispererPopupEscHandler(sessionContext), sessionContext) setPopupActionHandler( @@ -495,7 +437,8 @@ class CodeWhispererPopupManager { // TODO: handle bulk delete (delete word) case if (editor.document == event.document && editor.caretModel.offset == event.offset && - event.newLength > event.oldLength) { + event.newLength > event.oldLength + ) { dontClosePopupAndRun { super.documentChanged(event) editor.caretModel.moveCaretRelatively(event.newLength, 0, false, false, true) @@ -540,23 +483,8 @@ class CodeWhispererPopupManager { Disposer.register(sessionContext) { window?.removeComponentListener(windowListener) } } - val suggestionHoverEnterListener: EditorMouseMotionListener = object : EditorMouseMotionListener { override fun mouseMoved(e: EditorMouseEvent) { - e.mouseEvent.component -// println("current mouse offset : ${e.offset}, point: ${e.mouseEvent.point}") - val startOffset = editor.offsetToXY(editor.caretModel.offset) -// println("caret x y: ${startOffset}") - val point = e.mouseEvent.point - val right = startOffset.x + (e.inlay?.widthInPixels ?: 0) - val a = ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component) - val aPoint = - if (a != null) { - RelativePoint(Point(a.bounds.x, a.bounds.y)).getPoint(editor.contentComponent) - } else { - Point(0, 0) - } - if (e.inlay != null) { showPopup(sessionContext, force = true) } else { @@ -571,7 +499,7 @@ class CodeWhispererPopupManager { } private fun updateSelectedRecommendationLabelText(validSelectedIndex: Int, validCount: Int) { - if (CodeWhispererInvocationStatus.getInstance().hasExistingInvocation()) { + if (CodeWhispererInvocationStatus.getInstance().hasExistingServiceInvocation()) { popupComponents.recommendationInfoLabel.text = message("codewhisperer.popup.pagination_info") LOG.debug { "Pagination in progress. Current total: $validCount" } } else { @@ -676,7 +604,6 @@ class CodeWhispererPopupManager { if (isValidRecommendation(triple)) { curr++ } - } return -1 } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt index 90b0cbf244f..a2cea3c836f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt @@ -4,7 +4,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers import com.intellij.openapi.editor.actionSystem.EditorActionHandler -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext abstract class CodeWhispererEditorActionHandler(val sessionContext: SessionContext) : EditorActionHandler() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt index 1d3fe063c49..cdd510a31eb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.application.ApplicationManager 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.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt index e6c0156d756..836185a23b5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt @@ -9,7 +9,6 @@ import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.EditorActionHandler import com.intellij.openapi.util.TextRange -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt index eaec22d90d7..634f76e885a 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt @@ -4,11 +4,9 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService class CodeWhispererPopupEscHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt deleted file mode 100644 index 73854ef1cc5..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt +++ /dev/null @@ -1,20 +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.popup.handlers - -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.Caret -import com.intellij.openapi.editor.Editor -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager - -class CodeWhispererPopupLeftArrowHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { - override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - ApplicationManager.getApplication().messageBus.syncPublisher( - CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigatePrevious(sessionContext) - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt deleted file mode 100644 index 0fdd1a27e61..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt +++ /dev/null @@ -1,20 +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.popup.handlers - -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.Caret -import com.intellij.openapi.editor.Editor -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager - -class CodeWhispererPopupRightArrowHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { - override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - ApplicationManager.getApplication().messageBus.syncPublisher( - CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).navigateNext(sessionContext) - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt deleted file mode 100644 index c361ee60aaa..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt +++ /dev/null @@ -1,29 +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.popup.handlers - -import com.intellij.codeInsight.lookup.Lookup -import com.intellij.codeInsight.lookup.LookupFocusDegree -import com.intellij.codeInsight.lookup.LookupManager -import com.intellij.codeInsight.lookup.impl.LookupImpl -import com.intellij.codeInsight.template.impl.editorActions.ExpandLiveTemplateCustomAction -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.Caret -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.actionSystem.EditorAction -import com.intellij.openapi.editor.actionSystem.EditorActionHandler -import com.intellij.openapi.editor.actionSystem.EditorActionManager -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext -import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext -import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager - -class CodeWhispererPopupTabHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) { - override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - ApplicationManager.getApplication().messageBus.syncPublisher( - CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED - ).beforeAccept(sessionContext) - } -} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt index 47b53e99046..501409dc4ce 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt @@ -7,7 +7,6 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.TypedActionHandler -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt index 1f528c8ba5b..6a7a03e132e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt @@ -4,7 +4,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import com.intellij.openapi.application.ApplicationManager -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import java.awt.event.ActionEvent diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt index e6f6838b2a7..23f0975e66e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt @@ -3,7 +3,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import java.awt.event.ActionListener diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt index 0a15257eaae..d11f219a330 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt @@ -4,7 +4,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import com.intellij.openapi.application.ApplicationManager -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import java.awt.event.ActionEvent diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt index 04500415c1f..273a40a8e16 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt @@ -4,7 +4,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import com.intellij.openapi.application.ApplicationManager -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import java.awt.event.ActionEvent diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt index a4289e6031c..4ebea981628 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt @@ -6,7 +6,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.event.VisibleAreaEvent import com.intellij.openapi.editor.event.VisibleAreaListener -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus 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 1f5c09d0ce1..d1dcbcdf1cd 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 @@ -99,20 +99,21 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa val coroutineScope = applicationCoroutineScope() return run { - coroutineScope.launch(EDT) { - while (!hasEnoughDelaySinceLastTrigger()) { - if (!isActive) return@launch - delay(CodeWhispererConstants.IDLE_TIME_CHECK_INTERVAL) - } - - performAutomatedTriggerAction(editor, triggerType, latencyContext) - }.also { - lastTrigger = it + coroutineScope.launch(EDT) { + while (!hasEnoughDelaySinceLastTrigger()) { + if (!isActive) return@launch + delay(CodeWhispererConstants.IDLE_TIME_CHECK_INTERVAL) } + + performAutomatedTriggerAction(editor, triggerType, latencyContext) + }.also { + lastTrigger = it } + } } - private fun hasEnoughDelaySinceLastTrigger(): Boolean = lastCharTypedTime == null || lastCharTypedTime?.plusMillis(INVOCATION_DELAY)?.isBefore(Instant.now()) == true + private fun hasEnoughDelaySinceLastTrigger(): Boolean = + lastCharTypedTime == null || lastCharTypedTime?.plusMillis(INVOCATION_DELAY)?.isBefore(Instant.now()) == true private fun scheduleReset() { if (!alarm.isDisposed) { 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 b4abd7a9ce0..4c58139b354 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 @@ -16,7 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean @Service class CodeWhispererInvocationStatus { - private val isInvokingCodeWhisperer: AtomicBoolean = AtomicBoolean(false) + private val isInvokingService: AtomicBoolean = AtomicBoolean(false) private var invokingSessionId: String? = null var timeAtLastDocumentChanged: Instant = Instant.now() private set @@ -25,20 +25,15 @@ class CodeWhispererInvocationStatus { var popupStartTimestamp: Instant? = null private set - fun checkExistingInvocationAndSet(): Boolean = - if (isInvokingCodeWhisperer.getAndSet(true)) { - LOG.debug { "Have existing CodeWhisperer invocation, sessionId: $invokingSessionId" } - true - } else { - ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(true) - LOG.debug { "Starting CodeWhisperer invocation" } - false - } + fun startInvocation() { + isInvokingService.set(true) + LOG.debug { "Starting CodeWhisperer invocation" } + } - fun hasExistingInvocation(): Boolean = isInvokingCodeWhisperer.get() + fun hasExistingServiceInvocation(): Boolean = isInvokingService.get() fun finishInvocation() { - if (isInvokingCodeWhisperer.compareAndSet(true, false)) { + if (isInvokingService.compareAndSet(true, false)) { ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(false) LOG.debug { "Ending CodeWhisperer invocation" } invokingSessionId = null @@ -68,7 +63,6 @@ class CodeWhispererInvocationStatus { fun setDisplaySessionActive(value: Boolean) { isPopupActive = value -// println("set popup active to $value") } fun setInvocationStart() { 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 50fbdb24303..cdf068eb015 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 @@ -154,7 +154,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val currentJobId = jobId++ val requestContext = try { - getRequestContext(currentJobId, triggerTypeInfo, editor, project, psiFile) + getRequestContext(triggerTypeInfo, editor, project, psiFile) } catch (e: Exception) { LOG.debug { e.message.toString() } CodeWhispererTelemetryService.getInstance().sendFailedServiceInvocationEvent(project, e::class.simpleName) @@ -191,17 +191,12 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { } } - val invocationStatus = CodeWhispererInvocationStatus.getInstance() - if (invocationStatus.checkExistingInvocationAndSet() && false) { - return - } + CodeWhispererInvocationStatus.getInstance().startInvocation() invokeCodeWhispererInBackground(requestContext, currentJobId, latencyContext) } internal fun invokeCodeWhispererInBackground(requestContext: RequestContext, currentJobId: Int, latencyContext: LatencyContext): Job? { - // a placeholder value to indicate a session has started -// ongoingRequests[currentJobId] = null ongoingRequestsContext[currentJobId] = requestContext val sessionContext = sessionContext ?: SessionContext(requestContext.project, requestContext.editor, latencyContext = latencyContext) @@ -223,7 +218,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { var lastRecommendationIndex = -1 val line = requestContext.fileContextInfo.caretContext.leftContextOnCurrentLine - println("triggering, last char ${line}, jobId: $currentJobId") + println("triggering, last char $line, jobId: $currentJobId") val job = coroutineScope.launch { try { val responseIterable = CodeWhispererClientAdaptor.getInstance(requestContext.project).generateCompletionsPaginator( @@ -321,23 +316,17 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { coroutineScope, currentJobId ) -// if (!ongoingRequests.contains(currentJobId)) { -// LOG.debug { "Skipping sending remaining requests on jobId removed" } -// println("exit 1 , jobId: $currentJobId") -// break -// } } } } if (!isActive) { // If job is cancelled before we do another request, don't bother making // another API call to save resources - LOG.debug { "Skipping sending remaining requests on CodeWhisperer session exit" } - println("exit 2 , jobId: $currentJobId") - break + LOG.debug { "Skipping sending remaining requests on inactive CodeWhisperer session exit" } + return@launch } - if (requestCount >= 1) { - println("We only need one pagination request at the moment") + if (requestCount >= PAGINATION_REQUEST_COUNT_ALLOWED) { + LOG.debug { "Only $PAGINATION_REQUEST_COUNT_ALLOWED request per pagination session for now" } CodeWhispererInvocationStatus.getInstance().finishInvocation() break } @@ -483,15 +472,13 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // This extra check is needed because there's a time between when we get the response and // when we enter the EDT. if (!coroutine.isActive || sessionContext.isDisposed()) { - LOG.debug { "Stop showing CodeWhisperer recommendations on CodeWhisperer session exit. RequestId: $requestId" } - println("exit 3 , jobId: $jobId") + LOG.debug { "Stop showing CodeWhisperer recommendations on CodeWhisperer session exit. RequestId: $requestId, jobId: $jobId" } return } if (requestContext.editor.isDisposed) { - LOG.debug { "Stop showing all CodeWhisperer recommendations since editor is disposed. RequestId: $requestId" } + LOG.debug { "Stop showing all CodeWhisperer recommendations since editor is disposed. RequestId: $requestId, jobId: $jobId" } disposeDisplaySession(false) - println("exit 4 , jobId: $jobId") return } @@ -501,38 +488,33 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { requestContext.editor, requestContext.caretPosition ) - val isPopupShowing = checkRecommendationsValidity(jobId, currStates, false) + val isPopupShowing = checkRecommendationsValidity(currStates, false) val nextStates: InvocationContext? if (currStates == null) { // first response for the jobId - nextStates = initStates(jobId, sessionContext, requestContext, responseContext, response, caretMovement, coroutine) + nextStates = initStates(jobId, requestContext, responseContext, response, caretMovement, coroutine) // receiving a null state means caret has moved backward, // so we are going to cancel the current job if (nextStates == null) { - LOG.debug { "Exiting CodeWhisperer session. RequestId: $requestId" } -// buildInvalidInvocationContextForUTD(jobId, sessionContext, requestContext, responseContext, response.completions(), coroutine) -// disposeDisplaySession(false) - println("exit 5 , jobId: $jobId") return } } else { // subsequent responses for the jobId nextStates = updateStates(currStates, response) } - println("adding ${response.completions().size} completions from job ${jobId}") -// ongoingRequests[jobId] = nextStates + LOG.debug { "Adding ${response.completions().size} completions to the session. RequestId: $requestId, jobId: $jobId" } // TODO: may have bug when it's a mix of auto-trigger + manual trigger - val hasAtLeastOneValid = checkRecommendationsValidity(jobId, nextStates, true) + val hasAtLeastOneValid = checkRecommendationsValidity(nextStates, true) val allSuggestions = ongoingRequests.values.filterNotNull().flatMap { it.recommendationContext.details } val valid = allSuggestions.filter { !it.isDiscarded }.size - println("total: $valid valid, ${allSuggestions.size - valid} discarded") + LOG.debug { "Suggestions status: valid: $valid, discarded: ${allSuggestions.size - valid}" } // If there are no recommendations at all in this session, we need to manually send the user decision event here // since it won't be sent automatically later // TODO: may have bug; visit later - if (nextStates.recommendationContext.details.isEmpty() && response.nextToken().isEmpty()) { + if (nextStates.recommendationContext.details.isEmpty()) { LOG.debug { "Received just an empty list from this session, requestId: $requestId" } CodeWhispererTelemetryService.getInstance().sendUserDecisionEvent( requestContext, @@ -552,29 +534,21 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { ) } if (!hasAtLeastOneValid) { -// if (response.nextToken().isEmpty()) { - LOG.debug { "None of the recommendations are valid, exiting current CodeWhisperer pagination session" } - // TODO: decide whether or not to dispose what here - // only key here, after disposing this, the whole session will also end - if (ongoingRequests.keys.size == 1) { -// buildInvalidInvocationContextForUTD(jobId, sessionContext, requestContext, responseContext, response.completions(), coroutine) - disposeDisplaySession(false) - } else { - disposeJob(jobId) - sessionContext.selectedIndex = CodeWhispererPopupManager.getInstance().findNewSelectedIndex(true, sessionContext.selectedIndex) - } - println("exit 6 , jobId: $jobId") - return -// } + LOG.debug { "None of the recommendations are valid, exiting current CodeWhisperer pagination session" } + // If there's only one ongoing request, after disposing this, the entire session will also end + if (ongoingRequests.keys.size == 1) { + disposeDisplaySession(false) + } else { + disposeJob(jobId) + sessionContext.selectedIndex = CodeWhispererPopupManager.getInstance().findNewSelectedIndex(true, sessionContext.selectedIndex) + } } else { updateCodeWhisperer(sessionContext, nextStates, isPopupShowing) } -// return nextStates } private fun initStates( jobId: Int, - sessionContext: SessionContext, requestContext: RequestContext, responseContext: ResponseContext, response: GenerateCompletionsResponse, @@ -586,7 +560,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val visualPosition = requestContext.editor.caretModel.visualPosition if (caretMovement == CaretMovement.MOVE_BACKWARD) { - LOG.debug { "Caret moved backward, discarding all of the recommendations. Request ID: $requestId" } + LOG.debug { "Caret moved backward, discarding all of the recommendations and exiting the session. Request ID: $requestId, jobId: $jobId" } val detailContexts = recommendations.map { DetailContext("", it, it, true, false, "", getCompletionType(it)) }.toMutableList() @@ -640,7 +614,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { return states } - private fun checkRecommendationsValidity(jobId: Int, states: InvocationContext?, showHint: Boolean): Boolean { + private fun checkRecommendationsValidity(states: InvocationContext?, showHint: Boolean): Boolean { if (states == null) return false val details = states.recommendationContext.details @@ -660,23 +634,6 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { CodeWhispererPopupManager.getInstance().changeStatesForShowing(sessionContext, states, recommendationAdded) } - private fun buildInvalidInvocationContextForUTD( - jobId: Int, - sessionContext: SessionContext, - requestContext: RequestContext, - responseContext: ResponseContext, - recommendations: List, - coroutine: CoroutineScope - ) { - val detailContexts = recommendations.map { - DetailContext("", it, it, true, false, "", getCompletionType(it)) - }.toMutableList() - val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0), jobId) - ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext, coroutine) - -// CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(sessionContext, false) - } - @RequiresEdt private fun disposeJob(jobId: Int) { ongoingRequests[jobId]?.let { Disposer.dispose(it) } @@ -710,16 +667,16 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { fun getAllPaginationSessions() = ongoingRequests - fun updateTypeahead(typeaheadChange: String, typeaheadAdded: Boolean, sessionContext: SessionContext) { + fun updateTypeahead(typeaheadChange: String, typeaheadAdded: Boolean) { val recommendations = ongoingRequests.values.filterNotNull() recommendations.forEach { - val typeaheadOriginal = + val newTypeahead = if (typeaheadAdded) { it.recommendationContext.typeahead + typeaheadChange } else { if (typeaheadChange.length > it.recommendationContext.typeahead.length) { + LOG.debug { "Typeahead change is longer than the current typeahead, exiting the session" } disposeDisplaySession(false) - println("exit 7, ") return } it.recommendationContext.typeahead.substring( @@ -727,12 +684,11 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { it.recommendationContext.typeahead.length - typeaheadChange.length ) } - it.recommendationContext.typeahead = typeaheadOriginal + it.recommendationContext.typeahead = newTypeahead } } fun getRequestContext( - jobId: Int, triggerTypeInfo: TriggerTypeInfo, editor: Editor, project: Project, @@ -796,7 +752,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { // state update. val states = InvocationContext(requestContext, responseContext, recommendationContext) Disposer.register(states) { - coroutine.cancel(CancellationException("hahahah")) + coroutine.cancel(CancellationException("Cancelling the current coroutine when the pagination session context is disposed")) } return states } @@ -858,6 +814,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { companion object { private val LOG = getLogger() private const val MAX_REFRESH_ATTEMPT = 3 + private const val PAGINATION_REQUEST_COUNT_ALLOWED = 1 val CODEWHISPERER_CODE_COMPLETION_PERFORMED: Topic = Topic.create( "CodeWhisperer code completion service invoked", 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 c2d62feae60..8e10475fe0a 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 @@ -10,11 +10,8 @@ import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.SearchableConfigurable import com.intellij.openapi.options.ex.Settings import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.popup.ListSeparator -import com.intellij.ui.GroupedComboBoxRenderer import com.intellij.ui.components.ActionLink import com.intellij.ui.dsl.builder.bindIntText -import com.intellij.ui.dsl.builder.bindItem import com.intellij.ui.dsl.builder.bindSelected import com.intellij.ui.dsl.builder.panel import com.intellij.util.concurrency.EdtExecutorService @@ -96,14 +93,16 @@ class CodeWhispererConfigurable(private val project: Project) : }.comment(message("aws.settings.codewhisperer.automatic_import_adder.tooltip")) } - row() { + row { link("Configure inline suggestion keybindings") { e -> val settings = DataManager.getInstance().getDataContext(e.source as ActionLink).getData(Settings.KEY) ?: return@link val configurable: Configurable = settings.find("preferences.keymap") ?: return@link - // workaround for sometimes the string is not input there - settings.select(configurable, "inline suggestion") + + settings.select(configurable, Q_INLINE_KEYBINDING_SEARCH_TEXT) + + // workaround for certain cases for sometimes the string is not input there EdtExecutorService.getScheduledExecutorInstance().schedule({ - settings.select(configurable, "inline suggestion") + settings.select(configurable, Q_INLINE_KEYBINDING_SEARCH_TEXT) }, 500, TimeUnit.MILLISECONDS) } } @@ -203,4 +202,8 @@ class CodeWhispererConfigurable(private val project: Project) : } } } + + companion object { + private const val Q_INLINE_KEYBINDING_SEARCH_TEXT = "inline suggestion" + } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt index c45f38e8785..24105ace86f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererSettings.kt @@ -94,22 +94,6 @@ class CodeWhispererSettings : PersistentStateComponent if (state == null) return@forEach val decisions = mutableListOf() @@ -417,14 +410,12 @@ class CodeWhispererTelemetryService { decisions.add(suggestionState) } - print("jobId: $jobId, userDecisions: [") - decisions.forEach { print("$it, ") } - println("]") + LOG.debug { "jobId: $jobId, userDecisions: [${decisions.joinToString(", ")}]" } - with(aggregateUserDecision(decisions, hasUserAccepted)) { + with(aggregateUserDecision(decisions)) { // the order of the following matters // step 1, send out current decision - println("jobId: $jobId, userTriggerDecision: $this") + LOG.debug { "jobId: $jobId, userTriggerDecision: $this" } previousUserTriggerDecisionTimestamp = Instant.now() val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo() @@ -459,8 +450,6 @@ class CodeWhispererTelemetryService { } } } - - } /** @@ -468,10 +457,12 @@ class CodeWhispererTelemetryService { * - Accept if there is an Accept * - Reject if there is a Reject * - Empty if all decisions are Empty + * - Ignore if at least one suggestion is seen and there's an accept for another trigger in the same display session + * - Unseen if the whole trigger is not seen * - Record the accepted suggestion index * - Discard otherwise */ - fun aggregateUserDecision(decisions: List, hasUserAccepted: Boolean): CodewhispererSuggestionState { + fun aggregateUserDecision(decisions: List): CodewhispererSuggestionState { var isEmpty = true var isUnseen = true var isDiscard = true @@ -503,10 +494,6 @@ class CodeWhispererTelemetryService { CodewhispererSuggestionState.Unseen } else { CodewhispererSuggestionState.Ignore -// if (hasUserAccepted) { -// -// } -// CodewhispererSuggestionState.Discard } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererUserModificationTracker.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererUserModificationTracker.kt index 95093246fce..1c0174ec9a1 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererUserModificationTracker.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererUserModificationTracker.kt @@ -173,7 +173,7 @@ class CodeWhispererUserModificationTracker(private val project: Project) : Dispo } catch (e: Exception) { sendModificationTelemetry(acceptedSuggestion, null) // temp remove event sent as further discussion needed for metric calculation -// sendUserModificationTelemetryToServiceAPI(acceptedSuggestion, 1.0) + // sendUserModificationTelemetryToServiceAPI(acceptedSuggestion, 1.0) } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt index daae72c39bb..1775631587b 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt @@ -11,9 +11,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispere class CodeWhispererCodeReferenceActionListener : CodeWhispererUserActionListener { override fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) { - val (project, editor) = states.requestContext - val manager = CodeWhispererCodeReferenceManager.getInstance(project) + val manager = CodeWhispererCodeReferenceManager.getInstance(sessionContext.project) manager.insertCodeReference(states, previews, sessionContext.selectedIndex) - manager.addListeners(editor) + manager.addListeners(sessionContext.editor) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt index 988d0ed6e2b..519cd599bed 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt @@ -36,7 +36,7 @@ object CodeWhispererConstants { val AWSTemplateKeyWordsRegex = Regex("(AWSTemplateFormatVersion|Resources|AWS::|Description)") val AWSTemplateCaseInsensitiveKeyWordsRegex = Regex("(cloudformation|cfn|template|description)") - // TODO: this is currently set to 0 to trigger with 0 delays and rely on ML trigger to determine which characters to trigger at + // TODO: this is currently set to 0 to trigger with 0ms delay and rely on ML trigger to determine which characters to trigger at // We will monitor service side resource utilization and throttling. const val INVOCATION_DELAY: Long = 0 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 7f48c50142a..ada04ea915d 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 @@ -3,6 +3,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.util +import com.intellij.codeInsight.lookup.LookupManager import com.intellij.ide.BrowserUtil import com.intellij.notification.NotificationAction import com.intellij.openapi.application.ApplicationManager @@ -12,6 +13,8 @@ import com.intellij.openapi.editor.impl.EditorImpl import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.wm.WindowManager +import com.intellij.ui.ComponentUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -307,6 +310,12 @@ object CodeWhispererUtil { } } } + + fun setIntelliSensePopupAlpha(editor: Editor, alpha: Float) { + ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component)?.let { + WindowManager.getInstance().setAlphaModeRatio(it, alpha) + } + } } enum class CaretMovement { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt index e76a34d2e45..5d7261dc4d4 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt @@ -82,8 +82,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator 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.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants @@ -92,7 +90,6 @@ import software.aws.toolkits.jetbrains.settings.AwsSettings import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererCompletionType import software.aws.toolkits.telemetry.CodewhispererSuggestionState -import software.aws.toolkits.telemetry.CodewhispererTriggerType class CodeWhispererClientAdaptorTest { val projectRule = JavaCodeInsightTestFixtureRule() @@ -248,7 +245,6 @@ class CodeWhispererClientAdaptorTest { projectRule.fixture.openFileInEditor(file.virtualFile) } val requestContext = CodeWhispererService.getInstance().getRequestContext( - TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()), projectRule.fixture.editor, projectRule.project, file, diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt index ed633d91c01..c7fdae43077 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt @@ -128,7 +128,6 @@ class CodeWhispererServiceTest { projectRule.project.replaceService(FileContextProvider::class.java, mockFileContextProvider, disposableRule.disposable) val actual = sut.getRequestContext( - TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()), projectRule.fixture.editor, projectRule.project, file, @@ -154,7 +153,6 @@ class CodeWhispererServiceTest { } val actual = sut.getRequestContext( - TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()), projectRule.fixture.editor, projectRule.project, file, diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt index 6a30ae69e56..650ba19614e 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt @@ -107,7 +107,7 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { @Test fun `test pre-setup failure will send service invocation event with failed status`() { val codewhispererServiceSpy = spy(codewhispererService) { - onGeneric { getRequestContext(any(), any(), any(), any(), any()) } + onGeneric { getRequestContext(any(), any(), any(), any()) } .doAnswer { throw Exception() } } ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererServiceSpy, disposableRule.disposable) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt index 50e684132bd..5f980006638 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt @@ -184,7 +184,7 @@ open class CodeWhispererTestBase { runInEdtAndWait { projectRule.fixture.performEditorAction(codeWhispererRecommendationActionId) } - while (CodeWhispererInvocationStatus.getInstance().hasExistingInvocation()) { + while (CodeWhispererInvocationStatus.getInstance().hasExistingServiceInvocation()) { Thread.sleep(10) } } 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 8c4c562481e..3f647102276 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 @@ -15,7 +15,6 @@ import org.mockito.kotlin.doAnswer import org.mockito.kotlin.spy import org.mockito.kotlin.stub import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse -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.CodeWhispererService @@ -105,15 +104,13 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { val editorCaptor = argumentCaptor() val projectCaptor = argumentCaptor() val psiFileCaptor = argumentCaptor() - val latencyContextCaptor = argumentCaptor() codewhispererServiceSpy.stub { onGeneric { getRequestContext( triggerTypeCaptor.capture(), editorCaptor.capture(), projectCaptor.capture(), - psiFileCaptor.capture(), - latencyContextCaptor.capture() + psiFileCaptor.capture() ) }.doAnswer { val requestContext = codewhispererServiceSpy.getRequestContext( @@ -121,7 +118,6 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { editorCaptor.firstValue, projectCaptor.firstValue, psiFileCaptor.firstValue, - latencyContextCaptor.firstValue ) projectRule.fixture.type(userInput) requestContext diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt index 3abb32eb60b..94ab3ec1a95 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginAutoUpdater.kt @@ -13,7 +13,7 @@ class PluginAutoUpdater : ProjectActivity { override suspend fun execute(project: Project) { // We want the auto-update feature to be triggered only once per running application - if (!autoUpdateRunOnce.getAndSet(true) || PluginUpdateManager.getInstance().isBeta()) { + if (!autoUpdateRunOnce.getAndSet(true)) { PluginUpdateManager.getInstance().scheduleAutoUpdate() if (!AwsSettings.getInstance().isAutoUpdateFeatureNotificationShownOnce) { PluginUpdateManager.getInstance().notifyAutoUpdateFeature(project) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt index dd15bcbd017..9e25cfdf542 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/plugin/PluginUpdateManager.kt @@ -45,9 +45,9 @@ class PluginUpdateManager : Disposable { scheduleUpdateTask() val enabled = AwsSettings.getInstance().isAutoUpdateEnabled - LOG.debug { "AWS plugins checking for new updates. Auto update enabled: $enabled" } + LOG.debug { "AWS plugins checking for new updates. Auto update enabled: $enabled, isBeta: ${isBeta()}" } - if (!enabled) return + if (!enabled && !isBeta()) return runInEdt { ProgressManager.getInstance().run(object : Task.Backgroundable( diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 8f4ac51c8bc..bb1f167897b 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -759,6 +759,8 @@ codemodernizer.toolwindow.transformation.progress.running_time=Running time: {0} codewhisperer.actions.connect_github.title=Connect with Us on GitHub codewhisperer.actions.open_settings.title=Open Settings codewhisperer.actions.send_feedback.title=Send Feedback +codewhisperer.actions.switch_to_marketplace.progress.title=Switching to marketplace version +codewhisperer.actions.switch_to_marketplace.title=Switch Back to Marketplace codewhisperer.actions.view_documentation.title=View Documentation codewhisperer.codescan.apply_fix_button_label=Apply fix codewhisperer.codescan.apply_fix_button_tooltip=Apply suggested fix @@ -1501,6 +1503,10 @@ loading_resource.still_loading=Resources are still loading plugin.incompatible.fix=Disable incompatible plugins and restart IDE plugin.incompatible.message=The plugin versions for Amazon Q, AWS Toolkit, and AWS Toolkit Core must match or conflicts may occur. plugin.incompatible.title=AWS Plugin Incompatibility +q.beta.notification.end.title="Amazon Q current beta period ended" +q.beta.notification.end.message="The current beta period has ended on {0}, please switch to the marketplace version to continue using Amazon Q." +q.beta.notification.welcome.title="Welcome to Amazon Q Plugin Beta" +q.beta.notification.welcome.message="Thank you for participating in Amazon Q beta plugin testing. Plugin auto-update is always turned on to ensure the best beta experience." q.connection.disconnected=You don't have access to Amazon Q. Please authenticate to get started. q.connection.expired=Your Amazon Q session has timed out. Re-authenticate to continue. q.connection.invalid=You don't have access to Amazon Q. Please authenticate to get started. From fd318cbb17f160fdafb1dd9de2ebaa50d2074571 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Wed, 25 Sep 2024 05:24:22 -0700 Subject: [PATCH 09/11] test fixes --- .../CodeWhispererTelemetryService.kt | 4 +- .../codewhisperer/CodeWhispererAcceptTest.kt | 23 ++++---- .../CodeWhispererClientAdaptorTest.kt | 16 +++++- .../CodeWhispererCodeCoverageTrackerTest.kt | 12 ++-- .../CodeWhispererNavigationTest.kt | 24 ++++---- .../CodeWhispererReferencesTest.kt | 4 +- .../CodeWhispererRightContextTest.kt | 46 ++++++++-------- .../codewhisperer/CodeWhispererServiceTest.kt | 17 ++++-- .../CodeWhispererSettingsTest.kt | 10 +--- .../codewhisperer/CodeWhispererStateTest.kt | 27 +++++---- .../CodeWhispererTelemetryServiceTest.kt | 55 +++++++++++-------- .../CodeWhispererTelemetryTest.kt | 50 ++++++++++------- .../codewhisperer/CodeWhispererTestBase.kt | 28 +++++----- .../codewhisperer/CodeWhispererTestUtil.kt | 55 ++++++------------- .../CodeWhispererTypeaheadTest.kt | 53 ++---------------- .../CodeWhispererUserActionsTest.kt | 27 +++------ .../CodeWhispererUserInputTest.kt | 44 +++++++-------- 17 files changed, 226 insertions(+), 269 deletions(-) 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 298df39abbc..139c1ba677b 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 @@ -458,7 +458,7 @@ class CodeWhispererTelemetryService { * - Reject if there is a Reject * - Empty if all decisions are Empty * - Ignore if at least one suggestion is seen and there's an accept for another trigger in the same display session - * - Unseen if the whole trigger is not seen + * - Unseen if the whole trigger is not seen (but has valid suggestions) * - Record the accepted suggestion index * - Discard otherwise */ @@ -479,8 +479,6 @@ class CodeWhispererTelemetryService { isUnseen = false isEmpty = false isDiscard = false - } else if (decision == CodewhispererSuggestionState.Empty) { - isDiscard = false } else if (decision == CodewhispererSuggestionState.Discard) { isEmpty = false } 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 4026c090a72..a785644614f 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 @@ -8,11 +8,6 @@ import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.actionSystem.IdeActions -import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_LEFT -import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT -import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TAB -import com.intellij.openapi.editor.actionSystem.EditorActionManager import com.intellij.testFramework.runInEdtAndWait import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -22,6 +17,9 @@ import org.mockito.kotlin.doAnswer import org.mockito.kotlin.stub import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsRequest import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsResponse +import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.ACTION_KEY_ACCEPT +import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.ACTION_KEY_NAV_NEXT +import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.ACTION_KEY_NAV_PREV import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.generateMockCompletionDetail import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.javaFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.javaResponse @@ -125,9 +123,9 @@ class CodeWhispererAcceptTest : CodeWhispererTestBase() { @Test fun `test CodeWhisperer keyboard shortcuts should be prioritized to be executed`() { - testCodeWhispererKeyboardShortcutShouldBePrioritized(ACTION_EDITOR_TAB) - testCodeWhispererKeyboardShortcutShouldBePrioritized(ACTION_EDITOR_MOVE_CARET_RIGHT) - testCodeWhispererKeyboardShortcutShouldBePrioritized(ACTION_EDITOR_MOVE_CARET_LEFT) + testCodeWhispererKeyboardShortcutShouldBePrioritized(ACTION_KEY_ACCEPT) + testCodeWhispererKeyboardShortcutShouldBePrioritized(ACTION_KEY_NAV_PREV) + testCodeWhispererKeyboardShortcutShouldBePrioritized(ACTION_KEY_NAV_NEXT) } private fun testCodeWhispererKeyboardShortcutShouldBePrioritized(actionId: String) { @@ -160,8 +158,8 @@ class CodeWhispererAcceptTest : CodeWhispererTestBase() { // move the cursor to the correct trigger point (...void main) projectRule.fixture.editor.caretModel.moveToOffset(47) } - withCodeWhispererServiceInvokedAndWait { states -> - val recommendation = states.recommendationContext.details[0].reformatted.content() + withCodeWhispererServiceInvokedAndWait { session -> + val recommendation = codewhispererService.getAllSuggestionsPreviewInfo()[session.selectedIndex].detail.reformatted.content() val editor = projectRule.fixture.editor val expectedContext = buildContextWithRecommendation(recommendation + remaining) val startOffset = editor.caretModel.offset @@ -176,8 +174,9 @@ class CodeWhispererAcceptTest : CodeWhispererTestBase() { private fun acceptHelper(useKeyboard: Boolean) { if (useKeyboard) { - EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_TAB) - .execute(projectRule.fixture.editor, null, DataContext.EMPTY_CONTEXT) + ActionManager.getInstance().getAction(ACTION_KEY_ACCEPT).actionPerformed( + AnActionEvent.createFromDataContext("test", null, DataContext.EMPTY_CONTEXT) + ) } else { popupManagerSpy.popupComponents.acceptButton.doClick() } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt index 5d7261dc4d4..ae72562c7e7 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererClientAdaptorTest.kt @@ -82,6 +82,9 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWh import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext +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.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants @@ -90,6 +93,7 @@ import software.aws.toolkits.jetbrains.settings.AwsSettings import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererCompletionType import software.aws.toolkits.telemetry.CodewhispererSuggestionState +import software.aws.toolkits.telemetry.CodewhispererTriggerType class CodeWhispererClientAdaptorTest { val projectRule = JavaCodeInsightTestFixtureRule() @@ -235,23 +239,28 @@ class CodeWhispererClientAdaptorTest { @Test fun sendUserTriggerDecisionTelemetry() { - val mockModelConfiguraotr = mock { + val mockModelConfigurator = mock { on { activeCustomization(any()) } doReturn CodeWhispererCustomization("fake-arn", "fake-name") } - ApplicationManager.getApplication().replaceService(CodeWhispererModelConfigurator::class.java, mockModelConfiguraotr, disposableRule.disposable) + ApplicationManager.getApplication().replaceService(CodeWhispererModelConfigurator::class.java, mockModelConfigurator, disposableRule.disposable) val file = projectRule.fixture.addFileToProject("main.java", "public class Main {}") runInEdtAndWait { projectRule.fixture.openFileInEditor(file.virtualFile) } val requestContext = CodeWhispererService.getInstance().getRequestContext( + TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()), projectRule.fixture.editor, projectRule.project, file, - LatencyContext(codewhispererEndToEndStart = 0, codewhispererEndToEndEnd = 20000000) ) sut.sendUserTriggerDecisionTelemetry( + SessionContext( + projectRule.project, + projectRule.fixture.editor, + latencyContext = LatencyContext(codewhispererEndToEndStart = 0, codewhispererEndToEndEnd = 20000000) + ), requestContext, ResponseContext("fake-session-id"), CodewhispererCompletionType.Line, @@ -370,6 +379,7 @@ class CodeWhispererClientAdaptorTest { fun `sendTelemetryEvent for userTriggerDecision respects telemetry optin status, for SSO users`() { sendTelemetryEventOptOutCheckHelper { sut.sendUserTriggerDecisionTelemetry( + SessionContext(projectRule.project, mock(), latencyContext = LatencyContext()), aRequestContext(projectRule.project), aResponseContext(), aCompletionType(), diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeCoverageTrackerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeCoverageTrackerTest.kt index be94fa83287..8eadc7b27c8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeCoverageTrackerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeCoverageTrackerTest.kt @@ -53,6 +53,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +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.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo @@ -175,12 +176,11 @@ internal class CodeWhispererCodeCoverageTrackerTestPython : CodeWhispererCodeCov } }, null, - mock(), aString() ) val responseContext = ResponseContext("sessionId") val recommendationContext = RecommendationContext( - listOf( + mutableListOf( DetailContext( "requestId", pythonResponse.completions()[0], @@ -193,10 +193,11 @@ internal class CodeWhispererCodeCoverageTrackerTestPython : CodeWhispererCodeCov ), "x, y", "x, y", - mock() + mock(), + 0 ) - invocationContext = InvocationContext(requestContext, responseContext, recommendationContext, mock()) - sessionContext = SessionContext() + invocationContext = InvocationContext(requestContext, responseContext, recommendationContext) + sessionContext = SessionContext(project, fixture.editor, latencyContext = LatencyContext()) // it is needed because referenceManager is listening to CODEWHISPERER_USER_ACTION_PERFORMED topic project.replaceService(CodeWhispererCodeReferenceManager::class.java, mock(), disposableRule.disposable) @@ -339,6 +340,7 @@ internal class CodeWhispererCodeCoverageTrackerTestPython : CodeWhispererCodeCov ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_USER_ACTION_PERFORMED).afterAccept( invocationContext, mock(), + SessionContext(project, fixture.editor, latencyContext = LatencyContext()), rangeMarkerMock ) 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 1e3d42ffff0..8f2e184d057 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 @@ -3,11 +3,13 @@ package software.aws.toolkits.jetbrains.services.codewhisperer +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.actionSystem.IdeActions -import com.intellij.openapi.editor.actionSystem.EditorActionManager import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.ACTION_KEY_NAV_NEXT +import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.ACTION_KEY_NAV_PREV import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse import javax.swing.JButton @@ -34,10 +36,10 @@ class CodeWhispererNavigationTest : CodeWhispererTestBase() { } private fun testNavigation(isReverse: Boolean, useKeyboard: Boolean = false) { - withCodeWhispererServiceInvokedAndWait { + withCodeWhispererServiceInvokedAndWait { session -> val indexChange = if (isReverse) -1 else 1 - assertThat(popupManagerSpy.sessionContext.selectedIndex).isEqualTo(0) + assertThat(session.selectedIndex).isEqualTo(0) val expectedCount = pythonResponse.completions().size var expectedSelectedIndex: Int @@ -58,7 +60,7 @@ class CodeWhispererNavigationTest : CodeWhispererTestBase() { } } - assertThat(popupManagerSpy.sessionContext.selectedIndex).isEqualTo(expectedSelectedIndex) + assertThat(session.selectedIndex).isEqualTo(expectedSelectedIndex) assertThat(oppositeButton.isEnabled).isEqualTo(false) repeat(expectedCount - 1) { @@ -66,7 +68,7 @@ class CodeWhispererNavigationTest : CodeWhispererTestBase() { navigateHelper(isReverse, useKeyboard) assertThat(oppositeButton.isEnabled).isEqualTo(true) expectedSelectedIndex = (expectedSelectedIndex + indexChange) % expectedCount - assertThat(popupManagerSpy.sessionContext.selectedIndex).isEqualTo(expectedSelectedIndex) + assertThat(session.selectedIndex).isEqualTo(expectedSelectedIndex) checkRecommendationInfoLabelText(expectedSelectedIndex + 1, expectedCount) } assertThat(navigationButton.isEnabled).isEqualTo(false) @@ -75,13 +77,13 @@ class CodeWhispererNavigationTest : CodeWhispererTestBase() { private fun navigateHelper(isReverse: Boolean, useKeyboard: Boolean) { if (useKeyboard) { - val actionHandler = EditorActionManager.getInstance() + val actionHandler = ActionManager.getInstance() if (isReverse) { - val leftArrowHandler = actionHandler.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_LEFT) - leftArrowHandler.execute(projectRule.fixture.editor, null, DataContext.EMPTY_CONTEXT) + val leftArrowHandler = actionHandler.getAction(ACTION_KEY_NAV_PREV) + leftArrowHandler.actionPerformed(AnActionEvent.createFromDataContext("", null, DataContext.EMPTY_CONTEXT)) } else { - val rightArrowHandler = actionHandler.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT) - rightArrowHandler.execute(projectRule.fixture.editor, null, DataContext.EMPTY_CONTEXT) + val rightArrowHandler = actionHandler.getAction(ACTION_KEY_NAV_NEXT) + rightArrowHandler.actionPerformed(AnActionEvent.createFromDataContext("", null, DataContext.EMPTY_CONTEXT)) } } else { if (isReverse) { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferencesTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferencesTest.kt index f186740f1e8..0a050493a56 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferencesTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererReferencesTest.kt @@ -56,8 +56,8 @@ class CodeWhispererReferencesTest : CodeWhispererTestBase() { } } - withCodeWhispererServiceInvokedAndWait { states -> - states.recommendationContext.details.forEach { + withCodeWhispererServiceInvokedAndWait { session -> + codewhispererService.getAllSuggestionsPreviewInfo().map { it.detail }.forEach { assertThat(it.recommendation.references().isEmpty()).isEqualTo(invalid) } popupManagerSpy.popupComponents.acceptButton.doClick() diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRightContextTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRightContextTest.kt index e4202cba6b3..5d83170927f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRightContextTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRightContextTest.kt @@ -21,8 +21,8 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { fun `test recommendation equal to right context should not show recommendation`() { val rightContext = pythonResponse.completions()[0].content() setFileContext(pythonFileName, pythonTestLeftContext, rightContext) - withCodeWhispererServiceInvokedAndWait { states -> - val firstRecommendation = states.recommendationContext.details[0] + withCodeWhispererServiceInvokedAndWait { + val firstRecommendation = codewhispererService.getAllSuggestionsPreviewInfo()[0].detail assertThat(firstRecommendation.isDiscarded).isEqualTo(true) } } @@ -31,12 +31,12 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { fun `test right context resolution will remove reference span if reference is the same as right context`() { val rightContext = pythonResponse.completions()[0].content() setFileContext(pythonFileName, pythonTestLeftContext, rightContext) - withCodeWhispererServiceInvokedAndWait { states -> - val firstRecommendation = states.recommendationContext.details[0] + withCodeWhispererServiceInvokedAndWait { _ -> + val firstRecommendation = codewhispererService.getAllSuggestionsPreviewInfo()[0].detail assertThat(firstRecommendation.isDiscarded).isEqualTo(true) - val details = states.recommendationContext.details - details.forEach { - assertThat(it.reformatted.references().isEmpty()) + val details = codewhispererService.getAllSuggestionsPreviewInfo().map { it.detail } + details.forEach { detail -> + assertThat(detail.reformatted.references().isEmpty()) } } } @@ -47,10 +47,9 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { val lastNewLineIndex = firstRecommendationContent.lastIndexOf('\n') val rightContext = firstRecommendationContent.substring(lastNewLineIndex) setFileContext(pythonFileName, pythonTestLeftContext, rightContext) - withCodeWhispererServiceInvokedAndWait { states -> - val firstRecommendation = states.recommendationContext.details[0] - assertThat(firstRecommendation.isDiscarded).isEqualTo(false) - val firstDetail = states.recommendationContext.details[0] + withCodeWhispererServiceInvokedAndWait { + val firstDetail = codewhispererService.getAllSuggestionsPreviewInfo()[0].detail + assertThat(firstDetail.isDiscarded).isEqualTo(false) val span = firstDetail.reformatted.references()[0].recommendationContentSpan() assertThat(span.start()).isEqualTo(0) assertThat(span.end()).isEqualTo(lastNewLineIndex) @@ -84,9 +83,8 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { } withCodeWhispererServiceInvokedAndWait { states -> - val firstRecommendation = states.recommendationContext.details[0] - assertThat(firstRecommendation.isDiscarded).isEqualTo(false) - val firstDetail = states.recommendationContext.details[0] + val firstDetail = codewhispererService.getAllSuggestionsPreviewInfo()[0].detail + assertThat(firstDetail.isDiscarded).isEqualTo(false) val span = firstDetail.reformatted.references()[0].recommendationContentSpan() assertThat(span.start()).isEqualTo(0) assertThat(span.end()).isEqualTo(lastNewLineIndex) @@ -101,10 +99,10 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { val remaining = firstRecommendation.substring(0, remainingLength) val rightContext = pythonResponse.completions()[0].content().substring(remainingLength) setFileContext(pythonFileName, pythonTestLeftContext, rightContext) - withCodeWhispererServiceInvokedAndWait { states -> - assertThat(states.recommendationContext.details[0].reformatted.content()).isEqualTo(remaining) + withCodeWhispererServiceInvokedAndWait { session -> + assertThat(codewhispererService.getAllSuggestionsPreviewInfo()[0].detail.reformatted.content()).isEqualTo(remaining) popupManagerSpy.popupComponents.acceptButton.doClick() - assertThat(states.requestContext.editor.document.text).isEqualTo(pythonTestLeftContext + remaining + rightContext) + assertThat(session.editor.document.text).isEqualTo(pythonTestLeftContext + remaining + rightContext) } } @@ -115,8 +113,8 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { val remainingLength = Random.nextInt(newLineIndex, firstRecommendation.length) val rightContext = pythonResponse.completions()[0].content().substring(remainingLength) setFileContext(pythonFileName, pythonTestLeftContext, rightContext) - withCodeWhispererServiceInvokedAndWait { states -> - assertThat(states.recommendationContext.details[0].recommendation.content()).isEqualTo(firstRecommendation) + withCodeWhispererServiceInvokedAndWait { + assertThat(codewhispererService.getAllSuggestionsPreviewInfo()[0].detail.recommendation.content()).isEqualTo(firstRecommendation) } } @@ -128,10 +126,10 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { val remaining = firstRecommendation.substring(0, remainingLength) val rightContext = pythonResponse.completions()[0].content().substring(remainingLength) + "test" setFileContext(pythonFileName, pythonTestLeftContext, rightContext) - withCodeWhispererServiceInvokedAndWait { states -> - assertThat(states.recommendationContext.details[0].reformatted.content()).isEqualTo(remaining) + withCodeWhispererServiceInvokedAndWait { session -> + assertThat(codewhispererService.getAllSuggestionsPreviewInfo()[0].detail.reformatted.content()).isEqualTo(remaining) popupManagerSpy.popupComponents.acceptButton.doClick() - assertThat(states.requestContext.editor.document.text).isEqualTo(pythonTestLeftContext + remaining + rightContext) + assertThat(session.editor.document.text).isEqualTo(pythonTestLeftContext + remaining + rightContext) } } @@ -142,8 +140,8 @@ class CodeWhispererRightContextTest : CodeWhispererTestBase() { val remainingLength = Random.nextInt(newLineIndex, firstRecommendation.length) val rightContext = pythonResponse.completions()[0].content().substring(remainingLength) + "test" setFileContext(pythonFileName, pythonTestLeftContext, rightContext) - withCodeWhispererServiceInvokedAndWait { states -> - assertThat(states.recommendationContext.details[0].recommendation.content()).isEqualTo(firstRecommendation) + withCodeWhispererServiceInvokedAndWait { + assertThat(codewhispererService.getAllSuggestionsPreviewInfo()[0].detail.recommendation.content()).isEqualTo(firstRecommendation) } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt index c7fdae43077..1a24cb479cf 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt @@ -12,6 +12,7 @@ import com.intellij.testFramework.runInEdtAndWait import kotlinx.coroutines.async import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat +import org.junit.After import org.junit.Before import org.junit.Ignore import org.junit.Rule @@ -99,6 +100,11 @@ class CodeWhispererServiceTest { projectRule.project.replaceService(AwsConnectionManager::class.java, mock(), disposableRule.disposable) } + @After + fun tearDown() { + sut.disposeDisplaySession(false) + } + @Test fun `getRequestContext should have supplementalContext and customizatioArn if they're present`() { whenever(userGroupSetting.getUserGroup()).thenReturn(CodeWhispererUserGroup.CrossFile) @@ -128,10 +134,10 @@ class CodeWhispererServiceTest { projectRule.project.replaceService(FileContextProvider::class.java, mockFileContextProvider, disposableRule.disposable) val actual = sut.getRequestContext( + TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()), projectRule.fixture.editor, projectRule.project, - file, - LatencyContext() + file ) runTest { @@ -153,10 +159,10 @@ class CodeWhispererServiceTest { } val actual = sut.getRequestContext( + TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()), projectRule.fixture.editor, projectRule.project, - file, - LatencyContext() + file ) assertThat(actual.supplementalContext).isNotNull @@ -188,12 +194,11 @@ class CodeWhispererServiceTest { fileContextInfo = mockFileContext, supplementalContextDeferred = async { mockSupContext }, connection = ToolkitConnectionManager.getInstance(projectRule.project).activeConnection(), - latencyContext = LatencyContext(), customizationArn = "fake-arn" ) ) - sut.invokeCodeWhispererInBackground(mockRequestContext).join() + sut.invokeCodeWhispererInBackground(mockRequestContext, 0, LatencyContext())?.join() verify(mockRequestContext, times(1)).awaitSupplementalContext() verify(clientFacade).generateCompletionsPaginator(any()) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt index bde3d169f52..004854730ad 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererSettingsTest.kt @@ -4,7 +4,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.analysis.problemsView.toolWindow.ProblemsView -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service import com.intellij.openapi.wm.RegisterToolWindowTask import com.intellij.openapi.wm.ToolWindow @@ -18,14 +17,12 @@ import org.junit.Ignore import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.never -import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import software.aws.toolkits.jetbrains.core.ToolWindowHeadlessManagerImpl import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfiguration import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererSettings import software.aws.toolkits.jetbrains.services.codewhisperer.status.CodeWhispererStatusBarWidgetFactory @@ -34,14 +31,11 @@ import kotlin.test.fail class CodeWhispererSettingsTest : CodeWhispererTestBase() { - private lateinit var codewhispererServiceSpy: CodeWhispererService private lateinit var toolWindowHeadlessManager: ToolWindowHeadlessManagerImpl @Before override fun setUp() { super.setUp() - codewhispererServiceSpy = spy(codewhispererService) - ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererServiceSpy, disposableRule.disposable) // Create a mock ToolWindowManager with working implementation of setAvailable() and isAvailable() toolWindowHeadlessManager = object : ToolWindowHeadlessManagerImpl(projectRule.project) { @@ -83,7 +77,7 @@ class CodeWhispererSettingsTest : CodeWhispererTestBase() { whenever(stateManager.checkActiveCodeWhispererConnectionType(projectRule.project)).thenReturn(CodeWhispererLoginType.Logout) assertThat(isCodeWhispererEnabled(projectRule.project)).isFalse invokeCodeWhispererService() - verify(codewhispererServiceSpy, never()).showRecommendationsInPopup(any(), any(), any()) + verify(codewhispererService, never()).showRecommendationsInPopup(any(), any(), any()) } @Test @@ -92,7 +86,7 @@ class CodeWhispererSettingsTest : CodeWhispererTestBase() { assertThat(stateManager.isAutoEnabled()).isFalse runInEdtAndWait { projectRule.fixture.type(':') - verify(codewhispererServiceSpy, never()).showRecommendationsInPopup(any(), any(), any()) + verify(codewhispererService, never()).showRecommendationsInPopup(any(), any(), any()) } } 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 0397576e8b6..93c93654008 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 @@ -12,13 +12,16 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.telemetry.CodewhispererLanguage import software.aws.toolkits.telemetry.CodewhispererTriggerType +import kotlin.test.assertNotNull class CodeWhispererStateTest : CodeWhispererTestBase() { @Test fun `test CodeWhisperer invocation sets request metadata correctly`() { - withCodeWhispererServiceInvokedAndWait { states -> - val actualRequestContext = states.requestContext + withCodeWhispererServiceInvokedAndWait { session -> + val selectedJobId = codewhispererService.getAllSuggestionsPreviewInfo()[session.selectedIndex].jobId + val actualRequestContext = codewhispererService.ongoingRequestsContext[selectedJobId] + assertNotNull(actualRequestContext) val editor = projectRule.fixture.editor val (actualProject, actualEditor, actualTriggerTypeInfo, actualCaretPosition, actualFileContextInfo) = actualRequestContext val (actualCaretContext, actualFilename, actualProgrammingLanguage) = actualFileContextInfo @@ -46,8 +49,10 @@ class CodeWhispererStateTest : CodeWhispererTestBase() { @Test fun `test CodeWhisperer invocation sets response metadata correctly`() { - withCodeWhispererServiceInvokedAndWait { states -> - val actualResponseContext = states.responseContext + withCodeWhispererServiceInvokedAndWait { session -> + val selectedJobId = codewhispererService.getAllSuggestionsPreviewInfo()[session.selectedIndex].jobId + val actualResponseContext = codewhispererService.getAllPaginationSessions()[selectedJobId]?.responseContext + assertNotNull(actualResponseContext) assertThat(listOf(actualResponseContext.sessionId)).isEqualTo( pythonResponse.sdkHttpResponse().headers()[CodeWhispererService.KET_SESSION_ID] ) @@ -56,7 +61,9 @@ class CodeWhispererStateTest : CodeWhispererTestBase() { @Test fun `test CodeWhisperer invocation sets recommendation metadata correctly`() { - withCodeWhispererServiceInvokedAndWait { states -> + withCodeWhispererServiceInvokedAndWait { + val states = codewhispererService.getAllPaginationSessions()[0] + assertNotNull(states) val actualRecommendationContext = states.recommendationContext val (actualDetailContexts, actualUserInput) = actualRecommendationContext @@ -74,15 +81,13 @@ class CodeWhispererStateTest : CodeWhispererTestBase() { @Test fun `test CodeWhisperer invocation sets initial typeahead and selected index correctly`() { - withCodeWhispererServiceInvokedAndWait { - val sessionContext = popupManagerSpy.sessionContext - val actualSelectedIndex = sessionContext.selectedIndex - val actualTypeahead = sessionContext.typeahead - val actualTypeaheadOriginal = sessionContext.typeaheadOriginal + withCodeWhispererServiceInvokedAndWait { session -> + val actualSelectedIndex = session.selectedIndex + val preview = codewhispererService.getAllSuggestionsPreviewInfo()[actualSelectedIndex] + val actualTypeahead = preview.typeahead assertThat(actualSelectedIndex).isEqualTo(0) assertThat(actualTypeahead).isEqualTo("") - assertThat(actualTypeaheadOriginal).isEqualTo("") } } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt index 27f402128c3..7fd72c1503f 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryServiceTest.kt @@ -39,8 +39,10 @@ import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererCon import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager +import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext 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.service.CodeWhispererUserGroup import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererUserGroupSettings import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererUserGroupStates @@ -101,7 +103,7 @@ class CodeWhispererTelemetryServiceTest { mockClient = spy(CodeWhispererClientAdaptor.getInstance(projectRule.project)) mockClient.stub { onGeneric { - sendUserTriggerDecisionTelemetry(any(), any(), any(), any(), any(), any(), any()) + sendUserTriggerDecisionTelemetry(any(), any(), any(), any(), any(), any(), any(), any()) }.doAnswer { mock() } @@ -112,25 +114,20 @@ class CodeWhispererTelemetryServiceTest { @After fun cleanup() { sut.previousDecisions().clear() + CodeWhispererService.getInstance().disposeDisplaySession(false) + CodeWhispererService.getInstance().getAllPaginationSessions().clear() } @Test fun `test recordSuggestionState`() { fun assertSuggestionStates(expectedStates: List) { - val (recommendationContext, sessionContext) = aRecommendationContextAndSessionContext(expectedStates) + val recommendationContext = aRecommendationContext(expectedStates) val hasUserAccepted = expectedStates.any { it == CodewhispererSuggestionState.Accept } val details = recommendationContext.details val actualStates = mutableListOf() details.forEachIndexed { index, detail -> - val suggestionState = sut.recordSuggestionState( - index, - sessionContext.selectedIndex, - sessionContext.seen.contains(index), - hasUserAccepted, - detail.isDiscarded, - detail.recommendation.content().isEmpty() - ) + val suggestionState = sut.recordSuggestionState(detail, hasUserAccepted) actualStates.add(suggestionState) } @@ -171,7 +168,7 @@ class CodeWhispererTelemetryServiceTest { @Test fun `test aggregateUserDecision`() { - fun assertAggregateUserDecision(decisions: List, expected: CodewhispererPreviousSuggestionState) { + fun assertAggregateUserDecision(decisions: List, expected: CodewhispererSuggestionState) { val actual = sut.aggregateUserDecision(decisions) assertThat(actual).isEqualTo(expected) } @@ -184,7 +181,7 @@ class CodeWhispererTelemetryServiceTest { CodewhispererSuggestionState.Unseen, CodewhispererSuggestionState.Unseen ), - CodewhispererPreviousSuggestionState.Accept + CodewhispererSuggestionState.Accept ) assertAggregateUserDecision( @@ -195,7 +192,7 @@ class CodeWhispererTelemetryServiceTest { CodewhispererSuggestionState.Empty, CodewhispererSuggestionState.Ignore ), - CodewhispererPreviousSuggestionState.Reject + CodewhispererSuggestionState.Reject ) assertAggregateUserDecision( @@ -206,7 +203,7 @@ class CodeWhispererTelemetryServiceTest { CodewhispererSuggestionState.Discard, CodewhispererSuggestionState.Empty ), - CodewhispererPreviousSuggestionState.Discard + CodewhispererSuggestionState.Discard ) assertAggregateUserDecision( @@ -216,7 +213,7 @@ class CodeWhispererTelemetryServiceTest { CodewhispererSuggestionState.Empty, CodewhispererSuggestionState.Empty ), - CodewhispererPreviousSuggestionState.Empty + CodewhispererSuggestionState.Empty ) } @@ -234,6 +231,7 @@ class CodeWhispererTelemetryServiceTest { ) val supplementalContextInfo = aSupplementalContextInfo() + val sessionContext = aSessionContext(projectRule.project) val requestContext = aRequestContext(projectRule.project, mySupplementalContextInfo = supplementalContextInfo).also { runTest { it.awaitSupplementalContext() } } @@ -250,6 +248,7 @@ class CodeWhispererTelemetryServiceTest { } sut.sendUserTriggerDecisionEvent( + sessionContext, requestContext, responseContext, recommendationContext, @@ -267,7 +266,7 @@ class CodeWhispererTelemetryServiceTest { "codewhisperer_userTriggerDecision", 1, "codewhispererSessionId" to responseContext.sessionId, - "codewhispererFirstRequestId" to requestContext.latencyContext.firstRequestId, + "codewhispererFirstRequestId" to sessionContext.latencyContext.firstRequestId, "codewhispererCompletionType" to recommendationContext.details[0].completionType, "codewhispererLanguage" to requestContext.fileContextInfo.programmingLanguage.toTelemetryType(), "codewhispererTriggerType" to requestContext.triggerTypeInfo.triggerType, @@ -297,20 +296,24 @@ class CodeWhispererTelemetryServiceTest { @Test fun `sendUserDecisionEventForAll will send userDecision event for all suggestions`() { - doNothing().whenever(sut).sendUserTriggerDecisionEvent(any(), any(), any(), any(), any(), any(), any(), any()) + doNothing().whenever(sut).sendUserTriggerDecisionEvent(any(), any(), any(), any(), any(), any(), any(), any(), any()) val eventCount = mutableMapOf() var totalEventCount = 0 - val requestContext = aRequestContext(projectRule.project) - val responseContext = aResponseContext() fun assertUserDecision(decisions: List) { decisions.forEach { eventCount[it] = 1 + (eventCount[it] ?: 0) } totalEventCount += decisions.size - val (recommendationContext, sessionContext) = aRecommendationContextAndSessionContext(decisions) + CodeWhispererService.getInstance().getAllPaginationSessions()[0] = InvocationContext( + aRequestContext(projectRule.project), + aResponseContext(), + aRecommendationContext(decisions) + ) val hasUserAccept = decisions.any { it == CodewhispererSuggestionState.Accept } val popupShownDuration = Duration.ofSeconds(Random.nextLong(0, 30)) + val sessionContext = aSessionContext(projectRule.project) + sessionContext.selectedIndex = 0 sut.sendUserDecisionEventForAll( sessionContext, hasUserAccept, @@ -375,11 +378,16 @@ class CodeWhispererTelemetryServiceTest { val requestContext = aRequestContext(projectRule.project, mySupplementalContextInfo = supplementalContextInfo).also { runTest { it.awaitSupplementalContext() } } - val responseContext = aResponseContext() - val (recommendationContext, sessionContext) = aRecommendationContextAndSessionContext(decisions) val hasUserAccept = decisions.any { it == CodewhispererSuggestionState.Accept } val popupShownDuration = Duration.ofSeconds(Random.nextLong(0, 30)) + CodeWhispererService.getInstance().getAllPaginationSessions()[0] = InvocationContext( + requestContext, + aResponseContext(), + aRecommendationContext(decisions) + ) + val sessionContext = aSessionContext(projectRule.project) + sessionContext.selectedIndex = 0 sut.sendUserDecisionEventForAll( sessionContext, hasUserAccept, @@ -472,6 +480,7 @@ class CodeWhispererTelemetryServiceTest { ) AwsSettings.getInstance().isTelemetryEnabled = isTelemetryEnabled + val expectedSessionContext = aSessionContext(projectRule.project) val expectedRequestContext = aRequestContext(projectRule.project) val expectedResponseContext = aResponseContext() val expectedRecommendationContext = aRecommendationContext() @@ -482,6 +491,7 @@ class CodeWhispererTelemetryServiceTest { val expectedCharCount = 100 val expectedCompletionType = expectedRecommendationContext.details[0].completionType sut.sendUserTriggerDecisionEvent( + expectedSessionContext, expectedRequestContext, expectedResponseContext, expectedRecommendationContext, @@ -494,6 +504,7 @@ class CodeWhispererTelemetryServiceTest { if (isProTier || isTelemetryEnabled) { verify(mockClient).sendUserTriggerDecisionTelemetry( + eq(expectedSessionContext), eq(expectedRequestContext), eq(expectedResponseContext), eq(expectedCompletionType), diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt index 650ba19614e..02f1655d514 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt @@ -7,7 +7,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Editor -import com.intellij.openapi.ui.popup.JBPopup import com.intellij.psi.PsiDocumentManager import com.intellij.testFramework.TestActionEvent import com.intellij.testFramework.replaceService @@ -58,6 +57,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.R import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJava import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererUserGroupSettings import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.AcceptedSuggestionEntry @@ -74,6 +75,7 @@ import software.aws.toolkits.telemetry.CodewhispererSuggestionState import software.aws.toolkits.telemetry.CodewhispererTriggerType import software.aws.toolkits.telemetry.Result import java.time.Instant +import kotlin.test.assertNotNull class CodeWhispererTelemetryTest : CodeWhispererTestBase() { private val userDecision = "codewhisperer_userDecision" @@ -106,12 +108,10 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { @Test fun `test pre-setup failure will send service invocation event with failed status`() { - val codewhispererServiceSpy = spy(codewhispererService) { + codewhispererService.stub { onGeneric { getRequestContext(any(), any(), any(), any()) } .doAnswer { throw Exception() } } - ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererServiceSpy, disposableRule.disposable) - invokeCodeWhispererService() argumentCaptor().apply { @@ -195,7 +195,7 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { fun `test cancelling popup will send user decision event for all unseen but one rejected`() { val userGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup() withCodeWhispererServiceInvokedAndWait { states -> - popupManagerSpy.cancelPopup() + codewhispererService.disposeDisplaySession(false) val count = pythonResponse.completions().size argumentCaptor().apply { @@ -397,15 +397,18 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { @Test fun `test invoking CodeWhisperer will send service invocation event with sessionId and requestId from response`() { val userGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup() - withCodeWhispererServiceInvokedAndWait { states -> + withCodeWhispererServiceInvokedAndWait { session -> val metricCaptor = argumentCaptor() verify(batcher, atLeastOnce()).enqueue(metricCaptor.capture()) + val detail = codewhispererService.getAllSuggestionsPreviewInfo()[0].detail + val states = codewhispererService.getAllPaginationSessions()[0] + assertNotNull(states) assertEventsContainsFieldsAndCount( metricCaptor.allValues, serviceInvocation, 1, "codewhispererSessionId" to states.responseContext.sessionId, - "codewhispererRequestId" to states.recommendationContext.details[0].requestId, + "codewhispererRequestId" to detail.requestId, "codewhispererUserGroup" to userGroup.name, ) } @@ -414,18 +417,24 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { @Test fun `test userDecision events will record sessionId and requestId from response`() { val userGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup() - val statesCaptor = argumentCaptor() - withCodeWhispererServiceInvokedAndWait {} - verify(popupManagerSpy, timeout(5000).atLeastOnce()).render(statesCaptor.capture(), any(), any(), any()) - val states = statesCaptor.lastValue + val sessionCaptor = argumentCaptor() + var states: InvocationContext? = null + var previews: List? = null + withCodeWhispererServiceInvokedAndWait { + states = codewhispererService.getAllPaginationSessions()[0] + previews = codewhispererService.getAllSuggestionsPreviewInfo() + } + verify(popupManagerSpy, timeout(5000).atLeastOnce()).render(sessionCaptor.capture(), any(), any()) val metricCaptor = argumentCaptor() verify(batcher, atLeastOnce()).enqueue(metricCaptor.capture()) + assertNotNull(states) + assertNotNull(previews) assertEventsContainsFieldsAndCount( metricCaptor.allValues, userDecision, - states.recommendationContext.details.size, - "codewhispererSessionId" to states.responseContext.sessionId, - "codewhispererRequestId" to states.recommendationContext.details[0].requestId, + previews?.size ?: 0, + "codewhispererSessionId" to states?.responseContext?.sessionId, + "codewhispererRequestId" to previews?.get(0)?.detail?.requestId, "codewhispererUserGroup" to userGroup.name, ) } @@ -676,7 +685,7 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { } invokeCodeWhispererService() - verify(popupManagerSpy, never()).showPopup(any(), any(), any(), any(), any()) + verify(popupManagerSpy, never()).showPopup(any(), any()) runInEdtAndWait { val metricCaptor = argumentCaptor() verify(batcher, atLeastOnce()).enqueue(metricCaptor.capture()) @@ -699,7 +708,9 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { } doReturnConsecutively(listOf(pythonResponseWithNonEmptyToken, emptyListResponse)) } - withCodeWhispererServiceInvokedAndWait { } + withCodeWhispererServiceInvokedAndWait { + popupManagerSpy.popupComponents.acceptButton.doClick() + } runInEdtAndWait { val metricCaptor = argumentCaptor() @@ -781,13 +792,12 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() { val numOfEmptyRecommendations = response.completions().filter { it.content().isEmpty() }.size if (numOfEmptyRecommendations == response.completions().size) { - verify(popupManagerSpy, never()).showPopup(any(), any(), any(), any(), any()) + verify(popupManagerSpy, never()).showPopup(any(), any()) } else { - val popupCaptor = argumentCaptor() verify(popupManagerSpy, timeout(5000)) - .showPopup(any(), any(), popupCaptor.capture(), any(), any()) + .showPopup(any(), any()) runInEdtAndWait { - popupManagerSpy.closePopup(popupCaptor.lastValue) + codewhispererService.disposeDisplaySession(true) } } runInEdtAndWait { diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt index 5f980006638..169a6c30445 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt @@ -15,7 +15,6 @@ import org.junit.Rule import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.doNothing import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy @@ -38,7 +37,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhisper import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreActionState import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExploreStateType import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager -import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext +import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererRecommendationManager @@ -96,9 +95,11 @@ open class CodeWhispererTestBase { } } + codewhispererService = spy(CodeWhispererService.getInstance()) + ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererService, disposableRule.disposable) + popupManagerSpy = spy(CodeWhispererPopupManager.getInstance()) - popupManagerSpy.resetSession() - doNothing().`when`(popupManagerSpy).showPopup(any(), any(), any(), any(), any()) + codewhispererService.disposeDisplaySession(false) ApplicationManager.getApplication().replaceService(CodeWhispererPopupManager::class.java, popupManagerSpy, disposableRule.disposable) invocationStatusSpy = spy(CodeWhispererInvocationStatus.getInstance()) @@ -113,7 +114,6 @@ open class CodeWhispererTestBase { stateManager = spy(CodeWhispererExplorerActionManager.getInstance()) recommendationManager = CodeWhispererRecommendationManager.getInstance() - codewhispererService = CodeWhispererService.getInstance() editorManager = CodeWhispererEditorManager.getInstance() settingsManager = CodeWhispererSettings.getInstance() @@ -155,24 +155,22 @@ open class CodeWhispererTestBase { open fun tearDown() { stateManager.loadState(originalExplorerActionState) settingsManager.loadState(originalSettings) - popupManagerSpy.resetSession() - runInEdtAndWait { - popupManagerSpy.closePopup() - } + codewhispererService.disposeDisplaySession(true) } - fun withCodeWhispererServiceInvokedAndWait(runnable: (InvocationContext) -> Unit) { - val statesCaptor = argumentCaptor() + fun withCodeWhispererServiceInvokedAndWait(runnable: (SessionContext) -> Unit) { + val sessionCaptor = argumentCaptor() invokeCodeWhispererService() verify(popupManagerSpy, timeout(5000).atLeastOnce()) - .showPopup(statesCaptor.capture(), any(), any(), any(), any()) - val states = statesCaptor.lastValue + .showPopup(sessionCaptor.capture(), any()) + CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(true) + val session = sessionCaptor.lastValue runInEdtAndWait { try { - runnable(states) + runnable(session) } finally { - CodeWhispererPopupManager.getInstance().closePopup(states.popup) + codewhispererService.disposeDisplaySession(true) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt index 5dcbaaacc6f..85ff06206dd 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt @@ -3,6 +3,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer +import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.project.Project import kotlinx.coroutines.async @@ -69,6 +70,9 @@ object CodeWhispererTestUtil { const val codeWhispererCodeScanActionId = "codewhisperer.toolbar.security.scan" const val testValidAccessToken = "test_valid_access_token" const val testNextToken = "test_next_token" + const val ACTION_KEY_ACCEPT = "codewhisperer.inline.accept" + const val ACTION_KEY_NAV_PREV = "codewhisperer.inline.navigate.previous" + const val ACTION_KEY_NAV_NEXT = "codewhisperer.inline.navigate.next" private val testReferenceInfoPair = listOf( Pair("MIT", "testRepo1"), Pair("Apache-2.0", "testRepo2"), @@ -211,6 +215,12 @@ object CodeWhispererTestUtil { .build() } +fun aSessionContext( + project: Project = mock(), + editor: Editor = mock(), + latencyContext: LatencyContext = LatencyContext() +) = SessionContext(project, editor, latencyContext = latencyContext) + fun aRequestContext( project: Project, myFileContextInfo: FileContextInfo? = null, @@ -242,20 +252,6 @@ fun aRequestContext( fileContextInfo = myFileContextInfo ?: aFileContextInfo(), supplementalContextDeferred = supplementalContextDeferred, null, - LatencyContext( - Random.nextLong(), - Random.nextLong(), - Random.nextLong(), - Random.nextLong(), - Random.nextDouble(), - Random.nextLong(), - Random.nextLong(), - Random.nextLong(), - Random.nextLong(), - Random.nextLong(), - Random.nextLong(), - aString() - ), customizationArn = null ) } @@ -306,14 +302,15 @@ fun aRecommendationContext(): RecommendationContext { details, aString(), aString(), - VisualPosition(Random.nextInt(1, 100), Random.nextInt(1, 100)) + VisualPosition(Random.nextInt(1, 100), Random.nextInt(1, 100)), + 0 ) } /** * util to generate a RecommendationContext and a SessionContext given expected decisions */ -fun aRecommendationContextAndSessionContext(decisions: List): Pair { +fun aRecommendationContext(decisions: List): RecommendationContext { val table = CodewhispererSuggestionState.values().associateWith { 0 }.toMutableMap() decisions.forEach { table[it]?.let { curCount -> table[it] = 1 + curCount } @@ -331,6 +328,8 @@ fun aRecommendationContextAndSessionContext(decisions: List() - decisions.forEachIndexed { index, decision -> - if (decision != CodewhispererSuggestionState.Unseen) { - seen.add(index) - } - } - - val sessionContext = SessionContext( - selectedIndex = selectedIndex, - seen = seen - ) - return recommendationContext to sessionContext + return recommendationContext } fun aCompletion(content: String? = null, isEmpty: Boolean = false, referenceCount: Int? = null, importCount: Int? = null): Completion { 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 06db93b345c..50cc14009fe 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 @@ -34,58 +34,14 @@ class CodeWhispererTypeaheadTest : CodeWhispererTestBase() { testTypingTypeaheadMatchingRecommendationShouldMatchRightContext(testRightContext) } - @Test - fun `test typing blank typeahead should correctly update typeahead state`() { - val testTypeaheadOriginal = " " - testTypingTypeaheadWithLeadingSpaceShouldMatchTypeaheadStateCorrectly(testTypeaheadOriginal, 5, 1) - } - - @Test - fun `test typing typeahead with leading spaces and matching suffix should correctly update typeahead state`() { - val testTypeaheadOriginal = " test" - testTypingTypeaheadWithLeadingSpaceShouldMatchTypeaheadStateCorrectly(testTypeaheadOriginal, 3, 3) - } - - private fun testTypingTypeaheadWithLeadingSpaceShouldMatchTypeaheadStateCorrectly( - expectedTypeaheadOriginal: String, - expectedNumOfValidRecommendation: Int, - expectedSelectedAfterBackspace: Int - ) { - withCodeWhispererServiceInvokedAndWait { states -> - val editor = projectRule.fixture.editor - val startOffset = editor.caretModel.offset - expectedTypeaheadOriginal.forEach { char -> - projectRule.fixture.type(char) - val caretOffset = editor.caretModel.offset - val actualTypeaheadOriginal = editor.document.charsSequence.subSequence(startOffset, caretOffset).toString() - val actualTypeahead = actualTypeaheadOriginal.trimStart() - assertThat(popupManagerSpy.sessionContext.typeaheadOriginal).isEqualTo(actualTypeaheadOriginal) - assertThat(popupManagerSpy.sessionContext.typeahead).isEqualTo(actualTypeahead) - assertThat(states.popup.isDisposed).isFalse - } - checkRecommendationInfoLabelText(1, expectedNumOfValidRecommendation) - - // Backspacing for the same amount of times - expectedTypeaheadOriginal.forEach { _ -> - projectRule.fixture.type('\b') - val caretOffset = editor.caretModel.offset - val actualTypeaheadOriginal = editor.document.charsSequence.subSequence(startOffset, caretOffset).toString() - val actualTypeahead = actualTypeaheadOriginal.trimStart() - assertThat(popupManagerSpy.sessionContext.typeaheadOriginal).isEqualTo(actualTypeaheadOriginal) - assertThat(popupManagerSpy.sessionContext.typeahead).isEqualTo(actualTypeahead) - assertThat(states.popup.isDisposed).isFalse - } - checkRecommendationInfoLabelText(expectedSelectedAfterBackspace, 5) - } - } - private fun testTypingTypeaheadMatchingRecommendationShouldMatchRightContext(rightContext: String) { projectRule.fixture.configureByText(pythonFileName, pythonTestLeftContext + rightContext) runInEdtAndWait { projectRule.fixture.editor.caretModel.moveToOffset(pythonTestLeftContext.length) } - withCodeWhispererServiceInvokedAndWait { states -> - val recommendation = states.recommendationContext.details[0].reformatted.content() + withCodeWhispererServiceInvokedAndWait { session -> + var preview = codewhispererService.getAllSuggestionsPreviewInfo()[0] + val recommendation = preview.detail.reformatted.content() val editor = projectRule.fixture.editor val startOffset = editor.caretModel.offset recommendation.forEachIndexed { index, char -> @@ -93,7 +49,8 @@ class CodeWhispererTypeaheadTest : CodeWhispererTestBase() { projectRule.fixture.type(char) val caretOffset = editor.caretModel.offset val typeahead = editor.document.charsSequence.subSequence(startOffset, caretOffset).toString() - assertThat(popupManagerSpy.sessionContext.typeahead).isEqualTo(typeahead) + preview = codewhispererService.getAllSuggestionsPreviewInfo()[0] + assertThat(preview.typeahead).isEqualTo(typeahead) } } } 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 00c581709de..c08238b7a17 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 @@ -14,14 +14,12 @@ import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TEXT_END_WITH_ import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TEXT_START_WITH_SELECTION import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.event.VisibleAreaEvent -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.Test import org.mockito.Mockito.times import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.timeout @@ -95,7 +93,7 @@ class CodeWhispererUserActionsTest : CodeWhispererTestBase() { } withCodeWhispererServiceInvokedAndWait { projectRule.fixture.performEditorAction(actionId) - verify(popupManagerSpy, timeout(5000)).cancelPopup() + verify(codewhispererService, timeout(5000).atLeastOnce()).disposeDisplaySession(false) } } @@ -120,11 +118,9 @@ class CodeWhispererUserActionsTest : CodeWhispererTestBase() { projectRule.fixture.type('\n') val expectedFileContext = "$testLeftContext\n \n $testRightContext" assertThat(projectRule.fixture.editor.document.text).isEqualTo(expectedFileContext) - val popupCaptor = argumentCaptor() - verify(popupManagerSpy, timeout(5000)) - .showPopup(any(), any(), popupCaptor.capture(), any(), any()) + verify(popupManagerSpy, timeout(5000)).showPopup(any(), any()) runInEdtAndWait { - popupManagerSpy.closePopup(popupCaptor.lastValue) + codewhispererService.disposeDisplaySession(true) } } @@ -141,7 +137,7 @@ class CodeWhispererUserActionsTest : CodeWhispererTestBase() { CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(true) val listener = CodeWhispererScrollListener(states) listener.visibleAreaChanged(event) - verify(popupManagerSpy, times(2)).showPopup(any(), any(), any(), any(), any()) + verify(popupManagerSpy, times(2)).showPopup(any(), any()) } } @@ -163,15 +159,12 @@ class CodeWhispererUserActionsTest : CodeWhispererTestBase() { setFileContext(pythonFileName, "def", rightContext) projectRule.fixture.type('{') if (shouldtrigger) { - val popupCaptor = argumentCaptor() - verify(popupManagerSpy, timeout(5000).atLeastOnce()) - .showPopup(any(), any(), popupCaptor.capture(), any(), any()) + verify(popupManagerSpy, timeout(5000).atLeastOnce()).showPopup(any(), any()) runInEdtAndWait { - popupManagerSpy.closePopup(popupCaptor.lastValue) + codewhispererService.disposeDisplaySession(true) } } else { - verify(popupManagerSpy, times(0)) - .showPopup(any(), any(), any(), any(), any()) + verify(popupManagerSpy, times(0)).showPopup(any(), any()) } } @@ -179,11 +172,9 @@ class CodeWhispererUserActionsTest : CodeWhispererTestBase() { CodeWhispererExplorerActionManager.getInstance().setAutoEnabled(true) setFileContext(pythonFileName, prompt, "") projectRule.fixture.type('\n') - val popupCaptor = argumentCaptor() - verify(popupManagerSpy, timeout(5000).atLeast(times)) - .showPopup(any(), any(), popupCaptor.capture(), any(), any()) + verify(popupManagerSpy, timeout(5000).atLeast(times)).showPopup(any(), any()) runInEdtAndWait { - popupManagerSpy.closePopup(popupCaptor.lastValue) + codewhispererService.disposeDisplaySession(true) } } } 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 3f647102276..f534eeb37b6 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 @@ -3,20 +3,16 @@ package software.aws.toolkits.jetbrains.services.codewhisperer -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile -import com.intellij.testFramework.replaceService import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.spy import org.mockito.kotlin.stub import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo -import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService class CodeWhispererUserInputTest : CodeWhispererTestBase() { @@ -24,10 +20,8 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { fun `test no user input should show all recommendations`() { addUserInputAfterInvocation("") - withCodeWhispererServiceInvokedAndWait { states -> - val actualRecommendations = states.recommendationContext.details.map { - it.recommendation.content() - } + withCodeWhispererServiceInvokedAndWait { session -> + val actualRecommendations = codewhispererService.getAllSuggestionsPreviewInfo().map { it.detail.recommendation.content() } assertThat(actualRecommendations).isEqualTo(pythonResponse.completions().map { it.content() }) } } @@ -39,10 +33,11 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { val expectedRecommendations = pythonResponse.completions().map { it.content() } - withCodeWhispererServiceInvokedAndWait { states -> - val actualRecommendations = states.recommendationContext.details.map { it.recommendation.content() } + withCodeWhispererServiceInvokedAndWait { session -> + val previews = codewhispererService.getAllSuggestionsPreviewInfo() + val actualRecommendations = previews.map { it.detail.recommendation.content() } assertThat(actualRecommendations).isEqualTo(expectedRecommendations) - states.recommendationContext.details.forEachIndexed { index, context -> + previews.map { it.detail }.forEachIndexed { index, context -> val expectedDiscarded = !pythonResponse.completions()[index].content().startsWith(userInput) val actualDiscarded = context.isDiscarded assertThat(actualDiscarded).isEqualTo(expectedDiscarded) @@ -57,10 +52,11 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { val typeahead = " recommendation" - withCodeWhispererServiceInvokedAndWait { states -> + withCodeWhispererServiceInvokedAndWait { session -> projectRule.fixture.type(typeahead) - assertThat(popupManagerSpy.sessionContext.typeahead).isEqualTo(typeahead) - states.recommendationContext.details.forEachIndexed { index, actualContext -> + val previews = codewhispererService.getAllSuggestionsPreviewInfo() + assertThat(previews[session.selectedIndex].typeahead).isEqualTo(typeahead) + previews.map { it.detail }.forEachIndexed { index, actualContext -> val actualDiscarded = actualContext.isDiscarded val expectedDiscarded = !pythonResponse.completions()[index].content().startsWith(userInput + typeahead) assertThat(actualDiscarded).isEqualTo(expectedDiscarded) @@ -74,9 +70,10 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { addUserInputAfterInvocation(blankUserInput) val userInput = blankUserInput.trimStart() - withCodeWhispererServiceInvokedAndWait { states -> - assertThat(states.recommendationContext.userInputSinceInvocation).isEqualTo(userInput) - states.recommendationContext.details.forEachIndexed { _, actualContext -> + withCodeWhispererServiceInvokedAndWait { session -> + val previews = codewhispererService.getAllSuggestionsPreviewInfo() + assertThat(previews[session.selectedIndex].userInput).isEqualTo(userInput) + previews.map { it.detail }.forEachIndexed { _, actualContext -> assertThat(actualContext.isDiscarded).isEqualTo(false) } } @@ -88,9 +85,10 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { addUserInputAfterInvocation(userInputWithLeadingSpaces) val userInput = userInputWithLeadingSpaces.trimStart() - withCodeWhispererServiceInvokedAndWait { states -> - assertThat(states.recommendationContext.userInputSinceInvocation).isEqualTo(userInput) - states.recommendationContext.details.forEachIndexed { index, actualContext -> + withCodeWhispererServiceInvokedAndWait { session -> + val previews = codewhispererService.getAllSuggestionsPreviewInfo() + assertThat(previews[session.selectedIndex].userInput).isEqualTo(userInput) + previews.map { it.detail }.forEachIndexed { index, actualContext -> val actualDiscarded = actualContext.isDiscarded val expectedDiscarded = !pythonResponse.completions()[index].content().startsWith(userInput) assertThat(actualDiscarded).isEqualTo(expectedDiscarded) @@ -99,12 +97,11 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { } private fun addUserInputAfterInvocation(userInput: String) { - val codewhispererServiceSpy = spy(codewhispererService) val triggerTypeCaptor = argumentCaptor() val editorCaptor = argumentCaptor() val projectCaptor = argumentCaptor() val psiFileCaptor = argumentCaptor() - codewhispererServiceSpy.stub { + codewhispererService.stub { onGeneric { getRequestContext( triggerTypeCaptor.capture(), @@ -113,7 +110,7 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { psiFileCaptor.capture() ) }.doAnswer { - val requestContext = codewhispererServiceSpy.getRequestContext( + val requestContext = codewhispererService.getRequestContext( triggerTypeCaptor.firstValue, editorCaptor.firstValue, projectCaptor.firstValue, @@ -123,6 +120,5 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() { requestContext }.thenCallRealMethod() } - ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererServiceSpy, disposableRule.disposable) } } From 3a097251ada9085c6228ab8aa3805d1597dc0f7e Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Wed, 25 Sep 2024 12:02:35 -0700 Subject: [PATCH 10/11] detekt --- .../codewhisperer/service/CodeWhispererService.kt | 1 - .../aws/toolkits/resources/MessagesBundle.properties | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) 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 391518c3a99..4f4791afd7c 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 @@ -48,7 +48,6 @@ import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.core.coroutines.disposableCoroutineScope import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 1ed9df7272b..488e284efb1 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -850,9 +850,9 @@ codewhisperer.gettingstarted.panel.learn_more=Learn more codewhisperer.gettingstarted.panel.learn_more.with.q=Learn more about Amazon Q and Codewhisperer codewhisperer.gettingstarted.panel.licence_comment=Already have a license? codewhisperer.gettingstarted.panel.login_button=Use for free, no AWS account required -codewhisperer.inline.navigate.next=Navigate to Next Suggestion codewhisperer.inline.accept=Accept Suggestion codewhisperer.inline.force.accept=Force Accept Suggestion +codewhisperer.inline.navigate.next=Navigate to Next Suggestion codewhisperer.inline.navigate.previous=Navigate to Previous Suggestion codewhisperer.language.error={0} is currently not supported by Amazon Q codewhisperer.learn_page.banner.dismiss=Dismiss @@ -877,8 +877,8 @@ codewhisperer.notification.custom.simple.button.got_it=Got it codewhisperer.notification.custom.simple.button.select_another_customization=Select another customization codewhisperer.notification.custom.simple.button.select_customization=Select customization codewhisperer.notification.inline.shortcut_config.content=Check out the default and customize in the keymap settings. -codewhisperer.notification.inline.shortcut_config.title=Configurable Amazon Q suggestion shortcuts codewhisperer.notification.inline.shortcut_config.open_setting=Open keymap settings +codewhisperer.notification.inline.shortcut_config.title=Configurable Amazon Q suggestion shortcuts codewhisperer.notification.remote.ide_unsupported.message=Please update your IDE backend to a 2023.3 or later version to continue using Amazon Q inline suggestions. codewhisperer.notification.remote.ide_unsupported.title=Amazon Q inline suggestion not supported in this IDE version codewhisperer.notification.usage_limit.codescan.warn.content=Amazon Q: You have reached the monthly limit for project scans. @@ -1506,10 +1506,10 @@ loading_resource.still_loading=Resources are still loading plugin.incompatible.fix=Disable incompatible plugins and restart IDE plugin.incompatible.message=The plugin versions for Amazon Q, AWS Toolkit, and AWS Toolkit Core must match or conflicts may occur. plugin.incompatible.title=AWS Plugin Incompatibility -q.beta.notification.end.title="Amazon Q current beta period ended" q.beta.notification.end.message="The current beta period has ended on {0}, please switch to the marketplace version to continue using Amazon Q." -q.beta.notification.welcome.title="Welcome to Amazon Q Plugin Beta" +q.beta.notification.end.title="Amazon Q current beta period ended" q.beta.notification.welcome.message="Thank you for participating in Amazon Q beta plugin testing. Plugin auto-update is always turned on to ensure the best beta experience." +q.beta.notification.welcome.title="Welcome to Amazon Q Plugin Beta" q.connection.disconnected=You don't have access to Amazon Q. Please authenticate to get started. q.connection.expired=Your Amazon Q session has timed out. Re-authenticate to continue. q.connection.invalid=You don't have access to Amazon Q. Please authenticate to get started. From 6370f4b25ed83cfc295b547a8b5f629dd97e18c3 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 26 Sep 2024 13:49:59 -0700 Subject: [PATCH 11/11] use project userData to store session instead of passing in local states to actions --- .../actions/CodeWhispererAcceptAction.kt | 9 +++++---- .../actions/CodeWhispererForceAcceptAction.kt | 9 --------- .../actions/CodeWhispererNavigateNextAction.kt | 6 ++---- .../actions/CodeWhispererNavigatePrevAction.kt | 6 ++---- .../codewhisperer/popup/CodeWhispererPopupManager.kt | 11 ++--------- .../service/CodeWhispererInvocationStatus.kt | 1 + .../service/CodeWhispererRecommendationManager.kt | 1 - .../codewhisperer/service/CodeWhispererService.kt | 3 ++- 8 files changed, 14 insertions(+), 32 deletions(-) delete mode 100644 plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt 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 index 6b31b048b53..cce45029baf 100644 --- 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 @@ -9,14 +9,12 @@ 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.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.resources.message open class CodeWhispererAcceptAction(title: String = message("codewhisperer.inline.accept")) : AnAction(title), DumbAware { - var sessionContext: SessionContext? = null - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT override fun update(e: AnActionEvent) { @@ -25,10 +23,13 @@ open class CodeWhispererAcceptAction(title: String = message("codewhisperer.inli } override fun actionPerformed(e: AnActionEvent) { - val sessionContext = sessionContext ?: return + val sessionContext = e.project?.getUserData(CodeWhispererService.KEY_SESSION_CONTEXT) ?: return if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED ).beforeAccept(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/CodeWhispererForceAcceptAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt deleted file mode 100644 index fbea151598b..00000000000 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererForceAcceptAction.kt +++ /dev/null @@ -1,9 +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 software.aws.toolkits.resources.message - -// 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/CodeWhispererNavigateNextAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt index de7a0cbd382..3e091fd145c 100644 --- 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 @@ -9,14 +9,12 @@ 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.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.resources.message class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.navigate.next")), DumbAware { - var sessionContext: SessionContext? = null - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT override fun update(e: AnActionEvent) { @@ -25,7 +23,7 @@ class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.n } override fun actionPerformed(e: AnActionEvent) { - val sessionContext = sessionContext ?: return + val sessionContext = e.project?.getUserData(CodeWhispererService.KEY_SESSION_CONTEXT) ?: return if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED 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 index bc3c10a9948..94dd8ed57e5 100644 --- 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 @@ -9,14 +9,12 @@ 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.model.SessionContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus +import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService import software.aws.toolkits.resources.message class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.navigate.previous")), DumbAware { - var sessionContext: SessionContext? = null - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT override fun update(e: AnActionEvent) { @@ -25,7 +23,7 @@ class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.n } override fun actionPerformed(e: AnActionEvent) { - val sessionContext = sessionContext ?: return + val sessionContext = e.project?.getUserData(CodeWhispererService.KEY_SESSION_CONTEXT) ?: return if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return ApplicationManager.getApplication().messageBus.syncPublisher( CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index 444804b0077..8fd26b55344 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -33,6 +33,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.Key import com.intellij.openapi.wm.WindowManager import com.intellij.ui.ComponentUtil import com.intellij.ui.awt.RelativePoint @@ -383,15 +384,7 @@ class CodeWhispererPopupManager { private fun setPopupActionHandlers(sessionContext: SessionContext) { val actionManager = EditorActionManager.getInstance() - // TODO: find a better way to pass in the local sessionContext for the handler to know the session state - val prevAction = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous") as CodeWhispererNavigatePrevAction - prevAction.sessionContext = sessionContext - val nextAction = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next") as CodeWhispererNavigateNextAction - nextAction.sessionContext = sessionContext - val acceptAction = ActionManager.getInstance().getAction("codewhisperer.inline.accept") as CodeWhispererAcceptAction - acceptAction.sessionContext = sessionContext - val forceAcceptAction = ActionManager.getInstance().getAction("codewhisperer.inline.force.accept") as CodeWhispererForceAcceptAction - forceAcceptAction.sessionContext = sessionContext + sessionContext.project.putUserData(CodeWhispererService.KEY_SESSION_CONTEXT, sessionContext) setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, sessionContext), sessionContext) setPopupActionHandler(ACTION_EDITOR_ESCAPE, CodeWhispererPopupEscHandler(sessionContext), sessionContext) 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 4c58139b354..5bf5852bcc8 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 @@ -27,6 +27,7 @@ class CodeWhispererInvocationStatus { fun startInvocation() { isInvokingService.set(true) + ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(true) LOG.debug { "Starting CodeWhisperer invocation" } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt index d05f28e42e3..fb574f47c17 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt @@ -17,7 +17,6 @@ import kotlin.math.min @Service class CodeWhispererRecommendationManager { - var states: MutableList = mutableListOf() fun reformatReference(requestContext: RequestContext, recommendation: Completion): Completion { // startOffset is the offset at the start of user input since invocation val invocationStartOffset = requestContext.caretPosition.offset 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 4f4791afd7c..ed44be831b6 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 @@ -17,6 +17,7 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.Key import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.util.concurrency.annotations.RequiresEdt @@ -845,7 +846,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { "CodeWhisperer intelliSense popup on hover", CodeWhispererIntelliSenseOnHoverListener::class.java ) - val DATA_KEY_SESSION = DataKey.create("codewhisperer.session") + val KEY_SESSION_CONTEXT = Key.create("codewhisperer.session") fun getInstance(): CodeWhispererService = service() const val KET_SESSION_ID = "x-amzn-SessionId"