@@ -18,24 +18,36 @@ import com.intellij.openapi.project.Project
18
18
import com.intellij.openapi.util.Key
19
19
import com.intellij.util.io.await
20
20
import kotlinx.coroutines.CoroutineScope
21
+ import kotlinx.coroutines.TimeoutCancellationException
21
22
import 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
22
27
import org.eclipse.lsp4j.InitializeParams
23
28
import 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
24
33
import org.eclipse.lsp4j.jsonrpc.Launcher
25
34
import org.eclipse.lsp4j.launch.LSPLauncher
26
35
import org.slf4j.event.Level
27
36
import software.aws.toolkits.core.utils.getLogger
28
37
import software.aws.toolkits.core.utils.warn
29
38
import 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
30
41
import java.io.IOException
31
42
import java.io.OutputStreamWriter
32
43
import java.io.PipedInputStream
33
44
import java.io.PipedOutputStream
34
45
import java.io.PrintWriter
35
46
import java.io.StringWriter
47
+ import java.net.URI
36
48
import java.nio.charset.StandardCharsets
49
+ import java.time.Duration
37
50
import java.util.concurrent.Future
38
-
39
51
// https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java
40
52
// JB impl and redhat both use a wrapper to handle input buffering issue
41
53
internal class LSPProcessListener : ProcessListener {
@@ -70,7 +82,7 @@ internal class LSPProcessListener : ProcessListener {
70
82
}
71
83
72
84
@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 {
74
86
private val launcher: Launcher <AmazonQLanguageServer >
75
87
76
88
private val languageServer: AmazonQLanguageServer
@@ -80,6 +92,57 @@ class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disp
80
92
private val launcherFuture: Future <Void >
81
93
private val launcherHandler: KillableProcessHandler
82
94
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
+
83
146
init {
84
147
val cmd = GeneralCommandLine (" amazon-q-lsp" )
85
148
@@ -116,18 +179,14 @@ class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disp
116
179
launcherFuture = launcher.startListening()
117
180
118
181
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()
128
185
}
129
- // probably need a timeout
130
- ).await()
186
+ } catch (e: TimeoutCancellationException ) {
187
+ LOG .warn { " LSP initialization timed out" }
188
+ null
189
+ }
131
190
132
191
// then if this succeeds then we can allow the client to send requests
133
192
if (initializeResult == null ) {
0 commit comments