Skip to content

feat(amazonq): fetch filePatterns from initializeResponse for workspace messages #5457

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

Merged
merged 21 commits into from
May 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9ef15e7
get SupportedFiletype globs from initializeResult
samgst-amazon Mar 7, 2025
de6d12c
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
samgst-amazon Mar 7, 2025
750a232
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
samgst-amazon Mar 10, 2025
e61b663
detekt
samgst-amazon Mar 10, 2025
d28ab84
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
samgst-amazon Mar 12, 2025
cebf270
access initializeResult directly
samgst-amazon Mar 12, 2025
c997e48
access initializeResult directly
samgst-amazon Mar 12, 2025
3dd5101
detekt
samgst-amazon Mar 12, 2025
b8d2f11
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
samgst-amazon Mar 13, 2025
e44cc15
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
samgst-amazon Mar 13, 2025
8103b55
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
samgst-amazon Mar 18, 2025
c54d40c
refactor param
samgst-amazon Mar 18, 2025
1e5d3cd
Merge branch 'feature/q-lsp' into samgst/q-lsp-supported-filetypes
samgst-amazon Mar 20, 2025
ff7b344
fix merge
samgst-amazon Mar 20, 2025
54a5161
Merge branch 'main' into samgst/q-lsp-supported-filetypes
samgst-amazon Apr 18, 2025
8539f55
Merge branch 'main' into samgst/q-lsp-supported-filetypes
samgst-amazon Apr 21, 2025
aa37979
Merge branch 'main' into samgst/q-lsp-supported-filetypes
samgst-amazon Apr 30, 2025
59afc4e
Merge branch 'main' into samgst/q-lsp-supported-filetypes
samgst-amazon May 2, 2025
bbc4282
detekt
samgst-amazon May 2, 2025
bbbf3ac
Merge branch 'main' into samgst/q-lsp-supported-filetypes
samgst-amazon May 6, 2025
d3ae846
Merge branch 'main' into samgst/q-lsp-supported-filetypes
samgst-amazon May 7, 2025
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 @@ -317,15 +317,15 @@
}

// invokeOnCompletion results in weird lock/timeout error
initializeResult.asCompletableFuture().handleAsync { r, ex ->
initializeResult.asCompletableFuture().handleAsync { lspInitResult, ex ->

Check warning on line 320 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt#L320

Added line #L320 was not covered by tests
if (ex != null) {
return@handleAsync
}

this@AmazonQServerInstance.apply {
DefaultAuthCredentialsService(project, encryptionManager, this)
TextDocumentServiceHandler(project, this)
WorkspaceServiceHandler(project, this)
WorkspaceServiceHandler(project, lspInitResult, this)

Check warning on line 328 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt#L328

Added line #L328 was not covered by tests
DefaultModuleDependenciesService(project, this)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import org.eclipse.lsp4j.FileCreate
import org.eclipse.lsp4j.FileDelete
import org.eclipse.lsp4j.FileEvent
import org.eclipse.lsp4j.FileOperationFilter
import org.eclipse.lsp4j.FileRename
import org.eclipse.lsp4j.InitializeResult
import org.eclipse.lsp4j.RenameFilesParams
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.lsp4j.TextDocumentItem
Expand All @@ -37,20 +39,22 @@
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread
import java.nio.file.FileSystems
import java.nio.file.PathMatcher
import java.nio.file.Paths

class WorkspaceServiceHandler(
private val project: Project,
initializeResult: InitializeResult,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do we need to send the whole result or can we send the operations?

serverInstance: Disposable,
) : BulkFileListener,
ModuleRootListener {

private var lastSnapshot: List<WorkspaceFolder> = emptyList()
private val supportedFilePatterns = FileSystems.getDefault().getPathMatcher(
"glob:**/*.{ts,js,py,java}"
)
private val operationMatchers: MutableMap<FileOperationType, List<Pair<PathMatcher, String>>> = mutableMapOf()

Check warning on line 53 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L53

Added line #L53 was not covered by tests

init {
operationMatchers.putAll(initializePatterns(initializeResult))

Check warning on line 56 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L56

Added line #L56 was not covered by tests

project.messageBus.connect(serverInstance).subscribe(
VirtualFileManager.VFS_CHANGES,
this
Expand All @@ -62,12 +66,46 @@
)
}

enum class FileOperationType {
CREATE,
DELETE,
RENAME,
}

Check warning on line 73 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L70-L73

Added lines #L70 - L73 were not covered by tests

private fun initializePatterns(initializeResult: InitializeResult): Map<FileOperationType, List<Pair<PathMatcher, String>>> {
val patterns = mutableMapOf<FileOperationType, List<Pair<PathMatcher, String>>>()

Check warning on line 76 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L76

Added line #L76 was not covered by tests

initializeResult.capabilities?.workspace?.fileOperations?.let { fileOps ->
patterns[FileOperationType.CREATE] = createMatchers(fileOps.didCreate?.filters)
patterns[FileOperationType.DELETE] = createMatchers(fileOps.didDelete?.filters)
patterns[FileOperationType.RENAME] = createMatchers(fileOps.didRename?.filters)
}

Check warning on line 82 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L82

Added line #L82 was not covered by tests

return patterns

Check warning on line 84 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L84

Added line #L84 was not covered by tests
}

private fun createMatchers(filters: List<FileOperationFilter>?): List<Pair<PathMatcher, String>> =
filters?.map { filter ->
FileSystems.getDefault().getPathMatcher("glob:${filter.pattern.glob}") to filter.pattern.matches

Check warning on line 89 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L89

Added line #L89 was not covered by tests
}.orEmpty()

private fun shouldHandleFile(file: VirtualFile, operation: FileOperationType): Boolean {
val matchers = operationMatchers[operation] ?: return false
return matchers.any { (matcher, type) ->
when (type) {

Check warning on line 95 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L94-L95

Added lines #L94 - L95 were not covered by tests
"file" -> !file.isDirectory && matcher.matches(Paths.get(file.path))
"folder" -> file.isDirectory && matcher.matches(Paths.get(file.path))
else -> matcher.matches(Paths.get(file.path))
}

Check warning on line 99 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandler.kt#L98-L99

Added lines #L98 - L99 were not covered by tests
}
}

private fun didCreateFiles(events: List<VFileEvent>) {
AmazonQLspService.executeIfRunning(project) { languageServer ->
val validFiles = events.mapNotNull { event ->
when (event) {
is VFileCopyEvent -> {
val newFile = event.newParent.findChild(event.newChildName)?.takeIf { shouldHandleFile(it) }
val newFile = event.newParent.findChild(event.newChildName)?.takeIf { shouldHandleFile(it, FileOperationType.CREATE) }
?: return@mapNotNull null
toUriString(newFile)?.let { uri ->
FileCreate().apply {
Expand All @@ -76,7 +114,7 @@
}
}
else -> {
val file = event.file?.takeIf { shouldHandleFile(it) }
val file = event.file?.takeIf { shouldHandleFile(it, FileOperationType.CREATE) }
?: return@mapNotNull null
toUriString(file)?.let { uri ->
FileCreate().apply {
Expand All @@ -102,11 +140,11 @@
val validFiles = events.mapNotNull { event ->
when (event) {
is VFileDeleteEvent -> {
val file = event.file.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
val file = event.file.takeIf { shouldHandleFile(it, FileOperationType.DELETE) } ?: return@mapNotNull null
toUriString(file)
}
is VFileMoveEvent -> {
val oldFile = event.oldParent?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
val oldFile = event.oldParent?.takeIf { shouldHandleFile(it, FileOperationType.DELETE) } ?: return@mapNotNull null
toUriString(oldFile)
}
else -> null
Expand All @@ -132,7 +170,7 @@
val validRenames = events
.filter { it.propertyName == VirtualFile.PROP_NAME }
.mapNotNull { event ->
val renamedFile = event.file.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
val renamedFile = event.file.takeIf { shouldHandleFile(it, FileOperationType.RENAME) } ?: return@mapNotNull null
val oldFileName = event.oldValue as? String ?: return@mapNotNull null
val parentFile = renamedFile.parent ?: return@mapNotNull null

Expand Down Expand Up @@ -275,13 +313,4 @@
lastSnapshot = currentSnapshot
}
}

private fun shouldHandleFile(file: VirtualFile): Boolean {
if (file.isDirectory) {
return true // Matches "**/*" with matches: "folder"
}
val path = Paths.get(file.path)
val result = supportedFilePatterns.matches(path)
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams
import org.eclipse.lsp4j.DidCloseTextDocumentParams
import org.eclipse.lsp4j.DidOpenTextDocumentParams
import org.eclipse.lsp4j.FileChangeType
import org.eclipse.lsp4j.FileOperationFilter
import org.eclipse.lsp4j.FileOperationOptions
import org.eclipse.lsp4j.FileOperationPattern
import org.eclipse.lsp4j.FileOperationsServerCapabilities
import org.eclipse.lsp4j.InitializeResult
import org.eclipse.lsp4j.RenameFilesParams
import org.eclipse.lsp4j.ServerCapabilities
import org.eclipse.lsp4j.WorkspaceFolder
import org.eclipse.lsp4j.WorkspaceServerCapabilities
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage
import org.eclipse.lsp4j.services.TextDocumentService
import org.eclipse.lsp4j.services.WorkspaceService
Expand All @@ -52,11 +59,12 @@ import java.util.concurrent.CompletableFuture

class WorkspaceServiceHandlerTest {
private lateinit var project: Project
private lateinit var mockApplication: Application
private lateinit var mockInitializeResult: InitializeResult
private lateinit var mockLanguageServer: AmazonQLanguageServer
private lateinit var mockWorkspaceService: WorkspaceService
private lateinit var mockTextDocumentService: TextDocumentService
private lateinit var sut: WorkspaceServiceHandler
private lateinit var mockApplication: Application

@BeforeEach
fun setup() {
Expand Down Expand Up @@ -107,7 +115,38 @@ class WorkspaceServiceHandlerTest {
every { messageBus.connect(any<Disposable>()) } returns mockConnection
every { mockConnection.subscribe(any(), any()) } just runs

sut = WorkspaceServiceHandler(project, mockk())
// Mock InitializeResult with file operation patterns
mockInitializeResult = mockk<InitializeResult>()
val mockCapabilities = mockk<ServerCapabilities>()
val mockWorkspaceCapabilities = mockk<WorkspaceServerCapabilities>()
val mockFileOperations = mockk<FileOperationsServerCapabilities>()

val fileFilter = FileOperationFilter().apply {
pattern = FileOperationPattern().apply {
glob = "**/*.{ts,js,py,java}"
matches = "file"
}
}
val folderFilter = FileOperationFilter().apply {
pattern = FileOperationPattern().apply {
glob = "**/*"
matches = "folder"
}
}

val fileOperationOptions = FileOperationOptions().apply {
filters = listOf(fileFilter, folderFilter)
}

every { mockFileOperations.didCreate } returns fileOperationOptions
every { mockFileOperations.didDelete } returns fileOperationOptions
every { mockFileOperations.didRename } returns fileOperationOptions
every { mockWorkspaceCapabilities.fileOperations } returns mockFileOperations
every { mockCapabilities.workspace } returns mockWorkspaceCapabilities
every { mockInitializeResult.capabilities } returns mockCapabilities

// Create WorkspaceServiceHandler with mocked InitializeResult
sut = WorkspaceServiceHandler(project, mockInitializeResult, mockk())
}

@Test
Expand Down
Loading