Skip to content

Commit 9c19631

Browse files
Merge branch 'feature/q-lsp' into samgst/lsp-fileUtil
2 parents d628c81 + b403d8a commit 9c19631

File tree

12 files changed

+155
-75
lines changed

12 files changed

+155
-75
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 /doc: support making changes to architecture diagrams"
4+
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/DocConstants.kt

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonqDoc
55

6-
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUp
7-
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUpStatusType
8-
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUpTypes
9-
import software.aws.toolkits.resources.message
10-
116
const val FEATURE_EVALUATION_PRODUCT_NAME = "DocGeneration"
127

138
const val FEATURE_NAME = "Amazon Q Documentation Generation"
@@ -21,25 +16,8 @@ const val DEFAULT_RETRY_LIMIT = 0
2116
// Max allowed size for a repository in bytes
2217
const val MAX_PROJECT_SIZE_BYTES: Long = 200 * 1024 * 1024
2318

24-
enum class ModifySourceFolderErrorReason(
25-
private val reasonText: String,
26-
) {
27-
ClosedBeforeSelection("ClosedBeforeSelection"),
28-
NotInWorkspaceFolder("NotInWorkspaceFolder"),
29-
;
30-
31-
override fun toString(): String = reasonText
32-
}
33-
34-
val NEW_SESSION_FOLLOWUPS: List<FollowUp> = listOf(
35-
FollowUp(
36-
pillText = message("amazonqDoc.prompt.reject.new_task"),
37-
type = FollowUpTypes.NEW_TASK,
38-
status = FollowUpStatusType.Info
39-
),
40-
FollowUp(
41-
pillText = message("amazonqDoc.prompt.reject.close_session"),
42-
type = FollowUpTypes.CLOSE_SESSION,
43-
status = FollowUpStatusType.Info
44-
)
45-
)
19+
const val INFRA_DIAGRAM_PREFIX = "infra."
20+
const val DIAGRAM_SVG_EXT = "svg"
21+
const val DIAGRAM_DOT_EXT = "dot"
22+
val SUPPORTED_DIAGRAM_EXT_SET: Set<String> = setOf(DIAGRAM_SVG_EXT, DIAGRAM_DOT_EXT)
23+
val SUPPORTED_DIAGRAM_FILE_NAME_SET: Set<String> = SUPPORTED_DIAGRAM_EXT_SET.map { INFRA_DIAGRAM_PREFIX + it }.toSet()

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocController.kt

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import com.intellij.diff.requests.SimpleDiffRequest
1212
import com.intellij.diff.util.DiffUserDataKeys
1313
import com.intellij.ide.BrowserUtil
1414
import com.intellij.openapi.application.runInEdt
15+
import com.intellij.openapi.fileEditor.FileEditorManager
1516
import com.intellij.openapi.project.Project
1617
import com.intellij.openapi.roots.ProjectRootManager
1718
import com.intellij.openapi.vfs.LocalFileSystem
1819
import com.intellij.openapi.vfs.VfsUtil
1920
import com.intellij.openapi.wm.ToolWindowManager
21+
import com.intellij.testFramework.LightVirtualFile
2022
import kotlinx.coroutines.withContext
23+
import org.intellij.images.fileTypes.impl.SvgFileType
2124
import software.amazon.awssdk.services.codewhispererruntime.model.DocFolderLevel
2225
import software.amazon.awssdk.services.codewhispererruntime.model.DocInteractionType
2326
import software.amazon.awssdk.services.codewhispererruntime.model.DocUserDecision
@@ -33,6 +36,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitConte
3336
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
3437
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
3538
import software.aws.toolkits.jetbrains.services.amazonqDoc.DEFAULT_RETRY_LIMIT
39+
import software.aws.toolkits.jetbrains.services.amazonqDoc.DIAGRAM_SVG_EXT
3640
import software.aws.toolkits.jetbrains.services.amazonqDoc.DocException
3741
import software.aws.toolkits.jetbrains.services.amazonqDoc.FEATURE_NAME
3842
import software.aws.toolkits.jetbrains.services.amazonqDoc.InboundAppMessagesHandler
@@ -374,45 +378,51 @@ class DocController(
374378

375379
override suspend fun processOpenDiff(message: IncomingDocMessage.OpenDiff) {
376380
val session = getSessionInfo(message.tabId)
377-
378-
val project = context.project
379381
val sessionState = session.sessionState
380382

381-
when (sessionState) {
382-
is PrepareDocGenerationState -> {
383-
runInEdt {
384-
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedSourceFolder)
385-
386-
val leftDiffContent = if (existingFile == null) {
387-
EmptyContent()
388-
} else {
389-
DiffContentFactory.getInstance().create(project, existingFile)
390-
}
391-
392-
val newFileContent = sessionState.filePaths.find { it.zipFilePath == message.filePath }?.fileContent
383+
if (sessionState !is PrepareDocGenerationState) {
384+
logger.error { "$FEATURE_NAME: OpenDiff event is received for a conversation that has ${session.sessionState.phase} phase" }
385+
messenger.sendError(
386+
tabId = message.tabId,
387+
errMessage = message("amazonqFeatureDev.exception.open_diff_failed"),
388+
retries = 0,
389+
conversationId = session.conversationIdUnsafe
390+
)
391+
return
392+
}
393393

394-
val rightDiffContent = if (message.deleted || newFileContent == null) {
395-
EmptyContent()
396-
} else {
397-
DiffContentFactory.getInstance().create(newFileContent)
398-
}
394+
runInEdt {
395+
val newFileContent = sessionState.filePaths.find { it.zipFilePath == message.filePath }?.fileContent
399396

400-
val request = SimpleDiffRequest(message.filePath, leftDiffContent, rightDiffContent, null, null)
401-
request.putUserData(DiffUserDataKeys.FORCE_READ_ONLY, true)
397+
val isSvgFile = message.filePath.lowercase().endsWith(".".plus(DIAGRAM_SVG_EXT))
398+
if (isSvgFile && newFileContent != null) {
399+
// instead of diff display generated svg in edit/preview window
400+
val inMemoryFile = LightVirtualFile(
401+
message.filePath,
402+
SvgFileType.INSTANCE,
403+
newFileContent
404+
)
405+
inMemoryFile.isWritable = false
406+
FileEditorManager.getInstance(context.project).openFile(inMemoryFile, true)
407+
} else {
408+
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedSourceFolder)
409+
val leftDiffContent = if (existingFile == null) {
410+
EmptyContent()
411+
} else {
412+
DiffContentFactory.getInstance().create(context.project, existingFile)
413+
}
402414

403-
val newDiff = ChainDiffVirtualFile(SimpleDiffRequestChain(request), message.filePath)
404-
DiffEditorTabFilesManager.getInstance(context.project).showDiffFile(newDiff, true)
415+
val rightDiffContent = if (message.deleted || newFileContent == null) {
416+
EmptyContent()
417+
} else {
418+
DiffContentFactory.getInstance().create(newFileContent)
405419
}
406-
}
407420

408-
else -> {
409-
logger.error { "$FEATURE_NAME: OpenDiff event is received for a conversation that has ${session.sessionState.phase} phase" }
410-
messenger.sendError(
411-
tabId = message.tabId,
412-
errMessage = message("amazonqFeatureDev.exception.open_diff_failed"),
413-
retries = 0,
414-
conversationId = session.conversationIdUnsafe
415-
)
421+
val request = SimpleDiffRequest(message.filePath, leftDiffContent, rightDiffContent, null, null)
422+
request.putUserData(DiffUserDataKeys.FORCE_READ_ONLY, true)
423+
424+
val newDiff = ChainDiffVirtualFile(SimpleDiffRequestChain(request), message.filePath)
425+
DiffEditorTabFilesManager.getInstance(context.project).showDiffFile(newDiff, true)
416426
}
417427
}
418428
}
@@ -738,6 +748,7 @@ class DocController(
738748
SessionStatePhase.CODEGEN -> {
739749
onCodeGeneration(session, message, tabId, mode)
740750
}
751+
741752
else -> null
742753
}
743754

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/messages/DocMessagePublisherExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthNeededState
77
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
88
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.ProgressField
99
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.PromptProgressMessage
10-
import software.aws.toolkits.jetbrains.services.amazonqDoc.NEW_SESSION_FOLLOWUPS
10+
import software.aws.toolkits.jetbrains.services.amazonqDoc.ui.NEW_SESSION_FOLLOWUPS
1111
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.CodeReferenceGenerated
1212
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.DeletedFileInfo
1313
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.NewFileZipInfo

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/session/DocSession.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import software.aws.toolkits.jetbrains.common.session.SessionStateConfigData
2020
import software.aws.toolkits.jetbrains.common.util.AmazonQCodeGenService
2121
import software.aws.toolkits.jetbrains.common.util.resolveAndCreateOrUpdateFile
2222
import software.aws.toolkits.jetbrains.common.util.resolveAndDeleteFile
23-
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
2423
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
2524
import software.aws.toolkits.jetbrains.services.amazonqDoc.CODE_GENERATION_RETRY_LIMIT
2625
import software.aws.toolkits.jetbrains.services.amazonqDoc.FEATURE_NAME
@@ -40,7 +39,7 @@ import java.security.MessageDigest
4039
private val logger = getLogger<AmazonQCodeGenerateClient>()
4140

4241
class DocSession(val tabID: String, val project: Project) {
43-
var context: FeatureDevSessionContext
42+
var context: DocSessionContext = DocSessionContext(project, MAX_PROJECT_SIZE_BYTES)
4443
val sessionStartTime = System.currentTimeMillis()
4544

4645
var state: SessionState?
@@ -59,7 +58,6 @@ class DocSession(val tabID: String, val project: Project) {
5958
var isAuthenticating: Boolean
6059

6160
init {
62-
context = FeatureDevSessionContext(project, MAX_PROJECT_SIZE_BYTES)
6361
proxyClient = AmazonQCodeGenerateClient.getInstance(project)
6462
amazonQCodeGenService = AmazonQCodeGenService(proxyClient, project)
6563
state = ConversationNotStartedState("", tabID, token = null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonqDoc.session
5+
6+
import com.intellij.openapi.project.Project
7+
import com.intellij.openapi.vfs.VirtualFile
8+
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
9+
import software.aws.toolkits.jetbrains.services.amazonqDoc.SUPPORTED_DIAGRAM_EXT_SET
10+
import software.aws.toolkits.jetbrains.services.amazonqDoc.SUPPORTED_DIAGRAM_FILE_NAME_SET
11+
12+
class DocSessionContext(project: Project, maxProjectSizeBytes: Long? = null) : FeatureDevSessionContext(project, maxProjectSizeBytes) {
13+
14+
/**
15+
* Ensure diagram files are not ignored
16+
*/
17+
override fun getAdditionalGitIgnoreBinaryFilesRules(): Set<String> {
18+
val ignoreRules = super.getAdditionalGitIgnoreBinaryFilesRules()
19+
val diagramExtRulesInGitIgnoreFormatSet = SUPPORTED_DIAGRAM_EXT_SET.map { "*.$it" }.toSet()
20+
return ignoreRules - diagramExtRulesInGitIgnoreFormatSet
21+
}
22+
23+
/**
24+
* Ensure diagram files are not filtered
25+
*/
26+
override fun isFileExtensionAllowed(file: VirtualFile): Boolean {
27+
if (super.isFileExtensionAllowed(file)) {
28+
return true
29+
}
30+
31+
return file.extension != null && SUPPORTED_DIAGRAM_FILE_NAME_SET.contains(file.name)
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonqDoc.ui
5+
6+
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUp
7+
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUpStatusType
8+
import software.aws.toolkits.jetbrains.services.amazonqDoc.messages.FollowUpTypes
9+
import software.aws.toolkits.resources.message
10+
11+
val NEW_SESSION_FOLLOWUPS: List<FollowUp> = listOf(
12+
FollowUp(
13+
pillText = message("amazonqDoc.prompt.reject.new_task"),
14+
type = FollowUpTypes.NEW_TASK,
15+
status = FollowUpStatusType.Info
16+
),
17+
FollowUp(
18+
pillText = message("amazonqDoc.prompt.reject.close_session"),
19+
type = FollowUpTypes.CLOSE_SESSION,
20+
status = FollowUpStatusType.Info
21+
)
22+
)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.lookup.LookupManagerListener
99
import com.intellij.idea.AppMode
1010
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ENTER
1111
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_ESCAPE
12+
import com.intellij.openapi.actionSystem.IdeActions.ACTION_EDITOR_TAB
1213
import com.intellij.openapi.application.ApplicationManager
1314
import com.intellij.openapi.command.WriteCommandAction
1415
import com.intellij.openapi.components.Service
@@ -59,6 +60,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionConte
5960
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererEditorActionHandler
6061
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEnterHandler
6162
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupEscHandler
63+
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTabHandler
6264
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.handlers.CodeWhispererPopupTypedHandler
6365
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererAcceptButtonActionListener
6466
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererActionListener
@@ -72,6 +74,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhis
7274
import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager
7375
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererColorUtil.POPUP_DIM_HEX
7476
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.POPUP_INFO_TEXT_SIZE
77+
import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend
7578
import software.aws.toolkits.resources.message
7679
import java.awt.Point
7780
import java.awt.Rectangle
@@ -483,6 +486,10 @@ class CodeWhispererPopupManager {
483486
ACTION_EDITOR_ENTER,
484487
CodeWhispererPopupEnterHandler(EditorActionManager.getInstance().getActionHandler(ACTION_EDITOR_ENTER), states)
485488
)
489+
490+
if (isRunningOnRemoteBackend()) {
491+
setPopupActionHandler(ACTION_EDITOR_TAB, CodeWhispererPopupTabHandler(states))
492+
}
486493
}
487494

488495
private fun setPopupTypedHandler(newHandler: CodeWhispererPopupTypedHandler) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import com.intellij.openapi.editor.Caret
99
import com.intellij.openapi.editor.Editor
1010
import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
1111
import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager
12+
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus
1213

1314
class CodeWhispererPopupTabHandler(states: InvocationContext) : CodeWhispererEditorActionHandler(states) {
1415
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
16+
if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive()) return
1517
ApplicationManager.getApplication().messageBus.syncPublisher(
1618
CodeWhispererPopupManager.CODEWHISPERER_USER_ACTION_PERFORMED
1719
).beforeAccept(states, CodeWhispererPopupManager.getInstance().sessionContext)

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ object CodeWhispererConstants {
8181
const val FILE_SCAN_PAYLOAD_SIZE_LIMIT_IN_BYTES: Long = 1024 * 200 // 200KB
8282
const val AUTO_SCAN_DEBOUNCE_DELAY_IN_SECONDS: Long = 30
8383
const val CODE_FIX_CREATE_PAYLOAD_TIMEOUT_IN_SECONDS: Long = 10
84-
const val CODE_FIX_POLLING_INTERVAL_IN_SECONDS: Long = 1
85-
const val CODE_FIX_TIMEOUT_IN_SECONDS: Long = 60 // 60 seconds
84+
const val CODE_FIX_POLLING_INTERVAL_IN_SECONDS: Long = 5
85+
const val CODE_FIX_TIMEOUT_IN_SECONDS: Long = 120 // 120 seconds
8686
const val TOTAL_BYTES_IN_KB = 1024
8787
const val TOTAL_BYTES_IN_MB = 1024 * 1024
8888
const val TOTAL_MILLIS_IN_SECOND = 1000

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/GitIgnoreFilteringUtil.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ class GitIgnoreFilteringUtil(
5656
"*.a",
5757
"*.map",
5858
"*.graph",
59-
"*.so"
59+
"*.so",
60+
"*.csv",
61+
"*.dylib",
62+
"*.parquet",
63+
"*.xlsx"
6064
)
6165
)
6266
}
@@ -101,6 +105,18 @@ class GitIgnoreFilteringUtil(
101105

102106
suspend fun ignoreFile(file: VirtualFile): Boolean {
103107
// this method reads like something a JS dev would write and doesn't do what the author thinks
108+
109+
// ignores no extension files for test generation use case.
110+
val allowedNoExtFiles = setOf("Config", "Dockerfile", "README")
111+
if (useCase == CodeWhispererConstants.FeatureName.TEST_GENERATION &&
112+
!file.isDirectory &&
113+
file.extension.isNullOrEmpty() &&
114+
!allowedNoExtFiles.any {
115+
it.equals(file.name, ignoreCase = true)
116+
}
117+
) {
118+
return true
119+
}
104120
val deferredResults = ignorePatternsWithGitIgnore.map { pattern ->
105121
withContext(coroutineContext) {
106122
// avoid partial match (pattern.containsMatchIn) since it causes us matching files

0 commit comments

Comments
 (0)