Skip to content

Commit 78d68d6

Browse files
author
C Tidd
committed
feat(amazonq): Add support for multi-project workspaces.
1 parent e6f3806 commit 78d68d6

File tree

19 files changed

+256
-187
lines changed

19 files changed

+256
-187
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/session/SessionStateTypes.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
package software.aws.toolkits.jetbrains.common.session
55

66
import software.aws.toolkits.jetbrains.common.util.AmazonQCodeGenService
7-
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
7+
import software.aws.toolkits.jetbrains.services.amazonq.project.FeatureDevSessionContext
88

99
open class SessionStateConfig(
1010
open val conversationId: String,

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ import software.aws.toolkits.core.utils.info
3131
import software.aws.toolkits.core.utils.warn
3232
import software.aws.toolkits.jetbrains.common.util.selectFolder
3333
import software.aws.toolkits.jetbrains.core.coroutines.EDT
34-
import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeError
3534
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
3635
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
36+
import software.aws.toolkits.jetbrains.services.amazonq.project.RepoSizeError
3737
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
3838
import software.aws.toolkits.jetbrains.services.amazonqDoc.DEFAULT_RETRY_LIMIT
3939
import software.aws.toolkits.jetbrains.services.amazonqDoc.DIAGRAM_SVG_EXT
@@ -308,7 +308,7 @@ class DocController(
308308
private suspend fun promptForDocTarget(tabId: String) {
309309
val session = getSessionInfo(tabId)
310310

311-
val currentSourceFolder = session.context.selectedSourceFolder
311+
val currentSourceFolder = session.context.selectedRoot
312312

313313
try {
314314
messenger.sendFolderConfirmationMessage(
@@ -405,7 +405,7 @@ class DocController(
405405
inMemoryFile.isWritable = false
406406
FileEditorManager.getInstance(context.project).openFile(inMemoryFile, true)
407407
} else {
408-
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedSourceFolder)
408+
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedRoot)
409409
val leftDiffContent = if (existingFile == null) {
410410
EmptyContent()
411411
} else {
@@ -952,8 +952,8 @@ class DocController(
952952

953953
private suspend fun modifyDefaultSourceFolder(tabId: String) {
954954
val session = getSessionInfo(tabId)
955-
val currentSourceFolder = session.context.selectedSourceFolder
956-
val projectRoot = session.context.projectRoot
955+
val currentSourceFolder = session.context.selectedRoot
956+
val workspaceRoot = session.context.workspaceRoot
957957

958958
withContext(EDT) {
959959
messenger.sendAnswer(
@@ -999,15 +999,15 @@ class DocController(
999999
return@withContext
10001000
}
10011001

1002-
if (selectedFolder.path == projectRoot.path) {
1002+
if (selectedFolder.path == workspaceRoot.toString()) {
10031003
docGenerationTask.folderLevel = DocFolderLevel.ENTIRE_WORKSPACE
10041004
} else {
10051005
docGenerationTask.folderLevel = DocFolderLevel.SUB_FOLDER
10061006
}
10071007

10081008
logger.info { "Selected correct folder inside workspace: ${selectedFolder.path}" }
10091009

1010-
session.context.selectedSourceFolder = selectedFolder
1010+
session.context.selectedRoot = selectedFolder
10111011

10121012
promptForDocTarget(tabId)
10131013

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ class DocSession(val tabID: String, val project: Project) {
107107
* Triggered by the Insert code follow-up button to apply code changes.
108108
*/
109109
fun insertChanges(filePaths: List<NewFileZipInfo>, deletedFiles: List<DeletedFileInfo>) {
110-
val selectedSourceFolder = context.selectedSourceFolder.toNioPath()
110+
val selectedSourceFolder = context.selectedRoot
111111

112-
filePaths.forEach { resolveAndCreateOrUpdateFile(selectedSourceFolder, it.zipFilePath, it.fileContent) }
112+
filePaths.forEach { resolveAndCreateOrUpdateFile(selectedSourceFolder.toNioPath(), it.zipFilePath, it.fileContent) }
113113

114-
deletedFiles.forEach { resolveAndDeleteFile(selectedSourceFolder, it.zipFilePath) }
114+
deletedFiles.forEach { resolveAndDeleteFile(selectedSourceFolder.toNioPath(), it.zipFilePath) }
115115

116116
// Taken from https://intellij-support.jetbrains.com/hc/en-us/community/posts/206118439-Refresh-after-external-changes-to-project-structure-and-sources
117-
VfsUtil.markDirtyAndRefresh(true, true, true, context.selectedSourceFolder)
117+
VfsUtil.markDirtyAndRefresh(true, true, true, context.selectedRoot)
118118
}
119119

120120
private fun getFromReportedChanges(filePath: NewFileZipInfo): String? {
@@ -158,7 +158,7 @@ class DocSession(val tabID: String, val project: Project) {
158158
}
159159
} else {
160160
val sourceContent = reportedChange
161-
?: VfsUtil.findRelativeFile(filePath.zipFilePath, context.selectedSourceFolder)?.content()
161+
?: VfsUtil.findRelativeFile(filePath.zipFilePath, context.selectedRoot)?.content()
162162
.orEmpty()
163163
val diffMetrics = getDiffMetrics(sourceContent, content)
164164
totalAddedLines += diffMetrics.insertedLines
@@ -185,7 +185,7 @@ class DocSession(val tabID: String, val project: Project) {
185185
totalAddedChars += content.length
186186
totalAddedLines += content.split('\n').size
187187
} else {
188-
val existingFileContent = VfsUtil.findRelativeFile(filePath.zipFilePath, context.selectedSourceFolder)?.content()
188+
val existingFileContent = VfsUtil.findRelativeFile(filePath.zipFilePath, context.selectedRoot)?.content()
189189
val diffMetrics = getDiffMetrics(existingFileContent.orEmpty(), content)
190190
totalAddedLines += diffMetrics.insertedLines
191191
totalAddedChars += diffMetrics.insertedCharacters

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

+3-21
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,11 @@ package software.aws.toolkits.jetbrains.services.amazonqDoc.session
55

66
import com.intellij.openapi.project.Project
77
import com.intellij.openapi.vfs.VirtualFile
8-
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
8+
import software.aws.toolkits.jetbrains.services.amazonq.project.FeatureDevSessionContext
99
import software.aws.toolkits.jetbrains.services.amazonqDoc.SUPPORTED_DIAGRAM_EXT_SET
1010
import software.aws.toolkits.jetbrains.services.amazonqDoc.SUPPORTED_DIAGRAM_FILE_NAME_SET
1111

1212
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-
}
13+
override fun shouldIncludeFileIfNoExplicitIgnore(file: VirtualFile): Boolean =
14+
SUPPORTED_DIAGRAM_EXT_SET.any { file.path.endsWith(it) } || SUPPORTED_DIAGRAM_FILE_NAME_SET.contains(file.name)
3315
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

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

6-
import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeError
6+
import software.aws.toolkits.jetbrains.services.amazonq.project.RepoSizeError
77
import software.aws.toolkits.resources.message
88

99
/**

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt

+8-8
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ import software.aws.toolkits.core.utils.info
2929
import software.aws.toolkits.core.utils.warn
3030
import software.aws.toolkits.jetbrains.common.util.selectFolder
3131
import software.aws.toolkits.jetbrains.core.coroutines.EDT
32-
import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeError
3332
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
3433
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
3534
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
35+
import software.aws.toolkits.jetbrains.services.amazonq.project.RepoSizeError
3636
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
3737
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException
3838
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.DEFAULT_RETRY_LIMIT
@@ -249,7 +249,7 @@ class FeatureDevController(
249249
when (sessionState) {
250250
is PrepareCodeGenerationState -> {
251251
runInEdt {
252-
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedSourceFolder)
252+
val existingFile = VfsUtil.findRelativeFile(message.filePath, session.context.selectedRoot)
253253

254254
val leftDiffContent = if (existingFile == null) {
255255
EmptyContent()
@@ -336,7 +336,7 @@ class FeatureDevController(
336336
var pollAttempt = 0
337337
val pollDelayMs = 10L
338338
while (pollAttempt < 5) {
339-
val file = VfsUtil.findRelativeFile(message.filePath, session.context.selectedSourceFolder)
339+
val file = VfsUtil.findRelativeFile(message.filePath, session.context.selectedRoot)
340340
// Wait for the file to be created and/or updated to the new content:
341341
if (file != null && file.content() == filePaths.find { it.zipFilePath == fileToUpdate }?.fileContent) {
342342
// Open a diff, showing the changes have been applied and the file now has identical left/right state:
@@ -729,7 +729,7 @@ class FeatureDevController(
729729

730730
val codeWhispererSettings = CodeWhispererSettings.getInstance().getAutoBuildSetting()
731731
val hasDevFile = session.context.checkForDevFile()
732-
val isPromptedForAutoBuildFeature = codeWhispererSettings.containsKey(session.context.getWorkspaceRoot())
732+
val isPromptedForAutoBuildFeature = codeWhispererSettings.containsKey(session.context.workspaceRoot.toString())
733733

734734
if (hasDevFile && !isPromptedForAutoBuildFeature) {
735735
promptAllowQCommandsConsent(messenger, tabId)
@@ -812,8 +812,8 @@ class FeatureDevController(
812812

813813
private suspend fun modifyDefaultSourceFolder(tabId: String) {
814814
val session = getSessionInfo(tabId)
815-
val currentSourceFolder = session.context.selectedSourceFolder
816-
val projectRoot = session.context.projectRoot
815+
val currentSourceFolder = session.context.selectedRoot
816+
val workspaceRoot = session.context.workspaceRoot
817817

818818
val modifyFolderFollowUp = FollowUp(
819819
pillText = message("amazonqFeatureDev.follow_up.modify_source_folder"),
@@ -840,7 +840,7 @@ class FeatureDevController(
840840
}
841841

842842
// The folder is not in the workspace
843-
if (!selectedFolder.path.startsWith(projectRoot.path)) {
843+
if (!selectedFolder.path.startsWith(workspaceRoot.toString())) {
844844
logger.info { "Selected folder not in workspace: ${selectedFolder.path}" }
845845

846846
messenger.sendAnswer(
@@ -860,7 +860,7 @@ class FeatureDevController(
860860

861861
logger.info { "Selected correct folder inside workspace: ${selectedFolder.path}" }
862862

863-
session.context.selectedSourceFolder = selectedFolder
863+
session.context.selectedRoot = selectedFolder
864864
result = Result.Succeeded
865865

866866
messenger.sendAnswer(

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/CodeGenerationState.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class CodeGenerationState(
9696
var insertedCharacters = 0
9797
codeGenerationResult.newFiles.forEach { file ->
9898
// FIXME: Ideally, the before content should be read from the uploaded context instead of from disk, to avoid drift
99-
val before = config.repoContext.selectedSourceFolder
99+
val before = config.repoContext.selectedRoot
100100
.toNioPath()
101101
.resolve(file.zipFilePath)
102102
.toFile()

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/PrepareCodeGenerationState.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class PrepareCodeGenerationState(
4949
messenger.sendAnswerPart(tabId = this.tabID, message = message("amazonqFeatureDev.chat_message.uploading_code"))
5050
messenger.sendUpdatePlaceholder(tabId = this.tabID, newPlaceholder = message("amazonqFeatureDev.chat_message.uploading_code"))
5151

52-
val isAutoBuildFeatureEnabled = CodeWhispererSettings.getInstance().isAutoBuildFeatureEnabled(this.config.repoContext.getWorkspaceRoot())
52+
val isAutoBuildFeatureEnabled = CodeWhispererSettings.getInstance().isAutoBuildFeatureEnabled(this.config.repoContext.workspaceRoot.toString())
5353
val repoZipResult = config.repoContext.getProjectZip(isAutoBuildFeatureEnabled = isAutoBuildFeatureEnabled)
5454
val zipFileChecksum = repoZipResult.checksum
5555
zipFileLength = repoZipResult.contentLength

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/Session.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import com.intellij.openapi.project.Project
88
import com.intellij.openapi.vfs.VfsUtil
99
import software.aws.toolkits.jetbrains.common.util.resolveAndCreateOrUpdateFile
1010
import software.aws.toolkits.jetbrains.common.util.resolveAndDeleteFile
11-
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
1211
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
12+
import software.aws.toolkits.jetbrains.services.amazonq.project.FeatureDevSessionContext
1313
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CODE_GENERATION_RETRY_LIMIT
1414
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ConversationIdNotFoundException
1515
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME
@@ -130,7 +130,7 @@ class Session(val tabID: String, val project: Project) {
130130
) {
131131
val newFilePaths = filePaths.filter { !it.rejected && !it.changeApplied }
132132
val newDeletedFiles = deletedFiles.filter { !it.rejected && !it.changeApplied }
133-
val selectedSourceFolder = context.selectedSourceFolder.toNioPath()
133+
val selectedSourceFolder = context.selectedRoot.toNioPath()
134134

135135
runCatching {
136136
var insertedLines = 0
@@ -174,15 +174,15 @@ class Session(val tabID: String, val project: Project) {
174174
ReferenceLogController.addReferenceLog(references, project)
175175

176176
// Taken from https://intellij-support.jetbrains.com/hc/en-us/community/posts/206118439-Refresh-after-external-changes-to-project-structure-and-sources
177-
VfsUtil.markDirtyAndRefresh(true, true, true, context.selectedSourceFolder)
177+
VfsUtil.markDirtyAndRefresh(true, true, true, context.selectedRoot)
178178
}
179179

180180
// Suppressing because insertNewFiles needs to be a suspend function in order to be tested
181181
@Suppress("RedundantSuspendModifier")
182182
suspend fun insertNewFiles(
183183
filePaths: List<NewFileZipInfo>,
184184
) {
185-
val selectedSourceFolder = context.selectedSourceFolder.toNioPath()
185+
val selectedSourceFolder = context.selectedRoot.toNioPath()
186186

187187
filePaths.forEach {
188188
resolveAndCreateOrUpdateFile(selectedSourceFolder, it.zipFilePath, it.fileContent)
@@ -195,7 +195,7 @@ class Session(val tabID: String, val project: Project) {
195195
suspend fun applyDeleteFiles(
196196
deletedFiles: List<DeletedFileInfo>,
197197
) {
198-
val selectedSourceFolder = context.selectedSourceFolder.toNioPath()
198+
val selectedSourceFolder = context.selectedRoot.toNioPath()
199199

200200
deletedFiles.forEach {
201201
resolveAndDeleteFile(selectedSourceFolder, it.zipFilePath)

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/SessionStateTypes.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
package software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session
55

66
import com.fasterxml.jackson.annotation.JsonValue
7-
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
7+
import software.aws.toolkits.jetbrains.services.amazonq.project.FeatureDevSessionContext
88
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.CancellationTokenSource
99
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.FeatureDevService
1010
import software.aws.toolkits.jetbrains.services.cwc.messages.RecommendationContentSpan

plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevSessionContextTest.kt

+3-38
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import com.intellij.openapi.vfs.VirtualFile
54
import com.intellij.testFramework.RuleChain
65
import org.junit.Assert.assertEquals
7-
import org.junit.Assert.assertFalse
8-
import org.junit.Assert.assertTrue
96
import org.junit.Before
107
import org.junit.Rule
118
import org.junit.Test
129
import org.mockito.kotlin.mock
1310
import org.mockito.kotlin.whenever
14-
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
11+
import software.aws.toolkits.jetbrains.services.amazonq.project.FeatureDevSessionContext
1512
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevTestBase
1613
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.FeatureDevService
1714
import software.aws.toolkits.jetbrains.utils.rules.HeavyJavaCodeInsightTestFixtureRule
@@ -38,39 +35,7 @@ class FeatureDevSessionContextTest : FeatureDevTestBase(HeavyJavaCodeInsightTest
3835
featureDevSessionContext = FeatureDevSessionContext(featureDevService.project, 1024)
3936
}
4037

41-
@Test
42-
fun testWithDirectory() {
43-
val directory = mock<VirtualFile>()
44-
whenever(directory.extension).thenReturn(null)
45-
whenever(directory.isDirectory).thenReturn(true)
46-
assertTrue(featureDevSessionContext.isFileExtensionAllowed(directory))
47-
}
48-
49-
@Test
50-
fun testWithValidFile() {
51-
val ktFile = mock<VirtualFile>()
52-
whenever(ktFile.extension).thenReturn("kt")
53-
whenever(ktFile.path).thenReturn("code.kt")
54-
assertTrue(featureDevSessionContext.isFileExtensionAllowed(ktFile))
55-
}
56-
57-
@Test
58-
fun testWithInvalidFile() {
59-
val mediaFile = mock<VirtualFile>()
60-
whenever(mediaFile.extension).thenReturn("mp4")
61-
assertFalse(featureDevSessionContext.isFileExtensionAllowed(mediaFile))
62-
}
63-
64-
@Test
65-
fun testAllowedFilePath() {
66-
val allowedPaths = listOf("build.gradle", "gradle.properties", ".mvn/wrapper/maven-wrapper.properties")
67-
allowedPaths.forEach({
68-
val txtFile = mock<VirtualFile>()
69-
whenever(txtFile.path).thenReturn(it)
70-
whenever(txtFile.extension).thenReturn(it.split(".").last())
71-
assertTrue(featureDevSessionContext.isFileExtensionAllowed(txtFile))
72-
})
73-
}
38+
// FIXME: Add deeper tests, replacing previous shallow tests - BLOCKING
7439

7540
@Test
7641
fun testZipProjectWithoutAutoDev() {
@@ -182,7 +147,7 @@ class FeatureDevSessionContextTest : FeatureDevTestBase(HeavyJavaCodeInsightTest
182147
"src/file.png/"
183148
)
184149

185-
val patterns = sampleGitIgnorePatterns.map { pattern -> featureDevSessionContext.convertGitIgnorePatternToRegex(pattern).toRegex() }
150+
val patterns = sampleGitIgnorePatterns.map { pattern -> featureDevSessionContext.convertGitIgnorePatternToRegex(pattern) }
186151

187152
val matchedFiles = sampleFileNames.filter { fileName ->
188153
patterns.any { pattern ->

0 commit comments

Comments
 (0)