Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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 @@ -325,7 +325,7 @@
[email protected] {
DefaultAuthCredentialsService(project, encryptionManager, this)
TextDocumentServiceHandler(project, this)
WorkspaceServiceHandler(project, this)
WorkspaceServiceHandler(project, initializeResult.getCompleted(), 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 @@ -22,7 +22,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.WorkspaceFolder
import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent
Expand All @@ -31,20 +33,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 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 {
operationMatchers.putAll(initializePatterns(initializeResult))

Check warning on line 50 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

Added line #L50 was not covered by tests

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

enum class FileOperationType {
CREATE,
DELETE,
RENAME,
}

Check warning on line 67 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#L64-L67

Added lines #L64 - L67 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 70 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

Added line #L70 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 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

return patterns

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
}

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 83 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#L83

Added line #L83 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 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#L88-L89

Added lines #L88 - L89 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 93 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#L92-L93

Added lines #L92 - L93 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
toUriString(file)?.let { uri ->
FileCreate().apply {
this.uri = uri
Expand All @@ -80,7 +118,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
toUriString(file)?.let { uri ->
FileDelete().apply {
this.uri = uri
Expand All @@ -103,7 +141,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
if (event.newValue !is String) return@mapNotNull null

// Construct old and new URIs
Expand Down Expand Up @@ -186,13 +224,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 @@ -29,8 +29,15 @@ 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 All @@ -46,10 +53,11 @@ 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 sut: WorkspaceServiceHandler
private lateinit var mockApplication: Application

@BeforeEach
fun setup() {
Expand Down Expand Up @@ -94,7 +102,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