Skip to content

Commit cf3a25f

Browse files
authored
Merge branch 'main' into manodnyb/fixUiTests
2 parents 35f6b53 + b74ba31 commit cf3a25f

File tree

10 files changed

+109
-141
lines changed

10 files changed

+109
-141
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" : "/review: Code reviews are now created with additional workspace context to enable grouping of related scans in the backend"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "removal",
3+
"description" : "The Amazon Q inline suggestion popup goes back to being under the suggestions and is always showing."
4+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mockitoKotlin = "5.4.0"
2727
mockk = "1.13.10"
2828
nimbus-jose-jwt = "9.40"
2929
node-gradle = "7.0.2"
30-
telemetryGenerator = "1.0.297"
30+
telemetryGenerator = "1.0.301"
3131
testLogger = "4.0.0"
3232
testRetry = "1.5.10"
3333
# test-only; platform provides slf4j transitively at runtime

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import com.fasterxml.jackson.databind.DeserializationFeature
77
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
88
import com.fasterxml.jackson.module.kotlin.readValue
99
import com.intellij.openapi.application.ApplicationInfo
10+
import com.intellij.openapi.application.ApplicationManager
1011
import com.intellij.openapi.application.runInEdt
1112
import com.intellij.openapi.application.runReadAction
1213
import com.intellij.openapi.fileEditor.FileDocumentManager
1314
import com.intellij.openapi.project.Project
15+
import com.intellij.openapi.project.modules
16+
import com.intellij.openapi.roots.ModuleRootManager
1417
import com.intellij.openapi.vfs.LocalFileSystem
1518
import kotlinx.coroutines.delay
1619
import kotlinx.coroutines.ensureActive
1720
import kotlinx.coroutines.isActive
1821
import kotlinx.coroutines.time.withTimeout
1922
import kotlinx.coroutines.withContext
23+
import migration.software.aws.toolkits.jetbrains.settings.AwsSettings
24+
import org.apache.commons.codec.digest.DigestUtils
2025
import software.amazon.awssdk.services.codewhisperer.model.ArtifactType
2126
import software.amazon.awssdk.services.codewhisperer.model.CodeScanFindingsSchema
2227
import software.amazon.awssdk.services.codewhisperer.model.CodeScanStatus
@@ -33,12 +38,14 @@ import software.aws.toolkits.core.utils.Waiters.waitUntil
3338
import software.aws.toolkits.core.utils.debug
3439
import software.aws.toolkits.core.utils.getLogger
3540
import software.aws.toolkits.core.utils.info
41+
import software.aws.toolkits.core.utils.toHexString
3642
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig
3743
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext
3844
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
3945
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
4046
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CodeScanResponseContext
4147
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CreateUploadUrlServiceInvocationContext
48+
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
4249
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
4350
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_POLLING_INTERVAL_IN_SECONDS
4451
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FILE_SCANS_THROTTLING_MESSAGE
@@ -52,12 +59,14 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhisperer
5259
import software.aws.toolkits.jetbrains.services.codewhisperer.util.getTelemetryErrorMessage
5360
import software.aws.toolkits.jetbrains.utils.assertIsNonDispatchThread
5461
import software.aws.toolkits.resources.message
62+
import software.aws.toolkits.telemetry.CodewhispererCodeScanScope
5563
import software.aws.toolkits.telemetry.CodewhispererLanguage
5664
import java.nio.file.Path
5765
import java.time.Duration
5866
import java.time.Instant
5967
import java.util.UUID
6068
import kotlin.coroutines.coroutineContext
69+
import kotlin.io.path.pathString
6170

6271
class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
6372
private val clientToken: UUID = UUID.randomUUID()
@@ -103,7 +112,7 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
103112
// 2 & 3. CreateUploadURL and upload the context.
104113
currentCoroutineContext.ensureActive()
105114
val artifactsUploadStartTime = now()
106-
val codeScanName = UUID.randomUUID().toString()
115+
val codeScanName = generateScanName()
107116

108117
val taskType = if (sessionContext.codeAnalysisScope == CodeWhispererConstants.CodeAnalysisScope.PROJECT) {
109118
CodeWhispererConstants.UploadTaskType.SCAN_PROJECT
@@ -366,6 +375,26 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
366375
private fun isAutoScan(): Boolean =
367376
sessionContext.codeAnalysisScope == CodeWhispererConstants.CodeAnalysisScope.FILE && !sessionContext.sessionConfig.isInitiatedByChat()
368377

378+
private fun generateScanName(): String {
379+
val clientId = AwsSettings.getInstance().clientId
380+
val filePath = sessionContext.sessionConfig.getSelectedFile()?.toNioPath()?.pathString
381+
val scope = CodeWhispererTelemetryService.getInstance().mapToTelemetryScope(
382+
sessionContext.codeAnalysisScope,
383+
sessionContext.sessionConfig.isInitiatedByChat()
384+
)
385+
val projectId = if (scope != CodewhispererCodeScanScope.PROJECT && filePath != null) {
386+
filePath
387+
} else {
388+
ApplicationManager.getApplication().runReadAction<String> {
389+
sessionContext.project.modules.map { module ->
390+
ModuleRootManager.getInstance(module).contentRoots.firstOrNull()?.path
391+
}.joinToString(",")
392+
}
393+
}
394+
395+
return DigestUtils.sha256("$clientId::$projectId::$scope").toHexString()
396+
}
397+
369398
companion object {
370399
private val LOG = getLogger<CodeWhispererCodeScanSession>()
371400
private val MAPPER = jacksonObjectMapper()

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

Lines changed: 36 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package software.aws.toolkits.jetbrains.services.codewhisperer.popup
55

66
import com.intellij.codeInsight.hint.ParameterInfoController
77
import com.intellij.codeInsight.lookup.LookupManager
8-
import com.intellij.codeInsight.lookup.LookupManagerListener
98
import com.intellij.idea.AppMode
109
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ENTER
1110
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ESCAPE
@@ -26,8 +25,6 @@ import com.intellij.openapi.editor.event.CaretEvent
2625
import com.intellij.openapi.editor.event.CaretListener
2726
import com.intellij.openapi.editor.event.DocumentEvent
2827
import com.intellij.openapi.editor.event.DocumentListener
29-
import com.intellij.openapi.editor.event.EditorMouseEvent
30-
import com.intellij.openapi.editor.event.EditorMouseMotionListener
3128
import com.intellij.openapi.editor.event.SelectionEvent
3229
import com.intellij.openapi.editor.event.SelectionListener
3330
import com.intellij.openapi.fileEditor.FileEditorManager
@@ -65,10 +62,8 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.Cod
6562
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererAcceptButtonActionListener
6663
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererActionListener
6764
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererNextButtonActionListener
68-
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererPopupIntelliSenseAcceptListener
6965
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererPrevButtonActionListener
7066
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererScrollListener
71-
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.addIntelliSenseAcceptListener
7267
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
7368
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
7469
import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager
@@ -224,6 +219,7 @@ class CodeWhispererPopupManager {
224219
fun render(
225220
states: InvocationContext,
226221
sessionContext: SessionContext,
222+
overlappingLinesCount: Int,
227223
isRecommendationAdded: Boolean,
228224
) {
229225
updatePopupPanel(states, sessionContext)
@@ -246,7 +242,7 @@ class CodeWhispererPopupManager {
246242
states.requestContext.latencyContext.getPerceivedLatency(states.requestContext.triggerTypeInfo.triggerType)
247243
}
248244
if (!isRecommendationAdded) {
249-
showPopup(states, sessionContext, states.popup, visible = sessionContext.isPopupShowing)
245+
showPopup(states, sessionContext, states.popup, overlappingLinesCount)
250246
}
251247
}
252248

@@ -285,30 +281,28 @@ class CodeWhispererPopupManager {
285281
states: InvocationContext,
286282
sessionContext: SessionContext,
287283
popup: JBPopup,
288-
visible: Boolean = false,
284+
overlappingLinesCount: Int,
289285
) {
290286
val caretPoint = states.requestContext.editor.offsetToXY(states.requestContext.caretPosition.offset)
291287
val editor = states.requestContext.editor
292288
val detailContexts = states.recommendationContext.details
293289
val userInputOriginal = states.recommendationContext.userInputOriginal
290+
val userInput = states.recommendationContext.userInputSinceInvocation
294291
val selectedIndex = sessionContext.selectedIndex
295292
val typeaheadOriginal = sessionContext.typeaheadOriginal
296293
val typeahead = sessionContext.typeahead
297294
val userInputLines = userInputOriginal.split("\n").size - 1
295+
val lineCount = getReformattedRecommendation(detailContexts[selectedIndex], userInput).split("\n").size
298296
val additionalLines = typeaheadOriginal.split("\n").size - typeahead.split("\n").size
299297
val popupSize = (popup as AbstractPopup).preferredContentSize
298+
val yBelowLastLine = caretPoint.y + (lineCount + additionalLines + userInputLines - overlappingLinesCount) * editor.lineHeight
300299
val yAboveFirstLine = caretPoint.y - popupSize.height + (additionalLines + userInputLines) * editor.lineHeight
301300
val editorRect = editor.scrollingModel.visibleArea
302-
val popupRect = Rectangle(caretPoint.x, yAboveFirstLine, popupSize.width, popupSize.height)
301+
var popupRect = Rectangle(caretPoint.x, yBelowLastLine, popupSize.width, popupSize.height)
303302
var noEnoughSpaceForPopup = false
304303

305304
CodeWhispererInvocationStatus.getInstance().setDisplaySessionActive(true)
306305

307-
if (!editorRect.contains(popupRect)) {
308-
// popup location above first line don't work, so don't show the popup
309-
noEnoughSpaceForPopup = true
310-
}
311-
312306
// Check if the current editor still has focus. If not, don't show the popup.
313307
val isSameEditorAsTrigger = if (!AppMode.isRemoteDevHost()) {
314308
editor.contentComponent.isFocusOwner
@@ -321,8 +315,25 @@ class CodeWhispererPopupManager {
321315
return
322316
}
323317

324-
// popup to always display above the current editing line
325-
val popupLocation = Point(caretPoint.x, yAboveFirstLine)
318+
val popupLocation =
319+
if (!editorRect.contains(popupRect)) {
320+
popupRect = Rectangle(caretPoint.x, yAboveFirstLine, popupSize.width, popupSize.height)
321+
if (!editorRect.contains(popupRect)) {
322+
// both popup location (below last line and above first line) don't work, so don't show the popup
323+
noEnoughSpaceForPopup = true
324+
}
325+
LOG.debug {
326+
"Show popup above the first line of recommendation. " +
327+
"Editor position: $editorRect, popup position: $popupRect"
328+
}
329+
Point(caretPoint.x, yAboveFirstLine)
330+
} else {
331+
LOG.debug {
332+
"Show popup below the last line of recommendation. " +
333+
"Editor position: $editorRect, popup position: $popupRect"
334+
}
335+
Point(caretPoint.x, yBelowLastLine)
336+
}
326337

327338
val relativePopupLocationToEditor = RelativePoint(editor.contentComponent, popupLocation)
328339

@@ -335,12 +346,9 @@ class CodeWhispererPopupManager {
335346
}
336347
} else {
337348
if (!AppMode.isRemoteDevHost()) {
338-
if (visible && !noEnoughSpaceForPopup) {
339-
// TODO: will move to a keybinding listener once I found one
340-
popupComponents.prevButton.text = popupComponents.prevButtonText()
341-
popupComponents.nextButton.text = popupComponents.nextButtonText()
342-
popup.show(relativePopupLocationToEditor)
343-
}
349+
popupComponents.prevButton.text = popupComponents.prevButtonText()
350+
popupComponents.nextButton.text = popupComponents.nextButtonText()
351+
popup.show(relativePopupLocationToEditor)
344352
} else {
345353
// TODO: For now, the popup will always display below the suggestions, without checking
346354
// if the location the popup is about to show at stays in the editor window or not, due to
@@ -369,27 +377,14 @@ class CodeWhispererPopupManager {
369377
}
370378
}
371379

372-
bringSuggestionInlayToFront(editor, popup, sessionContext, !visible)
373-
}
374-
375-
// opposite == false: show Q, hide IntelliSense
376-
// opposite == true: show IntelliSense, hide Q
377-
fun bringSuggestionInlayToFront(
378-
editor: Editor,
379-
popup: JBPopup?,
380-
sessionContext: SessionContext,
381-
opposite: Boolean = false,
382-
) {
383-
val qInlinePopupAlpha = if (opposite) 1f else 0.1f
384-
val intelliSensePopupAlpha = if (opposite) 0f else 0.8f
385-
386-
(popup as AbstractPopup?)?.popupWindow?.let {
387-
WindowManager.getInstance().setAlphaModeRatio(it, qInlinePopupAlpha)
388-
}
389-
ComponentUtil.getWindow(LookupManager.getActiveLookup(editor)?.component)?.let {
390-
WindowManager.getInstance().setAlphaModeRatio(it, intelliSensePopupAlpha)
380+
// popup.popupWindow is null in remote host
381+
if (!AppMode.isRemoteDevHost()) {
382+
if (noEnoughSpaceForPopup) {
383+
WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 1f)
384+
} else {
385+
WindowManager.getInstance().setAlphaModeRatio(popup.popupWindow, 0.1f)
386+
}
391387
}
392-
sessionContext.isPopupShowing = !opposite
393388
}
394389

395390
fun initPopup(): JBPopup = JBPopupFactory.getInstance()
@@ -458,13 +453,6 @@ class CodeWhispererPopupManager {
458453
}
459454
}
460455
)
461-
states.requestContext.project.messageBus.connect(states).subscribe(
462-
LookupManagerListener.TOPIC,
463-
CodeWhispererPopupIntelliSenseAcceptListener(states)
464-
)
465-
LookupManager.getActiveLookup(states.requestContext.editor)?.let {
466-
addIntelliSenseAcceptListener(it, states)
467-
}
468456
}
469457

470458
private fun addButtonActionListeners(states: InvocationContext) {
@@ -556,18 +544,6 @@ class CodeWhispererPopupManager {
556544
window?.addComponentListener(windowListener)
557545
Disposer.register(states) { window?.removeComponentListener(windowListener) }
558546
}
559-
560-
val suggestionHoverEnterListener: EditorMouseMotionListener = object : EditorMouseMotionListener {
561-
override fun mouseMoved(e: EditorMouseEvent) {
562-
if (e.inlay != null) {
563-
showPopup(states, sessionContext, states.popup, visible = true)
564-
} else {
565-
bringSuggestionInlayToFront(editor, states.popup, sessionContext, opposite = true)
566-
}
567-
super.mouseMoved(e)
568-
}
569-
}
570-
editor.addEditorMouseMotionListener(suggestionHoverEnterListener, states)
571547
}
572548

573549
private fun updateSelectedRecommendationLabelText(validSelectedIndex: Int, validCount: Int) {

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,39 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener {
9191
CodeWhispererPopupManager.getInstance().render(
9292
states,
9393
sessionContext,
94+
overlappingLinesCount,
9495
isRecommendationAdded = false
9596
)
9697
}
9798

9899
override fun scrolled(states: InvocationContext, sessionContext: SessionContext) {
100+
if (states.popup.isDisposed) return
101+
val editor = states.requestContext.editor
102+
val editorManager = CodeWhispererEditorManager.getInstance()
103+
val selectedIndex = sessionContext.selectedIndex
104+
val typeahead = sessionContext.typeahead
105+
val detail = states.recommendationContext.details[selectedIndex]
106+
107+
// get matching brackets from recommendations to the brackets after caret position
108+
val remaining = CodeWhispererPopupManager.getInstance().getReformattedRecommendation(
109+
detail,
110+
states.recommendationContext.userInputSinceInvocation
111+
).substring(typeahead.length)
112+
113+
val remainingLines = remaining.split("\n")
114+
val otherLinesOfRemaining = remainingLines.drop(1)
115+
116+
// process other lines inlays, where we do tail-head matching as much as possible
117+
val overlappingLinesCount = editorManager.findOverLappingLines(
118+
editor,
119+
otherLinesOfRemaining,
120+
detail.isTruncatedOnRight,
121+
sessionContext
122+
)
99123
CodeWhispererPopupManager.getInstance().render(
100124
states,
101125
sessionContext,
126+
overlappingLinesCount,
102127
isRecommendationAdded = false
103128
)
104129
}
@@ -107,6 +132,7 @@ class CodeWhispererUIChangeListener : CodeWhispererPopupStateChangeListener {
107132
CodeWhispererPopupManager.getInstance().render(
108133
states,
109134
sessionContext,
135+
0,
110136
isRecommendationAdded = true
111137
)
112138
}

0 commit comments

Comments
 (0)