-
Notifications
You must be signed in to change notification settings - Fork 275
feat(q): stub out Q LSP logic #5352
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package software.aws.toolkits.jetbrains.services.amazonq.lsp | ||
|
|
||
| import org.eclipse.lsp4j.jsonrpc.services.JsonRequest | ||
| import org.eclipse.lsp4j.services.LanguageClient | ||
| import java.util.concurrent.CompletableFuture | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata | ||
|
|
||
| /** | ||
| * Requests sent by server to client | ||
| */ | ||
| interface AmazonQLanguageClient : LanguageClient { | ||
| @JsonRequest("aws/credentials/getConnectionMetadata") | ||
| fun getConnectionMetadata(): CompletableFuture<ConnectionMetadata> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| // Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package software.aws.toolkits.jetbrains.services.amazonq.lsp | ||
|
|
||
| import com.intellij.notification.NotificationType | ||
| import org.eclipse.lsp4j.ConfigurationParams | ||
| import org.eclipse.lsp4j.MessageActionItem | ||
| import org.eclipse.lsp4j.MessageParams | ||
| import org.eclipse.lsp4j.MessageType | ||
| import org.eclipse.lsp4j.PublishDiagnosticsParams | ||
| import org.eclipse.lsp4j.ShowMessageRequestParams | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData | ||
| import java.util.concurrent.CompletableFuture | ||
|
|
||
| /** | ||
| * Concrete implementation of [AmazonQLanguageClient] to handle messages sent from server | ||
| */ | ||
| class AmazonQLanguageClientImpl : AmazonQLanguageClient { | ||
| override fun telemetryEvent(`object`: Any) { | ||
| println(`object`) | ||
| } | ||
|
|
||
| override fun publishDiagnostics(diagnostics: PublishDiagnosticsParams) { | ||
| println(diagnostics) | ||
rli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| override fun showMessage(messageParams: MessageParams) { | ||
| val type = when (messageParams.type) { | ||
| MessageType.Error -> NotificationType.ERROR | ||
| MessageType.Warning -> NotificationType.WARNING | ||
| MessageType.Info, MessageType.Log -> NotificationType.INFORMATION | ||
| } | ||
| println("$type: ${messageParams.message}") | ||
rli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| override fun showMessageRequest(requestParams: ShowMessageRequestParams): CompletableFuture<MessageActionItem?>? { | ||
| println(requestParams) | ||
|
|
||
| return CompletableFuture.completedFuture(null) | ||
| } | ||
|
|
||
| override fun logMessage(message: MessageParams) { | ||
| showMessage(message) | ||
| } | ||
|
|
||
| override fun getConnectionMetadata() = CompletableFuture.completedFuture( | ||
|
Check notice on line 48 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt
|
||
Check noticeCode scanning / QDJVMC Function or property has platform type Note
Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable.
|
||
| ConnectionMetadata( | ||
| SsoProfileData("TODO") | ||
| ) | ||
| ) | ||
|
|
||
| override fun configuration(params: ConfigurationParams): CompletableFuture<List<Any>> { | ||
| if (params.items.isEmpty()) { | ||
| return CompletableFuture.completedFuture(null) | ||
| } | ||
|
|
||
| return CompletableFuture.completedFuture( | ||
| buildList { | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package software.aws.toolkits.jetbrains.services.amazonq.lsp | ||
|
|
||
| import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage | ||
| import org.eclipse.lsp4j.jsonrpc.services.JsonRequest | ||
| import org.eclipse.lsp4j.services.LanguageServer | ||
| import java.util.concurrent.CompletableFuture | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.UpdateCredentialsPayload | ||
|
|
||
| /** | ||
| * Remote interface exposed by the Amazon Q language server | ||
| */ | ||
| interface AmazonQLanguageServer : LanguageServer { | ||
| @JsonRequest("aws/credentials/token/update") | ||
| fun updateTokenCredentials(payload: UpdateCredentialsPayload): CompletableFuture<ResponseMessage> | ||
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| // Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package software.aws.toolkits.jetbrains.services.amazonq.lsp | ||
|
|
||
| import com.google.gson.ToNumberPolicy | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be using gson?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes lsp4j only works with gson |
||
| import com.intellij.execution.configurations.GeneralCommandLine | ||
| import com.intellij.execution.impl.ExecutionManagerImpl | ||
| import com.intellij.execution.process.KillableColoredProcessHandler | ||
| import com.intellij.execution.process.ProcessEvent | ||
| import com.intellij.execution.process.ProcessListener | ||
| import com.intellij.execution.process.ProcessOutputType | ||
| import com.intellij.openapi.Disposable | ||
| import com.intellij.openapi.components.Service | ||
| import com.intellij.openapi.components.service | ||
| import com.intellij.openapi.project.Project | ||
| import com.intellij.openapi.util.Key | ||
| import com.intellij.util.io.await | ||
| import kotlinx.coroutines.CoroutineScope | ||
| import kotlinx.coroutines.launch | ||
| import org.eclipse.lsp4j.ClientCapabilities | ||
|
||
| import org.eclipse.lsp4j.InitializeParams | ||
| import org.eclipse.lsp4j.InitializedParams | ||
| import org.eclipse.lsp4j.jsonrpc.Launcher | ||
| import org.eclipse.lsp4j.launch.LSPLauncher | ||
| import software.aws.toolkits.core.utils.getLogger | ||
| import software.aws.toolkits.jetbrains.isDeveloperMode | ||
| import java.io.IOException | ||
| import java.io.OutputStreamWriter | ||
| import java.io.PipedInputStream | ||
| import java.io.PipedOutputStream | ||
| import java.io.PrintWriter | ||
| import java.io.StringWriter | ||
| import java.nio.charset.StandardCharsets | ||
| import org.slf4j.event.Level | ||
|
|
||
| // https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java | ||
| // JB impl and redhat both use a wrapper to handle input buffering issue | ||
| internal class LSPProcessListener : ProcessListener { | ||
| private val outputStream = PipedOutputStream() | ||
| private val outputStreamWriter = OutputStreamWriter(outputStream, StandardCharsets.UTF_8) | ||
| val inputStream = PipedInputStream(outputStream) | ||
|
|
||
| override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { | ||
| if (ProcessOutputType.isStdout(outputType)) { | ||
| try { | ||
| this.outputStreamWriter.write(event.text) | ||
| this.outputStreamWriter.flush() | ||
| } catch (_: IOException) { | ||
| ExecutionManagerImpl.stopProcess(event.processHandler) | ||
| } | ||
| } else if (ProcessOutputType.isStderr(outputType)) { | ||
| LOG.warn("LSP process stderr: ${event.text}") | ||
| } | ||
| } | ||
|
|
||
| override fun processTerminated(event: ProcessEvent) { | ||
| try { | ||
| this.outputStreamWriter.close() | ||
| this.outputStream.close() | ||
| } catch (_: IOException) { | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| private val LOG = getLogger<LSPProcessListener>() | ||
| } | ||
| } | ||
|
|
||
| @Service(Service.Level.PROJECT) | ||
| class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disposable { | ||
|
Check warning on line 71 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
Check warningCode scanning / QDJVMC Unused symbol Warning
Class "AmazonQLspService" is never used
Check warningCode scanning / QDJVMC Constructor parameter is never used as a property Warning
Constructor parameter is never used as a property
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would this be triggered?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. future pr |
||
| private val launcher: Launcher<AmazonQLanguageServer> | ||
|
|
||
| private val languageServer: AmazonQLanguageServer | ||
| get() = launcher.remoteProxy | ||
|
|
||
| init { | ||
| val cmd = GeneralCommandLine("amazon-q-lsp") | ||
|
|
||
| val handler = KillableColoredProcessHandler.Silent(cmd) | ||
| val inputWrapper = LSPProcessListener() | ||
| handler.addProcessListener(inputWrapper) | ||
| handler.startNotify() | ||
|
|
||
| launcher = LSPLauncher.Builder<AmazonQLanguageServer>() | ||
| .setLocalService(AmazonQLanguageClientImpl()) | ||
| .setRemoteInterface(AmazonQLanguageServer::class.java) | ||
| .configureGson { | ||
| // TODO: maybe need adapter for initialize: | ||
| // https://github.com/aws/amazon-q-eclipse/blob/b9d5bdcd5c38e1dd8ad371d37ab93a16113d7d4b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/QLspTypeAdapterFactory.java | ||
|
|
||
| // otherwise Gson treats all numbers as double which causes deser issues | ||
| it.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) | ||
| }.traceMessages( | ||
| PrintWriter( | ||
| object : StringWriter() { | ||
| private val traceLogger = LOG.atLevel(if (isDeveloperMode()) Level.INFO else Level.DEBUG) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ohh this is a good idea, maybe we can convert this to a util? |
||
|
|
||
| override fun flush() { | ||
| traceLogger.log(buffer.toString()) | ||
| buffer.setLength(0) | ||
| } | ||
| } | ||
| ) | ||
| ) | ||
| .setInput(inputWrapper.inputStream) | ||
| .setOutput(handler.process.outputStream) | ||
| .create() | ||
|
|
||
| launcher.startListening() | ||
|
|
||
| cs.launch { | ||
| val initializeResult = languageServer.initialize( | ||
| InitializeParams().apply { | ||
| // does this work on windows | ||
| processId = ProcessHandle.current().pid().toInt() | ||
| // capabilities | ||
| // client info | ||
| // trace? | ||
| // workspace folders? | ||
| // anything else we need? | ||
| } | ||
| // probably need a timeout | ||
| ).await() | ||
|
|
||
| // then if this succeeds then we can allow the client to send requests | ||
| languageServer.initialized(InitializedParams()) | ||
| if (initializeResult == null) { | ||
| LOG.warn("LSP initialization failed") | ||
| handler.destroyProcess() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| override fun dispose() { | ||
| languageServer.apply { | ||
| shutdown().thenRun { exit() } | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| private val LOG = getLogger<AmazonQLspService>() | ||
| fun getInstance(project: Project): AmazonQLspService { | ||
| return project.service() | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials | ||
|
|
||
| data class ConnectionMetadata( | ||
| val sso: SsoProfileData | ||
| ) | ||
|
|
||
| data class SsoProfileData( | ||
| val startUrl: String | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials | ||
|
|
||
| data class UpdateCredentialsPayload( | ||
| val data: String, | ||
| val encrypted: String, | ||
| ) | ||
|
|
||
| data class UpdateCredentialsPayloadData( | ||
|
Check warning on line 8 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/credentials/UpdateCredentialsPayload.kt
|
||
|
||
| val data: BearerCredentials | ||
| ) | ||
|
|
||
| data class BearerCredentials( | ||
| val token: String | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: not needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're going to keep the prints until they are implemented properly