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 4 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 @@ -318,7 +318,7 @@

DefaultAuthCredentialsService(project, encryptionManager, this)
TextDocumentServiceHandler(project, this)
WorkspaceServiceHandler(project, this)
WorkspaceServiceHandler(project, initializeResult, this)

Check warning on line 321 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#L321

Added line #L321 was not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

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

if you rebase, you should have access to the server initialize result directly instead of needing to resolve the deferred value

cs.launch {
DefaultModuleDependenciesService(project, this@AmazonQServerInstance)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent
import kotlinx.coroutines.Deferred
import org.eclipse.lsp4j.CreateFilesParams
import org.eclipse.lsp4j.DeleteFilesParams
import org.eclipse.lsp4j.DidChangeWatchedFilesParams
Expand All @@ -22,28 +23,34 @@
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.WorkspaceFolder
import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
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,
private val initializeResult: Deferred<InitializeResult>,

Check warning on line 41 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#L41

Added line #L41 was not covered by tests
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 47 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#L47

Added line #L47 was not covered by tests

init {
initializeResult.invokeOnCompletion {
operationMatchers.putAll(initializePatterns(initializeResult.getCompleted()))
}

Check warning on line 52 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#L50-L52

Added lines #L50 - L52 were not covered by tests

project.messageBus.connect(serverInstance).subscribe(
VirtualFileManager.VFS_CHANGES,
this
Expand All @@ -55,10 +62,44 @@
)
}

enum class FileOperationType {
CREATE,
DELETE,
RENAME,
}

Check warning on line 69 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#L66-L69

Added lines #L66 - L69 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 72 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#L72

Added line #L72 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 78 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#L78

Added line #L78 was not covered by tests

return patterns

Check warning on line 80 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#L80

Added line #L80 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 85 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#L85

Added line #L85 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 91 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#L90-L91

Added lines #L90 - L91 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 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
}
}

private fun didCreateFiles(events: List<VFileEvent>) {
AmazonQLspService.executeIfRunning(project) { languageServer ->
val validFiles = events.mapNotNull { event ->
val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
val file = event.file?.takeIf { shouldHandleFile(it, FileOperationType.CREATE) } ?: return@mapNotNull null
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
FileCreate().apply {
this.uri = uri
Expand All @@ -79,7 +120,7 @@
private fun didDeleteFiles(events: List<VFileEvent>) {
AmazonQLspService.executeIfRunning(project) { languageServer ->
val validFiles = events.mapNotNull { event ->
val file = event.file?.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
val file = event.file?.takeIf { shouldHandleFile(it, FileOperationType.DELETE) } ?: return@mapNotNull null
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
FileDelete().apply {
this.uri = uri
Expand All @@ -102,7 +143,7 @@
val validRenames = events
.filter { it.propertyName == VirtualFile.PROP_NAME }
.mapNotNull { event ->
val file = event.file.takeIf { shouldHandleFile(it) } ?: return@mapNotNull null
val file = event.file.takeIf { shouldHandleFile(it, FileOperationType.RENAME) } ?: return@mapNotNull null
val oldName = event.oldValue as? String ?: return@mapNotNull null
if (event.newValue !is String) return@mapNotNull null

Expand Down Expand Up @@ -186,13 +227,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 @@ -23,14 +23,22 @@ import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.test.runTest
import org.eclipse.lsp4j.CreateFilesParams
import org.eclipse.lsp4j.DeleteFilesParams
import org.eclipse.lsp4j.DidChangeWatchedFilesParams
import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams
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.WorkspaceService
import org.junit.jupiter.api.Assertions.assertEquals
Expand Down Expand Up @@ -94,7 +102,40 @@ 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
val 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

val mockDeferred = CompletableDeferred(mockInitializeResult)

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

@Test
Expand Down
Loading