Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
@@ -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`)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: not needed?

Copy link
Contributor Author

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

}

override fun publishDiagnostics(diagnostics: PublishDiagnosticsParams) {
println(diagnostics)
}

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}")
}

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

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Function or property has platform type

Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable.

Check notice

Code 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>

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

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Function "updateTokenCredentials" is never used
}
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we be using gson?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

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

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused import directive

Unused import directive
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

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Constructor parameter is never used as a property

Constructor parameter is never used as a property

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

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Class "AmazonQLspService" is never used

Check warning

Code scanning / QDJVMC

Unused symbol Warning

Class "AmazonQLspService" is never used

Check warning

Code scanning / QDJVMC

Constructor parameter is never used as a property Warning

Constructor parameter is never used as a property
Copy link
Contributor

Choose a reason for hiding this comment

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

How would this be triggered?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Class "UpdateCredentialsPayloadData" is never used

Check warning

Code scanning / QDJVMC

Unused symbol Warning

Class "UpdateCredentialsPayloadData" is never used
val data: BearerCredentials
)

data class BearerCredentials(
val token: String
)
Loading