diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml
index f1df792e004..8b7e5614ddb 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml
@@ -81,9 +81,31 @@
+ text="Invoke Amazon Q Inline Suggestions">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
, context: DataContext): MutableList {
val results = actions.toMutableList()
+ if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return results
+
results.sortWith { a, b ->
- if (isCodeWhispererPopupAction(a)) {
+ if (isCodeWhispererForceAction(a)) {
+ return@sortWith -1
+ } else if (isCodeWhispererForceAction(b)) {
+ return@sortWith 1
+ }
+
+ if (a is ChooseItemAction) {
return@sortWith -1
- } else if (isCodeWhispererPopupAction(b)) {
+ } else if (b is ChooseItemAction) {
return@sortWith 1
- } else {
- 0
}
+
+ if (isCodeWhispererAcceptAction(a)) {
+ return@sortWith -1
+ } else if (isCodeWhispererAcceptAction(b)) {
+ return@sortWith 1
+ }
+
+ 0
}
return results
}
private fun isCodeWhispererAcceptAction(action: AnAction): Boolean =
- action is EditorAction && action.handler is CodeWhispererPopupTabHandler
+ action is CodeWhispererAcceptAction
+
+ private fun isCodeWhispererForceAcceptAction(action: AnAction): Boolean =
+ action is CodeWhispererForceAcceptAction
private fun isCodeWhispererNavigateAction(action: AnAction): Boolean =
- action is EditorAction && (
- action.handler is CodeWhispererPopupRightArrowHandler ||
- action.handler is CodeWhispererPopupLeftArrowHandler
- )
+ action is CodeWhispererNavigateNextAction || action is CodeWhispererNavigatePrevAction
- private fun isCodeWhispererPopupAction(action: AnAction): Boolean =
- isCodeWhispererAcceptAction(action) || isCodeWhispererNavigateAction(action)
+ 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
new file mode 100644
index 00000000000..ed02c8803a4
--- /dev/null
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt
@@ -0,0 +1,33 @@
+// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package software.aws.toolkits.jetbrains.services.codewhisperer.actions
+
+import com.intellij.openapi.actionSystem.ActionUpdateThread
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.actionSystem.CommonDataKeys
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.project.DumbAware
+import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
+import software.aws.toolkits.resources.message
+
+class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.navigate.next")), DumbAware {
+ override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
+
+ override fun update(e: AnActionEvent) {
+ e.presentation.isEnabled = e.project != null &&
+ e.getData(CommonDataKeys.EDITOR) != null &&
+ CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
+ }
+
+ override fun actionPerformed(e: AnActionEvent) {
+ val 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
new file mode 100644
index 00000000000..118f7738213
--- /dev/null
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigatePrevAction.kt
@@ -0,0 +1,33 @@
+// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package software.aws.toolkits.jetbrains.services.codewhisperer.actions
+
+import com.intellij.openapi.actionSystem.ActionUpdateThread
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.actionSystem.CommonDataKeys
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.project.DumbAware
+import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
+import software.aws.toolkits.resources.message
+
+class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.navigate.previous")), DumbAware {
+ override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
+
+ override fun update(e: AnActionEvent) {
+ e.presentation.isEnabled = e.project != null &&
+ e.getData(CommonDataKeys.EDITOR) != null &&
+ CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
+ }
+
+ override fun actionPerformed(e: AnActionEvent) {
+ val 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/ConnectWithAwsToContinueActionWarn.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ConnectWithAwsToContinueActionWarn.kt
deleted file mode 100644
index 0c19cfcff97..00000000000
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ConnectWithAwsToContinueActionWarn.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package software.aws.toolkits.jetbrains.services.codewhisperer.actions
-
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.application.runInEdt
-import com.intellij.openapi.project.DumbAwareAction
-import software.aws.toolkits.jetbrains.core.gettingstarted.requestCredentialsForCodeWhisperer
-import software.aws.toolkits.resources.message
-
-/**
- * Action prompting users to switch to SSO based credential, will nullify accountless credential (delete)
- */
-class ConnectWithAwsToContinueActionWarn : DumbAwareAction(message("codewhisperer.notification.accountless.warn.action.connect")) {
- override fun actionPerformed(e: AnActionEvent) {
- e.project?.let {
- runInEdt {
- requestCredentialsForCodeWhisperer(it)
- }
- }
- }
-}
-class ConnectWithAwsToContinueActionError : DumbAwareAction(message("codewhisperer.notification.accountless.error.action.connect")) {
- override fun actionPerformed(e: AnActionEvent) {
- e.project?.let {
- runInEdt {
- requestCredentialsForCodeWhisperer(it)
- }
- }
- }
-}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt
index 1dfd5d556be..03426f021b5 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt
@@ -46,6 +46,7 @@ import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererCon
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
@@ -92,6 +93,7 @@ interface CodeWhispererClientAdaptor : Disposable {
fun listAvailableCustomizations(): List
fun sendUserTriggerDecisionTelemetry(
+ sessionContext: SessionContext,
requestContext: RequestContext,
responseContext: ResponseContext,
completionType: CodewhispererCompletionType,
@@ -293,6 +295,7 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
}
override fun sendUserTriggerDecisionTelemetry(
+ sessionContext: SessionContext,
requestContext: RequestContext,
responseContext: ResponseContext,
completionType: CodewhispererCompletionType,
@@ -303,24 +306,24 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
): SendTelemetryEventResponse {
val fileContext = requestContext.fileContextInfo
val programmingLanguage = fileContext.programmingLanguage
- var e2eLatency = requestContext.latencyContext.getCodeWhispererEndToEndLatency()
+ var e2eLatency = sessionContext.latencyContext.getCodeWhispererEndToEndLatency()
// When we send a userTriggerDecision of Empty or Discard, we set the time users see the first
// suggestion to be now.
if (e2eLatency < 0) {
e2eLatency = TimeUnit.NANOSECONDS.toMillis(
- System.nanoTime() - requestContext.latencyContext.codewhispererEndToEndStart
+ System.nanoTime() - sessionContext.latencyContext.codewhispererEndToEndStart
).toDouble()
}
return bearerClient().sendTelemetryEvent { requestBuilder ->
requestBuilder.telemetryEvent { telemetryEventBuilder ->
telemetryEventBuilder.userTriggerDecisionEvent {
- it.requestId(requestContext.latencyContext.firstRequestId)
+ it.requestId(sessionContext.latencyContext.firstRequestId)
it.completionType(completionType.toCodeWhispererSdkType())
it.programmingLanguage { builder -> builder.languageName(programmingLanguage.toCodeWhispererRuntimeLanguage().languageId) }
it.sessionId(responseContext.sessionId)
it.recommendationLatencyMilliseconds(e2eLatency)
- it.triggerToResponseLatencyMilliseconds(requestContext.latencyContext.paginationFirstCompletionTime)
+ it.triggerToResponseLatencyMilliseconds(sessionContext.latencyContext.paginationFirstCompletionTime)
it.suggestionState(suggestionState.toCodeWhispererSdkType())
it.timestamp(Instant.now())
it.suggestionReferenceCount(suggestionReferenceCount)
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt
index 22c8c2176bf..62f1b2f47a7 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)
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt
index e699e97e856..6fec203cffb 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt
@@ -95,7 +95,7 @@ object CodeWhispererEditorUtil {
}
fun shouldSkipInvokingBasedOnRightContext(editor: Editor): Boolean {
- val caretContext = runReadAction { CodeWhispererEditorUtil.extractCaretContext(editor) }
+ val caretContext = runReadAction { extractCaretContext(editor) }
val rightContextLines = caretContext.rightFileContext.split(Regex("\r?\n"))
val rightContextCurrentLine = if (rightContextLines.isEmpty()) "" else rightContextLines[0]
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererTypedHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererTypedHandler.kt
index 18845694094..bbb9688eccf 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererTypedHandler.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererTypedHandler.kt
@@ -14,12 +14,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
class CodeWhispererTypedHandler : TypedHandlerDelegate() {
- private var triggerOnIdle: Job? = null
override fun charTyped(c: Char, project: Project, editor: Editor, psiFiles: PsiFile): Result {
- triggerOnIdle?.cancel()
-
- if (shouldSkipInvokingBasedOnRightContext(editor)
- ) {
+ if (shouldSkipInvokingBasedOnRightContext(editor)) {
return Result.CONTINUE
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt
index ce56ea8fc60..6c3bd789c2e 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdder.kt
@@ -14,15 +14,15 @@ 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.PreviewContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
abstract class CodeWhispererImportAdder {
abstract val supportedLanguages: List
abstract val dummyFileName: String
- fun insertImportStatements(states: InvocationContext, sessionContext: SessionContext) {
- val imports = states.recommendationContext.details[sessionContext.selectedIndex]
- .recommendation.mostRelevantMissingImports()
+ fun insertImportStatements(states: InvocationContext, previews: List, sessionContext: SessionContext) {
+ val imports = previews[sessionContext.selectedIndex].detail.recommendation.mostRelevantMissingImports()
LOG.info { "Adding ${imports.size} imports for completions, sessionId: ${states.responseContext.sessionId}" }
imports.forEach {
insertImportStatement(states, it)
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt
index 0a2f8110b5d..a583bd54cde 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/importadder/CodeWhispererImportAdderListener.kt
@@ -7,13 +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.PreviewContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener
import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererSettings
object CodeWhispererImportAdderListener : CodeWhispererUserActionListener {
internal val LOG = getLogger()
- override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {
+ override fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) {
if (!CodeWhispererSettings.getInstance().isImportAdderEnabled()) {
LOG.debug { "Import adder not enabled in user settings" }
return
@@ -28,6 +29,6 @@ object CodeWhispererImportAdderListener : CodeWhispererUserActionListener {
LOG.debug { "No import adder found for $language" }
return
}
- importAdder.insertImportStatements(states, sessionContext)
+ importAdder.insertImportStatements(states, previews, sessionContext)
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt
index e72269ce2f7..60daf84c948 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/inlay/CodeWhispererInlayManager.kt
@@ -6,28 +6,26 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.inlay
import com.intellij.idea.AppMode
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
-import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorCustomElementRenderer
import com.intellij.openapi.editor.Inlay
-import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.util.Disposer
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
@Service
class CodeWhispererInlayManager {
private val existingInlays = mutableListOf>()
- fun updateInlays(states: InvocationContext, chunks: List) {
- val editor = states.requestContext.editor
+ fun updateInlays(sessionContext: SessionContext, chunks: List) {
clearInlays()
chunks.forEach { chunk ->
- createCodeWhispererInlays(editor, chunk.inlayOffset, chunk.text, states.popup)
+ createCodeWhispererInlays(sessionContext, chunk.inlayOffset, chunk.text)
}
}
- private fun createCodeWhispererInlays(editor: Editor, startOffset: Int, inlayText: String, popup: JBPopup) {
+ private fun createCodeWhispererInlays(sessionContext: SessionContext, startOffset: Int, inlayText: String) {
if (inlayText.isEmpty()) return
+ val editor = sessionContext.editor
val firstNewlineIndex = inlayText.indexOf("\n")
val firstLine: String
val otherLines: String
@@ -49,7 +47,7 @@ class CodeWhispererInlayManager {
val inlineInlay = editor.inlayModel.addInlineElement(startOffset, true, firstLineRenderer)
inlineInlay?.let {
existingInlays.add(it)
- Disposer.register(popup, it)
+ Disposer.register(sessionContext, it)
}
}
@@ -73,7 +71,7 @@ class CodeWhispererInlayManager {
)
blockInlay?.let {
existingInlays.add(it)
- Disposer.register(popup, it)
+ Disposer.register(sessionContext, it)
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt
index cf465134162..5c3f53edcdb 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
@@ -4,18 +4,24 @@
package software.aws.toolkits.jetbrains.services.codewhisperer.model
import com.intellij.openapi.Disposable
+import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.markup.RangeHighlighter
+import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopup
+import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.concurrency.annotations.RequiresEdt
import software.amazon.awssdk.services.codewhispererruntime.model.Completion
import software.amazon.awssdk.services.codewhispererruntime.model.GenerateCompletionsResponse
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext
import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage
import software.aws.toolkits.jetbrains.services.codewhisperer.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.telemetry.CodeWhispererTelemetryService
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CrossFileStrategy
import software.aws.toolkits.jetbrains.services.codewhisperer.util.SupplementalContextStrategy
@@ -23,6 +29,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.UtgStrategy
import software.aws.toolkits.telemetry.CodewhispererCompletionType
import software.aws.toolkits.telemetry.CodewhispererTriggerType
import software.aws.toolkits.telemetry.Result
+import java.time.Duration
+import java.time.Instant
import java.util.concurrent.TimeUnit
data class Chunk(
@@ -81,10 +89,19 @@ data class SupplementalContextInfo(
}
data class RecommendationContext(
- val details: List,
+ val details: MutableList,
val userInputOriginal: String,
val userInputSinceInvocation: String,
val position: VisualPosition,
+ val jobId: Int,
+ var typeahead: String = "",
+)
+
+data class PreviewContext(
+ val jobId: Int,
+ val detail: DetailContext,
+ val userInput: String,
+ val typeahead: String,
)
data class DetailContext(
@@ -95,17 +112,47 @@ data class DetailContext(
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 project: Project,
+ val editor: Editor,
+ var popup: JBPopup? = null,
+ var selectedIndex: Int = -1,
val seen: MutableSet = mutableSetOf(),
- val isFirstTimeShowingPopup: Boolean = true,
+ var isFirstTimeShowingPopup: Boolean = true,
var toBeRemovedHighlighter: RangeHighlighter? = null,
var insertEndOffset: Int = -1,
-)
+ var popupOffset: Int = -1,
+ val latencyContext: LatencyContext,
+ var hasAccepted: Boolean = false
+) : Disposable {
+ private var isDisposed = false
+
+ @RequiresEdt
+ override fun dispose() {
+ CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(
+ this,
+ hasAccepted,
+ CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) }
+ )
+ CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(false)
+
+ if (hasAccepted) {
+ popup?.closeOk(null)
+ } else {
+ popup?.cancel()
+ }
+ popup?.let { Disposer.dispose(it) }
+ popup = null
+ CodeWhispererInvocationStatus.getInstance().finishInvocation()
+ isDisposed = true
+ }
+
+ fun isDisposed() = isDisposed
+}
data class RecommendationChunk(
val text: String,
@@ -124,16 +171,21 @@ data class InvocationContext(
val requestContext: RequestContext,
val responseContext: ResponseContext,
val recommendationContext: RecommendationContext,
- val popup: JBPopup,
) : Disposable {
- override fun dispose() {}
+ private var isDisposed = false
+
+ @RequiresEdt
+ override fun dispose() {
+ isDisposed = true
+ }
+
+ fun isDisposed() = isDisposed
}
data class WorkerContext(
val requestContext: RequestContext,
val responseContext: ResponseContext,
val response: GenerateCompletionsResponse,
- val popup: JBPopup,
)
data class CodeScanTelemetryEvent(
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt
index fcf2279f478..d1ce0c87ff3 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt
@@ -6,11 +6,16 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup
import com.intellij.icons.AllIcons
import com.intellij.ide.BrowserUtil
import com.intellij.idea.AppMode
+import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.ActionToolbar
import com.intellij.openapi.actionSystem.DefaultActionGroup
+import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.actionSystem.impl.ActionButton
+import com.intellij.openapi.keymap.KeymapUtil
+import com.intellij.openapi.keymap.MacKeymapUtil
+import com.intellij.openapi.util.SystemInfo
import com.intellij.ui.IdeBorderFactory
import com.intellij.ui.components.ActionLink
import com.intellij.util.ui.UIUtil
@@ -43,10 +48,33 @@ import javax.swing.JPanel
class CodeWhispererPopupComponents {
val prevButton = createNavigationButton(
- message("codewhisperer.popup.button.prev", POPUP_DIM_HEX)
+ message(
+ "codewhisperer.popup.button.prev",
+ POPUP_DIM_HEX,
+ run {
+ // TODO: Doesn't reflect dynamically if users change but didn't restart IDE
+ val shortcut = ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous")
+ .shortcutSet.shortcuts.first()
+ val keyStroke = (shortcut as KeyboardShortcut).firstKeyStroke
+ if (SystemInfo.isMac) {
+ MacKeymapUtil.getKeyStrokeText(keyStroke, " ", true)
+ } else {
+ KeymapUtil.getKeystrokeText(keyStroke)
+ }
+ }
+ )
)
val nextButton = createNavigationButton(
- message("codewhisperer.popup.button.next", POPUP_DIM_HEX)
+ message(
+ "codewhisperer.popup.button.next",
+ POPUP_DIM_HEX,
+ run {
+ // TODO: Doesn't reflect dynamically if users change but didn't restart IDE
+ KeymapUtil.getFirstKeyboardShortcutText(
+ ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next")
+ )
+ }
+ )
).apply {
preferredSize = prevButton.preferredSize
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt
index c485c52acbe..8d4e288b5f7 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt
@@ -5,30 +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.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()
- }
+class CodeWhispererPopupListener : JBPopupListener {
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)
+ 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 c548dff1d4d..946c7cce881 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
@@ -6,8 +6,10 @@ 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.ActionManager
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_BACKSPACE
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ENTER
+import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ESCAPE
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_LEFT
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TAB
@@ -29,35 +31,37 @@ import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.editor.event.SelectionEvent
import com.intellij.openapi.editor.event.SelectionListener
-import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.wm.WindowManager
import com.intellij.ui.ComponentUtil
import com.intellij.ui.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
import software.amazon.awssdk.services.codewhispererruntime.model.Reference
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
+import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererAcceptAction
+import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererForceAcceptAction
+import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererNavigateNextAction
+import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererNavigatePrevAction
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager
import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.addHorizontalGlue
import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.horizontalPanelConstraints
import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.inlineLabelConstraints
import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererEditorActionHandler
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupBackspaceHandler
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEnterHandler
-import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.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
@@ -65,6 +69,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.Co
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererPrevButtonActionListener
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererScrollListener
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_DIM_HEX
@@ -85,10 +90,6 @@ class CodeWhispererPopupManager {
var shouldListenerCancelPopup: Boolean = true
private set
- var sessionContext = SessionContext()
- private set
-
- private var myPopup: JBPopup? = null
init {
// Listen for global scheme changes
@@ -115,113 +116,107 @@ class CodeWhispererPopupManager {
)
}
- fun changeStates(
- states: InvocationContext,
- indexChange: Int,
- typeaheadChange: String,
- typeaheadAdded: Boolean,
- recommendationAdded: Boolean = false,
- ) {
- val (_, _, recommendationContext, popup) = states
- val (details) = recommendationContext
- if (recommendationAdded) {
- LOG.debug {
- "Add recommendations to the existing CodeWhisperer session, current number of recommendations: ${details.size}"
- }
- 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)
+ @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 selectedIndex = findNewSelectedIndex(
- isReverse,
- details,
- userInput,
- sessionContext.selectedIndex + indexChange,
- typeaheadOriginal
+ val isReverse = indexChange < 0
+ val selectedIndex = findNewSelectedIndex(isReverse, sessionContext.selectedIndex + indexChange)
+
+ sessionContext.selectedIndex = selectedIndex
+ sessionContext.isFirstTimeShowingPopup = false
+
+ ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
+ sessionContext
)
- if (selectedIndex == -1 || !isValidRecommendation(details[selectedIndex], userInput, typeaheadOriginal)) {
- LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" }
- cancelPopup(popup)
+ }
+
+ @RequiresEdt
+ fun changeStatesForTypeahead(
+ sessionContext: SessionContext,
+ typeaheadChange: String,
+ typeaheadAdded: Boolean
+ ) {
+ updateTypeahead(typeaheadChange, typeaheadAdded)
+ updateSessionSelectedIndex(sessionContext)
+ sessionContext.isFirstTimeShowingPopup = false
+
+ ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED).stateChanged(
+ sessionContext
+ )
+ }
+
+ @RequiresEdt
+ fun changeStatesForShowing(sessionContext: SessionContext, states: InvocationContext, recommendationAdded: Boolean = false) {
+ sessionContext.isFirstTimeShowingPopup = !recommendationAdded
+ if (recommendationAdded) {
+ ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_POPUP_STATE_CHANGED)
+ .recommendationAdded(states, sessionContext)
return
}
- val typeahead = resolveTypeahead(states, selectedIndex, typeaheadOriginal)
- val isFirstTimeShowingPopup = indexChange == 0 && typeaheadChange.isEmpty()
- sessionContext = SessionContext(
- typeahead,
- typeaheadOriginal,
- selectedIndex,
- sessionContext.seen,
- isFirstTimeShowingPopup,
- sessionContext.toBeRemovedHighlighter
- )
+
+ updateSessionSelectedIndex(sessionContext)
+ if (sessionContext.popupOffset == -1) {
+ sessionContext.popupOffset = sessionContext.editor.caretModel.offset
+ }
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) {
+ 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
+ }
+ it.recommendationContext.typeahead.substring(
+ 0,
+ it.recommendationContext.typeahead.length - typeaheadChange.length
+ )
+ }
+ it.recommendationContext.typeahead = newTypeahead
}
+ }
- for (i in 0..indexOfFirstNonWhiteSpace) {
- val subTypeahead = typeahead.substring(i)
- if (recommendation.startsWith(userInput + subTypeahead)) return subTypeahead
+ private fun updateSessionSelectedIndex(sessionContext: SessionContext) {
+ val selectedIndex = findNewSelectedIndex(false, sessionContext.selectedIndex)
+ if (selectedIndex == -1) {
+ LOG.debug { "None of the recommendation is valid at this point, cancelling the popup" }
+ CodeWhispererService.getInstance().disposeDisplaySession(false)
+ return
}
- return typeahead
+
+ sessionContext.selectedIndex = selectedIndex
}
- 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, isScrolling: 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
@@ -232,20 +227,11 @@ class CodeWhispererPopupManager {
// emit any events.
// 4. User navigating through the completions or typing as the completion shows. We should not update the latency
// end time and should not emit any events in this case.
- if (!isRecommendationAdded) {
- showPopup(states, sessionContext, states.popup, caretPoint, overlappingLinesCount)
- if (!isScrolling) {
- states.requestContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime()
- states.requestContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime()
- }
- }
- if (isScrolling ||
- CodeWhispererInvocationStatus.getInstance().hasExistingInvocation() ||
- !sessionContext.isFirstTimeShowingPopup
- ) {
- return
- }
- CodeWhispererTelemetryService.getInstance().sendClientComponentLatencyEvent(states)
+ if (isRecommendationAdded) return
+ showPopup(sessionContext)
+ if (isScrolling) return
+ sessionContext.latencyContext.codewhispererPostprocessingEnd = System.nanoTime()
+ sessionContext.latencyContext.codewhispererEndToEndEnd = System.nanoTime()
}
fun dontClosePopupAndRun(runnable: () -> Unit) {
@@ -257,84 +243,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)
@@ -347,8 +285,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
@@ -363,22 +304,6 @@ 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)
- }
}
}
@@ -386,166 +311,182 @@ 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) }
}
}
private fun updateSelectedRecommendationLabelText(validSelectedIndex: Int, validCount: Int) {
- if (CodeWhispererInvocationStatus.getInstance().hasExistingInvocation()) {
+ if (CodeWhispererInvocationStatus.getInstance().hasExistingServiceInvocation()) {
popupComponents.recommendationInfoLabel.text = message("codewhisperer.popup.pagination_info")
LOG.debug { "Pagination in progress. Current total: $validCount" }
} else {
@@ -622,14 +563,10 @@ class CodeWhispererPopupManager {
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) {
@@ -637,45 +574,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().filter { isValidRecommendation(it) }.size
+
+ 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 {
@@ -693,17 +619,17 @@ class CodeWhispererPopupManager {
}
interface CodeWhispererPopupStateChangeListener {
- fun stateChanged(states: InvocationContext, sessionContext: SessionContext) {}
- fun scrolled(states: InvocationContext, sessionContext: SessionContext) {}
+ fun stateChanged(sessionContext: SessionContext) {}
+ fun scrolled(sessionContext: SessionContext) {}
fun recommendationAdded(states: InvocationContext, sessionContext: SessionContext) {}
}
interface CodeWhispererUserActionListener {
- fun backspace(states: InvocationContext, diff: String) {}
- fun enter(states: InvocationContext, diff: String) {}
- fun type(states: InvocationContext, diff: String) {}
- fun navigatePrevious(states: InvocationContext) {}
- fun navigateNext(states: InvocationContext) {}
- fun beforeAccept(states: InvocationContext, sessionContext: SessionContext) {}
- fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {}
+ fun backspace(sessionContext: SessionContext, diff: String) {}
+ fun enter(sessionContext: SessionContext, diff: String) {}
+ fun type(sessionContext: SessionContext, diff: String) {}
+ fun navigatePrevious(sessionContext: SessionContext) {}
+ fun navigateNext(sessionContext: SessionContext) {}
+ fun beforeAccept(sessionContext: SessionContext) {}
+ fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) {}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt
index e4bb87feec8..e5dce240995 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererUIChangeListener.kt
@@ -15,22 +15,26 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationCo
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,21 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener {
// inlay chunks are chunks from first line(chunks) and an additional chunk from other lines
val inlayChunks = chunks + listOf(RecommendationChunk(otherLinesInlayText, 0, chunks.last().inlayOffset))
- CodeWhispererInlayManager.getInstance().updateInlays(states, inlayChunks)
+ CodeWhispererInlayManager.getInstance().updateInlays(sessionContext, inlayChunks)
CodeWhispererPopupManager.getInstance().render(
- states,
sessionContext,
- overlappingLinesCount,
isRecommendationAdded = false,
isScrolling = 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) {
+ sessionContext.isFirstTimeShowingPopup = false
+ CodeWhispererPopupManager.getInstance().render(sessionContext, isRecommendationAdded = false, isScrolling = true)
}
override fun recommendationAdded(states: InvocationContext, sessionContext: SessionContext) {
- CodeWhispererPopupManager.getInstance().render(
- states,
- sessionContext,
- 0,
- isRecommendationAdded = true,
- isScrolling = false
- )
+ sessionContext.isFirstTimeShowingPopup = false
+ CodeWhispererPopupManager.getInstance().render(sessionContext, isRecommendationAdded = true, isScrolling = false)
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt
index 0e02fb260bf..a2cea3c836f 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererEditorActionHandler.kt
@@ -4,6 +4,6 @@
package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
-abstract class CodeWhispererEditorActionHandler(val states: InvocationContext) : EditorActionHandler()
+abstract class CodeWhispererEditorActionHandler(val sessionContext: SessionContext) : EditorActionHandler()
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt
index ec2b656c6f4..cdd510a31eb 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupBackspaceHandler.kt
@@ -8,15 +8,16 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
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)
@@ -24,7 +25,7 @@ class CodeWhispererPopupBackspaceHandler(
val newText = "a".repeat(oldOffset - newOffset)
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).backspace(states, newText)
+ ).backspace(sessionContext, newText)
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEnterHandler.kt
index 25dc0a7c6ba..a36177f579e 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
@@ -1,21 +1,19 @@
// 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 com.intellij.openapi.editor.actionSystem.EditorActionHandler
import com.intellij.openapi.util.TextRange
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
class CodeWhispererPopupEnterHandler(
private val defaultHandler: EditorActionHandler,
- states: InvocationContext,
-) : CodeWhispererEditorActionHandler(states) {
+ sessionContext: SessionContext
+) : CodeWhispererEditorActionHandler(sessionContext) {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
val popupManager = CodeWhispererPopupManager.getInstance()
popupManager.dontClosePopupAndRun {
@@ -25,7 +23,7 @@ class CodeWhispererPopupEnterHandler(
val newText = editor.document.getText(TextRange.create(oldOffset, newOffset))
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).enter(states, newText)
+ ).enter(sessionContext, newText)
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt
new file mode 100644
index 00000000000..634f76e885a
--- /dev/null
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupEscHandler.kt
@@ -0,0 +1,16 @@
+// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers
+
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.editor.Caret
+import com.intellij.openapi.editor.Editor
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
+
+class CodeWhispererPopupEscHandler(sessionContext: SessionContext) : CodeWhispererEditorActionHandler(sessionContext) {
+ override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
+ CodeWhispererService.getInstance().disposeDisplaySession(false)
+ }
+}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupLeftArrowHandler.kt
deleted file mode 100644
index 020e1434808..00000000000
--- 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 9efba65a80b..00000000000
--- 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 c92eae91062..00000000000
--- 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 7e18feaf3e0..501409dc4ce 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/handlers/CodeWhispererPopupTypedHandler.kt
@@ -7,19 +7,19 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.TypedActionHandler
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
class CodeWhispererPopupTypedHandler(
private val defaultHandler: TypedActionHandler,
- val states: InvocationContext,
+ val sessionContext: SessionContext
) : TypedActionHandler {
override fun execute(editor: Editor, charTyped: Char, dataContext: DataContext) {
CodeWhispererPopupManager.getInstance().dontClosePopupAndRun {
defaultHandler.execute(editor, charTyped, dataContext)
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).type(states, charTyped.toString())
+ ).type(sessionContext, charTyped.toString())
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt
index 7bc77295a99..6a7a03e132e 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererAcceptButtonActionListener.kt
@@ -4,14 +4,14 @@
package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners
import com.intellij.openapi.application.ApplicationManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
import java.awt.event.ActionEvent
-class CodeWhispererAcceptButtonActionListener(states: InvocationContext) : CodeWhispererActionListener(states) {
+class CodeWhispererAcceptButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) {
override fun actionPerformed(e: ActionEvent?) {
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).beforeAccept(states, CodeWhispererPopupManager.getInstance().sessionContext)
+ ).beforeAccept(sessionContext)
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt
index c04f8cc444c..23f0975e66e 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererActionListener.kt
@@ -3,7 +3,7 @@
package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import java.awt.event.ActionListener
-abstract class CodeWhispererActionListener(val states: InvocationContext) : ActionListener
+abstract class CodeWhispererActionListener(val sessionContext: SessionContext) : ActionListener
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt
index ce1d34432ee..d11f219a330 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererNextButtonActionListener.kt
@@ -4,14 +4,14 @@
package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners
import com.intellij.openapi.application.ApplicationManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
import java.awt.event.ActionEvent
-class CodeWhispererNextButtonActionListener(states: InvocationContext) : CodeWhispererActionListener(states) {
+class CodeWhispererNextButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) {
override fun actionPerformed(e: ActionEvent?) {
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).navigateNext(states)
+ ).navigateNext(sessionContext)
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt
index e77fdf469b5..273a40a8e16 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPrevButtonActionListener.kt
@@ -4,14 +4,14 @@
package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners
import com.intellij.openapi.application.ApplicationManager
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
import java.awt.event.ActionEvent
-class CodeWhispererPrevButtonActionListener(states: InvocationContext) : CodeWhispererActionListener(states) {
+class CodeWhispererPrevButtonActionListener(sessionContext: SessionContext) : CodeWhispererActionListener(sessionContext) {
override fun actionPerformed(e: ActionEvent?) {
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
- ).navigatePrevious(states)
+ ).navigatePrevious(sessionContext)
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt
index f1dfab068a8..4ebea981628 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererScrollListener.kt
@@ -6,20 +6,20 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.event.VisibleAreaEvent
import com.intellij.openapi.editor.event.VisibleAreaListener
-import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
-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() &&
+ if (CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() &&
(oldRect.x != newRect.x || oldRect.y != newRect.y)
) {
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_POPUP_STATE_CHANGED
- ).scrolled(states, CodeWhispererPopupManager.getInstance().sessionContext)
+ ).scrolled(sessionContext)
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt
index 47c35c851d6..84ef44d0551 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
@@ -25,7 +25,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages
import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage
import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
-import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
import software.aws.toolkits.telemetry.CodewhispererAutomatedTriggerType
import software.aws.toolkits.telemetry.CodewhispererPreviousSuggestionState
import software.aws.toolkits.telemetry.CodewhispererTriggerType
@@ -55,9 +54,6 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa
fun tryInvokeAutoTrigger(editor: Editor, triggerType: CodeWhispererAutomatedTriggerType): Job? {
// only needed for Classifier group, thus calculate it lazily
val classifierResult: ClassifierResult by lazy { shouldTriggerClassifier(editor, triggerType.telemetryType) }
- val language = runReadAction {
- FileDocumentManager.getInstance().getFile(editor.document)?.programmingLanguage()
- } ?: CodeWhispererUnknownLanguage.INSTANCE
// we need classifier result for any type of triggering for classifier group for supported languages
triggerType.calculationResult = classifierResult.calculatedResult
@@ -95,28 +91,8 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa
val coroutineScope = applicationCoroutineScope()
- return when (triggerType) {
- is CodeWhispererAutomatedTriggerType.IdleTime -> run {
- coroutineScope.launch {
- // TODO: potential race condition between hasExistingInvocation and entering edt
- // but in that case we will just return in performAutomatedTriggerAction
- while (!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToInvokeCodeWhisperer() ||
- CodeWhispererInvocationStatus.getInstance().hasExistingInvocation()
- ) {
- if (!isActive) return@launch
- delay(CodeWhispererConstants.IDLE_TIME_CHECK_INTERVAL)
- }
- runInEdt {
- if (CodeWhispererInvocationStatus.getInstance().isPopupActive()) return@runInEdt
- performAutomatedTriggerAction(editor, CodeWhispererAutomatedTriggerType.IdleTime(), latencyContext)
- }
- }
- }
-
- else -> run {
- coroutineScope.launch(EDT) {
- performAutomatedTriggerAction(editor, triggerType, latencyContext)
- }
+ return coroutineScope.launch(EDT) {
+ performAutomatedTriggerAction(editor, triggerType, latencyContext)
}
}
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt
index 2a2d70bd903..5bf5852bcc8 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererInvocationStatus.kt
@@ -16,9 +16,8 @@ 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 +25,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 hasExistingInvocation(): Boolean = isInvokingCodeWhisperer.get()
+ fun hasExistingServiceInvocation(): Boolean = isInvokingService.get()
fun finishInvocation() {
- if (isInvokingCodeWhisperer.compareAndSet(true, false)) {
+ if (isInvokingService.compareAndSet(true, false)) {
ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_INVOCATION_STATE_CHANGED).invocationStateChanged(false)
LOG.debug { "Ending CodeWhisperer invocation" }
invokingSessionId = null
}
}
- fun setInvocationComplete() {
- timeAtLastInvocationComplete = Instant.now()
- }
-
fun documentChanged() {
timeAtLastDocumentChanged = Instant.now()
}
@@ -69,9 +60,9 @@ class CodeWhispererInvocationStatus {
return timeCanShowCodeWhisperer.isBefore(Instant.now())
}
- fun isPopupActive(): Boolean = isPopupActive
+ fun isDisplaySessionActive(): Boolean = isPopupActive
- fun setPopupActive(value: Boolean) {
+ fun setDisplaySessionActive(value: Boolean) {
isPopupActive = value
}
@@ -84,11 +75,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/CodeWhispererRecommendationManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt
index 2099b963999..7685e0a5c19 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.VisibleForTesting
import software.amazon.awssdk.services.codewhispererruntime.model.Completion
import software.amazon.awssdk.services.codewhispererruntime.model.Span
import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType
import kotlin.math.max
@@ -63,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
@@ -126,7 +127,7 @@ class CodeWhispererRecommendationManager {
overlap,
getCompletionType(it)
)
- }
+ }.toMutableList()
}
fun findRightContextOverlap(
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt
index 8b9c9d22949..989b20993e5 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt
@@ -6,6 +6,7 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.service
import com.intellij.codeInsight.hint.HintManager
import com.intellij.notification.NotificationAction
import com.intellij.openapi.Disposable
+import com.intellij.openapi.actionSystem.DataKey
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
@@ -15,8 +16,8 @@ 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
@@ -25,6 +26,7 @@ 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
@@ -46,7 +48,6 @@ import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.info
import software.aws.toolkits.core.utils.warn
-import software.aws.toolkits.jetbrains.core.coroutines.disposableCoroutineScope
import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext
import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
@@ -64,6 +65,7 @@ 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.InvocationContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.LatencyContext
+import software.aws.toolkits.jetbrains.services.codewhisperer.model.PreviewContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SupplementalContextInfo
@@ -88,12 +90,17 @@ 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.CancellationException
import java.util.concurrent.TimeUnit
@Service
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)
@@ -161,18 +168,27 @@ 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
if (!language.isCodeCompletionSupported() || (checkLeftContextKeywordsForJsonAndYaml(leftContext, language.languageId))) {
- LOG.debug { "Programming language $language is not supported by CodeWhisperer" }
+ LOG.debug { "Programming language $language is not supported by CodeWhisperer, jobId: $currentJobId" }
if (triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
showCodeWhispererInfoHint(
requestContext.editor,
@@ -183,7 +199,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 {
@@ -191,29 +207,28 @@ 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 fun invokeCodeWhispererInBackground(requestContext: RequestContext): Job {
- val popup = CodeWhispererPopupManager.getInstance().initPopup()
- Disposer.register(popup) { CodeWhispererInvocationStatus.getInstance().finishInvocation() }
+ internal fun invokeCodeWhispererInBackground(requestContext: RequestContext, currentJobId: Int, latencyContext: LatencyContext): Job? {
+ ongoingRequestsContext[currentJobId] = requestContext
+ val sessionContext = sessionContext ?: SessionContext(requestContext.project, requestContext.editor, latencyContext = latencyContext)
+
+ // In rare cases when there's an ongoing session and subsequent triggers are from a different project or editor --
+ // we will cancel the existing session(since we've already moved to a different project or editor simply return.
+ if (requestContext.project != sessionContext.project || requestContext.editor != sessionContext.editor) {
+ disposeDisplaySession(false)
+ return null
+ }
+ this.sessionContext = sessionContext
val workerContexts = mutableListOf()
- // When popup is disposed we will cancel this coroutine. The only places popup can get disposed should be
- // from CodeWhispererPopupManager.cancelPopup() and CodeWhispererPopupManager.closePopup().
- // It's possible and ok that coroutine will keep running until the next time we check it's state.
- // As long as we don't show to the user extra info we are good.
- val coroutineScope = disposableCoroutineScope(popup)
- var states: InvocationContext? = null
var lastRecommendationIndex = -1
- val job = coroutineScope.launch {
+ val job = cs.launch {
try {
val responseIterable = CodeWhispererClientAdaptor.getInstance(requestContext.project).generateCompletionsPaginator(
buildCodeWhispererRequest(
@@ -224,8 +239,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
)
var startTime = System.nanoTime()
- requestContext.latencyContext.codewhispererPreprocessingEnd = System.nanoTime()
- requestContext.latencyContext.paginationAllCompletionsStart = System.nanoTime()
+ latencyContext.codewhispererPreprocessingEnd = System.nanoTime()
+ latencyContext.paginationAllCompletionsStart = System.nanoTime()
CodeWhispererInvocationStatus.getInstance().setInvocationStart()
var requestCount = 0
for (response in responseIterable) {
@@ -236,13 +251,13 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
val requestId = response.responseMetadata().requestId()
val sessionId = response.sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
if (requestCount == 1) {
- requestContext.latencyContext.codewhispererPostprocessingStart = System.nanoTime()
- requestContext.latencyContext.paginationFirstCompletionTime = latency
- requestContext.latencyContext.firstRequestId = requestId
+ latencyContext.codewhispererPostprocessingStart = System.nanoTime()
+ latencyContext.paginationFirstCompletionTime = latency
+ latencyContext.firstRequestId = requestId
CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
}
if (response.nextToken().isEmpty()) {
- requestContext.latencyContext.paginationAllCompletionsEnd = System.nanoTime()
+ latencyContext.paginationAllCompletionsEnd = System.nanoTime()
}
val responseContext = ResponseContext(sessionId)
logServiceInvocation(requestId, requestContext, responseContext, response.completions(), latency, null)
@@ -250,6 +265,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_CODE_COMPLETION_PERFORMED)
.onSuccess(requestContext.fileContextInfo)
CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
+ currentJobId,
requestId,
requestContext,
responseContext,
@@ -271,11 +287,11 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
// task hasn't been finished yet, in this case simply add another task to the queue. If they
// see worker queue is empty, the previous tasks must have been finished before this. In this
// case render CodeWhisperer UI directly.
- val workerContext = WorkerContext(requestContext, responseContext, validatedResponse, popup)
+ val workerContext = WorkerContext(requestContext, responseContext, validatedResponse)
if (workerContexts.isNotEmpty()) {
workerContexts.add(workerContext)
} else {
- if (states == null && !popup.isDisposed &&
+ if (ongoingRequests.values.filterNotNull().isEmpty() &&
!CodeWhispererInvocationStatus.getInstance().hasEnoughDelayToShowCodeWhisperer()
) {
// It's the first response, and no enough delay before showing
@@ -285,7 +301,16 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
}
runInEdt {
workerContexts.forEach {
- states = processCodeWhispererUI(it, states)
+ processCodeWhispererUI(
+ sessionContext,
+ it,
+ ongoingRequests[currentJobId],
+ cs,
+ currentJobId
+ )
+ if (!ongoingRequests.contains(currentJobId)) {
+ cs.coroutineContext.cancel()
+ }
}
workerContexts.clear()
}
@@ -293,14 +318,28 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
workerContexts.add(workerContext)
} else {
// Have enough delay before showing for the first response, or it's subsequent responses
- states = processCodeWhispererUI(workerContext, states)
+ processCodeWhispererUI(
+ sessionContext,
+ workerContext,
+ ongoingRequests[currentJobId],
+ cs,
+ currentJobId
+ )
+ if (!ongoingRequests.contains(currentJobId)) {
+ cs.coroutineContext.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" }
+ LOG.debug { "Skipping sending remaining requests on inactive CodeWhisperer session exit" }
+ return@launch
+ }
+ if (requestCount >= PAGINATION_REQUEST_COUNT_ALLOWED) {
+ LOG.debug { "Only $PAGINATION_REQUEST_COUNT_ALLOWED request per pagination session for now" }
+ CodeWhispererInvocationStatus.getInstance().finishInvocation()
break
}
}
@@ -321,6 +360,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
val responseContext = ResponseContext(sessionId)
CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
+ currentJobId,
requestId,
requestContext,
responseContext,
@@ -350,7 +390,6 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
)
)
CodeWhispererInvocationStatus.getInstance().finishInvocation()
- CodeWhispererInvocationStatus.getInstance().setInvocationComplete()
requestContext.customizationArn?.let { CodeWhispererModelConfigurator.getInstance().invalidateCustomization(it) }
@@ -358,7 +397,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
showRecommendationsInPopup(
requestContext.editor,
requestContext.triggerTypeInfo,
- requestContext.latencyContext
+ latencyContext
)
}
return@launch
@@ -366,7 +405,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
requestId = e.requestId() ?: ""
sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
displayMessage = e.awsErrorDetails().errorMessage() ?: message("codewhisperer.trigger.error.server_side")
- } else if (e is software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeException) {
+ } else if (e is CodeWhispererRuntimeException) {
requestId = e.requestId() ?: ""
sessionId = e.awsErrorDetails().sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
displayMessage = e.awsErrorDetails().errorMessage() ?: message("codewhisperer.trigger.error.server_side")
@@ -389,6 +428,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
+ currentJobId,
requestId,
requestContext,
responseContext,
@@ -410,7 +450,7 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
// We should only show error hint when CodeWhisperer popup is not visible,
// and make it silent if CodeWhisperer popup is showing.
runInEdt {
- if (!CodeWhispererInvocationStatus.getInstance().isPopupActive()) {
+ if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) {
showCodeWhispererErrorHint(requestContext.editor, displayMessage)
}
}
@@ -418,15 +458,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
}
CodeWhispererInvocationStatus.getInstance().finishInvocation()
runInEdt {
- states?.let {
- CodeWhispererPopupManager.getInstance().updatePopupPanel(
- it,
- CodeWhispererPopupManager.getInstance().sessionContext
- )
- }
+ CodeWhispererPopupManager.getInstance().updatePopupPanel(sessionContext)
}
- } finally {
- CodeWhispererInvocationStatus.getInstance().setInvocationComplete()
}
}
@@ -434,61 +467,66 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
}
@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, coroutine)
- // receiving a null state means caret has moved backward or there's a conflict with
- // Intellisense popup, so we are going to cancel the 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.filter { !it.isDiscarded }.size
+ 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,
@@ -508,38 +546,42 @@ 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,
+ coroutine: CoroutineScope,
): 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, coroutine)
+ disposeDisplaySession(false)
return null
}
+
val userInputOriginal = CodeWhispererEditorManager.getInstance().getUserInputSinceInvocation(
requestContext.editor,
requestContext.caretPosition.offset
@@ -563,8 +605,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, coroutine)
+ return ongoingRequests[jobId]
}
private fun updateStates(
@@ -572,24 +615,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
@@ -604,35 +642,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,
+ psiFile: PsiFile
): RequestContext {
// 1. file context
val fileContext: FileContextInfo = runReadAction { FileContextProvider.getInstance(project).extractFileContext(editor, psiFile) }
@@ -657,7 +708,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 {
@@ -683,26 +734,18 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
requestContext: RequestContext,
responseContext: ResponseContext,
recommendationContext: RecommendationContext,
- popup: JBPopup,
+ coroutine: CoroutineScope
): 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) {
+ coroutine.cancel(CancellationException("Cancelling the current coroutine when the pagination session context is disposed"))
}
+ return states
}
private fun logServiceInvocation(
@@ -742,13 +785,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
return false
}
- if (CodeWhispererPopupManager.getInstance().hasConflictingPopups(editor)) {
- LOG.debug { "Find other active popup windows before triggering CodeWhisperer, not invoking service" }
- return false
- }
-
- if (CodeWhispererInvocationStatus.getInstance().isPopupActive()) {
- LOG.debug { "Find an existing CodeWhisperer popup window before triggering CodeWhisperer, not invoking service" }
+ if (CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) {
+ LOG.debug { "Find an existing CodeWhisperer session before triggering CodeWhisperer, not invoking service" }
return false
}
return true
@@ -767,11 +805,13 @@ 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 KEY_SESSION_CONTEXT = Key.create("codewhisperer.session")
fun getInstance(): CodeWhispererService = service()
const val KET_SESSION_ID = "x-amzn-SessionId"
@@ -828,8 +868,7 @@ data class RequestContext(
val fileContextInfo: FileContextInfo,
private val supplementalContextDeferred: Deferred,
val connection: ToolkitConnection?,
- val latencyContext: LatencyContext,
- val customizationArn: String?,
+ val customizationArn: String?
) {
// TODO: should make the entire getRequestContext() suspend function instead of making supplemental context only
var supplementalContext: SupplementalContextInfo? = null
@@ -842,7 +881,6 @@ data class RequestContext(
null
}
}
-
else -> field
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt
index 9db80018184..107f83059b3 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt
@@ -4,13 +4,17 @@
package software.aws.toolkits.jetbrains.services.codewhisperer.settings
import com.intellij.icons.AllIcons
+import com.intellij.ide.DataManager
import com.intellij.openapi.options.BoundConfigurable
+import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.SearchableConfigurable
+import com.intellij.openapi.options.ex.Settings
import com.intellij.openapi.project.Project
-import com.intellij.ui.dsl.builder.AlignX
+import com.intellij.ui.components.ActionLink
import com.intellij.ui.dsl.builder.bindIntText
import com.intellij.ui.dsl.builder.bindSelected
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.codewhisperer.credentials.CodeWhispererLoginType
@@ -18,6 +22,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhisp
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled
import software.aws.toolkits.resources.message
import java.awt.Font
+import java.util.concurrent.TimeUnit
// As the connection is project-level, we need to make this project-level too (we have different config for Sono vs SSO users)
class CodeWhispererConfigurable(private val project: Project) :
@@ -87,6 +92,21 @@ class CodeWhispererConfigurable(private val project: Project) :
bindSelected(codeWhispererSettings::isImportAdderEnabled, codeWhispererSettings::toggleImportAdder)
}.comment(message("aws.settings.codewhisperer.automatic_import_adder.tooltip"))
}
+
+ 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)
+
+ // 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)
+ }
+ }
}
group(message("aws.settings.codewhisperer.group.q_chat")) {
@@ -109,7 +129,7 @@ class CodeWhispererConfigurable(private val project: Project) :
intTextField(
range = IntRange(0, 50)
).bindIntText(codeWhispererSettings::getProjectContextIndexThreadCount, codeWhispererSettings::setProjectContextIndexThreadCount)
- .align(AlignX.FILL).apply {
+ .apply {
connect.subscribe(
ToolkitConnectionManagerListener.TOPIC,
object : ToolkitConnectionManagerListener {
@@ -126,7 +146,7 @@ class CodeWhispererConfigurable(private val project: Project) :
intTextField(
range = IntRange(1, 250)
).bindIntText(codeWhispererSettings::getProjectContextIndexMaxSize, codeWhispererSettings::setProjectContextIndexMaxSize)
- .align(AlignX.FILL).apply {
+ .apply {
connect.subscribe(
ToolkitConnectionManagerListener.TOPIC,
object : ToolkitConnectionManagerListener {
@@ -183,4 +203,8 @@ class CodeWhispererConfigurable(private val project: Project) :
}
}
}
+
+ companion object {
+ private const val Q_INLINE_KEYBINDING_SEARCH_TEXT = "inline suggestion"
+ }
}
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt
index aefa1b51dad..ee72b7a321a 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
@@ -24,6 +24,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.customization.Code
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.PreviewContext
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.CodeWhispererUserActionListener
@@ -86,7 +87,7 @@ abstract class CodeWhispererCodeCoverageTracker(
conn.subscribe(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED,
object : CodeWhispererUserActionListener {
- override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {
+ override fun afterAccept(states: InvocationContext, previews: List, sessionContext: SessionContext, rangeMarker: RangeMarker) {
if (states.requestContext.fileContextInfo.programmingLanguage != language) return
rangeMarkers.add(rangeMarker)
val originalRecommendation = extractRangeMarkerString(rangeMarker) ?: return
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt
index 9576c002e05..1775631587b 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/toolwindow/CodeWhispererCodeReferenceActionListener.kt
@@ -5,14 +5,14 @@ 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.PreviewContext
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererUserActionListener
class CodeWhispererCodeReferenceActionListener : CodeWhispererUserActionListener {
- override fun afterAccept(states: InvocationContext, sessionContext: SessionContext, rangeMarker: RangeMarker) {
- val (project, editor) = states.requestContext
- val manager = CodeWhispererCodeReferenceManager.getInstance(project)
- manager.insertCodeReference(states, sessionContext.selectedIndex)
- manager.addListeners(editor)
+ 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 7bb8dd16430..a3e2a90776b 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,6 +31,7 @@ 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.PreviewContext
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.EDITOR_CODE_REFERENCE_HOVER
import software.aws.toolkits.resources.message
import javax.swing.JLabel
@@ -109,11 +110,15 @@ class CodeWhispererCodeReferenceManager(private val project: Project) {
}
}
- fun insertCodeReference(states: InvocationContext, selectedIndex: Int) {
- val (requestContext, _, recommendationContext) = states
- val (_, editor, _, caretPosition) = requestContext
- val (_, detail, reformattedDetail) = recommendationContext.details[selectedIndex]
- insertCodeReference(detail.content(), reformattedDetail.references(), editor, caretPosition, detail)
+ fun insertCodeReference(states: InvocationContext, previews: List, selectedIndex: Int) {
+ val detail = previews[selectedIndex].detail
+ insertCodeReference(
+ detail.recommendation.content(),
+ detail.reformatted.references(),
+ states.requestContext.editor,
+ states.requestContext.caretPosition,
+ detail.recommendation
+ )
}
fun getReferenceLineNums(editor: Editor, start: Int, end: Int): String {
diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt
index 1e2cc7f680d..e7c4bbea8c9 100644
--- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt
+++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt
@@ -36,10 +36,6 @@ object CodeWhispererConstants {
val AWSTemplateKeyWordsRegex = Regex("(AWSTemplateFormatVersion|Resources|AWS::|Description)")
val AWSTemplateCaseInsensitiveKeyWordsRegex = Regex("(cloudformation|cfn|template|description)")
- // TODO: this is currently set to 2050 to account for the server side 0.5 TPS and and extra 50 ms buffer to
- // avoid ThrottlingException as much as possible.
- const val INVOCATION_INTERVAL: Long = 2050
-
const val Q_CUSTOM_LEARN_MORE_URI = "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/customizations.html"
const val Q_SUPPORTED_LANG_URI = "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-language-ide-support.html"
const val CODEWHISPERER_CODE_SCAN_LEARN_MORE_URI = "https://docs.aws.amazon.com/codewhisperer/latest/userguide/security-scans.html"
diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties
index 50418a9c139..35d5ec3edb9 100644
--- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties
+++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties
@@ -855,6 +855,10 @@ codewhisperer.gettingstarted.panel.learn_more=Learn more
codewhisperer.gettingstarted.panel.learn_more.with.q=Learn more about Amazon Q and Codewhisperer
codewhisperer.gettingstarted.panel.licence_comment=Already have a license?
codewhisperer.gettingstarted.panel.login_button=Use for free, no AWS account required
+codewhisperer.inline.accept=Accept Suggestion
+codewhisperer.inline.force.accept=Force Accept Suggestion
+codewhisperer.inline.navigate.next=Navigate to Next Suggestion
+codewhisperer.inline.navigate.previous=Navigate to Previous Suggestion
codewhisperer.language.error={0} is currently not supported by Amazon Q
codewhisperer.learn_page.banner.dismiss=Dismiss
codewhisperer.learn_page.banner.message.new_user=You can always return to this page by clicking "Learn" in the Amazon Q status bar menu.