@@ -18,24 +18,36 @@ import com.intellij.openapi.project.Project
1818import com.intellij.openapi.util.Key
1919import com.intellij.util.io.await
2020import kotlinx.coroutines.CoroutineScope
21+ import kotlinx.coroutines.TimeoutCancellationException
2122import kotlinx.coroutines.launch
23+ import kotlinx.coroutines.time.withTimeout
24+ import org.eclipse.lsp4j.ClientCapabilities
25+ import org.eclipse.lsp4j.ClientInfo
26+ import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities
2227import org.eclipse.lsp4j.InitializeParams
2328import org.eclipse.lsp4j.InitializedParams
29+ import org.eclipse.lsp4j.SynchronizationCapabilities
30+ import org.eclipse.lsp4j.TextDocumentClientCapabilities
31+ import org.eclipse.lsp4j.WorkspaceClientCapabilities
32+ import org.eclipse.lsp4j.WorkspaceFolder
2433import org.eclipse.lsp4j.jsonrpc.Launcher
2534import org.eclipse.lsp4j.launch.LSPLauncher
2635import org.slf4j.event.Level
2736import software.aws.toolkits.core.utils.getLogger
2837import software.aws.toolkits.core.utils.warn
2938import software.aws.toolkits.jetbrains.isDeveloperMode
39+ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata
40+ import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
3041import java.io.IOException
3142import java.io.OutputStreamWriter
3243import java.io.PipedInputStream
3344import java.io.PipedOutputStream
3445import java.io.PrintWriter
3546import java.io.StringWriter
47+ import java.net.URI
3648import java.nio.charset.StandardCharsets
49+ import java.time.Duration
3750import java.util.concurrent.Future
38-
3951// https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java
4052// JB impl and redhat both use a wrapper to handle input buffering issue
4153internal class LSPProcessListener : ProcessListener {
@@ -70,7 +82,7 @@ internal class LSPProcessListener : ProcessListener {
7082}
7183
7284@Service(Service .Level .PROJECT )
73- class AmazonQLspService (project : Project , private val cs : CoroutineScope ) : Disposable {
85+ class AmazonQLspService (private val project : Project , private val cs : CoroutineScope ) : Disposable {
7486 private val launcher: Launcher <AmazonQLanguageServer >
7587
7688 private val languageServer: AmazonQLanguageServer
@@ -80,6 +92,57 @@ class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disp
8092 private val launcherFuture: Future <Void >
8193 private val launcherHandler: KillableProcessHandler
8294
95+ private fun createClientCapabilities (): ClientCapabilities =
96+ ClientCapabilities ().apply {
97+ textDocument = TextDocumentClientCapabilities ().apply {
98+ // For didSaveTextDocument, other textDocument/ messages always mandatory
99+ synchronization = SynchronizationCapabilities ().apply {
100+ didSave = true
101+ }
102+ }
103+
104+ workspace = WorkspaceClientCapabilities ().apply {
105+ applyEdit = false
106+
107+ // For workspace folder changes
108+ workspaceFolders = true
109+
110+ // For file operations (create, delete)
111+ fileOperations = FileOperationsWorkspaceCapabilities ().apply {
112+ didCreate = true
113+ didDelete = true
114+ }
115+ }
116+ }
117+
118+ // needs case handling when project's base path is null: default projects/unit tests
119+ private fun createWorkspaceFolders (): List <WorkspaceFolder > =
120+ project.basePath?.let { basePath ->
121+ listOf (
122+ WorkspaceFolder (
123+ URI (" file://$basePath " ).toString(),
124+ project.name
125+ )
126+ )
127+ }.orEmpty() // no folders to report or workspace not folder based
128+
129+ private fun createClientInfo (): ClientInfo {
130+ val metadata = ClientMetadata .getDefault()
131+ return ClientInfo ().apply {
132+ name = metadata.awsProduct.toString()
133+ version = metadata.awsVersion
134+ }
135+ }
136+
137+ private fun createInitializeParams (): InitializeParams =
138+ InitializeParams ().apply {
139+ processId = ProcessHandle .current().pid().toInt()
140+ capabilities = createClientCapabilities()
141+ clientInfo = createClientInfo()
142+ workspaceFolders = createWorkspaceFolders()
143+ initializationOptions = createExtendedClientMetadata()
144+ }
145+
83146 init {
84147 val cmd = GeneralCommandLine (" amazon-q-lsp" )
85148
@@ -116,18 +179,14 @@ class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disp
116179 launcherFuture = launcher.startListening()
117180
118181 cs.launch {
119- val initializeResult = languageServer.initialize(
120- InitializeParams ().apply {
121- // does this work on windows
122- processId = ProcessHandle .current().pid().toInt()
123- // capabilities
124- // client info
125- // trace?
126- // workspace folders?
127- // anything else we need?
182+ val initializeResult = try {
183+ withTimeout(Duration .ofSeconds(30 )) {
184+ languageServer.initialize(createInitializeParams()).await()
128185 }
129- // probably need a timeout
130- ).await()
186+ } catch (e: TimeoutCancellationException ) {
187+ LOG .warn { " LSP initialization timed out" }
188+ null
189+ }
131190
132191 // then if this succeeds then we can allow the client to send requests
133192 if (initializeResult == null ) {
0 commit comments