diff --git a/.changes/next-release/feature-2c399a8b-1bee-48eb-8674-96fac8004d5f.json b/.changes/next-release/feature-2c399a8b-1bee-48eb-8674-96fac8004d5f.json
new file mode 100644
index 0000000000..23d7a5801e
--- /dev/null
+++ b/.changes/next-release/feature-2c399a8b-1bee-48eb-8674-96fac8004d5f.json
@@ -0,0 +1,4 @@
+{
+ "type" : "feature",
+ "description" : "Inline Auto trigger will now happen more consistently and will not conflict with JetBrains code completion."
+}
\ No newline at end of file
diff --git a/.changes/next-release/feature-8414d10c-d000-4488-9bc7-3a4616b9c7d5.json b/.changes/next-release/feature-8414d10c-d000-4488-9bc7-3a4616b9c7d5.json
new file mode 100644
index 0000000000..1c949bd6b4
--- /dev/null
+++ b/.changes/next-release/feature-8414d10c-d000-4488-9bc7-3a4616b9c7d5.json
@@ -0,0 +1,4 @@
+{
+ "type" : "feature",
+ "description" : "The key shortcuts for Q inline suggestions are now configurable from keymap settings. Default key shortcuts for navigating through suggestions are changed from left/right arrow keys to option(alt) + [ and option(alt) + ], respectively."
+}
\ No newline at end of file
diff --git a/.changes/next-release/feature-8be25635-8a25-4526-9f1c-515685fa32b4.json b/.changes/next-release/feature-8be25635-8a25-4526-9f1c-515685fa32b4.json
new file mode 100644
index 0000000000..1db61ad2fb
--- /dev/null
+++ b/.changes/next-release/feature-8be25635-8a25-4526-9f1c-515685fa32b4.json
@@ -0,0 +1,4 @@
+{
+ "type" : "feature",
+ "description" : "The Q suggestion inline popup will now hide by default and will show when the user hovers over the suggestion text, the IDE code suggestion popup will also appear to be more transparent to unblock seeing the multi-line suggestions."
+}
\ No newline at end of file
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 0c3e05ea1f..00bee1e0ed 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
@@ -5,12 +5,8 @@
-
-
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 d1c96ed0d6..cce45029ba 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
@@ -10,8 +10,8 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.DumbAware
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
+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 {
@@ -19,12 +19,12 @@ open class CodeWhispererAcceptAction(title: String = message("codewhisperer.inli
override fun update(e: AnActionEvent) {
e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null &&
- CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()
+ CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
}
override fun actionPerformed(e: AnActionEvent) {
- val sessionContext = e.project?.getUserData(CodeWhispererServiceNew.KEY_SESSION_CONTEXT) ?: return
- if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) 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)
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 8c81f986f5..59ee7dcd7c 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
@@ -7,77 +7,45 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
import com.intellij.openapi.actionSystem.ActionPromoter
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.editor.actionSystem.EditorAction
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
-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.service.CodeWhispererInvocationStatusNew
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
class CodeWhispererActionPromoter : ActionPromoter {
override fun promote(actions: MutableList, context: DataContext): MutableList {
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- val results = actions.toMutableList()
- if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) return results
-
- results.sortWith { a, b ->
- if (isCodeWhispererForceAction(a)) {
- return@sortWith -1
- } else if (isCodeWhispererForceAction(b)) {
- return@sortWith 1
- }
-
- if (a is ChooseItemAction) {
- return@sortWith -1
- } else if (b is ChooseItemAction) {
- return@sortWith 1
- }
+ val results = actions.toMutableList()
+ if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return results
- if (isCodeWhispererAcceptAction(a)) {
- return@sortWith -1
- } else if (isCodeWhispererAcceptAction(b)) {
- return@sortWith 1
- }
+ results.sortWith { a, b ->
+ if (isCodeWhispererForceAction(a)) {
+ return@sortWith -1
+ } else if (isCodeWhispererForceAction(b)) {
+ return@sortWith 1
+ }
- 0
+ if (a is ChooseItemAction) {
+ return@sortWith -1
+ } else if (b is ChooseItemAction) {
+ return@sortWith 1
}
- return results
- }
- val results = actions.toMutableList()
- results.sortWith { a, b ->
- if (isCodeWhispererPopupAction(a)) {
+
+ if (isCodeWhispererAcceptAction(a)) {
return@sortWith -1
- } else if (isCodeWhispererPopupAction(b)) {
+ } else if (isCodeWhispererAcceptAction(b)) {
return@sortWith 1
- } else {
- 0
}
+
+ 0
}
return results
}
private fun isCodeWhispererAcceptAction(action: AnAction): Boolean =
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- action is CodeWhispererAcceptAction
- } else {
- 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 =
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- action is CodeWhispererNavigateNextAction || action is CodeWhispererNavigatePrevAction
- } else {
- action is EditorAction && (
- action.handler is CodeWhispererPopupRightArrowHandler ||
- action.handler is CodeWhispererPopupLeftArrowHandler
- )
- }
-
- private fun isCodeWhispererPopupAction(action: AnAction): Boolean =
- isCodeWhispererAcceptAction(action) || isCodeWhispererNavigateAction(action)
+ action is CodeWhispererNavigateNextAction || action is CodeWhispererNavigatePrevAction
private fun isCodeWhispererForceAction(action: AnAction): Boolean =
isCodeWhispererForceAcceptAction(action) || isCodeWhispererNavigateAction(action)
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt
index 9efb2d0f18..ed02c8803a 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
@@ -10,8 +10,8 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.DumbAware
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
+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 {
@@ -20,12 +20,12 @@ class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.n
override fun update(e: AnActionEvent) {
e.presentation.isEnabled = e.project != null &&
e.getData(CommonDataKeys.EDITOR) != null &&
- CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()
+ CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
}
override fun actionPerformed(e: AnActionEvent) {
- val sessionContext = e.project?.getUserData(CodeWhispererServiceNew.KEY_SESSION_CONTEXT) ?: return
- if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) 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
).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
index 633dd52137..118f773821 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
@@ -10,8 +10,8 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.DumbAware
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
+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 {
@@ -20,12 +20,12 @@ class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.n
override fun update(e: AnActionEvent) {
e.presentation.isEnabled = e.project != null &&
e.getData(CommonDataKeys.EDITOR) != null &&
- CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()
+ CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
}
override fun actionPerformed(e: AnActionEvent) {
- val sessionContext = e.project?.getUserData(CodeWhispererServiceNew.KEY_SESSION_CONTEXT) ?: return
- if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) 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
).navigatePrevious(sessionContext)
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt
index 97fcc101b8..c5f4a54670 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererRecommendationAction.kt
@@ -10,12 +10,10 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.Key
import kotlinx.coroutines.Job
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
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.CodeWhispererServiceNew
import software.aws.toolkits.resources.message
import software.aws.toolkits.telemetry.CodewhispererTriggerType
import java.util.concurrent.atomic.AtomicReference
@@ -32,23 +30,12 @@ class CodeWhispererRecommendationAction : AnAction(message("codewhisperer.trigge
latencyContext.codewhispererPreprocessingStart = System.nanoTime()
latencyContext.codewhispererEndToEndStart = System.nanoTime()
val editor = e.getRequiredData(CommonDataKeys.EDITOR)
- if (!(
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- CodeWhispererServiceNew.getInstance().canDoInvocation(editor, CodewhispererTriggerType.OnDemand)
- } else {
- CodeWhispererService.getInstance().canDoInvocation(editor, CodewhispererTriggerType.OnDemand)
- }
- )
- ) {
+ if (!CodeWhispererService.getInstance().canDoInvocation(editor, CodewhispererTriggerType.OnDemand)) {
return
}
val triggerType = TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown())
- val job = if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- CodeWhispererServiceNew.getInstance().showRecommendationsInPopup(editor, triggerType, latencyContext)
- } else {
- CodeWhispererService.getInstance().showRecommendationsInPopup(editor, triggerType, latencyContext)
- }
+ val job = CodeWhispererService.getInstance().showRecommendationsInPopup(editor, triggerType, latencyContext)
e.getData(CommonDataKeys.EDITOR)?.getUserData(ACTION_JOB_KEY)?.set(job)
}
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 27feadf917..11fcf54850 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
@@ -44,10 +44,9 @@ import software.aws.toolkits.jetbrains.services.amazonq.codeWhispererUserContext
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.SessionContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.RequestContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.ResponseContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getTelemetryOptOutPreference
import software.aws.toolkits.jetbrains.services.codewhisperer.util.transform
@@ -90,6 +89,7 @@ interface CodeWhispererClientAdaptor : Disposable {
fun listAvailableCustomizations(): List
fun sendUserTriggerDecisionTelemetry(
+ sessionContext: SessionContext,
requestContext: RequestContext,
responseContext: ResponseContext,
completionType: CodewhispererCompletionType,
@@ -100,18 +100,6 @@ interface CodeWhispererClientAdaptor : Disposable {
acceptedCharCount: Int,
): SendTelemetryEventResponse
- fun sendUserTriggerDecisionTelemetry(
- sessionContext: SessionContextNew,
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- completionType: CodewhispererCompletionType,
- suggestionState: CodewhispererSuggestionState,
- suggestionReferenceCount: Int,
- lineCount: Int,
- numberOfRecommendations: Int,
- acceptedCharCount: Int,
- ): SendTelemetryEventResponse
-
fun sendCodePercentageTelemetry(
language: CodeWhispererProgrammingLanguage,
customizationArn: String?,
@@ -302,6 +290,7 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
}
override fun sendUserTriggerDecisionTelemetry(
+ sessionContext: SessionContext,
requestContext: RequestContext,
responseContext: ResponseContext,
completionType: CodewhispererCompletionType,
@@ -310,53 +299,6 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
lineCount: Int,
numberOfRecommendations: Int,
acceptedCharCount: Int,
- ): SendTelemetryEventResponse {
- val fileContext = requestContext.fileContextInfo
- val programmingLanguage = fileContext.programmingLanguage
- var e2eLatency = requestContext.latencyContext.getCodeWhispererEndToEndLatency()
-
- // When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value
- // and client side will set this value to 0.0.
- if (suggestionState != CodewhispererSuggestionState.Accept &&
- suggestionState != CodewhispererSuggestionState.Reject
- ) {
- e2eLatency = 0.0
- }
-
- return bearerClient().sendTelemetryEvent { requestBuilder ->
- requestBuilder.telemetryEvent { telemetryEventBuilder ->
- telemetryEventBuilder.userTriggerDecisionEvent {
- it.requestId(requestContext.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.perceivedLatencyMilliseconds(requestContext.latencyContext.perceivedLatency)
- it.suggestionState(suggestionState.toCodeWhispererSdkType())
- it.timestamp(Instant.now())
- it.suggestionReferenceCount(suggestionReferenceCount)
- it.generatedLine(lineCount)
- it.customizationArn(requestContext.customizationArn)
- it.numberOfRecommendations(numberOfRecommendations)
- it.acceptedCharacterCount(acceptedCharCount)
- }
- }
- requestBuilder.optOutPreference(getTelemetryOptOutPreference())
- requestBuilder.userContext(codeWhispererUserContext())
- }
- }
-
- override fun sendUserTriggerDecisionTelemetry(
- sessionContext: SessionContextNew,
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- completionType: CodewhispererCompletionType,
- suggestionState: CodewhispererSuggestionState,
- suggestionReferenceCount: Int,
- lineCount: Int,
- numberOfRecommendations: Int,
- acceptedCharCount: Int,
): SendTelemetryEventResponse {
val fileContext = requestContext.fileContextInfo
val programmingLanguage = fileContext.programmingLanguage
@@ -686,6 +628,8 @@ private fun CodewhispererSuggestionState.toCodeWhispererSdkType() = when {
this == CodewhispererSuggestionState.Reject -> SuggestionState.REJECT
this == CodewhispererSuggestionState.Empty -> SuggestionState.EMPTY
this == CodewhispererSuggestionState.Discard -> SuggestionState.DISCARD
+ this == CodewhispererSuggestionState.Ignore -> SuggestionState.MERGE
+ this == CodewhispererSuggestionState.Unseen -> SuggestionState.MERGE
else -> SuggestionState.UNKNOWN_TO_SDK_VERSION
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorListener.kt
index 323e66dfa4..37c97107bf 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorListener.kt
@@ -9,11 +9,9 @@ import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.fileEditor.FileDocumentManager
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled
import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererCodeCoverageTracker
class CodeWhispererEditorListener : EditorFactoryListener {
@@ -31,11 +29,7 @@ class CodeWhispererEditorListener : EditorFactoryListener {
// the most accurate code percentage data.
override fun documentChanged(event: DocumentEvent) {
if (!isCodeWhispererEnabled(project)) return
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- CodeWhispererInvocationStatusNew.getInstance().documentChanged()
- } else {
- CodeWhispererInvocationStatus.getInstance().documentChanged()
- }
+ CodeWhispererInvocationStatus.getInstance().documentChanged()
CodeWhispererCodeCoverageTracker.getInstance(project, language).apply {
activateTrackerIfNotActive()
documentChanged(event)
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 22c8c2176b..8fc7efc725 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,9 +11,9 @@ 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
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
@@ -23,17 +23,21 @@ import java.util.Stack
@Service
class CodeWhispererEditorManager {
- fun updateEditorWithRecommendation(states: InvocationContext, sessionContext: SessionContext) {
- val (requestContext, responseContext, recommendationContext) = states
- val (project, editor) = requestContext
+ 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) = sessionContext
val document = editor.document
val primaryCaret = editor.caretModel.primaryCaret
- val selectedIndex = sessionContext.selectedIndex
- val typeahead = sessionContext.typeahead
- val detail = recommendationContext.details[selectedIndex]
+ val typeahead = preview.typeahead
+ val detail = preview.detail
+ val userInput = preview.userInput
val reformatted = CodeWhispererPopupManager.getInstance().getReformattedRecommendation(
detail,
- recommendationContext.userInputSinceInvocation
+ userInput
)
val remainingRecommendation = reformatted.substring(typeahead.length)
val originalOffset = primaryCaret.offset - typeahead.length
@@ -43,6 +47,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)
@@ -67,7 +73,7 @@ class CodeWhispererEditorManager {
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED,
- ).afterAccept(states, sessionContext, rangeMarker)
+ ).afterAccept(states, previews, sessionContext, rangeMarker)
}
}
}
@@ -117,7 +123,9 @@ class CodeWhispererEditorManager {
totalDocLengthChecked < lineText.length &&
totalDocLengthChecked < recommendation.length
) {
+ var shouldContinue = true
val currentDocChar = lineText[totalDocLengthChecked]
+
if (!isMatchingSymbol(currentDocChar)) {
// currentDocChar is not a matching symbol, so we try to compare the remaining strings as a last step to match
val recommendationRemaining = recommendation.substring(current)
@@ -133,19 +141,20 @@ class CodeWhispererEditorManager {
totalDocLengthChecked++
// find symbol in the recommendation that will match this
- while (current < recommendation.length) {
+ while (current < recommendation.length && shouldContinue) {
val char = recommendation[current]
current++
// if char isn't a paired symbol, or it is, but it's not the matching currentDocChar or
// the opening version of it, then we're done
- if (!isMatchingSymbol(char) || (char != currentDocChar && PAIRED_BRACKETS[char] != currentDocChar)) {
- continue
- }
-
- // if char is an opening bracket, push it to the stack
- if (PAIRED_BRACKETS[char] == currentDocChar) {
- bracketsStack.push(char)
+ if (!isMatchingSymbol(char) ||
+ (char != currentDocChar && PAIRED_BRACKETS[char] != currentDocChar) ||
+ PAIRED_BRACKETS[char] == currentDocChar
+ ) {
+ // if char is an opening bracket, push it to the stack
+ if (PAIRED_BRACKETS[char] == currentDocChar) {
+ bracketsStack.push(char)
+ }
continue
}
@@ -155,13 +164,13 @@ class CodeWhispererEditorManager {
// on the stack
if (char.isWhitespace()) {
result.add(current to caretOffset + totalDocLengthChecked)
- break
+ shouldContinue = false
} else if (bracketsStack.isNotEmpty() && PAIRED_BRACKETS[bracketsStack.peek()] == char) {
bracketsStack.pop()
} else if (quotesStack.isNotEmpty() && quotesStack.peek().first == char) {
result.add(quotesStack.pop().second)
result.add(current to caretOffset + totalDocLengthChecked)
- break
+ shouldContinue = false
} else {
// char does not have a matching opening symbol in the stack, if it's a (opening) bracket,
// immediately add it to the result; if it's a quote, push it to the stack
@@ -170,7 +179,7 @@ class CodeWhispererEditorManager {
} else {
result.add(current to caretOffset + totalDocLengthChecked)
}
- break
+ shouldContinue = false
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManagerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManagerNew.kt
deleted file mode 100644
index a244c31232..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManagerNew.kt
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codewhisperer.editor
-
-import com.intellij.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.util.TextRange
-import com.intellij.psi.PsiDocumentManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManagerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryServiceNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CaretMovement
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_BRACKETS
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_QUOTES
-import java.time.Instant
-import java.util.Stack
-
-@Service
-class CodeWhispererEditorManagerNew {
- fun updateEditorWithRecommendation(sessionContext: SessionContextNew) {
- val previews = CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo()
- val selectedIndex = sessionContext.selectedIndex
- val preview = previews[selectedIndex]
- val states = CodeWhispererServiceNew.getInstance().getAllPaginationSessions()[preview.jobId] ?: return
- val (requestContext, responseContext) = states
- val (project, editor) = sessionContext
- val document = editor.document
- val primaryCaret = editor.caretModel.primaryCaret
- val typeahead = preview.typeahead
- val detail = preview.detail
- val userInput = preview.userInput
- val reformatted = CodeWhispererPopupManagerNew.getInstance().getReformattedRecommendation(
- detail,
- userInput
- )
- val remainingRecommendation = reformatted.substring(typeahead.length)
- val originalOffset = primaryCaret.offset - typeahead.length
-
- val endOffset = primaryCaret.offset + remainingRecommendation.length
-
- 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)
- primaryCaret.moveToOffset(endOffset + detail.rightOverlap.length)
- }
-
- ApplicationManager.getApplication().invokeLater {
- WriteCommandAction.runWriteCommandAction(project) {
- val rangeMarker = document.createRangeMarker(originalOffset, endOffset, true)
-
- CodeWhispererTelemetryServiceNew.getInstance().enqueueAcceptedSuggestionEntry(
- detail.requestId,
- requestContext,
- responseContext,
- Instant.now(),
- PsiDocumentManager.getInstance(project).getPsiFile(document)?.virtualFile,
- rangeMarker,
- remainingRecommendation,
- selectedIndex,
- detail.completionType
- )
-
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED,
- ).afterAccept(states, previews, sessionContext, rangeMarker)
- }
- }
- }
-
- private fun isMatchingSymbol(symbol: Char): Boolean =
- PAIRED_BRACKETS.containsKey(symbol) || PAIRED_BRACKETS.containsValue(symbol) || PAIRED_QUOTES.contains(symbol) ||
- symbol.isWhitespace()
-
- fun getUserInputSinceInvocation(editor: Editor, invocationOffset: Int): String {
- val currentOffset = editor.caretModel.primaryCaret.offset
- return editor.document.getText(TextRange(invocationOffset, currentOffset))
- }
-
- fun getCaretMovement(editor: Editor, caretPosition: CaretPosition): CaretMovement {
- val oldOffset = caretPosition.offset
- val newOffset = editor.caretModel.primaryCaret.offset
- return when {
- oldOffset < newOffset -> CaretMovement.MOVE_FORWARD
- oldOffset > newOffset -> CaretMovement.MOVE_BACKWARD
- else -> CaretMovement.NO_CHANGE
- }
- }
-
- fun getMatchingSymbolsFromRecommendation(
- editor: Editor,
- recommendation: String,
- isTruncatedOnRight: Boolean,
- sessionContext: SessionContextNew,
- ): List> {
- val result = mutableListOf>()
- val bracketsStack = Stack()
- val quotesStack = Stack>>()
- val caretOffset = editor.caretModel.primaryCaret.offset
- val document = editor.document
- val lineEndOffset = document.getLineEndOffset(document.getLineNumber(caretOffset))
- val lineText = document.charsSequence.subSequence(caretOffset, lineEndOffset)
-
- var totalDocLengthChecked = 0
- var current = 0
- var shouldContinue = true
-
- result.add(0 to caretOffset)
- result.add(recommendation.length + 1 to lineEndOffset)
-
- if (isTruncatedOnRight) return result
-
- while (current < recommendation.length &&
- totalDocLengthChecked < lineText.length &&
- totalDocLengthChecked < recommendation.length
- ) {
- val currentDocChar = lineText[totalDocLengthChecked]
- if (!isMatchingSymbol(currentDocChar)) {
- // currentDocChar is not a matching symbol, so we try to compare the remaining strings as a last step to match
- val recommendationRemaining = recommendation.substring(current)
- val rightContextRemaining = lineText.subSequence(totalDocLengthChecked, lineText.length).toString()
- if (recommendationRemaining == rightContextRemaining) {
- for (i in 1..recommendation.length - current) {
- result.add(current + i to caretOffset + totalDocLengthChecked + i)
- }
- result.sortBy { it.first }
- }
- break
- }
- totalDocLengthChecked++
-
- // find symbol in the recommendation that will match this
- while (current < recommendation.length && shouldContinue) {
- val char = recommendation[current]
- current++
-
- // if char isn't a paired symbol, or it is, but it's not the matching currentDocChar or
- // the opening version of it, then we're done
- if (!isMatchingSymbol(char) ||
- (char != currentDocChar && PAIRED_BRACKETS[char] != currentDocChar) ||
- PAIRED_BRACKETS[char] == currentDocChar
- ) {
- // if char is an opening bracket, push it to the stack
- if (PAIRED_BRACKETS[char] == currentDocChar) {
- bracketsStack.push(char)
- }
- continue
- }
-
- // char is currentDocChar, it's one of a bracket, a quote, or a whitespace character.
- // If it's a whitespace character, directly add it to the result,
- // if it's a bracket or a quote, check if this char is already having a matching opening symbol
- // on the stack
- if (char.isWhitespace()) {
- result.add(current to caretOffset + totalDocLengthChecked)
- shouldContinue = false
- } else if (bracketsStack.isNotEmpty() && PAIRED_BRACKETS[bracketsStack.peek()] == char) {
- bracketsStack.pop()
- } else if (quotesStack.isNotEmpty() && quotesStack.peek().first == char) {
- result.add(quotesStack.pop().second)
- result.add(current to caretOffset + totalDocLengthChecked)
- shouldContinue = false
- } else {
- // char does not have a matching opening symbol in the stack, if it's a (opening) bracket,
- // immediately add it to the result; if it's a quote, push it to the stack
- if (PAIRED_QUOTES.contains(char)) {
- quotesStack.push(char to (current to caretOffset + totalDocLengthChecked))
- } else {
- result.add(current to caretOffset + totalDocLengthChecked)
- }
- shouldContinue = false
- }
- }
- }
-
- // if there are any symbols left in the stack, add them to the result
- quotesStack.forEach { result.add(it.second) }
- result.sortBy { it.first }
-
- sessionContext.insertEndOffset = result[result.size - 2].second
-
- return result
- }
-
- // example: recommendation: document
- // line1
- // line2
- // line3 line3
- // line4
- // ...
- // number of lines overlapping would be one, and it will be line 3
- fun findOverLappingLines(
- editor: Editor,
- recommendationLines: List,
- isTruncatedOnRight: Boolean,
- sessionContext: SessionContextNew,
- ): Int {
- val caretOffset = editor.caretModel.offset
- if (isTruncatedOnRight) {
- // insertEndOffset value only makes sense when there are matching closing brackets, if there's right context
- // resolution applied, set this value to the current caret offset
- sessionContext.insertEndOffset = caretOffset
- return 0
- }
-
- val text = editor.document.charsSequence
- val document = editor.document
- val textLines = mutableListOf>()
- val caretLine = document.getLineNumber(caretOffset)
- var currentLineNum = caretLine + 1
- val recommendationLinesNotBlank = recommendationLines.filter { it.isNotBlank() }
- while (currentLineNum < document.lineCount && textLines.size < recommendationLinesNotBlank.size) {
- val currentLine = text.subSequence(
- document.getLineStartOffset(currentLineNum),
- document.getLineEndOffset(currentLineNum)
- )
- if (currentLine.isNotBlank()) {
- textLines.add(currentLine.toString() to document.getLineEndOffset(currentLineNum))
- }
- currentLineNum++
- }
-
- val numOfNonEmptyLinesMatching = countNonEmptyLinesMatching(recommendationLinesNotBlank, textLines)
- val numOfLinesMatching = countLinesMatching(recommendationLines, numOfNonEmptyLinesMatching)
- if (numOfNonEmptyLinesMatching > 0) {
- sessionContext.insertEndOffset = textLines[numOfNonEmptyLinesMatching - 1].second
- } else if (recommendationLines.isNotEmpty()) {
- sessionContext.insertEndOffset = document.getLineEndOffset(caretLine)
- }
-
- return numOfLinesMatching
- }
-
- private fun countLinesMatching(lines: List, targetNonEmptyLines: Int): Int {
- var count = 0
- var nonEmptyCount = 0
-
- for (line in lines.asReversed()) {
- if (nonEmptyCount == targetNonEmptyLines) {
- break
- }
- if (line.isNotBlank()) {
- nonEmptyCount++
- }
- count++
- }
- return count
- }
-
- private fun countNonEmptyLinesMatching(recommendationLines: List, textLines: List>): Int {
- // i lines we want to match
- for (i in textLines.size downTo 1) {
- val recommendationStart = recommendationLines.size - i
- var matching = true
- for (j in 0 until i) {
- if (recommendationLines[recommendationStart + j].trimEnd() != textLines[j].first.trimEnd()) {
- matching = false
- break
- }
- }
- if (matching) {
- return i
- }
- }
- return 0
- }
-
- companion object {
- fun getInstance(): CodeWhispererEditorManagerNew = service()
- }
-}
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 0d4f287372..6c3bd789c2 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
@@ -14,25 +14,14 @@ 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.InvocationContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
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.model.SessionContextNew
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()
- LOG.info { "Adding ${imports.size} imports for completions, sessionId: ${states.responseContext.sessionId}" }
- imports.forEach {
- insertImportStatement(states, it)
- }
- }
-
- fun insertImportStatements(states: InvocationContextNew, previews: List, sessionContext: SessionContextNew) {
+ 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 {
@@ -72,38 +61,6 @@ abstract class CodeWhispererImportAdder {
LOG.info { "Added import: $added" }
}
- private fun insertImportStatement(states: InvocationContextNew, import: Import) {
- val project = states.requestContext.project
- val editor = states.requestContext.editor
- val document = editor.document
- val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return
-
- val statement = import.statement()
- LOG.info { "Import statement to be added: $statement" }
- val newImport = createNewImportPsiElement(psiFile, statement)
- if (newImport == null) {
- LOG.debug { "Failed to create the import element using the import string" }
- return
- }
-
- if (!isSupportedImportStyle(newImport)) {
- LOG.debug { "Import statement \"${newImport.text}\" is not supported" }
- return
- }
-
- LOG.debug { "Checking duplicates with existing imports" }
- val hasDuplicate = hasDuplicatedImports(psiFile, editor, newImport)
- if (hasDuplicate) {
- LOG.debug { "Found duplicates with existing imports, not adding the new import" }
- return
- } else {
- LOG.debug { "Found no duplicates with existing imports" }
- }
-
- val added = addImport(psiFile, editor, newImport)
- LOG.info { "Added import: $added" }
- }
-
abstract fun createNewImportPsiElement(psiFile: PsiFile, statement: String): PsiElement?
open fun isSupportedImportStyle(newImport: PsiElement) = true
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 a5abde5d9b..8dbf816823 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
@@ -7,34 +7,14 @@ import com.intellij.openapi.editor.RangeMarker
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
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.model.SessionContextNew
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
object CodeWhispererImportAdderListener : CodeWhispererUserActionListener {
internal val LOG = getLogger()
- override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {
- if (!CodeWhispererSettings.getInstance().isImportAdderEnabled()) {
- LOG.debug { "Import adder not enabled in user settings" }
- return
- }
- val language = states.requestContext.fileContextInfo.programmingLanguage
- if (!language.isImportAdderSupported()) {
- LOG.debug { "Import adder is not supported for $language" }
- return
- }
- val importAdder = CodeWhispererImportAdder.get(language)
- if (importAdder == null) {
- LOG.debug { "No import adder found for $language" }
- return
- }
- importAdder.insertImportStatements(states, sessionContext)
- }
-
- override fun afterAccept(states: InvocationContextNew, previews: List, sessionContext: SessionContextNew, 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
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
deleted file mode 100644
index e72269ce2f..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt
+++ /dev/null
@@ -1,92 +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.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
-
-@Service
-class CodeWhispererInlayManager {
- private val existingInlays = mutableListOf>()
- fun updateInlays(states: InvocationContext, chunks: List) {
- val editor = states.requestContext.editor
- clearInlays()
-
- chunks.forEach { chunk ->
- createCodeWhispererInlays(editor, chunk.inlayOffset, chunk.text, states.popup)
- }
- }
-
- private fun createCodeWhispererInlays(editor: Editor, startOffset: Int, inlayText: String, popup: JBPopup) {
- if (inlayText.isEmpty()) return
- val firstNewlineIndex = inlayText.indexOf("\n")
- val firstLine: String
- val otherLines: String
- if (firstNewlineIndex != -1 && firstNewlineIndex < inlayText.length - 1) {
- firstLine = inlayText.substring(0, firstNewlineIndex)
- otherLines = inlayText.substring(firstNewlineIndex + 1)
- } else {
- firstLine = inlayText
- otherLines = ""
- }
-
- if (firstLine.isNotEmpty()) {
- val firstLineRenderer =
- if (!AppMode.isRemoteDevHost()) {
- CodeWhispererInlayInlineRenderer(firstLine)
- } else {
- InlineCompletionRemoteRendererFactory.createLineInlay(editor, firstLine)
- }
- val inlineInlay = editor.inlayModel.addInlineElement(startOffset, true, firstLineRenderer)
- inlineInlay?.let {
- existingInlays.add(it)
- Disposer.register(popup, it)
- }
- }
-
- if (otherLines.isEmpty()) {
- return
- }
- val otherLinesRenderers =
- if (!AppMode.isRemoteDevHost()) {
- listOf(CodeWhispererInlayBlockRenderer(otherLines))
- } else {
- InlineCompletionRemoteRendererFactory.createBlockInlays(editor, otherLines.split("\n"))
- }
-
- otherLinesRenderers.forEach { otherLinesRenderer ->
- val blockInlay = editor.inlayModel.addBlockElement(
- startOffset,
- true,
- false,
- 0,
- otherLinesRenderer
- )
- blockInlay?.let {
- existingInlays.add(it)
- Disposer.register(popup, it)
- }
- }
- }
-
- fun clearInlays() {
- existingInlays.forEach {
- Disposer.dispose(it)
- }
- existingInlays.clear()
- }
-
- companion object {
- @JvmStatic
- fun getInstance(): CodeWhispererInlayManager = service()
- }
-}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt
index 339b5aa8df..dad18f27a4 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManagerNew.kt
@@ -10,12 +10,12 @@ import com.intellij.openapi.editor.EditorCustomElementRenderer
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.util.Disposer
import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
@Service
class CodeWhispererInlayManagerNew {
private val existingInlays = mutableListOf>()
- fun updateInlays(sessionContext: SessionContextNew, chunks: List) {
+ fun updateInlays(sessionContext: SessionContext, chunks: List) {
clearInlays()
chunks.forEach { chunk ->
@@ -23,7 +23,7 @@ class CodeWhispererInlayManagerNew {
}
}
- private fun createCodeWhispererInlays(sessionContext: SessionContextNew, startOffset: Int, inlayText: String) {
+ private fun createCodeWhispererInlays(sessionContext: SessionContext, startOffset: Int, inlayText: String) {
if (inlayText.isEmpty()) return
val editor = sessionContext.editor
val firstNewlineIndex = inlayText.indexOf("\n")
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 91da779ab1..92438d225d 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
@@ -12,22 +12,20 @@ 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 kotlinx.coroutines.Deferred
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.amazonq.SUPPLEMENTAL_CONTEXT_TIMEOUT
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.CodeWhispererPopupManagerNew
+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.CodeWhispererIntelliSenseOnHoverListener
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryServiceNew
+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.util.CodeWhispererConstants
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.setIntelliSensePopupAlpha
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CrossFileStrategy
@@ -96,14 +94,7 @@ data class SupplementalContextInfo(
}
data class RecommendationContext(
- val details: List,
- val userInputOriginal: String,
- val userInputSinceInvocation: String,
- val position: VisualPosition,
-)
-
-data class RecommendationContextNew(
- val details: MutableList,
+ val details: MutableList,
val userInputOriginal: String,
val userInputSinceInvocation: String,
val position: VisualPosition,
@@ -113,7 +104,7 @@ data class RecommendationContextNew(
data class PreviewContext(
val jobId: Int,
- val detail: DetailContextNew,
+ val detail: DetailContext,
val userInput: String,
val typeahead: String,
)
@@ -126,31 +117,11 @@ data class DetailContext(
val isTruncatedOnRight: Boolean,
val rightOverlap: String = "",
val completionType: CodewhispererCompletionType,
-)
-
-data class DetailContextNew(
- val requestId: String,
- val recommendation: Completion,
- val reformatted: Completion,
- val isDiscarded: Boolean,
- val isTruncatedOnRight: Boolean,
- val rightOverlap: String = "",
- val completionType: CodewhispererCompletionType,
var hasSeen: Boolean = false,
var isAccepted: Boolean = false,
)
data class SessionContext(
- val typeahead: String = "",
- val typeaheadOriginal: String = "",
- val selectedIndex: Int = 0,
- val seen: MutableSet = mutableSetOf(),
- val isFirstTimeShowingPopup: Boolean = true,
- var toBeRemovedHighlighter: RangeHighlighter? = null,
- var insertEndOffset: Int = -1,
-)
-
-data class SessionContextNew(
val project: Project,
val editor: Editor,
var popup: JBPopup? = null,
@@ -165,10 +136,10 @@ data class SessionContextNew(
private var isDisposed = false
init {
project.messageBus.connect(this).subscribe(
- CodeWhispererServiceNew.CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER,
+ CodeWhispererService.CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER,
object : CodeWhispererIntelliSenseOnHoverListener {
override fun onEnter() {
- CodeWhispererPopupManagerNew.getInstance().bringSuggestionInlayToFront(editor, popup, opposite = true)
+ CodeWhispererPopupManager.getInstance().bringSuggestionInlayToFront(editor, popup, opposite = true)
}
}
)
@@ -176,13 +147,13 @@ data class SessionContextNew(
@RequiresEdt
override fun dispose() {
- CodeWhispererTelemetryServiceNew.getInstance().sendUserDecisionEventForAll(
+ CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(
this,
hasAccepted,
- CodeWhispererInvocationStatusNew.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) }
+ CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) }
)
setIntelliSensePopupAlpha(editor, 0f)
- CodeWhispererInvocationStatusNew.getInstance().setDisplaySessionActive(false)
+ CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(false)
if (hasAccepted) {
popup?.closeOk(null)
@@ -191,7 +162,7 @@ data class SessionContextNew(
}
popup?.let { Disposer.dispose(it) }
popup = null
- CodeWhispererInvocationStatusNew.getInstance().finishInvocation()
+ CodeWhispererInvocationStatus.getInstance().finishInvocation()
isDisposed = true
}
@@ -215,15 +186,6 @@ data class InvocationContext(
val requestContext: RequestContext,
val responseContext: ResponseContext,
val recommendationContext: RecommendationContext,
- val popup: JBPopup,
-) : Disposable {
- override fun dispose() {}
-}
-
-data class InvocationContextNew(
- val requestContext: RequestContextNew,
- val responseContext: ResponseContext,
- val recommendationContext: RecommendationContextNew,
) : Disposable {
private var isDisposed = false
@@ -234,17 +196,11 @@ data class InvocationContextNew(
fun isDisposed() = isDisposed
}
+
data class WorkerContext(
val requestContext: RequestContext,
val responseContext: ResponseContext,
val response: GenerateCompletionsResponse,
- val popup: JBPopup,
-)
-
-data class WorkerContextNew(
- val requestContext: RequestContextNew,
- val responseContext: ResponseContext,
- val response: GenerateCompletionsResponse,
)
data class CodeScanTelemetryEvent(
@@ -327,3 +283,37 @@ data class TryExampleRowContext(
val description: String,
val filename: String?,
)
+
+data class RequestContext(
+ val project: Project,
+ val editor: Editor,
+ val triggerTypeInfo: TriggerTypeInfo,
+ val caretPosition: CaretPosition,
+ val fileContextInfo: FileContextInfo,
+ private val supplementalContextDeferred: Deferred,
+ val connection: ToolkitConnection?,
+ val customizationArn: String?,
+) {
+ // TODO: should make the entire getRequestContext() suspend function instead of making supplemental context only
+ var supplementalContext: SupplementalContextInfo? = null
+ private set
+ get() = when (field) {
+ null -> {
+ if (!supplementalContextDeferred.isCompleted) {
+ error("attempt to access supplemental context before awaiting the deferred")
+ } else {
+ null
+ }
+ }
+ else -> field
+ }
+
+ suspend fun awaitSupplementalContext(): SupplementalContextInfo? {
+ supplementalContext = supplementalContextDeferred.await()
+ return supplementalContext
+ }
+}
+
+data class ResponseContext(
+ val sessionId: String,
+)
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 145e3cf624..8d4e288b5f 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,38 +5,11 @@ 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.model.InvocationContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
-import java.time.Duration
-import java.time.Instant
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
-class CodeWhispererPopupListener(private val states: InvocationContext) : 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,
- CodeWhispererPopupManager.getInstance().sessionContext,
- event.isOk,
- CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) }
- )
-
- CodeWhispererInvocationStatus.getInstance().setPopupActive(false)
- }
-}
-
-class CodeWhispererPopupListenerNew : JBPopupListener {
+class CodeWhispererPopupListener : JBPopupListener {
override fun onClosed(event: LightweightWindowEvent) {
super.onClosed(event)
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(event.isOk)
+ 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 29ffb766a8..60482a47e7 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,14 +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_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.actionSystem.IdeActions.ACTION_EDITOR_ESCAPE
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.components.Service
@@ -27,9 +24,10 @@ 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
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.JBPopupFactory
@@ -39,6 +37,7 @@ 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 software.amazon.awssdk.services.codewhispererruntime.model.Import
@@ -51,16 +50,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.InvocationContextNew
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.model.SessionContextNew
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.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.CodeWhispererPopupEscHandler
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
@@ -68,7 +63,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.telemetry.CodeWhispererTelemetryService
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
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
@@ -84,14 +79,10 @@ import javax.swing.JLabel
@Service
class CodeWhispererPopupManager {
- val popupComponents = CodeWhispererPopupComponents()
+ val popupComponents = CodeWhispererPopupComponentsNew()
var shouldListenerCancelPopup: Boolean = true
private set
- var sessionContext = SessionContext()
- private set
-
- private var myPopup: JBPopup? = null
init {
// Listen for global scheme changes
@@ -118,113 +109,106 @@ class CodeWhispererPopupManager {
)
}
- fun changeStates(
- states: InvocationContext,
- indexChange: Int,
+ @RequiresEdt
+ 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 isReverse = indexChange < 0
+ val selectedIndex = findNewSelectedIndex(isReverse, sessionContext.selectedIndex + indexChange)
+
+ sessionContext.selectedIndex = selectedIndex
+
+ ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
+ sessionContext
+ )
+ }
+
+ @RequiresEdt
+ fun changeStatesForTypeahead(
+ sessionContext: SessionContext,
typeaheadChange: String,
typeaheadAdded: Boolean,
- recommendationAdded: Boolean = false,
) {
- val (_, _, recommendationContext, popup) = states
- val (details) = recommendationContext
+ if (!updateTypeahead(typeaheadChange, typeaheadAdded)) return
+ if (!updateSessionSelectedIndex(sessionContext)) return
+
+ ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
+ sessionContext
+ )
+ }
+
+ @RequiresEdt
+ 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 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
- )
- }
- 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,
- details,
- userInput,
- sessionContext.selectedIndex + indexChange,
- typeaheadOriginal
- )
- if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex], userInput, typeaheadOriginal)) {
- LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" }
- cancelPopup(popup)
- return
+
+ if (!updateSessionSelectedIndex(sessionContext)) return
+ if (sessionContext.popupOffset == -1) {
+ sessionContext.popupOffset = sessionContext.editor.caretModel.offset
}
- val typeahead = resolveTypeahead(states, selectedIndex, typeaheadOriginal)
- val isFirstTimeShowingPopup = indexChange == 0 && typeaheadChange.isEmpty()
- sessionContext = SessionContext(
- typeahead,
- typeaheadOriginal,
- selectedIndex,
- sessionContext.seen,
- isFirstTimeShowingPopup,
- sessionContext.toBeRemovedHighlighter
- )
ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
- states,
sessionContext
)
}
- private fun resolveTypeahead(states: InvocationContext, selectedIndex: Int, typeahead: String): String {
- val recommendation = states.recommendationContext.details[selectedIndex].reformatted.content()
- val userInput = states.recommendationContext.userInputSinceInvocation
- var indexOfFirstNonWhiteSpace = typeahead.indexOfFirst { !it.isWhitespace() }
- if (indexOfFirstNonWhiteSpace == -1) {
- indexOfFirstNonWhiteSpace = typeahead.length
+ private fun updateTypeahead(typeaheadChange: String, typeaheadAdded: Boolean): Boolean {
+ val recommendations = CodeWhispererService.getInstance().getAllPaginationSessions().values.filterNotNull()
+ recommendations.forEach {
+ 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" }
+ CodeWhispererService.getInstance().disposeDisplaySession(false)
+ return false
+ }
+ it.recommendationContext.typeahead.substring(
+ 0,
+ it.recommendationContext.typeahead.length - typeaheadChange.length
+ )
+ }
+ it.recommendationContext.typeahead = newTypeahead
}
+ return true
+ }
- for (i in 0..indexOfFirstNonWhiteSpace) {
- val subTypeahead = typeahead.substring(i)
- if (recommendation.startsWith(userInput + subTypeahead)) return subTypeahead
+ private fun updateSessionSelectedIndex(sessionContext: SessionContext): Boolean {
+ 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 false
}
- return typeahead
+
+ sessionContext.selectedIndex = selectedIndex
+ return true
}
- fun updatePopupPanel(states: InvocationContext, sessionContext: SessionContext) {
- 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 typeaheadOriginal = sessionContext.typeaheadOriginal
- val validCount = getValidCount(details, userInput, typeaheadOriginal)
- val validSelectedIndex = getValidSelectedIndex(details, userInput, selectedIndex, typeaheadOriginal)
+ val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo()
+ if (selectedIndex >= previews.size) return
+ val validCount = getValidCount()
+ val validSelectedIndex = getValidSelectedIndex(selectedIndex)
updateSelectedRecommendationLabelText(validSelectedIndex, validCount)
updateNavigationPanel(validSelectedIndex, validCount)
- updateImportPanel(details[selectedIndex].recommendation.mostRelevantMissingImports())
- updateCodeReferencePanel(states.requestContext.project, details[selectedIndex].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)
-
- val caretPoint = states.requestContext.editor.offsetToXY(states.requestContext.caretPosition.offset)
- sessionContext.seen.add(sessionContext.selectedIndex)
+ fun render(sessionContext: SessionContext, isRecommendationAdded: Boolean) {
+ updatePopupPanel(sessionContext)
// 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
@@ -235,22 +219,16 @@ 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 (!CodeWhispererInvocationStatus.getInstance().isPopupActive()) {
- states.requestContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime()
- states.requestContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime()
- states.requestContext.latencyContext.perceivedLatency =
- states.requestContext.latencyContext.getPerceivedLatency(states.requestContext.triggerTypeInfo.triggerType)
- }
- if (!isRecommendationAdded) {
- showPopup(states, sessionContext, states.popup, caretPoint, overlappingLinesCount)
+ if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) {
+ sessionContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime()
+ sessionContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime()
+ val triggerTypeOfLastTrigger = CodeWhispererService.getInstance().getAllPaginationSessions()
+ .values.filterNotNull().last().requestContext.triggerTypeInfo.triggerType
+ sessionContext.latencyContext.perceivedLatency =
+ sessionContext.latencyContext.getPerceivedLatency(triggerTypeOfLastTrigger)
}
- if (isScrolling ||
- CodeWhispererInvocationStatus.getInstance().hasExistingServiceInvocation() ||
- !sessionContext.isFirstTimeShowingPopup
- ) {
- return
- }
- CodeWhispererTelemetryService.getInstance().sendClientComponentLatencyEvent(states)
+ if (isRecommendationAdded) return
+ showPopup(sessionContext)
}
fun dontClosePopupAndRun(runnable: () -> Unit) {
@@ -262,84 +240,36 @@ class CodeWhispererPopupManager {
}
}
- fun reset() {
- sessionContext = SessionContext()
- }
-
- fun cancelPopup(popup: JBPopup) {
- popup.cancel()
- Disposer.dispose(popup)
- }
-
- fun closePopup(popup: JBPopup) {
- popup.closeOk(null)
- Disposer.dispose(popup)
- }
-
- fun closePopup() {
- myPopup?.let {
- it.closeOk(null)
- Disposer.dispose(it)
+ fun showPopup(sessionContext: SessionContext, force: Boolean = false) {
+ val p = sessionContext.editor.offsetToXY(sessionContext.popupOffset)
+ val popup: JBPopup?
+ if (sessionContext.popup == null) {
+ popup = initPopup()
+ sessionContext.popup = popup
+ CodeWhispererInvocationStatus.getInstance().setPopupStartTimestamp()
+ initPopupListener(sessionContext, popup)
+ } else {
+ popup = sessionContext.popup
}
- }
-
- fun showPopup(
- states: InvocationContext,
- sessionContext: SessionContext,
- popup: JBPopup,
- p: Point,
- overlappingLinesCount: Int,
- ) {
- 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 typeaheadOriginal = sessionContext.typeaheadOriginal
- val typeahead = sessionContext.typeahead
+ 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(detailContexts[selectedIndex], userInput).split("\n").size
- val additionalLines = typeaheadOriginal.split("\n").size - typeahead.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 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)
+ 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
- }
- if (!isSameEditorAsTrigger) {
- LOG.debug { "Current editor no longer has focus, not showing the popup" }
- cancelPopup(popup)
- return
+ if (!editorRect.contains(popupRect)) {
+ // popup location above first line don't work, so don't show the popup
+ shouldHidePopup = true
}
- 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)
- }
+ // popup to always display above the current editing line
+ val popupLocation = Point(p.x, yAboveFirstLine)
val relativePopupLocationToEditor = RelativePoint(editor.contentComponent, popupLocation)
@@ -352,8 +282,11 @@ class CodeWhispererPopupManager {
}
} else {
if (!AppMode.isRemoteDevHost()) {
- popup.show(relativePopupLocationToEditor)
+ if (force && !shouldHidePopup) {
+ 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
@@ -368,22 +301,20 @@ class CodeWhispererPopupManager {
editor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, popupPositionForRemote)
popup.showInBestPositionFor(editor)
}
- val perceivedLatency = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged()
- CodeWhispererTelemetryService.getInstance().sendPerceivedLatencyEvent(
- detailContexts[selectedIndex].requestId,
- states.requestContext,
- states.responseContext,
- perceivedLatency
- )
}
- // popup.popupWindow is null in remote host
- if (!AppMode.isRemoteDevHost()) {
- if (shouldHidePopup) {
- WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 1f)
- } else {
- WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 0.1f)
- }
+ bringSuggestionInlayToFront(editor, popup, !force)
+ }
+
+ 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 as AbstractPopup?)?.popupWindow?.let {
+ WindowManager.getInstance().setAlphaModeRatio(it, qInlinePopupAlpha)
+ }
+ ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component)?.let {
+ WindowManager.getInstance().setAlphaModeRatio(it, intelliSensePopupAlpha)
}
}
@@ -391,162 +322,190 @@ class CodeWhispererPopupManager {
.createComponentPopupBuilder(popupComponents.panel, null)
.setAlpha(0.1F)
.setCancelOnClickOutside(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) {
- addPopupListener(states)
- states.requestContext.editor.scrollingModel.addVisibleAreaListener(CodeWhispererScrollListener(states), states)
- addButtonActionListeners(states)
- addMessageSubscribers(states)
- setPopupActionHandlers(states)
- addComponentListeners(states)
+ 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) {
- val listener = CodeWhispererPopupListener(states)
- states.popup.addListener(listener)
- Disposer.register(states) { states.popup.removeListener(listener) }
+ private fun addPopupListener(popup: JBPopup) {
+ val listener = CodeWhispererPopupListener()
+ popup.addListener(listener)
+ Disposer.register(popup) {
+ popup.removeListener(listener)
+ }
}
- private fun addMessageSubscribers(states: InvocationContext) {
- val connect = ApplicationManager.getApplication().messageBus.connect(states)
+ 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) {
- changeStates(states, 1, "", true)
+ override fun navigateNext(sessionContext: SessionContext) {
+ changeStatesForNavigation(sessionContext, 1)
}
- override fun navigatePrevious(states: InvocationContext) {
- changeStates(states, -1, "", true)
+ override fun navigatePrevious(sessionContext: SessionContext) {
+ changeStatesForNavigation(sessionContext, -1)
}
- override fun backspace(states: InvocationContext, diff: String) {
- changeStates(states, 0, diff, false)
+ override fun backspace(sessionContext: SessionContext, diff: String) {
+ changeStatesForTypeahead(sessionContext, diff, false)
}
- override fun enter(states: InvocationContext, diff: String) {
- changeStates(states, 0, 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)
}
}
- changeStates(states, 0, 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(states.popup)
+ CodeWhispererService.getInstance().disposeDisplaySession(true)
}
}
)
}
- private fun addButtonActionListeners(states: InvocationContext) {
- popupComponents.prevButton.addButtonActionListener(CodeWhispererPrevButtonActionListener(states))
- popupComponents.nextButton.addButtonActionListener(CodeWhispererNextButtonActionListener(states))
- popupComponents.acceptButton.addButtonActionListener(CodeWhispererAcceptButtonActionListener(states))
+ 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) {
+ 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(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))
+
+ sessionContext.project.putUserData(CodeWhispererService.KEY_SESSION_CONTEXT, sessionContext)
+
+ setPopupTypedHandler(CodeWhispererPopupTypedHandler(TypedAction.getInstance().rawHandler, 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
)
}
- 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) {
- 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(states.popup)
+ CodeWhispererService.getInstance().disposeDisplaySession(false)
}
super.selectionChanged(event)
}
}
- editor.selectionModel.addSelectionListener(codewhispererSelectionListener)
- Disposer.register(states) { 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) {
if (shouldListenerCancelPopup) {
- cancelPopup(states.popup)
+ // 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(sessionContext, event.newFragment.toString(), true)
+ }
+ return
+ } else {
+ 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(states.popup)
+ CodeWhispererService.getInstance().disposeDisplaySession(false)
}
super.caretPositionChanged(event)
}
}
- editor.caretModel.addCaretListener(codewhispererCaretListener)
- Disposer.register(states) { 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(states.popup)
+ override fun componentMoved(e: ComponentEvent) {
+ CodeWhispererService.getInstance().disposeDisplaySession(false)
+ super.componentMoved(e)
}
override fun componentShown(e: ComponentEvent?) {
- cancelPopup(states.popup)
+ 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) {
+ if (e.inlay != null) {
+ showPopup(sessionContext, force = true)
+ } else {
+ bringSuggestionInlayToFront(sessionContext.editor, sessionContext.popup, opposite = true)
+ }
+ super.mouseMoved(e)
+ }
}
+ editor.addEditorMouseMotionListener(suggestionHoverEnterListener, sessionContext)
}
private fun updateSelectedRecommendationLabelText(validSelectedIndex: Int, validCount: Int) {
@@ -623,18 +582,10 @@ class CodeWhispererPopupManager {
}
}
- fun hasConflictingPopups(editor: Editor): Boolean =
- ParameterInfoController.existsWithVisibleHintForEditor(editor, true) ||
- LookupManager.getActiveLookup(editor) != null
-
- private fun findNewSelectedIndex(
- isReverse: Boolean,
- detailContexts: List,
- userInput: String,
- start: Int,
- typeahead: String,
- ): Int {
- val count = detailContexts.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) {
@@ -642,45 +593,34 @@ class CodeWhispererPopupManager {
if (currIndex < 0) {
currIndex += count
}
- if (isValidRecommendation(detailContexts[currIndex], userInput, typeahead)) {
+ if (isValidRecommendation(previews[currIndex])) {
return currIndex
}
}
return -1
}
- private fun getValidCount(detailContexts: List, userInput: String, typeahead: String): Int =
- detailContexts.filter { isValidRecommendation(it, userInput, typeahead) }.size
-
- private fun getValidSelectedIndex(
- detailContexts: List,
- userInput: String,
- selectedIndex: Int,
- typeahead: String,
- ): Int {
- var currIndexIgnoreInvalid = 0
- detailContexts.forEachIndexed { index, value ->
+ private fun getValidCount(): Int =
+ CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo().count { isValidRecommendation(it) }
+
+ private fun getValidSelectedIndex(selectedIndex: Int): Int {
+ var curr = 0
+
+ val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo()
+ previews.forEachIndexed { index, preview ->
if (index == selectedIndex) {
- return currIndexIgnoreInvalid
+ return curr
}
- if (isValidRecommendation(value, userInput, typeahead)) {
- currIndexIgnoreInvalid++
+ if (isValidRecommendation(preview)) {
+ curr++
}
}
return -1
}
- 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
+ private fun isValidRecommendation(preview: PreviewContext): Boolean {
+ if (preview.detail.isDiscarded) return false
+ return preview.detail.recommendation.content().startsWith(preview.userInput + preview.typeahead)
}
companion object {
@@ -698,29 +638,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) {}
-
- fun stateChanged(sessionContext: SessionContextNew) {}
- fun scrolled(sessionContext: SessionContextNew) {}
- fun recommendationAdded(states: InvocationContextNew, sessionContext: SessionContextNew) {}
}
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, sessionContext: SessionContext, rangeMarker: RangeMarker) {}
-
- fun backspace(sessionContext: SessionContextNew, diff: String) {}
- fun enter(sessionContext: SessionContextNew, diff: String) {}
- fun type(sessionContext: SessionContextNew, diff: String) {}
- fun navigatePrevious(sessionContext: SessionContextNew) {}
- fun navigateNext(sessionContext: SessionContextNew) {}
- fun beforeAccept(sessionContext: SessionContextNew) {}
- fun afterAccept(states: InvocationContextNew, previews: List, sessionContext: SessionContextNew, 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/CodeWhispererPopupManagerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt
deleted file mode 100644
index 85304037f4..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManagerNew.kt
+++ /dev/null
@@ -1,630 +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
-
-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.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.VisualPosition
-import com.intellij.openapi.editor.actionSystem.EditorActionManager
-import com.intellij.openapi.editor.actionSystem.TypedAction
-import com.intellij.openapi.editor.colors.EditorColors
-import com.intellij.openapi.editor.colors.EditorColorsListener
-import com.intellij.openapi.editor.colors.EditorColorsManager
-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.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.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.ui.UIUtil
-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.editor.CodeWhispererEditorManagerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.addHorizontalGlue
-import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.horizontalPanelConstraints
-import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.inlineLabelConstraints
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager.Companion.CODEWHISPERER_POPUP_STATE_CHANGED
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager.Companion.CODEWHISPERER_USER_ACTION_PERFORMED
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererEditorActionHandlerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupBackspaceHandlerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEnterHandlerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEscHandler
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTypedHandlerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererAcceptButtonActionListenerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererActionListenerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererNextButtonActionListenerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererPrevButtonActionListenerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererScrollListenerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
-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
-import software.aws.toolkits.resources.message
-import java.awt.Point
-import java.awt.Rectangle
-import java.awt.event.ComponentAdapter
-import java.awt.event.ComponentEvent
-import java.awt.event.ComponentListener
-import javax.swing.JButton
-import javax.swing.JComponent
-import javax.swing.JLabel
-
-@Service
-class CodeWhispererPopupManagerNew {
- val popupComponents = CodeWhispererPopupComponentsNew()
-
- var shouldListenerCancelPopup: Boolean = true
- private set
-
- init {
- // Listen for global scheme changes
- ApplicationManager.getApplication().messageBus.connect().subscribe(
- EditorColorsManager.TOPIC,
- EditorColorsListener { scheme ->
- if (scheme == null) return@EditorColorsListener
- popupComponents.apply {
- panel.background = scheme.defaultBackground
- panel.components.forEach {
- it.background = scheme.getColor(EditorColors.DOCUMENTATION_COLOR)
- it.foreground = scheme.defaultForeground
- }
- buttonsPanel.components.forEach {
- it.foreground = UIUtil.getLabelForeground()
- }
- recommendationInfoLabel.foreground = UIUtil.getLabelForeground()
- codeReferencePanel.components.forEach {
- it.background = scheme.getColor(EditorColors.DOCUMENTATION_COLOR)
- it.foreground = UIUtil.getLabelForeground()
- }
- }
- }
- )
- }
-
- @RequiresEdt
- fun changeStatesForNavigation(sessionContext: SessionContextNew, indexChange: Int) {
- val validCount = getValidCount()
- val validSelectedIndex = getValidSelectedIndex(sessionContext.selectedIndex)
- if ((validSelectedIndex == validCount - 1 && indexChange == 1) ||
- (validSelectedIndex == 0 && indexChange == -1)
- ) {
- return
- }
- val isReverse = indexChange < 0
- val selectedIndex = findNewSelectedIndex(isReverse, sessionContext.selectedIndex + indexChange)
-
- sessionContext.selectedIndex = selectedIndex
-
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
- sessionContext
- )
- }
-
- @RequiresEdt
- fun changeStatesForTypeahead(
- sessionContext: SessionContextNew,
- typeaheadChange: String,
- typeaheadAdded: Boolean,
- ) {
- if (!updateTypeahead(typeaheadChange, typeaheadAdded)) return
- if (!updateSessionSelectedIndex(sessionContext)) return
-
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
- sessionContext
- )
- }
-
- @RequiresEdt
- fun changeStatesForShowing(sessionContext: SessionContextNew, states: InvocationContextNew, recommendationAdded: Boolean = false) {
- if (recommendationAdded) {
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED)
- .recommendationAdded(states, sessionContext)
- return
- }
-
- if (!updateSessionSelectedIndex(sessionContext)) return
- if (sessionContext.popupOffset == -1) {
- sessionContext.popupOffset = sessionContext.editor.caretModel.offset
- }
-
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
- sessionContext
- )
- }
-
- private fun updateTypeahead(typeaheadChange: String, typeaheadAdded: Boolean): Boolean {
- val recommendations = CodeWhispererServiceNew.getInstance().getAllPaginationSessions().values.filterNotNull()
- recommendations.forEach {
- 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" }
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
- return false
- }
- it.recommendationContext.typeahead.substring(
- 0,
- it.recommendationContext.typeahead.length - typeaheadChange.length
- )
- }
- it.recommendationContext.typeahead = newTypeahead
- }
- return true
- }
-
- private fun updateSessionSelectedIndex(sessionContext: SessionContextNew): Boolean {
- val selectedIndex = findNewSelectedIndex(false, sessionContext.selectedIndex)
- if (selectedIndex == -1) {
- LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" }
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
- return false
- }
-
- sessionContext.selectedIndex = selectedIndex
- return true
- }
-
- fun updatePopupPanel(sessionContext: SessionContextNew?) {
- if (sessionContext == null || sessionContext.selectedIndex == -1 || sessionContext.isDisposed()) return
- val selectedIndex = sessionContext.selectedIndex
- val previews = CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo()
- if (selectedIndex >= previews.size) return
- val validCount = getValidCount()
- val validSelectedIndex = getValidSelectedIndex(selectedIndex)
- updateSelectedRecommendationLabelText(validSelectedIndex, validCount)
- updateNavigationPanel(validSelectedIndex, validCount)
- updateImportPanel(previews[selectedIndex].detail.recommendation.mostRelevantMissingImports())
- updateCodeReferencePanel(sessionContext.project, previews[selectedIndex].detail.recommendation.references())
- }
-
- fun render(sessionContext: SessionContextNew, isRecommendationAdded: Boolean) {
- updatePopupPanel(sessionContext)
-
- // 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.
- // 2. New recommendations being added to the existing ones, we should not update the latency end time, and emit
- // the event if it's at the pagination end.
- // 3. User scrolling (so popup is changing positions), we should not update the latency end time and should not
- // 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 (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) {
- sessionContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime()
- sessionContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime()
- val triggerTypeOfLastTrigger = CodeWhispererServiceNew.getInstance().getAllPaginationSessions()
- .values.filterNotNull().last().requestContext.triggerTypeInfo.triggerType
- sessionContext.latencyContext.perceivedLatency =
- sessionContext.latencyContext.getPerceivedLatency(triggerTypeOfLastTrigger)
- }
- if (isRecommendationAdded) return
- showPopup(sessionContext)
- }
-
- fun dontClosePopupAndRun(runnable: () -> Unit) {
- try {
- shouldListenerCancelPopup = false
- runnable()
- } finally {
- shouldListenerCancelPopup = true
- }
- }
-
- fun showPopup(sessionContext: SessionContextNew, force: Boolean = false) {
- val p = sessionContext.editor.offsetToXY(sessionContext.popupOffset)
- val popup: JBPopup?
- if (sessionContext.popup == null) {
- popup = initPopup()
- sessionContext.popup = popup
- CodeWhispererInvocationStatusNew.getInstance().setPopupStartTimestamp()
- initPopupListener(sessionContext, popup)
- } else {
- popup = sessionContext.popup
- }
- val editor = sessionContext.editor
- val previews = CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo()
- val userInputOriginal = previews[sessionContext.selectedIndex].userInput
- val userInputLines = userInputOriginal.split("\n").size - 1
- val popupSize = (popup as AbstractPopup).preferredContentSize
- 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
-
- CodeWhispererInvocationStatusNew.getInstance().setDisplaySessionActive(true)
-
- if (!editorRect.contains(popupRect)) {
- // popup location above first line don't work, so don't show the popup
- shouldHidePopup = true
- }
-
- // popup to always display above the current editing line
- val popupLocation = Point(p.x, yAboveFirstLine)
-
- val relativePopupLocationToEditor = RelativePoint(editor.contentComponent, popupLocation)
-
- // TODO: visibleAreaChanged listener is not getting triggered in remote environment when scrolling
- if (popup.isVisible) {
- // Changing the position of BackendBeAbstractPopup does not work
- if (!shouldHidePopup && !AppMode.isRemoteDevHost()) {
- popup.setLocation(relativePopupLocationToEditor.screenPoint)
- popup.size = popup.preferredContentSize
- }
- } else {
- if (!AppMode.isRemoteDevHost()) {
- if (force && !shouldHidePopup) {
- 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
- val caretVisualPosition = editor.offsetToVisualPosition(editor.caretModel.offset)
-
- // display popup x lines below the caret where x is # of lines of suggestions, since inlays don't
- // count as visual lines, the final math will always be just increment 1 line.
- val popupPositionForRemote = VisualPosition(
- caretVisualPosition.line + 1,
- caretVisualPosition.column
- )
- editor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, popupPositionForRemote)
- popup.showInBestPositionFor(editor)
- }
- }
-
- bringSuggestionInlayToFront(editor, popup, !force)
- }
-
- 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 as AbstractPopup?)?.popupWindow?.let {
- WindowManager.getInstance().setAlphaModeRatio(it, qInlinePopupAlpha)
- }
- ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component)?.let {
- WindowManager.getInstance().setAlphaModeRatio(it, intelliSensePopupAlpha)
- }
- }
-
- fun initPopup(): JBPopup = JBPopupFactory.getInstance()
- .createComponentPopupBuilder(popupComponents.panel, null)
- .setAlpha(0.1F)
- .setCancelOnClickOutside(true)
- .setCancelOnWindowDeactivation(true)
- .createPopup()
-
- fun getReformattedRecommendation(detailContext: DetailContextNew, userInput: String) =
- detailContext.reformatted.content().substring(userInput.length)
-
- private fun initPopupListener(sessionContext: SessionContextNew, popup: JBPopup) {
- addPopupListener(popup)
- sessionContext.editor.scrollingModel.addVisibleAreaListener(CodeWhispererScrollListenerNew(sessionContext), sessionContext)
- addButtonActionListeners(sessionContext)
- addMessageSubscribers(sessionContext)
- setPopupActionHandlers(sessionContext)
- addComponentListeners(sessionContext)
- }
-
- private fun addPopupListener(popup: JBPopup) {
- val listener = CodeWhispererPopupListenerNew()
- popup.addListener(listener)
- Disposer.register(popup) {
- popup.removeListener(listener)
- }
- }
-
- private fun addMessageSubscribers(sessionContext: SessionContextNew) {
- val connect = ApplicationManager.getApplication().messageBus.connect(sessionContext)
- connect.subscribe(
- CODEWHISPERER_USER_ACTION_PERFORMED,
- object : CodeWhispererUserActionListener {
- override fun navigateNext(sessionContext: SessionContextNew) {
- changeStatesForNavigation(sessionContext, 1)
- }
-
- override fun navigatePrevious(sessionContext: SessionContextNew) {
- changeStatesForNavigation(sessionContext, -1)
- }
-
- override fun backspace(sessionContext: SessionContextNew, diff: String) {
- changeStatesForTypeahead(sessionContext, diff, false)
- }
-
- override fun enter(sessionContext: SessionContextNew, diff: String) {
- changeStatesForTypeahead(sessionContext, diff, true)
- }
-
- override fun type(sessionContext: SessionContextNew, diff: String) {
- // remove the character at primaryCaret if it's the same as the typed character
- 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(sessionContext.project) {
- document.deleteString(caretOffset, caretOffset + 1)
- }
- }
- changeStatesForTypeahead(sessionContext, diff, true)
- }
-
- override fun beforeAccept(sessionContext: SessionContextNew) {
- dontClosePopupAndRun {
- CodeWhispererEditorManagerNew.getInstance().updateEditorWithRecommendation(sessionContext)
- }
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(true)
- }
- }
- )
- }
-
- private fun addButtonActionListeners(sessionContext: SessionContextNew) {
- popupComponents.prevButton.addButtonActionListener(CodeWhispererPrevButtonActionListenerNew(sessionContext), sessionContext)
- popupComponents.nextButton.addButtonActionListener(CodeWhispererNextButtonActionListenerNew(sessionContext), sessionContext)
- popupComponents.acceptButton.addButtonActionListener(CodeWhispererAcceptButtonActionListenerNew(sessionContext), sessionContext)
- }
-
- private fun JButton.addButtonActionListener(listener: CodeWhispererActionListenerNew, sessionContext: SessionContextNew) {
- this.addActionListener(listener)
- Disposer.register(sessionContext) { this.removeActionListener(listener) }
- }
-
- private fun setPopupActionHandlers(sessionContext: SessionContextNew) {
- val actionManager = EditorActionManager.getInstance()
-
- sessionContext.project.putUserData(CodeWhispererServiceNew.KEY_SESSION_CONTEXT, sessionContext)
-
- setPopupTypedHandler(CodeWhispererPopupTypedHandlerNew(TypedAction.getInstance().rawHandler, sessionContext), sessionContext)
- setPopupActionHandler(ACTION_EDITOR_ESCAPE, CodeWhispererPopupEscHandler(sessionContext), sessionContext)
- setPopupActionHandler(
- ACTION_EDITOR_ENTER,
- CodeWhispererPopupEnterHandlerNew(actionManager.getActionHandler(ACTION_EDITOR_ENTER), sessionContext),
- sessionContext
- )
- setPopupActionHandler(
- ACTION_EDITOR_BACKSPACE,
- CodeWhispererPopupBackspaceHandlerNew(actionManager.getActionHandler(ACTION_EDITOR_BACKSPACE), sessionContext),
- sessionContext
- )
- }
-
- private fun setPopupTypedHandler(newHandler: CodeWhispererPopupTypedHandlerNew, sessionContext: SessionContextNew) {
- val oldTypedHandler = TypedAction.getInstance().setupRawHandler(newHandler)
- Disposer.register(sessionContext) { TypedAction.getInstance().setupRawHandler(oldTypedHandler) }
- }
-
- private fun setPopupActionHandler(id: String, newHandler: CodeWhispererEditorActionHandlerNew, sessionContext: SessionContextNew) {
- val oldHandler = EditorActionManager.getInstance().setActionHandler(id, newHandler)
- Disposer.register(sessionContext) { EditorActionManager.getInstance().setActionHandler(id, oldHandler) }
- }
-
- private fun addComponentListeners(sessionContext: SessionContextNew) {
- val editor = sessionContext.editor
- val codeWhispererSelectionListener: SelectionListener = object : SelectionListener {
- override fun selectionChanged(event: SelectionEvent) {
- if (shouldListenerCancelPopup) {
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
- }
- super.selectionChanged(event)
- }
- }
- editor.selectionModel.addSelectionListener(codeWhispererSelectionListener)
- Disposer.register(sessionContext) { editor.selectionModel.removeSelectionListener(codeWhispererSelectionListener) }
-
- val codeWhispererDocumentListener: DocumentListener = object : DocumentListener {
- override fun documentChanged(event: DocumentEvent) {
- 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(sessionContext, event.newFragment.toString(), true)
- }
- return
- } else {
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
- }
- }
- super.documentChanged(event)
- }
- }
- editor.document.addDocumentListener(codeWhispererDocumentListener, sessionContext)
-
- val codeWhispererCaretListener: CaretListener = object : CaretListener {
- override fun caretPositionChanged(event: CaretEvent) {
- if (shouldListenerCancelPopup) {
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
- }
- super.caretPositionChanged(event)
- }
- }
- 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(e: ComponentEvent) {
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
- super.componentMoved(e)
- }
-
- override fun componentShown(e: ComponentEvent?) {
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
- super.componentShown(e)
- }
- }
- window?.addComponentListener(windowListener)
- Disposer.register(sessionContext) { window?.removeComponentListener(windowListener) }
- }
-
- val suggestionHoverEnterListener: EditorMouseMotionListener = object : EditorMouseMotionListener {
- override fun mouseMoved(e: EditorMouseEvent) {
- if (e.inlay != null) {
- showPopup(sessionContext, force = true)
- } else {
- bringSuggestionInlayToFront(sessionContext.editor, sessionContext.popup, opposite = true)
- }
- super.mouseMoved(e)
- }
- }
- editor.addEditorMouseMotionListener(suggestionHoverEnterListener, sessionContext)
- }
-
- private fun updateSelectedRecommendationLabelText(validSelectedIndex: Int, validCount: Int) {
- if (CodeWhispererInvocationStatusNew.getInstance().hasExistingServiceInvocation()) {
- popupComponents.recommendationInfoLabel.text = message("codewhisperer.popup.pagination_info")
- LOG.debug { "Pagination in progress. Current total: $validCount" }
- } else {
- popupComponents.recommendationInfoLabel.text =
- message(
- "codewhisperer.popup.recommendation_info",
- validSelectedIndex + 1,
- validCount,
- POPUP_DIM_HEX
- )
- LOG.debug { "Updated popup recommendation label text. Index: $validSelectedIndex, total: $validCount" }
- }
- }
-
- private fun updateNavigationPanel(validSelectedIndex: Int, validCount: Int) {
- val multipleRecommendation = validCount > 1
- popupComponents.prevButton.isEnabled = multipleRecommendation && validSelectedIndex != 0
- popupComponents.nextButton.isEnabled = multipleRecommendation && validSelectedIndex != validCount - 1
- }
-
- private fun updateImportPanel(imports: List) {
- popupComponents.panel.apply {
- if (components.contains(popupComponents.importPanel)) {
- remove(popupComponents.importPanel)
- }
- }
- if (imports.isEmpty()) return
-
- val firstImport = imports.first()
- val choice = if (imports.size > 2) 2 else imports.size - 1
- val message = message("codewhisperer.popup.import_info", firstImport.statement(), imports.size - 1, choice)
- popupComponents.panel.add(popupComponents.importPanel, horizontalPanelConstraints)
- popupComponents.importLabel.text = message
- }
-
- private fun updateCodeReferencePanel(project: Project, references: List) {
- popupComponents.panel.apply {
- if (components.contains(popupComponents.codeReferencePanel)) {
- remove(popupComponents.codeReferencePanel)
- }
- }
- if (references.isEmpty()) return
-
- popupComponents.panel.add(popupComponents.codeReferencePanel, horizontalPanelConstraints)
- val licenses = references.map { it.licenseName() }.toSet()
- popupComponents.codeReferencePanelLink.apply {
- actionListeners.toList().forEach {
- removeActionListener(it)
- }
- addActionListener {
- CodeWhispererCodeReferenceManager.getInstance(project).showCodeReferencePanel()
- }
- }
- popupComponents.licenseCodePanel.apply {
- removeAll()
- add(popupComponents.licenseCodeLabelPrefixText, inlineLabelConstraints)
- licenses.forEachIndexed { i, license ->
- add(popupComponents.licenseLink(license), inlineLabelConstraints)
- if (i == licenses.size - 1) return@forEachIndexed
- add(JLabel(", "), inlineLabelConstraints)
- }
-
- add(JLabel(". "), inlineLabelConstraints)
- add(popupComponents.codeReferencePanelLink, inlineLabelConstraints)
- addHorizontalGlue()
- }
- popupComponents.licenseCodePanel.components.forEach {
- if (it !is JComponent) return@forEach
- it.font = it.font.deriveFont(POPUP_INFO_TEXT_SIZE)
- }
- }
-
- fun findNewSelectedIndex(isReverse: Boolean, selectedIndex: Int): Int {
- val start = if (selectedIndex == -1) 0 else selectedIndex
- val previews = CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo()
- val count = previews.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(previews[currIndex])) {
- return currIndex
- }
- }
- return -1
- }
-
- private fun getValidCount(): Int =
- CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo().count { isValidRecommendation(it) }
-
- private fun getValidSelectedIndex(selectedIndex: Int): Int {
- var curr = 0
-
- val previews = CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo()
- previews.forEachIndexed { index, preview ->
- if (index == selectedIndex) {
- return curr
- }
- if (isValidRecommendation(preview)) {
- curr++
- }
- }
- return -1
- }
-
- private fun isValidRecommendation(preview: PreviewContext): Boolean {
- if (preview.detail.isDiscarded) return false
- return preview.detail.recommendation.content().startsWith(preview.userInput + preview.typeahead)
- }
-
- companion object {
- private val LOG = getLogger()
- fun getInstance(): CodeWhispererPopupManagerNew = service()
- }
-}
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 e4bb87feec..967e691362 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
@@ -10,27 +10,31 @@ import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer
import com.intellij.xdebugger.ui.DebuggerColors
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.inlay.CodeWhispererInlayManagerNew
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
+ override fun stateChanged(sessionContext: SessionContext) {
+ val editor = sessionContext.editor
val editorManager = CodeWhispererEditorManager.getInstance()
+ val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo()
val selectedIndex = sessionContext.selectedIndex
- val typeahead = sessionContext.typeahead
- val detail = states.recommendationContext.details[selectedIndex]
+ 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))
+ detail.hasSeen = true
+
// get matching brackets from recommendations to the brackets after caret position
val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation(
detail,
- states.recommendationContext.userInputSinceInvocation
+ previews[selectedIndex].userInput,
).substring(typeahead.length)
val remainingLines = remaining.split("\n")
@@ -61,7 +65,7 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener {
},
HighlighterTargetArea.EXACT_RANGE
)
- Disposer.register(states.popup) {
+ Disposer.register(sessionContext) {
editor.markupModel.removeHighlighter(rangeHighlighter)
}
sessionContext.toBeRemovedHighlighter = rangeHighlighter
@@ -87,57 +91,18 @@ 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)
+ CodeWhispererInlayManagerNew.getInstance().updateInlays(sessionContext, inlayChunks)
CodeWhispererPopupManager.getInstance().render(
- states,
sessionContext,
- overlappingLinesCount,
- isRecommendationAdded = false,
- isScrolling = false
+ isRecommendationAdded = false
)
}
- override fun scrolled(states: InvocationContext, sessionContext: SessionContext) {
- if (states.popup.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]
-
- // get matching brackets from recommendations to the brackets after caret position
- val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation(
- detail,
- states.recommendationContext.userInputSinceInvocation
- ).substring(typeahead.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)
}
override fun recommendationAdded(states: InvocationContext, sessionContext: SessionContext) {
- CodeWhispererPopupManager.getInstance().render(
- states,
- sessionContext,
- 0,
- isRecommendationAdded = true,
- isScrolling = false
- )
+ CodeWhispererPopupManager.getInstance().render(sessionContext, isRecommendationAdded = true)
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListenerNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListenerNew.kt
deleted file mode 100644
index ac7ccbe21a..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListenerNew.kt
+++ /dev/null
@@ -1,108 +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
-
-import com.intellij.openapi.editor.markup.EffectType
-import com.intellij.openapi.editor.markup.HighlighterLayer
-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 software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManagerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.inlay.CodeWhispererInlayManagerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererRecommendationManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
-
-class CodeWhispererUIChangeListenerNew : CodeWhispererPopupStateChangeListener {
- override fun stateChanged(sessionContext: SessionContextNew) {
- val editor = sessionContext.editor
- val editorManager = CodeWhispererEditorManagerNew.getInstance()
- val previews = CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo()
- val selectedIndex = sessionContext.selectedIndex
- 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))
-
- detail.hasSeen = true
-
- // get matching brackets from recommendations to the brackets after caret position
- val remaining = CodeWhispererPopupManagerNew.getInstance().getReformattedRecommendation(
- detail,
- previews[selectedIndex].userInput,
- ).substring(typeahead.length)
-
- val remainingLines = remaining.split("\n")
- val firstLineOfRemaining = remainingLines.first()
- val otherLinesOfRemaining = remainingLines.drop(1)
-
- // process first line inlays, where we do subsequence matching as much as possible
- val matchingSymbols = editorManager.getMatchingSymbolsFromRecommendation(
- editor,
- firstLineOfRemaining,
- detail.isTruncatedOnRight,
- sessionContext
- )
-
- sessionContext.toBeRemovedHighlighter?.let {
- editor.markupModel.removeHighlighter(it)
- }
-
- // Add the strike-though hint for the remaining non-matching first-line right context for multi-line completions
- if (!detail.isTruncatedOnRight && otherLinesOfRemaining.isNotEmpty()) {
- val rangeHighlighter = editor.markupModel.addRangeHighlighter(
- matchingSymbols[matchingSymbols.size - 2].second,
- lineEndOffset,
- HighlighterLayer.LAST + 1,
- TextAttributes().apply {
- effectType = EffectType.STRIKEOUT
- effectColor = editor.colorsScheme.getAttributes(DebuggerColors.INLINED_VALUES_EXECUTION_LINE).foregroundColor
- },
- HighlighterTargetArea.EXACT_RANGE
- )
- Disposer.register(sessionContext) {
- editor.markupModel.removeHighlighter(rangeHighlighter)
- }
- sessionContext.toBeRemovedHighlighter = rangeHighlighter
- }
-
- val chunks = CodeWhispererRecommendationManager.getInstance().buildRecommendationChunks(
- firstLineOfRemaining,
- matchingSymbols
- )
-
- // process other lines inlays, where we do tail-head matching as much as possible
- val overlappingLinesCount = editorManager.findOverLappingLines(
- editor,
- otherLinesOfRemaining,
- detail.isTruncatedOnRight,
- sessionContext
- )
-
- var otherLinesInlayText = ""
- otherLinesOfRemaining.subList(0, otherLinesOfRemaining.size - overlappingLinesCount).forEach {
- otherLinesInlayText += "\n" + it
- }
-
- // 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))
- CodeWhispererInlayManagerNew.getInstance().updateInlays(sessionContext, inlayChunks)
- CodeWhispererPopupManagerNew.getInstance().render(
- sessionContext,
- isRecommendationAdded = false
- )
- }
-
- override fun scrolled(sessionContext: SessionContextNew) {
- CodeWhispererPopupManagerNew.getInstance().render(sessionContext, isRecommendationAdded = false)
- }
-
- override fun recommendationAdded(states: InvocationContextNew, sessionContext: SessionContextNew) {
- CodeWhispererPopupManagerNew.getInstance().render(sessionContext, isRecommendationAdded = true)
- }
-}
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 90962ac187..a2cea3c836 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,8 +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.SessionContextNew
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
-abstract class CodeWhispererEditorActionHandler(val states: InvocationContext) : EditorActionHandler()
-abstract class CodeWhispererEditorActionHandlerNew(val sessionContext: SessionContextNew) : 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 eee58c0ddc..a3f40b8eec 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,35 +8,15 @@ 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.SessionContextNew
+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.popup.CodeWhispererPopupManagerNew
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 {
- val oldOffset = editor.caretModel.offset
- defaultHandler.execute(editor, caret, dataContext)
- val newOffset = editor.caretModel.offset
- val newText = "a".repeat(oldOffset - newOffset)
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).backspace(states, newText)
- }
- }
-}
-
-class CodeWhispererPopupBackspaceHandlerNew(
- private val defaultHandler: EditorActionHandler,
- sessionContext: SessionContextNew,
-) : CodeWhispererEditorActionHandlerNew(sessionContext) {
- override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
- val popupManager = CodeWhispererPopupManagerNew.getInstance()
popupManager.dontClosePopupAndRun {
val oldOffset = editor.caretModel.offset
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 f7f29c49f8..1b165f76f1 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,35 +9,15 @@ 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.SessionContextNew
+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.popup.CodeWhispererPopupManagerNew
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 {
- val oldOffset = editor.caretModel.offset
- defaultHandler.execute(editor, caret, dataContext)
- val newOffset = editor.caretModel.offset
- val newText = editor.document.getText(TextRange.create(oldOffset, newOffset))
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).enter(states, newText)
- }
- }
-}
-
-class CodeWhispererPopupEnterHandlerNew(
- private val defaultHandler: EditorActionHandler,
- sessionContext: SessionContextNew,
-) : CodeWhispererEditorActionHandlerNew(sessionContext) {
- override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
- val popupManager = CodeWhispererPopupManagerNew.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/popup/handlers/CodeWhispererPopupEscHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt
index b628ad0c60..634f76e885 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
@@ -6,11 +6,11 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
-class CodeWhispererPopupEscHandler(sessionContext: SessionContextNew) : CodeWhispererEditorActionHandlerNew(sessionContext) {
+class CodeWhispererPopupEscHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
- CodeWhispererServiceNew.getInstance().disposeDisplaySession(false)
+ 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
deleted file mode 100644
index 020e143480..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt
+++ /dev/null
@@ -1,19 +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.popup.CodeWhispererPopupManager
-
-class CodeWhispererPopupLeftArrowHandler(states: InvocationContext) : CodeWhispererEditorActionHandler(states) {
- override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).navigatePrevious(states)
- }
-}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/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 9efba65a80..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupRightArrowHandler.kt
+++ /dev/null
@@ -1,19 +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.popup.CodeWhispererPopupManager
-
-class CodeWhispererPopupRightArrowHandler(states: InvocationContext) : CodeWhispererEditorActionHandler(states) {
- override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).navigateNext(states)
- }
-}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/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 c92eae9106..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTabHandler.kt
+++ /dev/null
@@ -1,19 +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.popup.CodeWhispererPopupManager
-
-class CodeWhispererPopupTabHandler(states: InvocationContext) : 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)
- }
-}
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 81baa037d2..e7bb42f480 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,31 +7,15 @@ 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.SessionContextNew
+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.popup.CodeWhispererPopupManagerNew
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())
- }
- }
-}
-
-class CodeWhispererPopupTypedHandlerNew(
- private val defaultHandler: TypedActionHandler,
- val sessionContext: SessionContextNew,
-) : TypedActionHandler {
- override fun execute(editor: Editor, charTyped: Char, dataContext: DataContext) {
- CodeWhispererPopupManagerNew.getInstance().dontClosePopupAndRun {
defaultHandler.execute(editor, charTyped, dataContext)
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/listeners/CodeWhispererAcceptButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt
index fe5696adb0..6a7a03e132 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,20 +4,11 @@
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.SessionContextNew
+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) {
- override fun actionPerformed(e: ActionEvent?) {
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).beforeAccept(states, CodeWhispererPopupManager.getInstance().sessionContext)
- }
-}
-
-class CodeWhispererAcceptButtonActionListenerNew(sessionContext: SessionContextNew) : CodeWhispererActionListenerNew(sessionContext) {
+class CodeWhispererAcceptButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) {
override fun actionPerformed(e: ActionEvent?) {
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/listeners/CodeWhispererActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt
index 368a4fde79..23f0975e66 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,9 +3,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.SessionContextNew
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import java.awt.event.ActionListener
-abstract class CodeWhispererActionListener(val states: InvocationContext) : ActionListener
-abstract class CodeWhispererActionListenerNew(val sessionContext: SessionContextNew) : 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 2b9418aaa3..d11f219a33 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,20 +4,11 @@
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.SessionContextNew
+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) {
- override fun actionPerformed(e: ActionEvent?) {
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).navigateNext(states)
- }
-}
-
-class CodeWhispererNextButtonActionListenerNew(sessionContext: SessionContextNew) : CodeWhispererActionListenerNew(sessionContext) {
+class CodeWhispererNextButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) {
override fun actionPerformed(e: ActionEvent?) {
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/listeners/CodeWhispererPrevButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt
index 44ad7f1e3f..273a40a8e1 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,20 +4,11 @@
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.SessionContextNew
+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) {
- override fun actionPerformed(e: ActionEvent?) {
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).navigatePrevious(states)
- }
-}
-
-class CodeWhispererPrevButtonActionListenerNew(sessionContext: SessionContextNew) : CodeWhispererActionListenerNew(sessionContext) {
+class CodeWhispererPrevButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) {
override fun actionPerformed(e: ActionEvent?) {
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/listeners/CodeWhispererScrollListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt
index 658f64909d..4ebea98162 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,31 +6,15 @@ 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.SessionContextNew
+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.CodeWhispererInvocationStatusNew
-class CodeWhispererScrollListener(private val states: InvocationContext) : 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() &&
- (oldRect.x != newRect.x || oldRect.y != newRect.y)
- ) {
- ApplicationManager.getApplication().messageBus.syncPublisher(
- CodeWhispererPopupManager.CODEWHISPERER_POPUP_STATE_CHANGED
- ).scrolled(states, CodeWhispererPopupManager.getInstance().sessionContext)
- }
- }
-}
-
-class CodeWhispererScrollListenerNew(private val sessionContext: SessionContextNew) : VisibleAreaListener {
- override fun visibleAreaChanged(e: VisibleAreaEvent) {
- val oldRect = e.oldRectangle
- val newRect = e.newRectangle
- if (CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive() &&
+ if (CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() &&
(oldRect.x != newRect.x || oldRect.y != newRect.y)
) {
ApplicationManager.getApplication().messageBus.syncPublisher(
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt
index fe73ff91e4..c536f0c0ca 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerHandler.kt
@@ -6,7 +6,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service
import com.intellij.openapi.editor.Editor
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo
import software.aws.toolkits.telemetry.CodewhispererTriggerType
@@ -20,11 +19,7 @@ interface CodeWhispererAutoTriggerHandler {
val triggerTypeInfo = TriggerTypeInfo(CodewhispererTriggerType.AutoTrigger, automatedTriggerType)
LOG.debug { "autotriggering CodeWhisperer with type ${automatedTriggerType.telemetryType}" }
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- CodeWhispererServiceNew.getInstance().showRecommendationsInPopup(editor, triggerTypeInfo, latencyContext)
- } else {
- CodeWhispererService.getInstance().showRecommendationsInPopup(editor, triggerTypeInfo, latencyContext)
- }
+ CodeWhispererService.getInstance().showRecommendationsInPopup(editor, triggerTypeInfo, latencyContext)
}
companion object {
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 10f6438e88..545d8a5b1e 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
@@ -17,13 +17,11 @@ import kotlinx.coroutines.launch
import org.apache.commons.collections4.queue.CircularFifoQueue
import software.aws.toolkits.jetbrains.core.coroutines.EDT
import software.aws.toolkits.jetbrains.core.coroutines.applicationCoroutineScope
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil
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.telemetry.CodeWhispererTelemetryServiceNew
import software.aws.toolkits.telemetry.CodewhispererAutomatedTriggerType
import software.aws.toolkits.telemetry.CodewhispererPreviousSuggestionState
import software.aws.toolkits.telemetry.CodewhispererTriggerType
@@ -79,14 +77,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa
// real auto trigger logic
fun invoke(editor: Editor, triggerType: CodeWhispererAutomatedTriggerType): Job? {
- if (!(
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- CodeWhispererServiceNew.getInstance().canDoInvocation(editor, CodewhispererTriggerType.AutoTrigger)
- } else {
- CodeWhispererService.getInstance().canDoInvocation(editor, CodewhispererTriggerType.AutoTrigger)
- }
- )
- ) {
+ if (!CodeWhispererService.getInstance().canDoInvocation(editor, CodewhispererTriggerType.AutoTrigger)) {
return null
}
@@ -179,12 +170,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa
var previousOneAccept: Double = 0.0
var previousOneReject: Double = 0.0
var previousOneOther: Double = 0.0
- val previousOneDecision =
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- CodeWhispererTelemetryServiceNew.getInstance().previousUserTriggerDecision
- } else {
- CodeWhispererTelemetryService.getInstance().previousUserTriggerDecision
- }
+ val previousOneDecision = CodeWhispererTelemetryService.getInstance().previousUserTriggerDecision
if (previousOneDecision == null) {
previousOneAccept = 0.0
previousOneReject = 0.0
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 9019195081..ad078da6a0 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
@@ -9,16 +9,14 @@ import com.intellij.openapi.components.service
import com.intellij.util.messages.Topic
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
import java.time.Duration
import java.time.Instant
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
- private var timeAtLastInvocationComplete: Instant? = null
var timeAtLastDocumentChanged: Instant = Instant.now()
private set
private var isPopupActive: Boolean = false
@@ -26,30 +24,22 @@ 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)
+ ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(true)
+ LOG.debug { "Starting CodeWhisperer invocation" }
+ }
- fun hasExistingServiceInvocation(): 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
}
}
- fun setInvocationComplete() {
- timeAtLastInvocationComplete = Instant.now()
- }
-
fun documentChanged() {
timeAtLastDocumentChanged = Instant.now()
}
@@ -65,13 +55,13 @@ class CodeWhispererInvocationStatus {
}
fun hasEnoughDelayToShowCodeWhisperer(): Boolean {
- val timeCanShowCodeWhisperer = timeAtLastDocumentChanged.plusMillis(CodeWhispererConstants.POPUP_DELAY)
+ val timeCanShowCodeWhisperer = timeAtLastDocumentChanged.plusMillis(50)
return timeCanShowCodeWhisperer.isBefore(Instant.now())
}
- fun isPopupActive(): Boolean = isPopupActive
+ fun isDisplaySessionActive(): Boolean = isPopupActive
- fun setPopupActive(value: Boolean) {
+ fun setDisplaySessionActive(value: Boolean) {
isPopupActive = value
}
@@ -84,11 +74,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/CodeWhispererInvocationStatusNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatusNew.kt
deleted file mode 100644
index 2c465f8be9..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatusNew.kt
+++ /dev/null
@@ -1,81 +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.service
-
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.components.Service
-import com.intellij.openapi.components.service
-import software.aws.toolkits.core.utils.debug
-import software.aws.toolkits.core.utils.getLogger
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus.Companion.CODEWHISPERER_INVOCATION_STATE_CHANGED
-import java.time.Duration
-import java.time.Instant
-import java.util.concurrent.atomic.AtomicBoolean
-
-@Service
-class CodeWhispererInvocationStatusNew {
- private val isInvokingService: AtomicBoolean = AtomicBoolean(false)
- private var invokingSessionId: String? = null
- var timeAtLastDocumentChanged: Instant = Instant.now()
- private set
- private var isPopupActive: Boolean = false
- private var timeAtLastInvocationStart: Instant? = null
- var popupStartTimestamp: Instant? = null
- private set
-
- fun startInvocation() {
- isInvokingService.set(true)
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(true)
- LOG.debug { "Starting CodeWhisperer invocation" }
- }
-
- fun hasExistingServiceInvocation(): Boolean = isInvokingService.get()
-
- fun finishInvocation() {
- if (isInvokingService.compareAndSet(true, false)) {
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(false)
- LOG.debug { "Ending CodeWhisperer invocation" }
- invokingSessionId = null
- }
- }
-
- fun documentChanged() {
- timeAtLastDocumentChanged = Instant.now()
- }
-
- fun setPopupStartTimestamp() {
- popupStartTimestamp = Instant.now()
- }
-
- fun getTimeSinceDocumentChanged(): Double {
- val timeSinceDocumentChanged = Duration.between(timeAtLastDocumentChanged, Instant.now())
- val timeInDouble = timeSinceDocumentChanged.toMillis().toDouble()
- return timeInDouble
- }
-
- fun hasEnoughDelayToShowCodeWhisperer(): Boolean {
- val timeCanShowCodeWhisperer = timeAtLastDocumentChanged.plusMillis(50)
- return timeCanShowCodeWhisperer.isBefore(Instant.now())
- }
-
- fun isDisplaySessionActive(): Boolean = isPopupActive
-
- fun setDisplaySessionActive(value: Boolean) {
- isPopupActive = value
- }
-
- fun setInvocationStart() {
- timeAtLastInvocationStart = Instant.now()
- }
-
- fun setInvocationSessionId(sessionId: String?) {
- LOG.debug { "Set current CodeWhisperer invocation sessionId: $sessionId" }
- invokingSessionId = sessionId
- }
-
- companion object {
- private val LOG = getLogger()
- fun getInstance(): CodeWhispererInvocationStatusNew = service()
- }
-}
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 486f2e2eba..ae85ce662b 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,8 +9,8 @@ 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.DetailContextNew
import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.RequestContext
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType
import kotlin.math.max
import kotlin.math.min
@@ -49,38 +49,6 @@ class CodeWhispererRecommendationManager {
.build()
}
- fun reformatReference(requestContext: RequestContextNew, recommendation: Completion): Completion {
- // startOffset is the offset at the start of user input since invocation
- val invocationStartOffset = requestContext.caretPosition.offset
-
- val startOffsetSinceUserInput = requestContext.editor.caretModel.offset
- val endOffset = invocationStartOffset + recommendation.content().length
-
- if (startOffsetSinceUserInput > endOffset) return recommendation
-
- val reformattedReferences = recommendation.references().filter {
- val referenceStart = invocationStartOffset + it.recommendationContentSpan().start()
- val referenceEnd = invocationStartOffset + it.recommendationContentSpan().end()
- referenceStart < endOffset && referenceEnd > startOffsetSinceUserInput
- }.map {
- val referenceStart = invocationStartOffset + it.recommendationContentSpan().start()
- val referenceEnd = invocationStartOffset + it.recommendationContentSpan().end()
- val updatedReferenceStart = max(referenceStart, startOffsetSinceUserInput)
- val updatedReferenceEnd = min(referenceEnd, endOffset)
- it.toBuilder().recommendationContentSpan(
- Span.builder()
- .start(updatedReferenceStart - invocationStartOffset)
- .end(updatedReferenceEnd - invocationStartOffset)
- .build()
- ).build()
- }
-
- return Completion.builder()
- .content(recommendation.content())
- .references(reformattedReferences)
- .build()
- }
-
fun buildRecommendationChunks(
recommendation: String,
matchingSymbols: List>,
@@ -96,7 +64,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
@@ -162,77 +130,6 @@ class CodeWhispererRecommendationManager {
}.toMutableList()
}
- fun buildDetailContext(
- requestContext: RequestContextNew,
- userInput: String,
- recommendations: List,
- requestId: String,
- ): MutableList {
- val seen = mutableSetOf()
- return recommendations.map {
- val isDiscardedByUserInput = !it.content().startsWith(userInput) || it.content() == userInput
- if (isDiscardedByUserInput) {
- return@map DetailContextNew(
- requestId,
- it,
- it,
- isDiscarded = true,
- isTruncatedOnRight = false,
- rightOverlap = "",
- getCompletionType(it)
- )
- }
-
- val overlap = findRightContextOverlap(requestContext, it)
- val overlapIndex = it.content().lastIndexOf(overlap)
- val truncatedContent =
- if (overlap.isNotEmpty() && overlapIndex >= 0) {
- it.content().substring(0, overlapIndex)
- } else {
- it.content()
- }
- val truncated = it.toBuilder()
- .content(truncatedContent)
- .build()
- val isDiscardedByUserInputForTruncated = !truncated.content().startsWith(userInput) || truncated.content() == userInput
- if (isDiscardedByUserInputForTruncated) {
- return@map DetailContextNew(
- requestId,
- it,
- truncated,
- isDiscarded = true,
- isTruncatedOnRight = true,
- rightOverlap = overlap,
- getCompletionType(it)
- )
- }
-
- val isDiscardedByRightContextTruncationDedupe = !seen.add(truncated.content())
- val isDiscardedByBlankAfterTruncation = truncated.content().isBlank()
- if (isDiscardedByRightContextTruncationDedupe || isDiscardedByBlankAfterTruncation) {
- return@map DetailContextNew(
- requestId,
- it,
- truncated,
- isDiscarded = true,
- truncated.content().length != it.content().length,
- overlap,
- getCompletionType(it)
- )
- }
- val reformatted = reformatReference(requestContext, truncated)
- DetailContextNew(
- requestId,
- it,
- reformatted,
- isDiscarded = false,
- truncated.content().length != it.content().length,
- overlap,
- getCompletionType(it)
- )
- }.toMutableList()
- }
-
fun findRightContextOverlap(
requestContext: RequestContext,
recommendation: Completion,
@@ -244,17 +141,6 @@ class CodeWhispererRecommendationManager {
return findRightContextOverlap(rightContext, recommendationContent)
}
- fun findRightContextOverlap(
- requestContext: RequestContextNew,
- recommendation: Completion,
- ): String {
- val document = requestContext.editor.document
- val caret = requestContext.editor.caretModel.primaryCaret
- val rightContext = document.charsSequence.subSequence(caret.offset, document.charsSequence.length).toString()
- val recommendationContent = recommendation.content()
- return findRightContextOverlap(rightContext, recommendationContent)
- }
-
@VisibleForTesting
fun findRightContextOverlap(rightContext: String, recommendationContent: String): String {
val rightContextFirstLine = rightContext.substringBefore("\n")
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 f11c501402..49ef7e1edc 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,14 +15,14 @@ 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.openapi.util.Key
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.CancellationException
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
@@ -46,11 +46,8 @@ 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.EDT
-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
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection
import software.aws.toolkits.jetbrains.services.amazonq.SUPPLEMENTAL_CONTEXT_TIMEOUT
@@ -62,12 +59,14 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhisper
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.language.languages.CodeWhispererJson
-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.RequestContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.ResponseContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo
import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo
@@ -96,6 +95,10 @@ import java.util.concurrent.TimeUnit
class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
private val codeInsightSettingsFacade = CodeInsightsSettingsFacade()
private var refreshFailure: Int = 0
+ private val ongoingRequests = mutableMapOf()
+ val ongoingRequestsContext = mutableMapOf()
+ private var jobId = 0
+ private var sessionContext: SessionContext? = null
init {
Disposer.register(this, codeInsightSettingsFacade)
@@ -163,13 +166,22 @@ 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(triggerTypeInfo, editor, project, psiFile)
} catch (e: Exception) {
LOG.debug { e.message.toString() }
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) {
+ LOG.debug { "same caretContext found from job: $k, left context ${vCaretContext.leftContextOnCurrentLine}, jobId: $currentJobId" }
+ return
+ }
+ }
val language = requestContext.fileContextInfo.programmingLanguage
val leftContext = requestContext.fileContextInfo.caretContext.leftFileContext
@@ -191,7 +203,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 {
@@ -199,208 +211,163 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
}
}
- val invocationStatus = CodeWhispererInvocationStatus.getInstance()
- if (invocationStatus.checkExistingInvocationAndSet()) {
- return
- }
+ CodeWhispererInvocationStatus.getInstance().startInvocation()
- invokeCodeWhispererInBackground(requestContext)
+ invokeCodeWhispererInBackground(requestContext, currentJobId, latencyContext)
}
- internal suspend fun invokeCodeWhispererInBackground(requestContext: RequestContext): Job {
- val popup = withContext(EDT) {
- CodeWhispererPopupManager.getInstance().initPopup().also {
- Disposer.register(it) { CodeWhispererInvocationStatus.getInstance().finishInvocation() }
- }
+ internal suspend fun invokeCodeWhispererInBackground(requestContext: RequestContext, currentJobId: Int, latencyContext: LatencyContext) {
+ 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
}
+ this.sessionContext = sessionContext
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().
+
+ // When session is disposed we will cancel this coroutine. The only places session can get disposed should be
+ // from CodeWhispererService.disposeDisplaySession().
// 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)
-
- var states: InvocationContext? = null
var lastRecommendationIndex = -1
- val job = coroutineScope.launch {
- try {
- val responseIterable = CodeWhispererClientAdaptor.getInstance(requestContext.project).generateCompletionsPaginator(
- buildCodeWhispererRequest(
- requestContext.fileContextInfo,
- requestContext.awaitSupplementalContext(),
- requestContext.customizationArn
- )
+ try {
+ val responseIterable = CodeWhispererClientAdaptor.getInstance(requestContext.project).generateCompletionsPaginator(
+ buildCodeWhispererRequest(
+ requestContext.fileContextInfo,
+ requestContext.awaitSupplementalContext(),
+ requestContext.customizationArn
)
+ )
- var startTime = System.nanoTime()
- requestContext.latencyContext.codewhispererPreprocessingEnd = System.nanoTime()
- requestContext.latencyContext.paginationAllCompletionsStart = System.nanoTime()
- CodeWhispererInvocationStatus.getInstance().setInvocationStart()
- var requestCount = 0
- for (response in responseIterable) {
- requestCount++
- val endTime = System.nanoTime()
- val latency = TimeUnit.NANOSECONDS.toMillis(endTime - startTime).toDouble()
- startTime = endTime
- 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 =
- (endTime - requestContext.latencyContext.codewhispererEndToEndStart).toDouble()
- requestContext.latencyContext.firstRequestId = requestId
- CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
- }
- if (response.nextToken().isEmpty()) {
- requestContext.latencyContext.paginationAllCompletionsEnd = System.nanoTime()
- }
- val responseContext = ResponseContext(sessionId)
- logServiceInvocation(requestId, requestContext, responseContext, response.completions(), latency, null)
- lastRecommendationIndex += response.completions().size
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_CODE_COMPLETION_PERFORMED)
- .onSuccess(requestContext.fileContextInfo)
- CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
- requestId,
- requestContext,
- responseContext,
- lastRecommendationIndex,
- true,
- latency,
- null
- )
+ var startTime = System.nanoTime()
+ latencyContext.codewhispererPreprocessingEnd = System.nanoTime()
+ latencyContext.paginationAllCompletionsStart = System.nanoTime()
+ CodeWhispererInvocationStatus.getInstance().setInvocationStart()
+ var requestCount = 0
+ for (response in responseIterable) {
+ requestCount++
+ val endTime = System.nanoTime()
+ val latency = TimeUnit.NANOSECONDS.toMillis(endTime - startTime).toDouble()
+ startTime = endTime
+ val requestId = response.responseMetadata().requestId()
+ val sessionId = response.sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
+ if (requestCount == 1) {
+ latencyContext.codewhispererPostprocessingStart = System.nanoTime()
+ latencyContext.paginationFirstCompletionTime = latency
+ latencyContext.firstRequestId = requestId
+ CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
+ }
+ if (response.nextToken().isEmpty()) {
+ latencyContext.paginationAllCompletionsEnd = System.nanoTime()
+ }
+ val responseContext = ResponseContext(sessionId)
+ logServiceInvocation(requestId, requestContext, responseContext, response.completions(), latency, null)
+ lastRecommendationIndex += response.completions().size
+ ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_CODE_COMPLETION_PERFORMED)
+ .onSuccess(requestContext.fileContextInfo)
+ CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
+ currentJobId,
+ requestId,
+ requestContext,
+ responseContext,
+ lastRecommendationIndex,
+ true,
+ latency,
+ null
+ )
- val validatedResponse = validateResponse(response)
+ val validatedResponse = validateResponse(response)
- runInEdt {
- // If delay is not met, add them to the worker queue and process them later.
- // On first response, workers queue must be empty. If there's enough delay before showing,
- // process CodeWhisperer UI rendering and workers queue will remain empty throughout this
- // CodeWhisperer session. If there's not enough delay before showing, the CodeWhisperer UI rendering task
- // will be added to the workers queue.
- // On subsequent responses, if they see workers queue is not empty, it means the first worker
- // 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)
- if (workerContexts.isNotEmpty()) {
- workerContexts.add(workerContext)
- } else {
- if (states == null && !popup.isDisposed &&
- !CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer()
- ) {
- // It's the first response, and no enough delay before showing
- projectCoroutineScope(requestContext.project).launch {
- while (!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer()) {
- delay(CodeWhispererConstants.POPUP_DELAY_CHECK_INTERVAL)
- }
- runInEdt {
- workerContexts.forEach {
- states = processCodeWhispererUI(it, states)
+ runInEdt {
+ // If delay is not met, add them to the worker queue and process them later.
+ // On first response, workers queue must be empty. If there's enough delay before showing,
+ // process CodeWhisperer UI rendering and workers queue will remain empty throughout this
+ // CodeWhisperer session. If there's not enough delay before showing, the CodeWhisperer UI rendering task
+ // will be added to the workers queue.
+ // On subsequent responses, if they see workers queue is not empty, it means the first worker
+ // 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)
+ if (workerContexts.isNotEmpty()) {
+ workerContexts.add(workerContext)
+ } else {
+ 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()) {
+ delay(CodeWhispererConstants.POPUP_DELAY_CHECK_INTERVAL)
+ }
+ runInEdt {
+ workerContexts.forEach {
+ processCodeWhispererUI(
+ sessionContext,
+ it,
+ ongoingRequests[currentJobId],
+ cs,
+ currentJobId
+ )
+ if (!ongoingRequests.contains(currentJobId)) {
+ job?.cancel()
}
- workerContexts.clear()
}
+ workerContexts.clear()
}
- workerContexts.add(workerContext)
- } else {
- // Have enough delay before showing for the first response, or it's subsequent responses
- states = processCodeWhispererUI(workerContext, states)
+ }
+ workerContexts.add(workerContext)
+ } else {
+ // Have enough delay before showing for the first response, or it's subsequent responses
+ processCodeWhispererUI(
+ sessionContext,
+ workerContext,
+ ongoingRequests[currentJobId],
+ cs,
+ currentJobId
+ )
+ if (!ongoingRequests.contains(currentJobId)) {
+ job?.cancel()
}
}
}
- 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" }
- break
- }
}
- } catch (e: Exception) {
- val requestId: String
- val sessionId: String
- val displayMessage: String
-
- if (
- CodeWhispererConstants.Customization.invalidCustomizationExceptionPredicate(e) ||
- e is ResourceNotFoundException
- ) {
- (e as CodeWhispererRuntimeException)
-
- requestId = e.requestId() ?: ""
- sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
- val exceptionType = e::class.simpleName
- val responseContext = ResponseContext(sessionId)
-
- CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
- requestId,
- requestContext,
- responseContext,
- lastRecommendationIndex,
- false,
- 0.0,
- exceptionType
- )
-
- LOG.debug {
- "The provided customization ${requestContext.customizationArn} is not found, " +
- "will fallback to the default and retry generate completion"
- }
- logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
-
- notifyWarn(
- title = "",
- content = message("codewhisperer.notification.custom.not_available"),
- project = requestContext.project,
- notificationActions = listOf(
- NotificationAction.create(
- message("codewhisperer.notification.custom.simple.button.select_another_customization")
- ) { _, notification ->
- CodeWhispererModelConfigurator.getInstance().showConfigDialog(requestContext.project)
- notification.expire()
- }
- )
- )
+ if (!cs.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 inactive CodeWhisperer session exit" }
+ return
+ }
+ if (requestCount >= PAGINATION_REQUEST_COUNT_ALLOWED) {
+ LOG.debug { "Only $PAGINATION_REQUEST_COUNT_ALLOWED request per pagination session for now" }
CodeWhispererInvocationStatus.getInstance().finishInvocation()
- CodeWhispererInvocationStatus.getInstance().setInvocationComplete()
-
- requestContext.customizationArn?.let { CodeWhispererModelConfigurator.getInstance().invalidateCustomization(it) }
-
- projectCoroutineScope(requestContext.project).launch {
- showRecommendationsInPopup(
- requestContext.editor,
- requestContext.triggerTypeInfo,
- requestContext.latencyContext
- )
- }
- return@launch
- } else if (e is CodeWhispererException) {
- 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) {
- 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 {
- requestId = ""
- sessionId = ""
- val statusCode = if (e is SdkServiceException) e.statusCode() else 0
- displayMessage =
- if (statusCode >= 500) {
- message("codewhisperer.trigger.error.server_side")
- } else {
- message("codewhisperer.trigger.error.client_side")
- }
- if (statusCode < 500) {
- LOG.debug(e) { "Error invoking CodeWhisperer service" }
- }
+ break
}
+ }
+ } catch (e: Exception) {
+ val requestId: String
+ val sessionId: String
+ val displayMessage: String
+
+ if (
+ CodeWhispererConstants.Customization.invalidCustomizationExceptionPredicate(e) ||
+ e is ResourceNotFoundException
+ ) {
+ (e as CodeWhispererRuntimeException)
+
+ requestId = e.requestId().orEmpty()
+ sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
val exceptionType = e::class.simpleName
val responseContext = ResponseContext(sessionId)
- CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
- logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
+
CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
+ currentJobId,
requestId,
requestContext,
responseContext,
@@ -410,95 +377,158 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
exceptionType
)
- if (e is ThrottlingException &&
- e.message == CodeWhispererConstants.THROTTLING_MESSAGE
- ) {
- CodeWhispererExplorerActionManager.getInstance().setSuspended(requestContext.project)
- if (requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
- notifyErrorCodeWhispererUsageLimit(requestContext.project)
- }
- } else {
- if (requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
- // We should only show error hint when CodeWhisperer popup is not visible,
- // and make it silent if CodeWhisperer popup is showing.
- if (!CodeWhispererInvocationStatus.getInstance().isPopupActive()) {
- showCodeWhispererErrorHint(requestContext.editor, displayMessage)
+ LOG.debug {
+ "The provided customization ${requestContext.customizationArn} is not found, " +
+ "will fallback to the default and retry generate completion"
+ }
+ logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
+
+ notifyWarn(
+ title = "",
+ content = message("codewhisperer.notification.custom.not_available"),
+ project = requestContext.project,
+ notificationActions = listOf(
+ NotificationAction.create(
+ message("codewhisperer.notification.custom.simple.button.select_another_customization")
+ ) { _, notification ->
+ CodeWhispererModelConfigurator.getInstance().showConfigDialog(requestContext.project)
+ notification.expire()
}
+ )
+ )
+ CodeWhispererInvocationStatus.getInstance().finishInvocation()
+
+ requestContext.customizationArn?.let { CodeWhispererModelConfigurator.getInstance().invalidateCustomization(it) }
+
+ showRecommendationsInPopup(
+ requestContext.editor,
+ requestContext.triggerTypeInfo,
+ latencyContext
+ )
+ return
+ } else if (e is CodeWhispererException) {
+ requestId = e.requestId().orEmpty()
+ 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 CodeWhispererRuntimeException) {
+ requestId = e.requestId().orEmpty()
+ sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
+ displayMessage = e.awsErrorDetails().errorMessage() ?: message("codewhisperer.trigger.error.server_side")
+ } else {
+ requestId = ""
+ sessionId = ""
+ val statusCode = if (e is SdkServiceException) e.statusCode() else 0
+ displayMessage =
+ if (statusCode >= 500) {
+ message("codewhisperer.trigger.error.server_side")
+ } else {
+ message("codewhisperer.trigger.error.client_side")
}
+ if (statusCode < 500) {
+ LOG.debug(e) { "Error invoking CodeWhisperer service" }
}
- CodeWhispererInvocationStatus.getInstance().finishInvocation()
- runInEdt {
- states?.let {
- CodeWhispererPopupManager.getInstance().updatePopupPanel(
- it,
- CodeWhispererPopupManager.getInstance().sessionContext
- )
+ }
+ val exceptionType = e::class.simpleName
+ val responseContext = ResponseContext(sessionId)
+ CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
+ logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
+ CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
+ currentJobId,
+ requestId,
+ requestContext,
+ responseContext,
+ lastRecommendationIndex,
+ false,
+ 0.0,
+ exceptionType
+ )
+
+ if (e is ThrottlingException &&
+ e.message == CodeWhispererConstants.THROTTLING_MESSAGE
+ ) {
+ CodeWhispererExplorerActionManager.getInstance().setSuspended(requestContext.project)
+ if (requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
+ notifyErrorCodeWhispererUsageLimit(requestContext.project)
+ }
+ } else {
+ if (requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
+ // 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().isDisplaySessionActive()) {
+ showCodeWhispererErrorHint(requestContext.editor, displayMessage)
+ }
}
}
- } finally {
- CodeWhispererInvocationStatus.getInstance().setInvocationComplete()
+ }
+ CodeWhispererInvocationStatus.getInstance().finishInvocation()
+ runInEdt {
+ CodeWhispererPopupManager.getInstance().updatePopupPanel(sessionContext)
}
}
-
- return job
}
@RequiresEdt
- private fun processCodeWhispererUI(workerContext: WorkerContext, currStates: InvocationContext?): InvocationContext? {
+ private fun processCodeWhispererUI(
+ sessionContext: SessionContext,
+ workerContext: WorkerContext,
+ currStates: InvocationContext?,
+ coroutine: CoroutineScope,
+ jobId: Int,
+ ) {
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 (popup.isDisposed) {
- LOG.debug { "Stop showing CodeWhisperer recommendations on CodeWhisperer session exit. RequestId: $requestId" }
- return null
+ if (!coroutine.isActive || sessionContext.isDisposed()) {
+ LOG.debug { "Stop showing CodeWhisperer recommendations on CodeWhisperer session exit. RequestId: $requestId, jobId: $jobId" }
+ return
}
if (requestContext.editor.isDisposed) {
- LOG.debug { "Stop showing CodeWhisperer recommendations since editor is disposed. RequestId: $requestId" }
- CodeWhispererPopupManager.getInstance().cancelPopup(popup)
- return null
+ LOG.debug { "Stop showing all CodeWhisperer recommendations since editor is disposed. RequestId: $requestId, jobId: $jobId" }
+ disposeDisplaySession(false)
+ return
}
- if (response.nextToken().isEmpty()) {
- CodeWhispererInvocationStatus.getInstance().finishInvocation()
- }
+ CodeWhispererInvocationStatus.getInstance().finishInvocation()
val caretMovement = CodeWhispererEditorManager.getInstance().getCaretMovement(
requestContext.editor,
requestContext.caretPosition
)
- val isPopupShowing: Boolean
+ val isPopupShowing = checkRecommendationsValidity(currStates, false)
val nextStates: InvocationContext?
if (currStates == null) {
- // first response
- nextStates = initStates(requestContext, responseContext, response, caretMovement, popup)
- isPopupShowing = false
+ // first response for the jobId
+ nextStates = initStates(jobId, requestContext, responseContext, response, caretMovement)
- // 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
+ // receiving a null state means caret has moved backward,
+ // 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)
- return null
+ return
}
} else {
- // subsequent responses
+ // subsequent responses for the jobId
nextStates = updateStates(currStates, response)
- isPopupShowing = checkRecommendationsValidity(currStates, false)
}
+ LOG.debug { "Adding ${response.completions().size} completions to the session. RequestId: $requestId, jobId: $jobId" }
- val hasAtLeastOneValid = checkRecommendationsValidity(nextStates, response.nextToken().isEmpty())
+ // TODO: may have bug when it's a mix of auto-trigger + manual trigger
+ val hasAtLeastOneValid = checkRecommendationsValidity(nextStates, true)
+ val allSuggestions = ongoingRequests.values.filterNotNull().flatMap { it.recommendationContext.details }
+ val valid = allSuggestions.count { !it.isDiscarded }
+ 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
- if (nextStates.recommendationContext.details.isEmpty() && response.nextToken().isEmpty()) {
+ // TODO: may have bug; visit later
+ if (nextStates.recommendationContext.details.isEmpty()) {
LOG.debug { "Received just an empty list from this session, requestId: $requestId" }
CodeWhispererTelemetryService.getInstance().sendUserDecisionEvent(
requestContext,
@@ -518,38 +548,41 @@ 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)
- return null
+ 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(nextStates, isPopupShowing)
+ updateCodeWhisperer(sessionContext, nextStates, isPopupShowing)
}
- return nextStates
}
private fun initStates(
+ jobId: Int,
requestContext: RequestContext,
responseContext: ResponseContext,
response: GenerateCompletionsResponse,
caretMovement: CaretMovement,
- popup: JBPopup,
): 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)
+ 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()
+ val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0), jobId)
+ ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext)
+ disposeDisplaySession(false)
return null
}
+
val userInputOriginal = CodeWhispererEditorManager.getInstance().getUserInputSinceInvocation(
requestContext.editor,
requestContext.caretPosition.offset
@@ -573,8 +606,9 @@ 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)
+ ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext)
+ return ongoingRequests[jobId]
}
private fun updateStates(
@@ -582,24 +616,19 @@ 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)
- 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)
+ return states
}
- private fun checkRecommendationsValidity(states: InvocationContext, showHint: Boolean): Boolean {
+ private fun checkRecommendationsValidity(states: InvocationContext?, showHint: Boolean): Boolean {
+ if (states == null) return false
val details = states.recommendationContext.details
// set to true when at least one is not discarded or empty
@@ -614,35 +643,48 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
return hasAtLeastOneValid
}
- private fun updateCodeWhisperer(states: InvocationContext, recommendationAdded: Boolean) {
- CodeWhispererPopupManager.getInstance().changeStates(states, 0, "", true, recommendationAdded)
+ private fun updateCodeWhisperer(sessionContext: SessionContext, states: InvocationContext, recommendationAdded: Boolean) {
+ CodeWhispererPopupManager.getInstance().changeStatesForShowing(sessionContext, states, recommendationAdded)
}
- private fun sendDiscardedUserDecisionEventForAll(
- requestContext: RequestContext,
- responseContext: ResponseContext,
- recommendations: List,
- ) {
- val detailContexts = recommendations.map {
- DetailContext("", it, it, true, false, "", getCompletionType(it))
- }
- val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0))
+ @RequiresEdt
+ private fun disposeJob(jobId: Int) {
+ ongoingRequests[jobId]?.let { Disposer.dispose(it) }
+ ongoingRequests.remove(jobId)
+ ongoingRequestsContext.remove(jobId)
+ }
- CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(
- requestContext,
- responseContext,
- recommendationContext,
- SessionContext(),
- false
- )
+ @RequiresEdt
+ fun disposeDisplaySession(accept: Boolean) {
+ // avoid duplicate session disposal logic
+ if (sessionContext == null || sessionContext?.isDisposed() == true) return
+
+ 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() =
+ ongoingRequests.values.filterNotNull().flatMap { element ->
+ val context = element.recommendationContext
+ context.details.map {
+ PreviewContext(context.jobId, it, context.userInputSinceInvocation, context.typeahead)
+ }
+ }
+
+ fun getAllPaginationSessions() = ongoingRequests
+
fun getRequestContext(
triggerTypeInfo: TriggerTypeInfo,
editor: Editor,
project: Project,
psiFile: PsiFile,
- latencyContext: LatencyContext,
): RequestContext {
// 1. file context
val fileContext: FileContextInfo = runReadAction { FileContextProvider.getInstance(project).extractFileContext(editor, psiFile) }
@@ -667,7 +709,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 {
@@ -693,26 +735,17 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
requestContext: RequestContext,
responseContext: ResponseContext,
recommendationContext: RecommendationContext,
- 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)
- return states
- }
-
- private fun addPopupChildDisposables(popup: JBPopup) {
- codeInsightSettingsFacade.disableCodeInsightUntil(popup)
-
- Disposer.register(popup) {
- CodeWhispererPopupManager.getInstance().reset()
+ val states = InvocationContext(requestContext, responseContext, recommendationContext)
+ Disposer.register(states) {
+ job?.cancel(CancellationException("Cancelling the current coroutine when the pagination session context is disposed"))
}
+ return states
}
private fun logServiceInvocation(
@@ -734,8 +767,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
"Left context of current line: ${requestContext.fileContextInfo.caretContext.leftContextOnCurrentLine}, " +
"Cursor line: ${requestContext.caretPosition.line}, " +
"Caret offset: ${requestContext.caretPosition.offset}, " +
- (latency?.let { "Latency: $latency, " } ?: "") +
- (exceptionType?.let { "Exception Type: $it, " } ?: "") +
+ latency?.let { "Latency: $latency, " }.orEmpty() +
+ exceptionType?.let { "Exception Type: $it, " }.orEmpty() +
"Recommendations: \n${recommendationLogs ?: "None"}"
}
}
@@ -752,28 +785,19 @@ 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
}
- private fun showCodeWhispererInfoHint(editor: Editor, message: String) {
- runInEdt {
- HintManager.getInstance().showInformationHint(editor, message, HintManager.UNDER)
- }
+ fun showCodeWhispererInfoHint(editor: Editor, message: String) {
+ HintManager.getInstance().showInformationHint(editor, message, HintManager.UNDER)
}
- private fun showCodeWhispererErrorHint(editor: Editor, message: String) {
- runInEdt {
- HintManager.getInstance().showErrorHint(editor, message, HintManager.UNDER)
- }
+ fun showCodeWhispererErrorHint(editor: Editor, message: String) {
+ HintManager.getInstance().showErrorHint(editor, message, HintManager.UNDER)
}
override fun dispose() {}
@@ -781,12 +805,19 @@ 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",
CodeWhispererCodeCompletionServiceListener::class.java
)
+ val CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER: Topic = Topic.create(
+ "CodeWhisperer intelliSense popup on hover",
+ CodeWhispererIntelliSenseOnHoverListener::class.java
+ )
+ val KEY_SESSION_CONTEXT = Key.create("codewhisperer.session")
+
fun getInstance(): CodeWhispererService = service()
const val KET_SESSION_ID = "x-amzn-SessionId"
private var reAuthPromptShown = false
@@ -834,42 +865,10 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
}
}
-data class RequestContext(
- val project: Project,
- val editor: Editor,
- val triggerTypeInfo: TriggerTypeInfo,
- val caretPosition: CaretPosition,
- 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
- var supplementalContext: SupplementalContextInfo? = null
- private set
- get() = when (field) {
- null -> {
- if (!supplementalContextDeferred.isCompleted) {
- error("attempt to access supplemental context before awaiting the deferred")
- } else {
- null
- }
- }
-
- else -> field
- }
-
- suspend fun awaitSupplementalContext(): SupplementalContextInfo? {
- supplementalContext = supplementalContextDeferred.await()
- return supplementalContext
- }
+interface CodeWhispererIntelliSenseOnHoverListener {
+ fun onEnter() {}
}
-data class ResponseContext(
- val sessionId: String,
-)
-
interface CodeWhispererCodeCompletionServiceListener {
fun onSuccess(fileContextInfo: FileContextInfo) {}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt
deleted file mode 100644
index 7f43e3d088..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererServiceNew.kt
+++ /dev/null
@@ -1,898 +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.service
-
-import com.intellij.codeInsight.hint.HintManager
-import com.intellij.notification.NotificationAction
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.ApplicationInfo
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.application.runReadAction
-import com.intellij.openapi.components.Service
-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.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
-import com.intellij.util.messages.Topic
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.isActive
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import software.amazon.awssdk.core.exception.SdkServiceException
-import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList
-import software.amazon.awssdk.services.codewhisperer.model.CodeWhispererException
-import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException
-import software.amazon.awssdk.services.codewhispererruntime.model.Completion
-import software.amazon.awssdk.services.codewhispererruntime.model.FileContext
-import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsRequest
-import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsResponse
-import software.amazon.awssdk.services.codewhispererruntime.model.ProgrammingLanguage
-import software.amazon.awssdk.services.codewhispererruntime.model.RecommendationsWithReferencesPreference
-import software.amazon.awssdk.services.codewhispererruntime.model.ResourceNotFoundException
-import software.amazon.awssdk.services.codewhispererruntime.model.SupplementalContext
-import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingException
-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.getCoroutineBgContext
-import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope
-import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
-import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
-import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection
-import software.aws.toolkits.jetbrains.services.amazonq.SUPPLEMENTAL_CONTEXT_TIMEOUT
-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.editor.CodeWhispererEditorManagerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getCaretPosition
-import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.isSupportedJsonFormat
-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.language.languages.CodeWhispererJson
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
-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.RecommendationContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.WorkerContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManagerNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService.Companion.CODEWHISPERER_CODE_COMPLETION_PERFORMED
-import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryServiceNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CaretMovement
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeInsightsSettingsFacade
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getTelemetryOptOutPreference
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider
-import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
-import software.aws.toolkits.jetbrains.utils.isInjectedText
-import software.aws.toolkits.jetbrains.utils.isQExpired
-import software.aws.toolkits.jetbrains.utils.notifyWarn
-import software.aws.toolkits.resources.message
-import software.aws.toolkits.telemetry.CodewhispererCompletionType
-import software.aws.toolkits.telemetry.CodewhispererSuggestionState
-import software.aws.toolkits.telemetry.CodewhispererTriggerType
-import java.util.concurrent.TimeUnit
-
-@Service
-class CodeWhispererServiceNew(private val cs: CoroutineScope) : Disposable {
- private val codeInsightSettingsFacade = CodeInsightsSettingsFacade()
- private var refreshFailure: Int = 0
- private val ongoingRequests = mutableMapOf()
- val ongoingRequestsContext = mutableMapOf()
- private var jobId = 0
- private var sessionContext: SessionContextNew? = null
-
- init {
- Disposer.register(this, codeInsightSettingsFacade)
- }
-
- private var job: Job? = null
- fun showRecommendationsInPopup(
- editor: Editor,
- triggerTypeInfo: TriggerTypeInfo,
- latencyContext: LatencyContext,
- ): Job? {
- if (job == null || job?.isCompleted == true) {
- job = cs.launch(getCoroutineBgContext()) {
- doShowRecommendationsInPopup(editor, triggerTypeInfo, latencyContext)
- }
- }
-
- // did some wrangling, but compiler didn't believe this can't be null
- return job
- }
-
- private suspend fun doShowRecommendationsInPopup(
- editor: Editor,
- triggerTypeInfo: TriggerTypeInfo,
- latencyContext: LatencyContext,
- ) {
- val project = editor.project ?: return
- if (!isCodeWhispererEnabled(project)) return
-
- latencyContext.credentialFetchingStart = System.nanoTime()
-
- // try to refresh automatically if possible, otherwise ask user to login again
- if (isQExpired(project)) {
- // consider changing to only running once a ~minute since this is relatively expensive
- // say the connection is un-refreshable if refresh fails for 3 times
- val shouldReauth = if (refreshFailure < MAX_REFRESH_ATTEMPT) {
- val attempt = withContext(getCoroutineBgContext()) {
- promptReAuth(project)
- }
-
- if (!attempt) {
- refreshFailure++
- }
-
- attempt
- } else {
- true
- }
-
- if (shouldReauth) {
- return
- }
- }
-
- latencyContext.credentialFetchingEnd = System.nanoTime()
- val psiFile = runReadAction { PsiDocumentManager.getInstance(project).getPsiFile(editor.document) }
-
- if (psiFile == null) {
- LOG.debug { "No PSI file for the current document" }
- if (triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
- showCodeWhispererInfoHint(editor, message("codewhisperer.trigger.document.unsupported"))
- }
- return
- }
- val isInjectedFile = runReadAction { psiFile.isInjectedText() }
- if (isInjectedFile) return
-
- val currentJobId = jobId++
- val requestContext = try {
- getRequestContext(triggerTypeInfo, editor, project, psiFile)
- } catch (e: Exception) {
- LOG.debug { e.message.toString() }
- CodeWhispererTelemetryServiceNew.getInstance().sendFailedServiceInvocationEvent(project, e::class.simpleName)
- return
- }
- val caretContext = requestContext.fileContextInfo.caretContext
- ongoingRequestsContext.forEach { (k, v) ->
- val vCaretContext = v.fileContextInfo.caretContext
- if (vCaretContext == caretContext) {
- LOG.debug { "same caretContext found from job: $k, left context ${vCaretContext.leftContextOnCurrentLine}, jobId: $currentJobId" }
- return
- }
- }
-
- val language = requestContext.fileContextInfo.programmingLanguage
- val leftContext = requestContext.fileContextInfo.caretContext.leftFileContext
- if (!language.isCodeCompletionSupported() || (
- language is CodeWhispererJson && !isSupportedJsonFormat(
- requestContext.fileContextInfo.filename,
- leftContext
- )
- )
- ) {
- LOG.debug { "Programming language $language is not supported by CodeWhisperer" }
- if (triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
- showCodeWhispererInfoHint(
- requestContext.editor,
- message("codewhisperer.language.error", psiFile.fileType.name)
- )
- }
- return
- }
-
- LOG.debug {
- "Calling CodeWhisperer service, jobId: $currentJobId, trigger type: ${triggerTypeInfo.triggerType}" +
- if (triggerTypeInfo.triggerType == CodewhispererTriggerType.AutoTrigger) {
- ", auto-trigger type: ${triggerTypeInfo.automatedTriggerType}"
- } else {
- ""
- }
- }
-
- CodeWhispererInvocationStatusNew.getInstance().startInvocation()
-
- invokeCodeWhispererInBackground(requestContext, currentJobId, latencyContext)
- }
-
- internal suspend fun invokeCodeWhispererInBackground(requestContext: RequestContextNew, currentJobId: Int, latencyContext: LatencyContext) {
- ongoingRequestsContext[currentJobId] = requestContext
- val sessionContext = sessionContext ?: SessionContextNew(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
- }
- this.sessionContext = sessionContext
-
- val workerContexts = mutableListOf()
-
- // When session is disposed we will cancel this coroutine. The only places session can get disposed should be
- // from CodeWhispererService.disposeDisplaySession().
- // 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.
- var lastRecommendationIndex = -1
-
- try {
- val responseIterable = CodeWhispererClientAdaptor.getInstance(requestContext.project).generateCompletionsPaginator(
- buildCodeWhispererRequest(
- requestContext.fileContextInfo,
- requestContext.awaitSupplementalContext(),
- requestContext.customizationArn
- )
- )
-
- var startTime = System.nanoTime()
- latencyContext.codewhispererPreprocessingEnd = System.nanoTime()
- latencyContext.paginationAllCompletionsStart = System.nanoTime()
- CodeWhispererInvocationStatusNew.getInstance().setInvocationStart()
- var requestCount = 0
- for (response in responseIterable) {
- requestCount++
- val endTime = System.nanoTime()
- val latency = TimeUnit.NANOSECONDS.toMillis(endTime - startTime).toDouble()
- startTime = endTime
- val requestId = response.responseMetadata().requestId()
- val sessionId = response.sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
- if (requestCount == 1) {
- latencyContext.codewhispererPostprocessingStart = System.nanoTime()
- latencyContext.paginationFirstCompletionTime = latency
- latencyContext.firstRequestId = requestId
- CodeWhispererInvocationStatusNew.getInstance().setInvocationSessionId(sessionId)
- }
- if (response.nextToken().isEmpty()) {
- latencyContext.paginationAllCompletionsEnd = System.nanoTime()
- }
- val responseContext = ResponseContext(sessionId)
- logServiceInvocation(requestId, requestContext, responseContext, response.completions(), latency, null)
- lastRecommendationIndex += response.completions().size
- ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_CODE_COMPLETION_PERFORMED)
- .onSuccess(requestContext.fileContextInfo)
- CodeWhispererTelemetryServiceNew.getInstance().sendServiceInvocationEvent(
- currentJobId,
- requestId,
- requestContext,
- responseContext,
- lastRecommendationIndex,
- true,
- latency,
- null
- )
-
- val validatedResponse = validateResponse(response)
-
- runInEdt {
- // If delay is not met, add them to the worker queue and process them later.
- // On first response, workers queue must be empty. If there's enough delay before showing,
- // process CodeWhisperer UI rendering and workers queue will remain empty throughout this
- // CodeWhisperer session. If there's not enough delay before showing, the CodeWhisperer UI rendering task
- // will be added to the workers queue.
- // On subsequent responses, if they see workers queue is not empty, it means the first worker
- // 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 = WorkerContextNew(requestContext, responseContext, validatedResponse)
- if (workerContexts.isNotEmpty()) {
- workerContexts.add(workerContext)
- } else {
- if (ongoingRequests.values.filterNotNull().isEmpty() &&
- !CodeWhispererInvocationStatusNew.getInstance().hasEnoughDelayToShowCodeWhisperer()
- ) {
- // It's the first response, and no enough delay before showing
- projectCoroutineScope(requestContext.project).launch {
- while (!CodeWhispererInvocationStatusNew.getInstance().hasEnoughDelayToShowCodeWhisperer()) {
- delay(CodeWhispererConstants.POPUP_DELAY_CHECK_INTERVAL)
- }
- runInEdt {
- workerContexts.forEach {
- processCodeWhispererUI(
- sessionContext,
- it,
- ongoingRequests[currentJobId],
- cs,
- currentJobId
- )
- if (!ongoingRequests.contains(currentJobId)) {
- job?.cancel()
- }
- }
- workerContexts.clear()
- }
- }
- workerContexts.add(workerContext)
- } else {
- // Have enough delay before showing for the first response, or it's subsequent responses
- processCodeWhispererUI(
- sessionContext,
- workerContext,
- ongoingRequests[currentJobId],
- cs,
- currentJobId
- )
- if (!ongoingRequests.contains(currentJobId)) {
- job?.cancel()
- }
- }
- }
- }
- if (!cs.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 inactive CodeWhisperer session exit" }
- return
- }
- if (requestCount >= PAGINATION_REQUEST_COUNT_ALLOWED) {
- LOG.debug { "Only $PAGINATION_REQUEST_COUNT_ALLOWED request per pagination session for now" }
- CodeWhispererInvocationStatusNew.getInstance().finishInvocation()
- break
- }
- }
- } catch (e: Exception) {
- val requestId: String
- val sessionId: String
- val displayMessage: String
-
- if (
- CodeWhispererConstants.Customization.invalidCustomizationExceptionPredicate(e) ||
- e is ResourceNotFoundException
- ) {
- (e as CodeWhispererRuntimeException)
-
- requestId = e.requestId().orEmpty()
- sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
- val exceptionType = e::class.simpleName
- val responseContext = ResponseContext(sessionId)
-
- CodeWhispererTelemetryServiceNew.getInstance().sendServiceInvocationEvent(
- currentJobId,
- requestId,
- requestContext,
- responseContext,
- lastRecommendationIndex,
- false,
- 0.0,
- exceptionType
- )
-
- LOG.debug {
- "The provided customization ${requestContext.customizationArn} is not found, " +
- "will fallback to the default and retry generate completion"
- }
- logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
-
- notifyWarn(
- title = "",
- content = message("codewhisperer.notification.custom.not_available"),
- project = requestContext.project,
- notificationActions = listOf(
- NotificationAction.create(
- message("codewhisperer.notification.custom.simple.button.select_another_customization")
- ) { _, notification ->
- CodeWhispererModelConfigurator.getInstance().showConfigDialog(requestContext.project)
- notification.expire()
- }
- )
- )
- CodeWhispererInvocationStatusNew.getInstance().finishInvocation()
-
- requestContext.customizationArn?.let { CodeWhispererModelConfigurator.getInstance().invalidateCustomization(it) }
-
- showRecommendationsInPopup(
- requestContext.editor,
- requestContext.triggerTypeInfo,
- latencyContext
- )
- return
- } else if (e is CodeWhispererException) {
- requestId = e.requestId().orEmpty()
- 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 CodeWhispererRuntimeException) {
- requestId = e.requestId().orEmpty()
- sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
- displayMessage = e.awsErrorDetails().errorMessage() ?: message("codewhisperer.trigger.error.server_side")
- } else {
- requestId = ""
- sessionId = ""
- val statusCode = if (e is SdkServiceException) e.statusCode() else 0
- displayMessage =
- if (statusCode >= 500) {
- message("codewhisperer.trigger.error.server_side")
- } else {
- message("codewhisperer.trigger.error.client_side")
- }
- if (statusCode < 500) {
- LOG.debug(e) { "Error invoking CodeWhisperer service" }
- }
- }
- val exceptionType = e::class.simpleName
- val responseContext = ResponseContext(sessionId)
- CodeWhispererInvocationStatusNew.getInstance().setInvocationSessionId(sessionId)
- logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
- CodeWhispererTelemetryServiceNew.getInstance().sendServiceInvocationEvent(
- currentJobId,
- requestId,
- requestContext,
- responseContext,
- lastRecommendationIndex,
- false,
- 0.0,
- exceptionType
- )
-
- if (e is ThrottlingException &&
- e.message == CodeWhispererConstants.THROTTLING_MESSAGE
- ) {
- CodeWhispererExplorerActionManager.getInstance().setSuspended(requestContext.project)
- if (requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
- notifyErrorCodeWhispererUsageLimit(requestContext.project)
- }
- } else {
- if (requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
- // We should only show error hint when CodeWhisperer popup is not visible,
- // and make it silent if CodeWhisperer popup is showing.
- runInEdt {
- if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) {
- showCodeWhispererErrorHint(requestContext.editor, displayMessage)
- }
- }
- }
- }
- CodeWhispererInvocationStatusNew.getInstance().finishInvocation()
- runInEdt {
- CodeWhispererPopupManagerNew.getInstance().updatePopupPanel(sessionContext)
- }
- }
- }
-
- @RequiresEdt
- private fun processCodeWhispererUI(
- sessionContext: SessionContextNew,
- workerContext: WorkerContextNew,
- currStates: InvocationContextNew?,
- coroutine: CoroutineScope,
- jobId: Int,
- ) {
- val requestContext = workerContext.requestContext
- val responseContext = workerContext.responseContext
- val response = workerContext.response
- 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 (!coroutine.isActive || sessionContext.isDisposed()) {
- 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, jobId: $jobId" }
- disposeDisplaySession(false)
- return
- }
-
- CodeWhispererInvocationStatusNew.getInstance().finishInvocation()
-
- val caretMovement = CodeWhispererEditorManagerNew.getInstance().getCaretMovement(
- requestContext.editor,
- requestContext.caretPosition
- )
- val isPopupShowing = checkRecommendationsValidity(currStates, false)
- val nextStates: InvocationContextNew?
- if (currStates == null) {
- // first response for the jobId
- nextStates = initStates(jobId, requestContext, responseContext, response, caretMovement)
-
- // receiving a null state means caret has moved backward,
- // so we are going to cancel the current job
- if (nextStates == null) {
- return
- }
- } else {
- // subsequent responses for the jobId
- nextStates = updateStates(currStates, response)
- }
- 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(nextStates, true)
- val allSuggestions = ongoingRequests.values.filterNotNull().flatMap { it.recommendationContext.details }
- val valid = allSuggestions.count { !it.isDiscarded }
- 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()) {
- LOG.debug { "Received just an empty list from this session, requestId: $requestId" }
- CodeWhispererTelemetryServiceNew.getInstance().sendUserDecisionEvent(
- requestContext,
- responseContext,
- DetailContextNew(
- requestId,
- Completion.builder().build(),
- Completion.builder().build(),
- false,
- false,
- "",
- CodewhispererCompletionType.Line
- ),
- -1,
- CodewhispererSuggestionState.Empty,
- nextStates.recommendationContext.details.size
- )
- }
- if (!hasAtLeastOneValid) {
- 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 = CodeWhispererPopupManagerNew.getInstance().findNewSelectedIndex(true, sessionContext.selectedIndex)
- }
- } else {
- updateCodeWhisperer(sessionContext, nextStates, isPopupShowing)
- }
- }
-
- private fun initStates(
- jobId: Int,
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- response: GenerateCompletionsResponse,
- caretMovement: CaretMovement,
- ): InvocationContextNew? {
- val requestId = response.responseMetadata().requestId()
- val recommendations = response.completions()
- val visualPosition = requestContext.editor.caretModel.visualPosition
-
- if (caretMovement == CaretMovement.MOVE_BACKWARD) {
- LOG.debug { "Caret moved backward, discarding all of the recommendations and exiting the session. Request ID: $requestId, jobId: $jobId" }
- val detailContexts = recommendations.map {
- DetailContextNew("", it, it, true, false, "", getCompletionType(it))
- }.toMutableList()
- val recommendationContext = RecommendationContextNew(detailContexts, "", "", VisualPosition(0, 0), jobId)
- ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext)
- disposeDisplaySession(false)
- return null
- }
-
- val userInputOriginal = CodeWhispererEditorManagerNew.getInstance().getUserInputSinceInvocation(
- requestContext.editor,
- requestContext.caretPosition.offset
- )
- val userInput =
- if (caretMovement == CaretMovement.NO_CHANGE) {
- LOG.debug { "Caret position not changed since invocation. Request ID: $requestId" }
- ""
- } else {
- userInputOriginal.trimStart().also {
- LOG.debug {
- "Caret position moved forward since invocation. Request ID: $requestId, " +
- "user input since invocation: $userInputOriginal, " +
- "user input without leading spaces: $it"
- }
- }
- }
- val detailContexts = CodeWhispererRecommendationManager.getInstance().buildDetailContext(
- requestContext,
- userInput,
- recommendations,
- requestId
- )
- val recommendationContext = RecommendationContextNew(detailContexts, userInputOriginal, userInput, visualPosition, jobId)
- ongoingRequests[jobId] = buildInvocationContext(requestContext, responseContext, recommendationContext)
- return ongoingRequests[jobId]
- }
-
- private fun updateStates(
- states: InvocationContextNew,
- response: GenerateCompletionsResponse,
- ): InvocationContextNew {
- val recommendationContext = states.recommendationContext
- val newDetailContexts = CodeWhispererRecommendationManager.getInstance().buildDetailContext(
- states.requestContext,
- recommendationContext.userInputSinceInvocation,
- response.completions(),
- response.responseMetadata().requestId()
- )
-
- recommendationContext.details.addAll(newDetailContexts)
- return states
- }
-
- private fun checkRecommendationsValidity(states: InvocationContextNew?, showHint: Boolean): Boolean {
- if (states == null) return false
- val details = states.recommendationContext.details
-
- // set to true when at least one is not discarded or empty
- val hasAtLeastOneValid = details.any { !it.isDiscarded && it.recommendation.content().isNotEmpty() }
-
- if (!hasAtLeastOneValid && showHint && states.requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
- showCodeWhispererInfoHint(
- states.requestContext.editor,
- message("codewhisperer.popup.no_recommendations")
- )
- }
- return hasAtLeastOneValid
- }
-
- private fun updateCodeWhisperer(sessionContext: SessionContextNew, states: InvocationContextNew, recommendationAdded: Boolean) {
- CodeWhispererPopupManagerNew.getInstance().changeStatesForShowing(sessionContext, states, recommendationAdded)
- }
-
- @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
-
- 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() =
- ongoingRequests.values.filterNotNull().flatMap { element ->
- val context = element.recommendationContext
- context.details.map {
- PreviewContext(context.jobId, it, context.userInputSinceInvocation, context.typeahead)
- }
- }
-
- fun getAllPaginationSessions() = ongoingRequests
-
- fun getRequestContext(
- triggerTypeInfo: TriggerTypeInfo,
- editor: Editor,
- project: Project,
- psiFile: PsiFile,
- ): RequestContextNew {
- // 1. file context
- val fileContext: FileContextInfo = runReadAction { FileContextProvider.getInstance(project).extractFileContext(editor, psiFile) }
-
- // the upper bound for supplemental context duration is 50ms
- // 2. supplemental context
- val supplementalContext = cs.async {
- try {
- FileContextProvider.getInstance(project).extractSupplementalFileContext(psiFile, fileContext, timeout = SUPPLEMENTAL_CONTEXT_TIMEOUT)
- } catch (e: Exception) {
- LOG.warn { "Run into unexpected error when fetching supplemental context, error: ${e.message}" }
- null
- }
- }
-
- // 3. caret position
- val caretPosition = runReadAction { getCaretPosition(editor) }
-
- // 4. connection
- val connection = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(CodeWhispererConnection.getInstance())
-
- // 5. customization
- val customizationArn = CodeWhispererModelConfigurator.getInstance().activeCustomization(project)?.arn
-
- return RequestContextNew(project, editor, triggerTypeInfo, caretPosition, fileContext, supplementalContext, connection, customizationArn)
- }
-
- fun validateResponse(response: GenerateCompletionsResponse): GenerateCompletionsResponse {
- // If contentSpans in reference are not consistent with content(recommendations),
- // remove the incorrect references.
- val validatedRecommendations = response.completions().map {
- val validReferences = it.hasReferences() && it.references().isNotEmpty() &&
- it.references().none { reference ->
- val span = reference.recommendationContentSpan()
- span.start() > span.end() || span.start() < 0 || span.end() > it.content().length
- }
- if (validReferences) {
- it
- } else {
- it.toBuilder().references(DefaultSdkAutoConstructList.getInstance()).build()
- }
- }
-
- return response.toBuilder().completions(validatedRecommendations).build()
- }
-
- private fun buildInvocationContext(
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- recommendationContext: RecommendationContextNew,
- ): InvocationContextNew {
- // 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 = InvocationContextNew(requestContext, responseContext, recommendationContext)
- Disposer.register(states) {
- job?.cancel(CancellationException("Cancelling the current coroutine when the pagination session context is disposed"))
- }
- return states
- }
-
- private fun logServiceInvocation(
- requestId: String,
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- recommendations: List,
- latency: Double?,
- exceptionType: String?,
- ) {
- val recommendationLogs = recommendations.map { it.content().trimEnd() }
- .reduceIndexedOrNull { index, acc, recommendation -> "$acc\n[${index + 1}]\n$recommendation" }
- LOG.info {
- "SessionId: ${responseContext.sessionId}, " +
- "RequestId: $requestId, " +
- "Jetbrains IDE: ${ApplicationInfo.getInstance().fullApplicationName}, " +
- "IDE version: ${ApplicationInfo.getInstance().apiVersion}, " +
- "Filename: ${requestContext.fileContextInfo.filename}, " +
- "Left context of current line: ${requestContext.fileContextInfo.caretContext.leftContextOnCurrentLine}, " +
- "Cursor line: ${requestContext.caretPosition.line}, " +
- "Caret offset: ${requestContext.caretPosition.offset}, " +
- latency?.let { "Latency: $latency, " }.orEmpty() +
- exceptionType?.let { "Exception Type: $it, " }.orEmpty() +
- "Recommendations: \n${recommendationLogs ?: "None"}"
- }
- }
-
- fun canDoInvocation(editor: Editor, type: CodewhispererTriggerType): Boolean {
- editor.project?.let {
- if (!isCodeWhispererEnabled(it)) {
- return false
- }
- }
-
- if (type == CodewhispererTriggerType.AutoTrigger && !CodeWhispererExplorerActionManager.getInstance().isAutoEnabled()) {
- LOG.debug { "CodeWhisperer auto-trigger is disabled, not invoking service" }
- return false
- }
-
- if (CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) {
- LOG.debug { "Find an existing CodeWhisperer session before triggering CodeWhisperer, not invoking service" }
- return false
- }
- return true
- }
-
- fun showCodeWhispererInfoHint(editor: Editor, message: String) {
- HintManager.getInstance().showInformationHint(editor, message, HintManager.UNDER)
- }
-
- fun showCodeWhispererErrorHint(editor: Editor, message: String) {
- HintManager.getInstance().showErrorHint(editor, message, HintManager.UNDER)
- }
-
- override fun dispose() {}
-
- companion object {
- private val LOG = getLogger()
- private const val MAX_REFRESH_ATTEMPT = 3
- private const val PAGINATION_REQUEST_COUNT_ALLOWED = 1
-
- val CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER: Topic = Topic.create(
- "CodeWhisperer intelliSense popup on hover",
- CodeWhispererIntelliSenseOnHoverListener::class.java
- )
- val KEY_SESSION_CONTEXT = Key.create("codewhisperer.session")
-
- fun getInstance(): CodeWhispererServiceNew = service()
- const val KET_SESSION_ID = "x-amzn-SessionId"
- private var reAuthPromptShown = false
-
- fun markReAuthPromptShown() {
- reAuthPromptShown = true
- }
-
- fun hasReAuthPromptBeenShown() = reAuthPromptShown
-
- fun buildCodeWhispererRequest(
- fileContextInfo: FileContextInfo,
- supplementalContext: SupplementalContextInfo?,
- customizationArn: String?,
- ): GenerateCompletionsRequest {
- val programmingLanguage = ProgrammingLanguage.builder()
- .languageName(fileContextInfo.programmingLanguage.toCodeWhispererRuntimeLanguage().languageId)
- .build()
- val fileContext = FileContext.builder()
- .leftFileContent(fileContextInfo.caretContext.leftFileContext)
- .rightFileContent(fileContextInfo.caretContext.rightFileContext)
- .filename(fileContextInfo.fileRelativePath ?: fileContextInfo.filename)
- .programmingLanguage(programmingLanguage)
- .build()
- val supplementalContexts = supplementalContext?.contents?.map {
- SupplementalContext.builder()
- .content(it.content)
- .filePath(it.path)
- .build()
- }.orEmpty()
- val includeCodeWithReference = if (CodeWhispererSettings.getInstance().isIncludeCodeWithReference()) {
- RecommendationsWithReferencesPreference.ALLOW
- } else {
- RecommendationsWithReferencesPreference.BLOCK
- }
-
- return GenerateCompletionsRequest.builder()
- .fileContext(fileContext)
- .supplementalContexts(supplementalContexts)
- .referenceTrackerConfiguration { it.recommendationsWithReferences(includeCodeWithReference) }
- .customizationArn(customizationArn)
- .optOutPreference(getTelemetryOptOutPreference())
- .build()
- }
- }
-}
-
-data class RequestContextNew(
- val project: Project,
- val editor: Editor,
- val triggerTypeInfo: TriggerTypeInfo,
- val caretPosition: CaretPosition,
- val fileContextInfo: FileContextInfo,
- private val supplementalContextDeferred: Deferred,
- val connection: ToolkitConnection?,
- val customizationArn: String?,
-) {
- // TODO: should make the entire getRequestContext() suspend function instead of making supplemental context only
- var supplementalContext: SupplementalContextInfo? = null
- private set
- get() = when (field) {
- null -> {
- if (!supplementalContextDeferred.isCompleted) {
- error("attempt to access supplemental context before awaiting the deferred")
- } else {
- null
- }
- }
- else -> field
- }
-
- suspend fun awaitSupplementalContext(): SupplementalContextInfo? {
- supplementalContext = supplementalContextDeferred.await()
- return supplementalContext
- }
-}
-
-interface CodeWhispererIntelliSenseOnHoverListener {
- fun onEnter() {}
-}
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 8951d04aed..f8d07a2606 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
@@ -17,7 +17,6 @@ import com.intellij.ui.dsl.builder.panel
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.amazonq.CodeWhispererFeatureConfigService
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererLoginType
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled
@@ -95,20 +94,18 @@ class CodeWhispererConfigurable(private val project: Project) :
}.comment(message("aws.settings.codewhisperer.automatic_import_adder.tooltip"))
}
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- row {
- link("Configure inline suggestion keybindings") { e ->
- // TODO: user needs feedback if these are null
- val settings = DataManager.getInstance().getDataContext(e.source as ActionLink).getData(Settings.KEY) ?: return@link
- val configurable: Configurable = settings.find("preferences.keymap") ?: return@link
+ row {
+ link("Configure inline suggestion keybindings") { e ->
+ // TODO: user needs feedback if these are null
+ val settings = DataManager.getInstance().getDataContext(e.source as ActionLink).getData(Settings.KEY) ?: return@link
+ val configurable: Configurable = settings.find("preferences.keymap") ?: return@link
- settings.select(configurable, Q_INLINE_KEYBINDING_SEARCH_TEXT)
+ 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, Q_INLINE_KEYBINDING_SEARCH_TEXT)
- }, 500, TimeUnit.MILLISECONDS)
- }
+ // workaround for certain cases for sometimes the string is not input there
+ EdtExecutorService.getScheduledExecutorInstance().schedule({
+ settings.select(configurable, Q_INLINE_KEYBINDING_SEARCH_TEXT)
+ }, 500, TimeUnit.MILLISECONDS)
}
}
}
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 f182197658..ce5de79f1f 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererIntelliSenseAutoTriggerListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/startup/CodeWhispererIntelliSenseAutoTriggerListener.kt
@@ -11,10 +11,9 @@ 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.amazonq.CodeWhispererFeatureConfigService
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.CodeWhispererServiceNew
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
import java.awt.Component
object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener {
@@ -33,6 +32,7 @@ object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener {
CodeWhispererAutoTriggerService.getInstance().tryInvokeAutoTrigger(editor, CodeWhispererAutomatedTriggerType.IntelliSense())
cleanup()
}
+
override fun lookupCanceled(event: LookupEvent) {
cleanup()
}
@@ -42,21 +42,20 @@ object CodeWhispererIntelliSenseAutoTriggerListener : LookupManagerListener {
}
})
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- (newLookup as LookupImpl).component.addMouseHoverListener(
- newLookup,
- object : HoverListener() {
- override fun mouseEntered(component: Component, x: Int, y: Int) {
- runReadAction {
- newLookup.project.messageBus.syncPublisher(
- CodeWhispererServiceNew.CODEWHISPERER_INTELLISENSE_POPUP_ON_HOVER,
- ).onEnter()
- }
+ (newLookup as LookupImpl).component.addMouseHoverListener(
+ newLookup,
+ object : HoverListener() {
+ override fun mouseEntered(component: Component, x: Int, y: Int) {
+ 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) {}
}
- )
- }
+
+ 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/status/CodeWhispererStatusBarWidget.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/status/CodeWhispererStatusBarWidget.kt
index 2e42b47f65..b7f7c8b333 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
@@ -21,14 +21,12 @@ import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener
import software.aws.toolkits.jetbrains.core.credentials.profiles.ProfileWatcher
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.QActionGroups.Q_SIGNED_OUT_ACTION_GROUP
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomizationListener
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
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.CodeWhispererInvocationStatusNew
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.reconnectCodeWhisperer
import software.aws.toolkits.jetbrains.utils.isQConnected
import software.aws.toolkits.jetbrains.utils.isQExpired
@@ -125,13 +123,7 @@ class CodeWhispererStatusBarWidget(project: Project) :
AllIcons.General.BalloonWarning
} else if (!isQConnected(project)) {
AllIcons.RunConfigurations.TestState.Run
- } else if (
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- CodeWhispererInvocationStatusNew.getInstance().hasExistingServiceInvocation()
- } else {
- CodeWhispererInvocationStatus.getInstance().hasExistingServiceInvocation()
- }
- ) {
+ } else if (CodeWhispererInvocationStatus.getInstance().hasExistingServiceInvocation()) {
// AnimatedIcon can't serialize over remote host
if (!AppMode.isRemoteDevHost()) {
AnimatedIcon.Default()
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 dc512c1ca9..e5f0e504be 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
@@ -19,16 +19,13 @@ import org.jetbrains.annotations.TestOnly
import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
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.FileContextInfo
import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
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.model.SessionContextNew
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
@@ -89,52 +86,30 @@ abstract class CodeWhispererCodeCoverageTracker(
if (!isTelemetryEnabled() || isActive.getAndSet(true)) return
val conn = ApplicationManager.getApplication().messageBus.connect()
- if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
- conn.subscribe(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED,
- object : CodeWhispererUserActionListener {
- override fun afterAccept(
- states: InvocationContextNew,
- previews: List,
- sessionContext: SessionContextNew,
- rangeMarker: RangeMarker,
- ) {
- if (states.requestContext.fileContextInfo.programmingLanguage != language) return
- rangeMarkers.add(rangeMarker)
- val originalRecommendation = extractRangeMarkerString(rangeMarker) ?: return
- rangeMarker.putUserData(KEY_REMAINING_RECOMMENDATION, originalRecommendation)
- runReadAction {
- // also increment total tokens because accepted tokens are part of it
- incrementTotalCharsCount(rangeMarker.document, originalRecommendation.length)
- // avoid counting CodeWhisperer inserted suggestion twice in total tokens
- if (rangeMarker.textRange.length in 2..49 && originalRecommendation.trim().isNotEmpty()) {
- incrementTotalCharsCount(rangeMarker.document, -rangeMarker.textRange.length)
- }
- }
- }
- }
- )
- } else {
- conn.subscribe(
- CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED,
- object : CodeWhispererUserActionListener {
- override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {
- if (states.requestContext.fileContextInfo.programmingLanguage != language) return
- rangeMarkers.add(rangeMarker)
- val originalRecommendation = extractRangeMarkerString(rangeMarker) ?: return
- rangeMarker.putUserData(KEY_REMAINING_RECOMMENDATION, originalRecommendation)
- runReadAction {
- // also increment total tokens because accepted tokens are part of it
- incrementTotalCharsCount(rangeMarker.document, originalRecommendation.length)
- // avoid counting CodeWhisperer inserted suggestion twice in total tokens
- if (rangeMarker.textRange.length in 2..49 && originalRecommendation.trim().isNotEmpty()) {
- incrementTotalCharsCount(rangeMarker.document, -rangeMarker.textRange.length)
- }
+ conn.subscribe(
+ CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED,
+ object : CodeWhispererUserActionListener {
+ 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
+ rangeMarker.putUserData(KEY_REMAINING_RECOMMENDATION, originalRecommendation)
+ runReadAction {
+ // also increment total tokens because accepted tokens are part of it
+ incrementTotalCharsCount(rangeMarker.document, originalRecommendation.length)
+ // avoid counting CodeWhisperer inserted suggestion twice in total tokens
+ if (rangeMarker.textRange.length in 2..49 && originalRecommendation.trim().isNotEmpty()) {
+ incrementTotalCharsCount(rangeMarker.document, -rangeMarker.textRange.length)
}
}
}
- )
- }
+ }
+ )
conn.subscribe(
CodeWhispererService.CODEWHISPERER_CODE_COMPLETION_PERFORMED,
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 77f62b65b9..adda99e442 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
@@ -14,6 +14,7 @@ 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
@@ -23,14 +24,14 @@ 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.RequestContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.ResponseContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
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.CodeWhispererInvocationStatus
-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.service.CodeWhispererService
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCodeWhispererStartUrl
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getConnectionStartUrl
@@ -59,11 +60,9 @@ class CodeWhispererTelemetryService {
private var previousUserTriggerDecisionTimestamp: Instant? = null
- private val codewhispererTimeSinceLastUserDecision: Double?
- get() {
- return previousUserTriggerDecisionTimestamp?.let {
- Duration.between(it, Instant.now()).toMillis().toDouble()
- }
+ private val codewhispererTimeSinceLastUserDecision: Double? =
+ previousUserTriggerDecisionTimestamp?.let {
+ Duration.between(it, Instant.now()).toMillis().toDouble()
}
val previousUserTriggerDecision: CodewhispererPreviousSuggestionState?
@@ -90,6 +89,7 @@ class CodeWhispererTelemetryService {
}
fun sendServiceInvocationEvent(
+ jobId: Int,
requestId: String,
requestContext: RequestContext,
responseContext: ResponseContext,
@@ -98,6 +98,7 @@ class CodeWhispererTelemetryService {
latency: Double,
exceptionType: String?,
) {
+ LOG.debug { "Sending serviceInvocation for $requestId, jobId: $jobId" }
val (triggerType, automatedTriggerType) = requestContext.triggerTypeInfo
val (offset, line) = requestContext.caretPosition
@@ -186,6 +187,7 @@ class CodeWhispererTelemetryService {
}
fun sendUserTriggerDecisionEvent(
+ sessionContext: SessionContext,
requestContext: RequestContext,
responseContext: ResponseContext,
recommendationContext: RecommendationContext,
@@ -222,6 +224,7 @@ class CodeWhispererTelemetryService {
try {
val response = CodeWhispererClientAdaptor.getInstance(project)
.sendUserTriggerDecisionTelemetry(
+ sessionContext,
requestContext,
responseContext,
completionType,
@@ -246,7 +249,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,
@@ -265,7 +268,7 @@ class CodeWhispererTelemetryService {
codewhispererTypeaheadLength = recommendationContext.userInputSinceInvocation.length.toLong(),
codewhispererTimeSinceLastDocumentChange = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged(),
codewhispererTimeSinceLastUserDecision = codewhispererTimeSinceLastUserDecision,
- codewhispererTimeToFirstRecommendation = requestContext.latencyContext.paginationFirstCompletionTime,
+ codewhispererTimeToFirstRecommendation = sessionContext.latencyContext.paginationFirstCompletionTime,
codewhispererPreviousSuggestionState = previousUserTriggerDecision,
codewhispererSuggestionState = suggestionState,
codewhispererClassifierResult = classifierResult,
@@ -386,59 +389,66 @@ class CodeWhispererTelemetryService {
}
fun sendUserDecisionEventForAll(
- requestContext: RequestContext,
- responseContext: ResponseContext,
- recommendationContext: RecommendationContext,
sessionContext: SessionContext,
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)
+ var hasSentRejectedTrigger = false
+ CodeWhispererService.getInstance().getAllPaginationSessions().forEach { (jobId, state) ->
+ if (state == null) return@forEach
+ val details = state.recommendationContext.details
- decisions.add(suggestionState)
- }
+ val decisions = details.mapIndexed { index, detail ->
+ val suggestionState = recordSuggestionState(detail, hasUserAccepted)
+ sendUserDecisionEvent(state.requestContext, state.responseContext, detail, index, suggestionState, details.size)
- with(aggregateUserDecision(decisions)) {
- // the order of the following matters
- // step 1, send out current decision
- previousUserTriggerDecisionTimestamp = Instant.now()
+ suggestionState
+ }
+ LOG.debug { "jobId: $jobId, userDecisions: [${decisions.joinToString(", ")}]" }
- val referenceCount = if (hasUserAccepted && detailContexts[sessionContext.selectedIndex].recommendation.hasReferences()) 1 else 0
- val acceptedContent =
- if (hasUserAccepted) {
- detailContexts[sessionContext.selectedIndex].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
- )
+ with(aggregateUserDecision(decisions, hasSentRejectedTrigger)) {
+ // the order of the following matters
+ // step 1, send out current decision
- // 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 we have sent one reject in this display session(which can contain multiple triggers),
+ // we will not send rejects for the remaining triggers in this display session.
+ if (this == CodewhispererSuggestionState.Reject) {
+ hasSentRejectedTrigger = true
+ }
+ LOG.debug { "jobId: $jobId, userTriggerDecision: $this" }
+ previousUserTriggerDecisionTimestamp = Instant.now()
+
+ val previews = CodeWhispererService.getInstance().getAllSuggestionsPreviewInfo()
+ val recommendation =
+ if (hasUserAccepted) {
+ previews[sessionContext.selectedIndex].detail.recommendation
+ } else {
+ Completion.builder().content("").references(emptyList()).build()
+ }
+ val referenceCount = if (hasUserAccepted && recommendation.hasReferences()) 1 else 0
+ val acceptedContent = recommendation.content()
+ val generatedLineCount = if (acceptedContent.isEmpty()) 0 else acceptedContent.split("\n").size
+ val acceptedCharCount = acceptedContent.length
+ sendUserTriggerDecisionEvent(
+ sessionContext,
+ state.requestContext,
+ state.responseContext,
+ state.recommendationContext,
+ this,
+ popupShownTime,
+ referenceCount,
+ generatedLineCount,
+ acceptedCharCount
+ )
+
+ // step 2, put current decision into queue for later reference
+ 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)
+ }
+ }
}
}
@@ -447,96 +457,68 @@ 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 (but has valid suggestions)
* - Record the accepted suggestion index
* - Discard otherwise
*/
- fun aggregateUserDecision(decisions: List): CodewhispererPreviousSuggestionState {
+ fun aggregateUserDecision(
+ decisions: List,
+ hasRejectedTrigger: 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 if (!hasRejectedTrigger) {
+ CodewhispererSuggestionState.Reject
+ } else {
+ CodewhispererSuggestionState.Ignore
+ }
+ } else if (decision == CodewhispererSuggestionState.Unseen) {
+ isEmpty = false
+ isDiscard = false
+ } else if (decision == CodewhispererSuggestionState.Ignore) {
+ isUnseen = false
+ isEmpty = false
+ 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
}
}
- 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,
- 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,
- codewhispererCustomizationArn = requestContext.customizationArn,
- )
- }
-
fun sendOnboardingClickEvent(language: CodeWhispererProgrammingLanguage, taskType: CodewhispererGettingStartedTask) {
// Project instance is not needed. We look at these metrics for each clientId.
CodewhispererTelemetry.onboardingClick(project = null, codewhispererLanguage = language.toTelemetryType(), codewhispererGettingStartedTask = taskType)
}
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/telemetry/CodeWhispererTelemetryServiceNew.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryServiceNew.kt
deleted file mode 100644
index 7ed73527d5..0000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryServiceNew.kt
+++ /dev/null
@@ -1,520 +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.telemetry
-
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.components.Service
-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 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
-import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
-import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue
-import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
-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.DetailContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContextNew
-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.CodeWhispererInvocationStatus
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContextNew
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCodeWhispererStartUrl
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getConnectionStartUrl
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getGettingStartedTaskType
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled
-import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
-import software.aws.toolkits.telemetry.CodewhispererCodeScanScope
-import software.aws.toolkits.telemetry.CodewhispererCompletionType
-import software.aws.toolkits.telemetry.CodewhispererGettingStartedTask
-import software.aws.toolkits.telemetry.CodewhispererLanguage
-import software.aws.toolkits.telemetry.CodewhispererPreviousSuggestionState
-import software.aws.toolkits.telemetry.CodewhispererSuggestionState
-import software.aws.toolkits.telemetry.CodewhispererTelemetry
-import software.aws.toolkits.telemetry.CodewhispererTriggerType
-import software.aws.toolkits.telemetry.Component
-import software.aws.toolkits.telemetry.Result
-import java.time.Duration
-import java.time.Instant
-import java.util.Queue
-
-@Service
-class CodeWhispererTelemetryServiceNew {
- // store previous 5 userTrigger decisions
- private val previousUserTriggerDecisions = CircularFifoQueue(5)
-
- private var previousUserTriggerDecisionTimestamp: Instant? = null
-
- private val codewhispererTimeSinceLastUserDecision: Double? =
- previousUserTriggerDecisionTimestamp?.let {
- Duration.between(it, Instant.now()).toMillis().toDouble()
- }
-
- val previousUserTriggerDecision: CodewhispererPreviousSuggestionState?
- get() = if (previousUserTriggerDecisions.isNotEmpty()) previousUserTriggerDecisions.last() else null
-
- companion object {
- fun getInstance(): CodeWhispererTelemetryServiceNew = service()
- val LOG = getLogger()
- const val NO_ACCEPTED_INDEX = -1
- }
-
- fun sendFailedServiceInvocationEvent(project: Project, exceptionType: String?) {
- CodewhispererTelemetry.serviceInvocation(
- project = project,
- codewhispererCursorOffset = 0,
- codewhispererLanguage = CodewhispererLanguage.Unknown,
- codewhispererLastSuggestionIndex = -1,
- codewhispererLineNumber = 0,
- codewhispererTriggerType = CodewhispererTriggerType.Unknown,
- duration = 0.0,
- reason = exceptionType,
- success = false,
- )
- }
-
- fun sendServiceInvocationEvent(
- jobId: Int,
- requestId: String,
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- lastRecommendationIndex: Int,
- invocationSuccess: Boolean,
- latency: Double,
- exceptionType: String?,
- ) {
- LOG.debug { "Sending serviceInvocation for $requestId, jobId: $jobId" }
- val (triggerType, automatedTriggerType) = requestContext.triggerTypeInfo
- val (offset, line) = requestContext.caretPosition
-
- // since python now only supports UTG but not cross file context
- val supContext = if (requestContext.fileContextInfo.programmingLanguage.isUTGSupported() &&
- requestContext.supplementalContext?.isUtg == true
- ) {
- requestContext.supplementalContext
- } else if (requestContext.fileContextInfo.programmingLanguage.isSupplementalContextSupported() &&
- requestContext.supplementalContext?.isUtg == false
- ) {
- requestContext.supplementalContext
- } else {
- null
- }
-
- val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType()
- val startUrl = getConnectionStartUrl(requestContext.connection)
- CodewhispererTelemetry.serviceInvocation(
- project = requestContext.project,
- codewhispererAutomatedTriggerType = automatedTriggerType.telemetryType,
- codewhispererCompletionType = CodewhispererCompletionType.Line,
- codewhispererCursorOffset = offset.toLong(),
- codewhispererGettingStartedTask = getGettingStartedTaskType(requestContext.editor),
- codewhispererLanguage = codewhispererLanguage,
- codewhispererLastSuggestionIndex = lastRecommendationIndex.toLong(),
- codewhispererLineNumber = line.toLong(),
- codewhispererRequestId = requestId,
- codewhispererSessionId = responseContext.sessionId,
- codewhispererTriggerType = triggerType,
- duration = latency,
- reason = exceptionType,
- success = invocationSuccess,
- credentialStartUrl = startUrl,
- codewhispererImportRecommendationEnabled = CodeWhispererSettings.getInstance().isImportAdderEnabled(),
- codewhispererSupplementalContextTimeout = supContext?.isProcessTimeout,
- codewhispererSupplementalContextIsUtg = supContext?.isUtg,
- codewhispererSupplementalContextLatency = supContext?.latency?.toDouble(),
- codewhispererSupplementalContextLength = supContext?.contentLength?.toLong(),
- codewhispererCustomizationArn = requestContext.customizationArn,
- )
- }
-
- fun sendUserDecisionEvent(
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- detailContext: DetailContextNew,
- index: Int,
- suggestionState: CodewhispererSuggestionState,
- numOfRecommendations: Int,
- ) {
- val requestId = detailContext.requestId
- val recommendation = detailContext.recommendation
- val (project, _, triggerTypeInfo) = requestContext
- val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType()
- val supplementalContext = requestContext.supplementalContext
-
- LOG.debug {
- "Recording user decisions of recommendation. " +
- "Index: $index, " +
- "State: $suggestionState, " +
- "Request ID: $requestId, " +
- "Recommendation: ${recommendation.content()}"
- }
- val startUrl = getConnectionStartUrl(requestContext.connection)
- val importEnabled = CodeWhispererSettings.getInstance().isImportAdderEnabled()
- CodewhispererTelemetry.userDecision(
- project = project,
- codewhispererCompletionType = detailContext.completionType,
- codewhispererGettingStartedTask = getGettingStartedTaskType(requestContext.editor),
- codewhispererLanguage = codewhispererLanguage,
- codewhispererPaginationProgress = numOfRecommendations.toLong(),
- codewhispererRequestId = requestId,
- codewhispererSessionId = responseContext.sessionId,
- codewhispererSuggestionIndex = index.toLong(),
- codewhispererSuggestionReferenceCount = recommendation.references().size.toLong(),
- codewhispererSuggestionReferences = jacksonObjectMapper().writeValueAsString(recommendation.references().map { it.licenseName() }.toSet().toList()),
- codewhispererSuggestionImportCount = if (importEnabled) recommendation.mostRelevantMissingImports().size.toLong() else null,
- codewhispererSuggestionState = suggestionState,
- codewhispererTriggerType = triggerTypeInfo.triggerType,
- credentialStartUrl = startUrl,
- codewhispererSupplementalContextIsUtg = supplementalContext?.isUtg,
- codewhispererSupplementalContextLength = supplementalContext?.contentLength?.toLong(),
- codewhispererSupplementalContextTimeout = supplementalContext?.isProcessTimeout,
- )
- }
-
- fun sendUserTriggerDecisionEvent(
- sessionContext: SessionContextNew,
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- recommendationContext: RecommendationContextNew,
- suggestionState: CodewhispererSuggestionState,
- popupShownTime: Duration?,
- suggestionReferenceCount: Int,
- generatedLineCount: Int,
- acceptedCharCount: Int,
- ) {
- val project = requestContext.project
- val totalImportCount = recommendationContext.details.fold(0) { grandTotal, detail ->
- grandTotal + detail.recommendation.mostRelevantMissingImports().size
- }
-
- val automatedTriggerType = requestContext.triggerTypeInfo.automatedTriggerType
- val triggerChar = if (automatedTriggerType is CodeWhispererAutomatedTriggerType.SpecialChar) {
- automatedTriggerType.specialChar.toString()
- } else {
- null
- }
-
- val language = requestContext.fileContextInfo.programmingLanguage
-
- val classifierResult = requestContext.triggerTypeInfo.automatedTriggerType.calculationResult
-
- val classifierThreshold = CodeWhispererAutoTriggerService.getThreshold()
-
- val supplementalContext = requestContext.supplementalContext
- val completionType = if (recommendationContext.details.isEmpty()) CodewhispererCompletionType.Line else recommendationContext.details[0].completionType
-
- // only send if it's a pro tier user
- projectCoroutineScope(project).launch {
- runIfIdcConnectionOrTelemetryEnabled(project) {
- try {
- val response = CodeWhispererClientAdaptor.getInstance(project)
- .sendUserTriggerDecisionTelemetry(
- sessionContext,
- requestContext,
- responseContext,
- completionType,
- suggestionState,
- suggestionReferenceCount,
- generatedLineCount,
- recommendationContext.details.size,
- acceptedCharCount
- )
- LOG.debug {
- "Successfully sent user trigger decision telemetry. RequestId: ${response.responseMetadata().requestId()}"
- }
- } catch (e: Exception) {
- val requestId = if (e is CodeWhispererRuntimeException) e.requestId() else null
- LOG.debug {
- "Failed to send user trigger decision telemetry. RequestId: $requestId, ErrorMessage: ${e.message}"
- }
- }
- }
- }
-
- CodewhispererTelemetry.userTriggerDecision(
- project = project,
- codewhispererSessionId = responseContext.sessionId,
- codewhispererFirstRequestId = sessionContext.latencyContext.firstRequestId,
- credentialStartUrl = getConnectionStartUrl(requestContext.connection),
- codewhispererIsPartialAcceptance = null,
- codewhispererPartialAcceptanceCount = null,
- codewhispererCharactersAccepted = acceptedCharCount.toLong(),
- codewhispererCharactersRecommended = null,
- codewhispererCompletionType = completionType,
- codewhispererLanguage = language.toTelemetryType(),
- codewhispererTriggerType = requestContext.triggerTypeInfo.triggerType,
- codewhispererAutomatedTriggerType = automatedTriggerType.telemetryType,
- codewhispererLineNumber = requestContext.caretPosition.line.toLong(),
- codewhispererCursorOffset = requestContext.caretPosition.offset.toLong(),
- codewhispererSuggestionCount = recommendationContext.details.size.toLong(),
- codewhispererSuggestionImportCount = totalImportCount.toLong(),
- codewhispererTotalShownTime = popupShownTime?.toMillis()?.toDouble(),
- codewhispererTriggerCharacter = triggerChar,
- codewhispererTypeaheadLength = recommendationContext.userInputSinceInvocation.length.toLong(),
- codewhispererTimeSinceLastDocumentChange = CodeWhispererInvocationStatus.getInstance().getTimeSinceDocumentChanged(),
- codewhispererTimeSinceLastUserDecision = codewhispererTimeSinceLastUserDecision,
- codewhispererTimeToFirstRecommendation = sessionContext.latencyContext.paginationFirstCompletionTime,
- codewhispererPreviousSuggestionState = previousUserTriggerDecision,
- codewhispererSuggestionState = suggestionState,
- codewhispererClassifierResult = classifierResult,
- codewhispererClassifierThreshold = classifierThreshold,
- codewhispererCustomizationArn = requestContext.customizationArn,
- codewhispererSupplementalContextIsUtg = supplementalContext?.isUtg,
- codewhispererSupplementalContextLength = supplementalContext?.contentLength?.toLong(),
- codewhispererSupplementalContextTimeout = supplementalContext?.isProcessTimeout,
- codewhispererSupplementalContextStrategyId = supplementalContext?.strategy.toString(),
- codewhispererGettingStartedTask = getGettingStartedTaskType(requestContext.editor),
- codewhispererFeatureEvaluations = CodeWhispererFeatureConfigService.getInstance().getFeatureConfigsTelemetry()
- )
- }
-
- fun sendSecurityScanEvent(codeScanEvent: CodeScanTelemetryEvent, project: Project? = null) {
- val payloadContext = codeScanEvent.codeScanResponseContext.payloadContext
- val serviceInvocationContext = codeScanEvent.codeScanResponseContext.serviceInvocationContext
- val codeScanJobId = codeScanEvent.codeScanResponseContext.codeScanJobId
- val totalIssues = codeScanEvent.codeScanResponseContext.codeScanTotalIssues
- val issuesWithFixes = codeScanEvent.codeScanResponseContext.codeScanIssuesWithFixes
- val reason = codeScanEvent.codeScanResponseContext.reason
- val startUrl = getConnectionStartUrl(codeScanEvent.connection)
- val codeAnalysisScope = codeScanEvent.codeAnalysisScope
- val passive = codeAnalysisScope == CodeWhispererConstants.CodeAnalysisScope.FILE
-
- LOG.debug {
- "Recording code security scan event. \n" +
- "Total number of security scan issues found: $totalIssues, \n" +
- "Number of security scan issues with fixes: $issuesWithFixes, \n" +
- "Language: ${payloadContext.language}, \n" +
- "Uncompressed source payload size in bytes: ${payloadContext.srcPayloadSize}, \n" +
- "Uncompressed build payload size in bytes: ${payloadContext.buildPayloadSize}, \n" +
- "Compressed source zip file size in bytes: ${payloadContext.srcZipFileSize}, \n" +
- "Total project size in bytes: ${codeScanEvent.totalProjectSizeInBytes}, \n" +
- "Total duration of the security scan job in milliseconds: ${codeScanEvent.duration}, \n" +
- "Context truncation duration in milliseconds: ${payloadContext.totalTimeInMilliseconds}, \n" +
- "Artifacts upload duration in milliseconds: ${serviceInvocationContext.artifactsUploadDuration}, \n" +
- "Service invocation duration in milliseconds: ${serviceInvocationContext.serviceInvocationDuration}, \n" +
- "Total number of lines scanned: ${payloadContext.totalLines}, \n" +
- "Reason: $reason \n" +
- "Scope: ${codeAnalysisScope.value} \n" +
- "Passive: $passive \n"
- }
- CodewhispererTelemetry.securityScan(
- project = project,
- codewhispererCodeScanLines = payloadContext.totalLines,
- codewhispererCodeScanJobId = codeScanJobId,
- codewhispererCodeScanProjectBytes = codeScanEvent.totalProjectSizeInBytes,
- codewhispererCodeScanSrcPayloadBytes = payloadContext.srcPayloadSize,
- codewhispererCodeScanBuildPayloadBytes = payloadContext.buildPayloadSize,
- codewhispererCodeScanSrcZipFileBytes = payloadContext.srcZipFileSize,
- codewhispererCodeScanTotalIssues = totalIssues.toLong(),
- codewhispererCodeScanIssuesWithFixes = issuesWithFixes.toLong(),
- codewhispererLanguage = payloadContext.language,
- duration = codeScanEvent.duration,
- contextTruncationDuration = payloadContext.totalTimeInMilliseconds,
- artifactsUploadDuration = serviceInvocationContext.artifactsUploadDuration,
- codeScanServiceInvocationsDuration = serviceInvocationContext.serviceInvocationDuration,
- reason = reason,
- result = codeScanEvent.result,
- credentialStartUrl = startUrl,
- codewhispererCodeScanScope = CodewhispererCodeScanScope.from(codeAnalysisScope.value),
- passive = passive
- )
- }
-
- fun sendCodeScanIssueHoverEvent(issue: CodeWhispererCodeScanIssue) {
- CodewhispererTelemetry.codeScanIssueHover(
- findingId = issue.findingId,
- detectorId = issue.detectorId,
- ruleId = issue.ruleId,
- includesFix = issue.suggestedFixes.isNotEmpty(),
- credentialStartUrl = getCodeWhispererStartUrl(issue.project)
- )
- }
-
- fun sendCodeScanIssueApplyFixEvent(issue: CodeWhispererCodeScanIssue, result: Result, reason: String? = null) {
- CodewhispererTelemetry.codeScanIssueApplyFix(
- findingId = issue.findingId,
- detectorId = issue.detectorId,
- ruleId = issue.ruleId,
- component = Component.Hover,
- result = result,
- reason = reason,
- credentialStartUrl = getCodeWhispererStartUrl(issue.project)
- )
- }
-
- fun enqueueAcceptedSuggestionEntry(
- requestId: String,
- requestContext: RequestContextNew,
- responseContext: ResponseContext,
- time: Instant,
- vFile: VirtualFile?,
- range: RangeMarker,
- suggestion: String,
- selectedIndex: Int,
- completionType: CodewhispererCompletionType,
- ) {
- val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage
- CodeWhispererUserModificationTracker.getInstance(requestContext.project).enqueue(
- AcceptedSuggestionEntry(
- time,
- vFile,
- range,
- suggestion,
- responseContext.sessionId,
- requestId,
- selectedIndex,
- requestContext.triggerTypeInfo.triggerType,
- completionType,
- codewhispererLanguage,
- null,
- null,
- requestContext.connection
- )
- )
- }
-
- fun sendUserDecisionEventForAll(
- sessionContext: SessionContextNew,
- hasUserAccepted: Boolean,
- popupShownTime: Duration? = null,
- ) {
- CodeWhispererServiceNew.getInstance().getAllPaginationSessions().forEach { (jobId, state) ->
- if (state == null) return@forEach
- val details = state.recommendationContext.details
-
- val decisions = details.mapIndexed { index, detail ->
- val suggestionState = recordSuggestionState(detail, hasUserAccepted)
- sendUserDecisionEvent(state.requestContext, state.responseContext, detail, index, suggestionState, details.size)
-
- suggestionState
- }
- LOG.debug { "jobId: $jobId, userDecisions: [${decisions.joinToString(", ")}]" }
-
- with(aggregateUserDecision(decisions)) {
- // the order of the following matters
- // step 1, send out current decision
- LOG.debug { "jobId: $jobId, userTriggerDecision: $this" }
- previousUserTriggerDecisionTimestamp = Instant.now()
-
- val previews = CodeWhispererServiceNew.getInstance().getAllSuggestionsPreviewInfo()
- val recommendation =
- if (hasUserAccepted) {
- previews[sessionContext.selectedIndex].detail.recommendation
- } else {
- Completion.builder().content("").references(emptyList()).build()
- }
- val referenceCount = if (hasUserAccepted && recommendation.hasReferences()) 1 else 0
- val acceptedContent = recommendation.content()
- val generatedLineCount = if (acceptedContent.isEmpty()) 0 else acceptedContent.split("\n").size
- val acceptedCharCount = acceptedContent.length
- sendUserTriggerDecisionEvent(
- sessionContext,
- state.requestContext,
- state.responseContext,
- state.recommendationContext,
- this,
- popupShownTime,
- referenceCount,
- generatedLineCount,
- acceptedCharCount
- )
-
- // step 2, put current decision into queue for later reference
- 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)
- }
- }
- }
- }
-
- /**
- * Aggregate recommendation level user decision to trigger level user decision based on the following rule
- * - 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 (but has valid suggestions)
- * - Record the accepted suggestion index
- * - Discard otherwise
- */
- fun aggregateUserDecision(decisions: List): CodewhispererSuggestionState {
- var isEmpty = true
- var isUnseen = true
- var isDiscard = true
-
- for (decision in decisions) {
- if (decision == CodewhispererSuggestionState.Accept) {
- return CodewhispererSuggestionState.Accept
- } else if (decision == CodewhispererSuggestionState.Reject) {
- 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.Discard) {
- isEmpty = false
- }
- }
-
- return if (isEmpty) {
- CodewhispererSuggestionState.Empty
- } else if (isDiscard) {
- CodewhispererSuggestionState.Discard
- } else if (isUnseen) {
- CodewhispererSuggestionState.Unseen
- } else {
- CodewhispererSuggestionState.Ignore
- }
- }
-
- fun sendOnboardingClickEvent(language: CodeWhispererProgrammingLanguage, taskType: CodewhispererGettingStartedTask) {
- // Project instance is not needed. We look at these metrics for each clientId.
- CodewhispererTelemetry.onboardingClick(project = null, codewhispererLanguage = language.toTelemetryType(), codewhispererGettingStartedTask = taskType)
- }
-
- fun recordSuggestionState(
- detail: DetailContextNew,
- hasUserAccepted: Boolean,
- ): CodewhispererSuggestionState =
- if (detail.recommendation.content().isEmpty()) {
- CodewhispererSuggestionState.Empty
- } else if (detail.isDiscarded) {
- CodewhispererSuggestionState.Discard
- } else if (!detail.hasSeen) {
- CodewhispererSuggestionState.Unseen
- } else if (hasUserAccepted) {
- if (detail.isAccepted) {
- CodewhispererSuggestionState.Accept
- } else {
- CodewhispererSuggestionState.Ignore
- }
- } else {
- CodewhispererSuggestionState.Reject
- }
-
- @TestOnly
- fun previousDecisions(): Queue {
- assert(ApplicationManager.getApplication().isUnitTestMode)
- return this.previousUserTriggerDecisions
- }
-}
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 c2951db615..1775631587 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
@@ -5,23 +5,12 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow
import com.intellij.openapi.editor.RangeMarker
import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
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.model.SessionContextNew
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener
class CodeWhispererCodeReferenceActionListener : CodeWhispererUserActionListener {
- override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {
- val (project, editor) = states.requestContext
- val manager = CodeWhispererCodeReferenceManager.getInstance(project)
- manager.insertCodeReference(states, sessionContext.selectedIndex)
- manager.addListeners(editor)
- }
-}
-
-class CodeWhispererCodeReferenceActionListenerNew : CodeWhispererUserActionListener {
- override fun afterAccept(states: InvocationContextNew, previews: List, sessionContext: SessionContextNew, rangeMarker: RangeMarker) {
+ override fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) {
val manager = CodeWhispererCodeReferenceManager.getInstance(sessionContext.project)
manager.insertCodeReference(states, previews, sessionContext.selectedIndex)
manager.addListeners(sessionContext.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 60ddf3987d..8adafe97b9 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
@@ -31,7 +31,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhisper
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.InvocationContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContextNew
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
@@ -118,7 +117,7 @@ class CodeWhispererCodeReferenceManager(private val project: Project) {
insertCodeReference(detail.content(), reformattedDetail.references(), editor, caretPosition, detail)
}
- fun insertCodeReference(states: InvocationContextNew, previews: List, selectedIndex: Int) {
+ fun insertCodeReference(states: InvocationContext, previews: List, selectedIndex: Int) {
val detail = previews[selectedIndex].detail
insertCodeReference(
detail.recommendation.content(),
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 ad4968d2eb..cee402f7a0 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
@@ -7,12 +7,6 @@ import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.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 +16,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 +122,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 +157,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 +173,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) { projectRule.project }
+ )
} 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 854d74469a..64f37b633d 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
@@ -83,10 +83,11 @@ 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.ResponseContext
+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
import software.aws.toolkits.jetbrains.settings.AwsSettings
import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule
@@ -238,10 +239,10 @@ 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 {
@@ -252,10 +253,14 @@ class CodeWhispererClientAdaptorTest {
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,
@@ -263,7 +268,7 @@ class CodeWhispererClientAdaptorTest {
3,
1,
2,
- 10
+ 10,
)
argumentCaptor().apply {
@@ -376,6 +381,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(),
@@ -383,7 +389,7 @@ class CodeWhispererClientAdaptorTest {
0,
1,
2,
- 10
+ 10,
)
}
}
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 a82f8700eb..371d9538e2 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,13 +53,14 @@ 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.RequestContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.ResponseContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager.Companion.CODEWHISPERER_USER_ACTION_PERFORMED
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.CodeCoverageTokens
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererCodeCoverageTracker
import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager
@@ -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
)
@@ -422,6 +424,7 @@ internal class CodeWhispererCodeCoverageTrackerTestPython : CodeWhispererCodeCov
metricCaptor.allValues,
CODE_PERCENTAGE,
1,
+ CWSPR_PERCENTAGE to "3",
CWSPR_ACCEPTED_TOKENS to "2",
CWSPR_RAW_ACCEPTED_TOKENS to "3",
CWSPR_TOTAL_TOKENS to "100",
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 1e3d42ffff..ab95d5bff4 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,12 @@
package software.aws.toolkits.jetbrains.services.codewhisperer
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.actionSystem.IdeActions
-import com.intellij.openapi.editor.actionSystem.EditorActionManager
+import com.intellij.openapi.actionSystem.ActionManager
+import com.intellij.openapi.actionSystem.AnActionEvent
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 +35,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 +59,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 +67,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 +76,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) { projectRule.project })
} 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) { projectRule.project })
}
} else {
if (isReverse) {
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRecommendationManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRecommendationManagerTest.kt
index c80416c986..221bf896fd 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRecommendationManagerTest.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererRecommendationManagerTest.kt
@@ -19,8 +19,8 @@ import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import software.amazon.awssdk.services.codewhispererruntime.model.Completion
import software.aws.toolkits.core.utils.test.aString
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.RequestContext
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererRecommendationManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule
class CodeWhispererRecommendationManagerTest {
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 f186740f1e..0a050493a5 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 e4202cba6b..5d83170927 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 1d5646d386..3c2d8e1c0f 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
@@ -19,9 +20,9 @@ import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.timeout
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -36,17 +37,16 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.customization.Code
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.language.languages.CodeWhispererJava
-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.Chunk
import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo
import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.RequestContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo
import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo
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.CodeWhispererService
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider
import software.aws.toolkits.jetbrains.utils.rules.JavaCodeInsightTestFixtureRule
@@ -95,31 +95,9 @@ class CodeWhispererServiceTest {
projectRule.project.replaceService(AwsConnectionManager::class.java, mock(), disposableRule.disposable)
}
- @Test
- fun `getRequestContext should use correct fileContext and timeout to fetch supplementalContext`() = runTest {
- val fileContextProvider = FileContextProvider.getInstance(projectRule.project)
- val fileContextProviderSpy = spy(fileContextProvider)
- projectRule.project.replaceService(FileContextProvider::class.java, fileContextProviderSpy, disposableRule.disposable)
-
- val requestContext = sut.getRequestContext(
- TriggerTypeInfo(CodewhispererTriggerType.AutoTrigger, CodeWhispererAutomatedTriggerType.Enter()),
- editor = projectRule.fixture.editor,
- project = projectRule.project,
- file,
- LatencyContext()
- )
-
- requestContext.awaitSupplementalContext()
- val fileContextCaptor = argumentCaptor()
- verify(fileContextProviderSpy, times(1)).extractSupplementalFileContext(eq(file), fileContextCaptor.capture(), eq(100))
- assertThat(fileContextCaptor.firstValue).isEqualTo(
- FileContextInfo(
- CaretContext(leftFileContext = "", rightFileContext = "public class Main {}", leftContextOnCurrentLine = ""),
- "main.java",
- CodeWhispererJava.INSTANCE,
- "main.java"
- )
- )
+ @After
+ fun tearDown() {
+ sut.disposeDisplaySession(false)
}
@Test
@@ -153,8 +131,7 @@ class CodeWhispererServiceTest {
TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()),
projectRule.fixture.editor,
projectRule.project,
- file,
- LatencyContext()
+ file
)
runTest {
@@ -178,8 +155,7 @@ class CodeWhispererServiceTest {
TriggerTypeInfo(CodewhispererTriggerType.OnDemand, CodeWhispererAutomatedTriggerType.Unknown()),
projectRule.fixture.editor,
projectRule.project,
- file,
- LatencyContext()
+ file
)
assertThat(actual.supplementalContext).isNotNull
@@ -211,14 +187,13 @@ 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())
- verify(mockRequestContext, times(1)).awaitSupplementalContext()
+ verify(mockRequestContext, timeout(5000).atLeastOnce()).awaitSupplementalContext()
verify(clientFacade).generateCompletionsPaginator(any())
argumentCaptor {
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 3a640c15bc..e9e7a30e18 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.status.CodeWhispererStatusBarWidgetFactory
import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceToolWindowFactory
import software.aws.toolkits.jetbrains.settings.CodeWhispererConfiguration
@@ -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 0397576e8b..93c9365400 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 6314087592..0de2d4b4c8 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
@@ -37,8 +37,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.telemetry.CodeWhispererTelemetryService
import software.aws.toolkits.jetbrains.services.telemetry.NoOpPublisher
import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
@@ -86,7 +88,7 @@ class CodeWhispererTelemetryServiceTest {
mockClient = spy(CodeWhispererClientAdaptor.getInstance(projectRule.project))
mockClient.stub {
onGeneric {
- sendUserTriggerDecisionTelemetry(any(), any(), any(), any(), any(), any(), any(), any())
+ sendUserTriggerDecisionTelemetry(any(), any(), any(), any(), any(), any(), any(), any(), any())
}.doAnswer {
mock()
}
@@ -97,25 +99,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)
}
@@ -156,8 +153,8 @@ class CodeWhispererTelemetryServiceTest {
@Test
fun `test aggregateUserDecision`() {
- fun assertAggregateUserDecision(decisions: List, expected: CodewhispererPreviousSuggestionState) {
- val actual = sut.aggregateUserDecision(decisions)
+ fun assertAggregateUserDecision(decisions: List, expected: CodewhispererSuggestionState) {
+ val actual = sut.aggregateUserDecision(decisions, false)
assertThat(actual).isEqualTo(expected)
}
@@ -169,7 +166,7 @@ class CodeWhispererTelemetryServiceTest {
CodewhispererSuggestionState.Unseen,
CodewhispererSuggestionState.Unseen
),
- CodewhispererPreviousSuggestionState.Accept
+ CodewhispererSuggestionState.Accept
)
assertAggregateUserDecision(
@@ -180,7 +177,7 @@ class CodeWhispererTelemetryServiceTest {
CodewhispererSuggestionState.Empty,
CodewhispererSuggestionState.Ignore
),
- CodewhispererPreviousSuggestionState.Reject
+ CodewhispererSuggestionState.Reject
)
assertAggregateUserDecision(
@@ -191,7 +188,7 @@ class CodeWhispererTelemetryServiceTest {
CodewhispererSuggestionState.Discard,
CodewhispererSuggestionState.Empty
),
- CodewhispererPreviousSuggestionState.Discard
+ CodewhispererSuggestionState.Discard
)
assertAggregateUserDecision(
@@ -201,7 +198,26 @@ class CodeWhispererTelemetryServiceTest {
CodewhispererSuggestionState.Empty,
CodewhispererSuggestionState.Empty
),
- CodewhispererPreviousSuggestionState.Empty
+ CodewhispererSuggestionState.Empty
+ )
+ }
+
+ @Test
+ fun `test aggregateUserDecision when there was a previous reject`() {
+ fun assertAggregateUserDecision(decisions: List, expected: CodewhispererSuggestionState) {
+ val actual = sut.aggregateUserDecision(decisions, true)
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ assertAggregateUserDecision(
+ listOf(
+ CodewhispererSuggestionState.Discard,
+ CodewhispererSuggestionState.Discard,
+ CodewhispererSuggestionState.Reject,
+ CodewhispererSuggestionState.Empty,
+ CodewhispererSuggestionState.Ignore
+ ),
+ CodewhispererSuggestionState.Ignore
)
}
@@ -219,6 +235,7 @@ class CodeWhispererTelemetryServiceTest {
)
val supplementalContextInfo = aSupplementalContextInfo()
+ val sessionContext = aSessionContext(projectRule.project)
val requestContext = aRequestContext(projectRule.project, mySupplementalContextInfo = supplementalContextInfo).also {
runTest { it.awaitSupplementalContext() }
}
@@ -235,6 +252,7 @@ class CodeWhispererTelemetryServiceTest {
}
sut.sendUserTriggerDecisionEvent(
+ sessionContext,
requestContext,
responseContext,
recommendationContext,
@@ -252,7 +270,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,
@@ -281,21 +299,29 @@ 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))
- sut.sendUserDecisionEventForAll(requestContext, responseContext, recommendationContext, sessionContext, hasUserAccept, popupShownDuration)
+ val sessionContext = aSessionContext(projectRule.project)
+ sessionContext.selectedIndex = 0
+ sut.sendUserDecisionEventForAll(
+ sessionContext,
+ hasUserAccept,
+ popupShownDuration
+ )
argumentCaptor().apply {
verify(batcher, atLeastOnce()).enqueue(capture())
@@ -348,12 +374,21 @@ 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))
- sut.sendUserDecisionEventForAll(requestContext, responseContext, recommendationContext, sessionContext, hasUserAccept, popupShownDuration)
+ CodeWhispererService.getInstance().getAllPaginationSessions()[0] = InvocationContext(
+ requestContext,
+ aResponseContext(),
+ aRecommendationContext(decisions)
+ )
+ val sessionContext = aSessionContext(projectRule.project)
+ sessionContext.selectedIndex = 0
+ sut.sendUserDecisionEventForAll(
+ sessionContext,
+ hasUserAccept,
+ popupShownDuration
+ )
argumentCaptor().apply {
verify(batcher, atLeastOnce()).enqueue(capture())
@@ -439,6 +474,7 @@ class CodeWhispererTelemetryServiceTest {
)
AwsSettings.getInstance().isTelemetryEnabled = isTelemetryEnabled
+ val expectedSessionContext = aSessionContext(projectRule.project)
val expectedRequestContext = aRequestContext(projectRule.project)
val expectedResponseContext = aResponseContext()
val expectedRecommendationContext = aRecommendationContext()
@@ -449,6 +485,7 @@ class CodeWhispererTelemetryServiceTest {
val expectedCharCount = 100
val expectedCompletionType = expectedRecommendationContext.details[0].completionType
sut.sendUserTriggerDecisionEvent(
+ expectedSessionContext,
expectedRequestContext,
expectedResponseContext,
expectedRecommendationContext,
@@ -456,11 +493,12 @@ class CodeWhispererTelemetryServiceTest {
expectedDuration,
expectedSuggestionReferenceCount,
expectedGeneratedLineCount,
- expectedCharCount
+ expectedCharCount,
)
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 6bd9d00629..4ba1665cd3 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.telemetry.AcceptedSuggestionEntry
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererCodeCoverageTracker
@@ -73,6 +74,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"
@@ -105,12 +107,10 @@ 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()) }
+ codewhispererService.stub {
+ onGeneric { getRequestContext(any(), any(), any(), any()) }
.doAnswer { throw Exception() }
}
- ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererServiceSpy, disposableRule.disposable)
-
invokeCodeWhispererService()
argumentCaptor().apply {
@@ -189,7 +189,7 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() {
@Test
fun `test cancelling popup will send user decision event for all unseen but one rejected`() {
withCodeWhispererServiceInvokedAndWait { states ->
- popupManagerSpy.cancelPopup(states.popup)
+ codewhispererService.disposeDisplaySession(false)
val count = pythonResponse.completions().size
argumentCaptor().apply {
@@ -373,68 +373,45 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() {
@Test
fun `test invoking CodeWhisperer will send service invocation event with sessionId and requestId from response`() {
- 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,
)
}
}
@Test
fun `test userDecision events will record sessionId and requestId from response`() {
- val statesCaptor = argumentCaptor()
- withCodeWhispererServiceInvokedAndWait {}
- verify(popupManagerSpy, timeout(5000).atLeastOnce()).render(statesCaptor.capture(), any(), 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())
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,
)
}
- @Test
- fun `test showing IntelliSense after triggering CodeWhisperer will send userDecision events of state Discard`() {
- 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(),
- )
- }
- }
-
@Test
fun `test codePercentage tracker will not be activated if CWSPR terms of service is not accepted`() {
val exploreManagerMock = mock {
@@ -672,7 +649,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())
@@ -693,7 +670,9 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() {
} doReturnConsecutively(listOf(pythonResponseWithNonEmptyToken, emptyListResponse))
}
- withCodeWhispererServiceInvokedAndWait { }
+ withCodeWhispererServiceInvokedAndWait {
+ popupManagerSpy.popupComponents.acceptButton.doClick()
+ }
runInEdtAndWait {
val metricCaptor = argumentCaptor()
@@ -731,11 +710,11 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() {
}
@Test
- fun `test toggle autoSugestion will emit autoSuggestionActivation telemetry (popup)`() {
+ fun `test toggle autoSuggestion will emit autoSuggestionActivation telemetry (popup)`() {
val metricCaptor = argumentCaptor()
doNothing().`when`(batcher).enqueue(metricCaptor.capture())
- Pause().actionPerformed(TestActionEvent { projectRule.project })
+ Pause().actionPerformed(TestActionEvent.createTestEvent { projectRule.project })
assertEventsContainsFieldsAndCount(
metricCaptor.allValues,
awsModifySetting,
@@ -744,7 +723,7 @@ class CodeWhispererTelemetryTest : CodeWhispererTestBase() {
"settingState" to CodeWhispererConstants.AutoSuggestion.DEACTIVATED
)
- Resume().actionPerformed(TestActionEvent { projectRule.project })
+ Resume().actionPerformed(TestActionEvent.createTestEvent { projectRule.project })
assertEventsContainsFieldsAndCount(
metricCaptor.allValues,
awsModifySetting,
@@ -772,13 +751,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 c6687d501b..93f65236fa 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
@@ -19,7 +19,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.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.timeout
@@ -41,7 +40,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
@@ -97,9 +96,11 @@ open class CodeWhispererTestBase {
}
}
+ codewhispererService = spy(CodeWhispererService.getInstance())
+ ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererService, disposableRule.disposable)
+
popupManagerSpy = spy(CodeWhispererPopupManager.getInstance())
- popupManagerSpy.reset()
- 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())
@@ -114,7 +115,6 @@ open class CodeWhispererTestBase {
stateManager = spy(CodeWhispererExplorerActionManager.getInstance())
recommendationManager = CodeWhispererRecommendationManager.getInstance()
- codewhispererService = CodeWhispererService.getInstance()
editorManager = CodeWhispererEditorManager.getInstance()
settingsManager = CodeWhispererSettings.getInstance()
@@ -153,24 +153,22 @@ open class CodeWhispererTestBase {
open fun tearDown() {
stateManager.loadState(originalExplorerActionState)
settingsManager.loadState(originalSettings)
- popupManagerSpy.reset()
- 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/CodeWhispererTypeaheadTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTypeaheadTest.kt
index 87aa376692..50cc14009f 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 d46a603e61..c08238b7a1 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(any())
+ 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)
}
}
@@ -138,10 +134,10 @@ 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())
+ 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 8c4c562481..f534eeb37b 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,21 +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.LatencyContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.TriggerTypeInfo
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
class CodeWhispererUserInputTest : CodeWhispererTestBase() {
@@ -25,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() })
}
}
@@ -40,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)
@@ -58,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)
@@ -75,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)
}
}
@@ -89,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)
@@ -100,33 +97,28 @@ class CodeWhispererUserInputTest : CodeWhispererTestBase() {
}
private fun addUserInputAfterInvocation(userInput: String) {
- val codewhispererServiceSpy = spy(codewhispererService)
val triggerTypeCaptor = argumentCaptor()
val editorCaptor = argumentCaptor()
val projectCaptor = argumentCaptor()
val psiFileCaptor = argumentCaptor()
- val latencyContextCaptor = argumentCaptor()
- codewhispererServiceSpy.stub {
+ codewhispererService.stub {
onGeneric {
getRequestContext(
triggerTypeCaptor.capture(),
editorCaptor.capture(),
projectCaptor.capture(),
- psiFileCaptor.capture(),
- latencyContextCaptor.capture()
+ psiFileCaptor.capture()
)
}.doAnswer {
- val requestContext = codewhispererServiceSpy.getRequestContext(
+ val requestContext = codewhispererService.getRequestContext(
triggerTypeCaptor.firstValue,
editorCaptor.firstValue,
projectCaptor.firstValue,
psiFileCaptor.firstValue,
- latencyContextCaptor.firstValue
)
projectRule.fixture.type(userInput)
requestContext
}.thenCallRealMethod()
}
- ApplicationManager.getApplication().replaceService(CodeWhispererService::class.java, codewhispererServiceSpy, disposableRule.disposable)
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt
index 330ce9a092..894154880c 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestUtil.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/tstFixtures/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
@@ -46,13 +47,13 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContex
import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo
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.RequestContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.ResponseContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo
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.RequestContext
-import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CrossFileStrategy
import software.aws.toolkits.jetbrains.services.codewhisperer.util.UtgStrategy
import software.aws.toolkits.telemetry.CodewhispererCompletionType
@@ -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"),
@@ -172,10 +176,10 @@ object CodeWhispererTestUtil {
const val leftContext_success_Iac = "# Create an S3 Bucket named CodeWhisperer in CloudFormation"
const val leftContext_failure_Iac = "Create an S3 Bucket named CodeWhisperer"
- internal fun pythonResponseWithToken(token: String): GenerateCompletionsResponse =
+ fun pythonResponseWithToken(token: String): GenerateCompletionsResponse =
pythonResponse.toBuilder().nextToken(token).build()
- internal fun generateMockCompletionDetail(content: String): Completion {
+ fun generateMockCompletionDetail(content: String): Completion {
val referenceInfo = getReferenceInfo()
return Completion.builder().content(content)
.references(
@@ -184,9 +188,9 @@ object CodeWhispererTestUtil {
.build()
}
- internal fun getReferenceInfo() = testReferenceInfoPair[Random.nextInt(testReferenceInfoPair.size)]
+ fun getReferenceInfo() = testReferenceInfoPair[Random.nextInt(testReferenceInfoPair.size)]
- internal fun generateMockCompletionDetail(
+ fun generateMockCompletionDetail(
content: String,
licenseName: String,
repository: String,
@@ -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,21 +252,6 @@ fun aRequestContext(
fileContextInfo = myFileContextInfo ?: aFileContextInfo(),
supplementalContextDeferred = supplementalContextDeferred,
null,
- LatencyContext(
- Random.nextLong(),
- Random.nextLong(),
- Random.nextLong(),
- Random.nextLong(),
- Random.nextDouble(),
- Random.nextDouble(),
- Random.nextLong(),
- Random.nextLong(),
- Random.nextLong(),
- Random.nextLong(),
- Random.nextLong(),
- Random.nextLong(),
- aString()
- ),
customizationArn = null
)
}
@@ -307,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 }
@@ -332,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/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt
index de57180cba..6516430bb5 100644
--- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt
+++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/CodeWhispererFeatureConfigService.kt
@@ -42,14 +42,6 @@ class CodeWhispererFeatureConfigService {
featureConfigs[it.feature()] = FeatureContext(it.feature(), it.variation(), it.value())
}
- // Only apply new auto-trigger UX to BID users
- val isNewAutoTriggerUX = getNewAutoTriggerUX()
- if (isNewAutoTriggerUX) {
- calculateIfIamIdentityCenterConnection(project) {
- featureConfigs.remove(NEW_AUTO_TRIGGER_UX)
- }
- }
-
val customizationArnOverride = featureConfigs[CUSTOMIZATION_ARN_OVERRIDE_NAME]?.value?.stringValue()
if (customizationArnOverride != null) {
// Double check if server-side wrongly returns a customizationArn to BID users
@@ -111,8 +103,6 @@ class CodeWhispererFeatureConfigService {
fun getCustomizationFeature(): FeatureContext? = getFeature(CUSTOMIZATION_ARN_OVERRIDE_NAME)
- fun getNewAutoTriggerUX(): Boolean = getFeatureValueForKey(NEW_AUTO_TRIGGER_UX).stringValue() == "TREATMENT"
-
fun getInlineCompletion(): Boolean = getFeatureValueForKey(INLINE_COMPLETION).stringValue() == "TREATMENT"
// Get the feature value for the given key.
@@ -132,7 +122,6 @@ class CodeWhispererFeatureConfigService {
private const val TEST_FEATURE_NAME = "testFeature"
private const val INLINE_COMPLETION = "ProjectContextV2"
const val CUSTOMIZATION_ARN_OVERRIDE_NAME = "customizationArnOverride"
- private const val NEW_AUTO_TRIGGER_UX = "newAutoTriggerUX"
private val LOG = getLogger()
// TODO: add real feature later
@@ -149,11 +138,6 @@ class CodeWhispererFeatureConfigService {
"customizationARN",
FeatureValue.builder().stringValue("").build()
),
- NEW_AUTO_TRIGGER_UX to FeatureContext(
- NEW_AUTO_TRIGGER_UX,
- "CONTROL",
- FeatureValue.builder().stringValue("CONTROL").build()
- ),
INLINE_COMPLETION to FeatureContext(
INLINE_COMPLETION,
"CONTROL",