From 4fbb75fe8ab064bfe782babfd55432ce1f360686 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 27 Feb 2025 13:54:58 -0800 Subject: [PATCH 1/4] feat(amazonq): expose lsp server capabilities to consumers of AmazonQLspService For certain capabilities, the client implementation needs to be aware of what resources the server is interested in. In the case of `WorkspaceEdit`, we need to expose the filters returned by the server in the initialization handshake. ```json "fileOperations": { "didCreate": { "filters": [ { "pattern": { "glob": "**/*.{ts,js,py,java}", "matches": "file" } }, { "pattern": { "glob": "**/*", "matches": "folder" } } ] }, "didRename": { "filters": [ { "pattern": { "glob": "**/*.{ts,js,py,java}", "matches": "file" } }, { "pattern": { "glob": "**/*", "matches": "folder" } } ] }, "didDelete": { "filters": [ { "pattern": { "glob": "**/*.{ts,js,py,java}", "matches": "file" } }, { "pattern": { "glob": "**/*", "matches": "folder" } } ] } } ``` --- .../services/amazonq/lsp/AmazonQLspService.kt | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 49141741de0..825da8f7163 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -21,10 +21,8 @@ import com.intellij.openapi.util.Key import com.intellij.util.io.await import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -33,7 +31,7 @@ import org.eclipse.lsp4j.ClientCapabilities import org.eclipse.lsp4j.ClientInfo import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities import org.eclipse.lsp4j.InitializeParams -import org.eclipse.lsp4j.InitializedParams +import org.eclipse.lsp4j.InitializeResult import org.eclipse.lsp4j.SynchronizationCapabilities import org.eclipse.lsp4j.TextDocumentClientCapabilities import org.eclipse.lsp4j.WorkspaceClientCapabilities @@ -95,6 +93,8 @@ internal class LSPProcessListener : ProcessListener { @Service(Service.Level.PROJECT) class AmazonQLspService(private val project: Project, private val cs: CoroutineScope) : Disposable { private var instance: Deferred + val capabilities + get() = instance.getCompleted().initializeResult.getCompleted().capabilities // dont allow lsp commands if server is restarting private val mutex = Mutex(false) @@ -110,7 +110,7 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS Disposer.register(this@AmazonQLspService, it) } // wait for handshake to complete - instance.initializer.join() + instance.initializeResult.join() instance } @@ -140,7 +140,7 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS try { val i = it.await() - if (i.initializer.isActive) { + if (i.initializeResult.isActive) { // not initialized return } @@ -154,17 +154,17 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS instance = start() } - suspend fun execute(runnable: suspend (AmazonQLanguageServer) -> T): T { + suspend fun execute(runnable: suspend AmazonQLspService.(AmazonQLanguageServer) -> T): T { val lsp = withTimeout(10.seconds) { val holder = mutex.withLock { instance }.await() - holder.initializer.join() + holder.initializeResult.join() holder.languageServer } return runnable(lsp) } - fun executeSync(runnable: suspend (AmazonQLanguageServer) -> T): T = + fun executeSync(runnable: suspend AmazonQLspService.(AmazonQLanguageServer) -> T): T = runBlocking(cs.coroutineContext) { execute(runnable) } @@ -173,7 +173,7 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS private val LOG = getLogger() fun getInstance(project: Project) = project.service() - fun executeIfRunning(project: Project, runnable: (AmazonQLanguageServer) -> T): T? = + fun executeIfRunning(project: Project, runnable: AmazonQLspService.(AmazonQLanguageServer) -> T): T? = project.serviceIfCreated()?.executeSync(runnable) } } @@ -189,7 +189,7 @@ private class AmazonQServerInstance(private val project: Project, private val cs @Suppress("ForbiddenVoid") private val launcherFuture: Future private val launcherHandler: KillableProcessHandler - val initializer: Job + val initializeResult: Deferred private fun createClientCapabilities(): ClientCapabilities = ClientCapabilities().apply { @@ -244,7 +244,8 @@ private class AmazonQServerInstance(private val project: Project, private val cs init { val cmd = GeneralCommandLine( - "amazon-q-lsp", + "/opt/homebrew/opt/node@20/bin/node", + "/Users/richali/idetools/language-servers/app/aws-lsp-codewhisperer-runtimes/out/token-standalone.js", "--stdio", "--set-credentials-encryption-key", ) @@ -281,7 +282,7 @@ private class AmazonQServerInstance(private val project: Project, private val cs launcherFuture = launcher.startListening() - initializer = cs.launch { + initializeResult = cs.async { // encryption info must be sent within 5s or Flare process will exit encryptionManager.writeInitializationPayload(launcherHandler.process.outputStream) @@ -300,8 +301,10 @@ private class AmazonQServerInstance(private val project: Project, private val cs // then if this succeeds then we can allow the client to send requests if (initializeResult == null) { launcherHandler.destroyProcess() + error("LSP initialization failed") } - languageServer.initialized(InitializedParams()) + + initializeResult } } From 712dbcb79131ebf668d62eca2bad1b2e3273b8c5 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 27 Feb 2025 13:59:36 -0800 Subject: [PATCH 2/4] revert --- .../jetbrains/services/amazonq/lsp/AmazonQLspService.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 825da8f7163..635184c8aed 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -32,6 +32,7 @@ import org.eclipse.lsp4j.ClientInfo import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities import org.eclipse.lsp4j.InitializeParams import org.eclipse.lsp4j.InitializeResult +import org.eclipse.lsp4j.InitializedParams import org.eclipse.lsp4j.SynchronizationCapabilities import org.eclipse.lsp4j.TextDocumentClientCapabilities import org.eclipse.lsp4j.WorkspaceClientCapabilities @@ -244,8 +245,7 @@ private class AmazonQServerInstance(private val project: Project, private val cs init { val cmd = GeneralCommandLine( - "/opt/homebrew/opt/node@20/bin/node", - "/Users/richali/idetools/language-servers/app/aws-lsp-codewhisperer-runtimes/out/token-standalone.js", + "amazon-q-lsp", "--stdio", "--set-credentials-encryption-key", ) @@ -303,6 +303,7 @@ private class AmazonQServerInstance(private val project: Project, private val cs launcherHandler.destroyProcess() error("LSP initialization failed") } + languageServer.initialized(InitializedParams()) initializeResult } From 93c045232c964427814aefde423c2127caf861c0 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 3 Mar 2025 13:17:36 -0800 Subject: [PATCH 3/4] syntax --- .../amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt index 95d5f6e38d8..388e2286082 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsServiceTest.kt @@ -43,8 +43,8 @@ class DefaultAuthCredentialsServiceTest { every { mockLspService.executeSync>(any()) } coAnswers { - val func = firstArg CompletableFuture>() - func.invoke(mockLanguageServer) + val func = firstArg CompletableFuture>() + func.invoke(mockLspService, mockLanguageServer) } // Mock message bus From 1809facd4c19dc18b6f5f842dbed49002233594f Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 3 Mar 2025 15:42:46 -0800 Subject: [PATCH 4/4] tst --- .../amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt index adb9e105f2c..66315a9d059 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/workspace/WorkspaceServiceHandlerTest.kt @@ -75,8 +75,8 @@ class WorkspaceServiceHandlerTest { every { mockLspService.executeSync>(any()) } coAnswers { - val func = firstArg CompletableFuture>() - func.invoke(mockLanguageServer) + val func = firstArg CompletableFuture>() + func.invoke(mockLspService, mockLanguageServer) } // Mock workspace service