Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto trigger revamp #4958

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,31 @@
</group>

<action class="software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererRecommendationAction"
text="Show Code Suggestions">
text="Invoke Amazon Q Inline Suggestions">
<keyboard-shortcut keymap="$default" first-keystroke="alt C"/>
</action>
<action id="codewhisperer.inline.navigate.previous"
class="software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererNavigatePrevAction"
text="Navigate to Previous Inline Suggestion" description="Navigate to previous inline suggestion">
<keyboard-shortcut keymap="$default" first-keystroke="alt OPEN_BRACKET"/>
</action>
<action id="codewhisperer.inline.navigate.next"
class="software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererNavigateNextAction"
text="Navigate to Next Inline Suggestion" description="Navigate to next inline suggestion">
<keyboard-shortcut keymap="$default" first-keystroke="alt CLOSE_BRACKET"/>
</action>
<action id="codewhisperer.inline.accept"
class="software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererAcceptAction"
text="Accept the Current Inline Suggestion" description="Accept the current inline suggestions">
<keyboard-shortcut keymap="$default" first-keystroke="TAB"/>
</action>
<action id="codewhisperer.inline.force.accept"
class="software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererForceAcceptAction"
text="Force Accept the Current Inline Suggestion" description="Force accept the current inline suggestion">
<keyboard-shortcut keymap="$default" first-keystroke="alt TAB"/>
<keyboard-shortcut keymap="$default" first-keystroke="alt ENTER"/>
</action>

<group id="aws.toolkit.codewhisperer.toolbar.security">
<action
id="codewhisperer.toolbar.security.scan"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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

open class CodeWhispererAcceptAction(title: String = message("codewhisperer.inline.accept")) : AnAction(title), DumbAware {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT

override fun update(e: AnActionEvent) {
e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null &&
CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
}

override fun actionPerformed(e: AnActionEvent) {
val sessionContext = e.project?.getUserData(CodeWhispererService.KEY_SESSION_CONTEXT) ?: return
if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return
ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
).beforeAccept(sessionContext)
}
}

// A same accept action but different key shortcut and different promoter logic
class CodeWhispererForceAcceptAction(title: String = message("codewhisperer.inline.force.accept")) : CodeWhispererAcceptAction(title)
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,50 @@

package software.aws.toolkits.jetbrains.services.codewhisperer.actions

import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
import com.intellij.openapi.actionSystem.ActionPromoter
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.actionSystem.EditorAction
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupLeftArrowHandler
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupRightArrowHandler
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTabHandler
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus

class CodeWhispererActionPromoter : ActionPromoter {
override fun promote(actions: MutableList<out AnAction>, context: DataContext): MutableList<AnAction> {
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)
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,6 +93,7 @@ interface CodeWhispererClientAdaptor : Disposable {
fun listAvailableCustomizations(): List<CodeWhispererCustomization>

fun sendUserTriggerDecisionTelemetry(
sessionContext: SessionContext,
requestContext: RequestContext,
responseContext: ResponseContext,
completionType: CodewhispererCompletionType,
Expand Down Expand Up @@ -293,6 +295,7 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
}

override fun sendUserTriggerDecisionTelemetry(
sessionContext: SessionContext,
requestContext: RequestContext,
responseContext: ResponseContext,
completionType: CodewhispererCompletionType,
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -67,7 +73,7 @@ class CodeWhispererEditorManager {

ApplicationManager.getApplication().messageBus.syncPublisher(
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED,
).afterAccept(states, sessionContext, rangeMarker)
).afterAccept(states, previews, sessionContext, rangeMarker)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading