Skip to content

Commit a7f2fe4

Browse files
authored
Add configurable key shortcuts for inline and make JB and Q suggestions co-exist (#5270)
* Add configurable key shortcuts for inline and make JB and Q suggestions co-exist * fix tests * test fix * add change notes * reomve unused var * fix delete edge cases * remove necessary code * remove unnecessary handler * fix a bug in userInput + typeahead case * address comments * ignore flaky test * fix detektTest and revert @ignore * fix message order
1 parent 65fc1a2 commit a7f2fe4

File tree

34 files changed

+474
-419
lines changed

34 files changed

+474
-419
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Amazon Q: Now the Amazon Q suggestions can co-exist with Jetbrains suggestions, with tab behavior configurable in the settings."
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Amazon Q: Amazon Q inline now has configurable shortcuts for various actions including accept and browsing through suggestions."
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Amazon Q: The suggestion popup will hide by default and will be displayed when the suggestion is being hovered over."
4+
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererAcceptAction.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,23 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
1010
import com.intellij.openapi.application.ApplicationManager
1111
import com.intellij.openapi.project.DumbAware
1212
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
13-
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
14-
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
13+
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
1514
import software.aws.toolkits.resources.message
1615

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

2019
override fun update(e: AnActionEvent) {
2120
e.presentation.isEnabled = e.project != null && e.getData(CommonDataKeys.EDITOR) != null &&
22-
CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()
21+
CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
2322
}
2423

2524
override fun actionPerformed(e: AnActionEvent) {
26-
val sessionContext = e.project?.getUserData(CodeWhispererServiceNew.KEY_SESSION_CONTEXT) ?: return
27-
if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) return
25+
val states = e.project?.getUserData(CodeWhispererPopupManager.KEY_INVOCATION_CONTEXT) ?: return
26+
if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return
2827
ApplicationManager.getApplication().messageBus.syncPublisher(
2928
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
30-
).beforeAccept(sessionContext)
29+
).beforeAccept(states, CodeWhispererPopupManager.getInstance().sessionContext)
3130
}
3231
}
3332

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererActionPromoter.kt

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,30 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
77
import com.intellij.openapi.actionSystem.ActionPromoter
88
import com.intellij.openapi.actionSystem.AnAction
99
import com.intellij.openapi.actionSystem.DataContext
10-
import com.intellij.openapi.editor.actionSystem.EditorAction
11-
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
12-
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupLeftArrowHandler
13-
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupRightArrowHandler
14-
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTabHandler
10+
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
1511
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
12+
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
1613

1714
class CodeWhispererActionPromoter : ActionPromoter {
1815
override fun promote(actions: MutableList<out AnAction>, context: DataContext): MutableList<AnAction> {
19-
if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
20-
val results = actions.toMutableList()
21-
if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) return results
16+
val results = actions.toMutableList()
17+
if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive() &&
18+
!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
19+
) {
20+
return results
21+
}
2222

23-
results.sortWith { a, b ->
24-
if (isCodeWhispererForceAction(a)) {
23+
results.sortWith { a, b ->
24+
if (isCodeWhispererForceAction(a)) {
25+
return@sortWith -1
26+
} else if (isCodeWhispererForceAction(b)) {
27+
return@sortWith 1
28+
}
29+
30+
if (CodeWhispererSettings.getInstance().isQPrioritizedForTabAccept()) {
31+
if (isCodeWhispererAcceptAction(a)) {
2532
return@sortWith -1
26-
} else if (isCodeWhispererForceAction(b)) {
33+
} else if (isCodeWhispererAcceptAction(b)) {
2734
return@sortWith 1
2835
}
2936

@@ -32,52 +39,42 @@ class CodeWhispererActionPromoter : ActionPromoter {
3239
} else if (b is ChooseItemAction) {
3340
return@sortWith 1
3441
}
42+
} else {
43+
if (a is ChooseItemAction) {
44+
return@sortWith -1
45+
} else if (b is ChooseItemAction) {
46+
return@sortWith 1
47+
}
3548

3649
if (isCodeWhispererAcceptAction(a)) {
3750
return@sortWith -1
3851
} else if (isCodeWhispererAcceptAction(b)) {
3952
return@sortWith 1
4053
}
41-
42-
0
4354
}
44-
return results
55+
56+
0
4557
}
46-
val results = actions.toMutableList()
58+
4759
results.sortWith { a, b ->
48-
if (isCodeWhispererPopupAction(a)) {
60+
if (isCodeWhispererNavigateAction(a)) {
4961
return@sortWith -1
50-
} else if (isCodeWhispererPopupAction(b)) {
62+
} else if (isCodeWhispererNavigateAction(b)) {
5163
return@sortWith 1
52-
} else {
53-
0
5464
}
65+
66+
0
5567
}
5668
return results
5769
}
5870

59-
private fun isCodeWhispererAcceptAction(action: AnAction): Boolean =
60-
if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
61-
action is CodeWhispererAcceptAction
62-
} else {
63-
action is EditorAction && action.handler is CodeWhispererPopupTabHandler
64-
}
71+
private fun isCodeWhispererAcceptAction(action: AnAction): Boolean = action is CodeWhispererAcceptAction
6572

6673
private fun isCodeWhispererForceAcceptAction(action: AnAction): Boolean =
6774
action is CodeWhispererForceAcceptAction
6875

6976
private fun isCodeWhispererNavigateAction(action: AnAction): Boolean =
70-
if (CodeWhispererFeatureConfigService.getInstance().getNewAutoTriggerUX()) {
71-
action is CodeWhispererNavigateNextAction || action is CodeWhispererNavigatePrevAction
72-
} else {
73-
action is EditorAction && (
74-
action.handler is CodeWhispererPopupRightArrowHandler ||
75-
action.handler is CodeWhispererPopupLeftArrowHandler
76-
)
77-
}
78-
79-
private fun isCodeWhispererPopupAction(action: AnAction): Boolean =
80-
isCodeWhispererAcceptAction(action) || isCodeWhispererNavigateAction(action)
77+
action is CodeWhispererNavigateNextAction || action is CodeWhispererNavigatePrevAction
8178

8279
private fun isCodeWhispererForceAction(action: AnAction): Boolean =
8380
isCodeWhispererForceAcceptAction(action) || isCodeWhispererNavigateAction(action)

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigateNextAction.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
1010
import com.intellij.openapi.application.ApplicationManager
1111
import com.intellij.openapi.project.DumbAware
1212
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
13-
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
14-
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
13+
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
1514
import software.aws.toolkits.resources.message
1615

1716
class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.navigate.next")), DumbAware {
@@ -20,14 +19,14 @@ class CodeWhispererNavigateNextAction : AnAction(message("codewhisperer.inline.n
2019
override fun update(e: AnActionEvent) {
2120
e.presentation.isEnabled = e.project != null &&
2221
e.getData(CommonDataKeys.EDITOR) != null &&
23-
CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()
22+
CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
2423
}
2524

2625
override fun actionPerformed(e: AnActionEvent) {
27-
val sessionContext = e.project?.getUserData(CodeWhispererServiceNew.KEY_SESSION_CONTEXT) ?: return
28-
if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) return
26+
val states = e.project?.getUserData(CodeWhispererPopupManager.KEY_INVOCATION_CONTEXT) ?: return
27+
if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return
2928
ApplicationManager.getApplication().messageBus.syncPublisher(
3029
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
31-
).navigateNext(sessionContext)
30+
).navigateNext(states)
3231
}
3332
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/CodeWhispererNavigatePrevAction.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
1010
import com.intellij.openapi.application.ApplicationManager
1111
import com.intellij.openapi.project.DumbAware
1212
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
13-
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatusNew
14-
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererServiceNew
13+
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
1514
import software.aws.toolkits.resources.message
1615

1716
class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.navigate.previous")), DumbAware {
@@ -20,14 +19,14 @@ class CodeWhispererNavigatePrevAction : AnAction(message("codewhisperer.inline.n
2019
override fun update(e: AnActionEvent) {
2120
e.presentation.isEnabled = e.project != null &&
2221
e.getData(CommonDataKeys.EDITOR) != null &&
23-
CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()
22+
CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()
2423
}
2524

2625
override fun actionPerformed(e: AnActionEvent) {
27-
val sessionContext = e.project?.getUserData(CodeWhispererServiceNew.KEY_SESSION_CONTEXT) ?: return
28-
if (!CodeWhispererInvocationStatusNew.getInstance().isDisplaySessionActive()) return
26+
val states = e.project?.getUserData(CodeWhispererPopupManager.KEY_INVOCATION_CONTEXT) ?: return
27+
if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return
2928
ApplicationManager.getApplication().messageBus.syncPublisher(
3029
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
31-
).navigatePrevious(sessionContext)
30+
).navigatePrevious(states)
3231
}
3332
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,29 @@
33

44
package software.aws.toolkits.jetbrains.services.codewhisperer.editor
55

6+
import com.intellij.notification.NotificationAction
67
import com.intellij.openapi.application.ApplicationManager
78
import com.intellij.openapi.command.WriteCommandAction
89
import com.intellij.openapi.components.Service
910
import com.intellij.openapi.components.service
1011
import com.intellij.openapi.editor.Editor
12+
import com.intellij.openapi.options.ShowSettingsUtil
1113
import com.intellij.openapi.util.TextRange
1214
import com.intellij.psi.PsiDocumentManager
1315
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition
1416
import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
1517
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
1618
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
19+
import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable
1720
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
1821
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.QFeatureEvent
1922
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.broadcastQEvent
2023
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CaretMovement
2124
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_BRACKETS
2225
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.PAIRED_QUOTES
26+
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
27+
import software.aws.toolkits.jetbrains.utils.notifyInfo
28+
import software.aws.toolkits.resources.message
2329
import java.time.Instant
2430
import java.util.Stack
2531

@@ -75,6 +81,26 @@ class CodeWhispererEditorManager {
7581
).afterAccept(states, sessionContext, rangeMarker)
7682
}
7783
}
84+
85+
// Display tab accept priority once when the first accept is made
86+
if (!CodeWhispererSettings.getInstance().isTabAcceptPriorityNotificationShownOnce()) {
87+
notifyInfo(
88+
"Amazon Q",
89+
message("codewhisperer.inline.settings.tab_priority.notification.text"),
90+
project = project,
91+
listOf(
92+
NotificationAction.create(
93+
message("codewhisperer.actions.open_settings.title")
94+
) { _, notification ->
95+
ShowSettingsUtil.getInstance().showSettingsDialog(project, CodeWhispererConfigurable::class.java)
96+
},
97+
NotificationAction.create(
98+
message("codewhisperer.notification.custom.simple.button.got_it")
99+
) { _, notification -> notification.expire() }
100+
)
101+
)
102+
CodeWhispererSettings.getInstance().setTabAcceptPriorityNotificationShownOnce(true)
103+
}
78104
}
79105

80106
private fun isMatchingSymbol(symbol: Char): Boolean =

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,10 @@ data class SessionContext(
145145
val typeaheadOriginal: String = "",
146146
val selectedIndex: Int = 0,
147147
val seen: MutableSet<Int> = mutableSetOf(),
148-
val isFirstTimeShowingPopup: Boolean = true,
149148
var toBeRemovedHighlighter: RangeHighlighter? = null,
150149
var insertEndOffset: Int = -1,
150+
var isPopupShowing: Boolean = false,
151+
var perceivedLatency: Double = -1.0,
151152
)
152153

153154
data class SessionContextNew(

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupComponents.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,23 @@ import javax.swing.JLabel
4444
import javax.swing.JPanel
4545

4646
class CodeWhispererPopupComponents {
47-
val prevButton = createNavigationButton(
48-
message("codewhisperer.popup.button.prev", POPUP_DIM_HEX)
47+
val prevButton = createNavigationButton(prevButtonText())
48+
fun prevButtonText() =
49+
message(
50+
"codewhisperer.popup.button.prev",
51+
POPUP_DIM_HEX,
52+
KeymapUtil.getFirstKeyboardShortcutText(
53+
ActionManager.getInstance().getAction("codewhisperer.inline.navigate.previous")
54+
)
55+
)
56+
val nextButton = createNavigationButton(nextButtonText()).apply { preferredSize = prevButton.preferredSize }
57+
fun nextButtonText() = message(
58+
"codewhisperer.popup.button.next",
59+
POPUP_DIM_HEX,
60+
KeymapUtil.getFirstKeyboardShortcutText(
61+
ActionManager.getInstance().getAction("codewhisperer.inline.navigate.next")
62+
)
4963
)
50-
val nextButton = createNavigationButton(
51-
message("codewhisperer.popup.button.next", POPUP_DIM_HEX)
52-
).apply {
53-
preferredSize = prevButton.preferredSize
54-
}
5564
val acceptButton = createNavigationButton(
5665
message("codewhisperer.popup.button.accept", POPUP_DIM_HEX)
5766
)

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupListener.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class CodeWhispererPopupListener(private val states: InvocationContext) : JBPopu
3030
CodeWhispererInvocationStatus.getInstance().popupStartTimestamp?.let { Duration.between(it, Instant.now()) }
3131
)
3232

33-
CodeWhispererInvocationStatus.getInstance().setPopupActive(false)
33+
CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(false)
3434
}
3535
}
3636

0 commit comments

Comments
 (0)