@@ -41,10 +41,13 @@ import globals from '../../shared/extensionGlobals'
41
41
import { ResourcePaths } from '../../shared/lsp/types'
42
42
import { createServerOptions } from '../../shared/lsp/utils/platform'
43
43
import { waitUntil } from '../../shared/utilities/timeoutUtils'
44
+ import { ToolkitError } from '../../shared/errors'
45
+ import { ChildProcess } from '../../shared/utilities/processUtils'
44
46
45
47
const localize = nls . loadMessageBundle ( )
46
48
47
49
const key = crypto . randomBytes ( 32 )
50
+ const logger = getLogger ( 'amazonqLsp.lspClient' )
48
51
49
52
/**
50
53
* LspClient manages the API call between VS Code extension and LSP server
@@ -80,7 +83,7 @@ export class LspClient {
80
83
const resp = await this . client ?. sendRequest ( BuildIndexRequestType , encryptedRequest )
81
84
return resp
82
85
} catch ( e ) {
83
- getLogger ( ) . error ( `LspClient: buildIndex error: ${ e } ` )
86
+ logger . error ( `buildIndex error: ${ e } ` )
84
87
return undefined
85
88
}
86
89
}
@@ -95,7 +98,7 @@ export class LspClient {
95
98
const resp = await this . client ?. sendRequest ( QueryVectorIndexRequestType , encryptedRequest )
96
99
return resp
97
100
} catch ( e ) {
98
- getLogger ( ) . error ( `LspClient: queryVectorIndex error: ${ e } ` )
101
+ logger . error ( `queryVectorIndex error: ${ e } ` )
99
102
return [ ]
100
103
}
101
104
}
@@ -111,7 +114,7 @@ export class LspClient {
111
114
const resp : any = await this . client ?. sendRequest ( QueryInlineProjectContextRequestType , encrypted )
112
115
return resp
113
116
} catch ( e ) {
114
- getLogger ( ) . error ( `LspClient: queryInlineProjectContext error: ${ e } ` )
117
+ logger . error ( `queryInlineProjectContext error: ${ e } ` )
115
118
throw e
116
119
}
117
120
}
@@ -132,7 +135,7 @@ export class LspClient {
132
135
const resp = await this . client ?. sendRequest ( UpdateIndexV2RequestType , encryptedRequest )
133
136
return resp
134
137
} catch ( e ) {
135
- getLogger ( ) . error ( `LspClient: updateIndex error: ${ e } ` )
138
+ logger . error ( `updateIndex error: ${ e } ` )
136
139
return undefined
137
140
}
138
141
}
@@ -144,7 +147,7 @@ export class LspClient {
144
147
const resp : any = await this . client ?. sendRequest ( QueryRepomapIndexRequestType , await this . encrypt ( request ) )
145
148
return resp
146
149
} catch ( e ) {
147
- getLogger ( ) . error ( `LspClient: QueryRepomapIndex error: ${ e } ` )
150
+ logger . error ( `QueryRepomapIndex error: ${ e } ` )
148
151
throw e
149
152
}
150
153
}
@@ -157,7 +160,7 @@ export class LspClient {
157
160
)
158
161
return resp
159
162
} catch ( e ) {
160
- getLogger ( ) . error ( `LspClient: queryInlineProjectContext error: ${ e } ` )
163
+ logger . error ( `queryInlineProjectContext error: ${ e } ` )
161
164
throw e
162
165
}
163
166
}
@@ -174,7 +177,7 @@ export class LspClient {
174
177
)
175
178
return resp
176
179
} catch ( e ) {
177
- getLogger ( ) . error ( `LspClient: getContextCommandItems error: ${ e } ` )
180
+ logger . error ( `getContextCommandItems error: ${ e } ` )
178
181
throw e
179
182
}
180
183
}
@@ -190,7 +193,7 @@ export class LspClient {
190
193
)
191
194
return resp || [ ]
192
195
} catch ( e ) {
193
- getLogger ( ) . error ( `LspClient: getContextCommandPrompt error: ${ e } ` )
196
+ logger . error ( `getContextCommandPrompt error: ${ e } ` )
194
197
throw e
195
198
}
196
199
}
@@ -204,7 +207,7 @@ export class LspClient {
204
207
)
205
208
return resp
206
209
} catch ( e ) {
207
- getLogger ( ) . error ( `LspClient: getIndexSequenceNumber error: ${ e } ` )
210
+ logger . error ( `getIndexSequenceNumber error: ${ e } ` )
208
211
throw e
209
212
}
210
213
}
@@ -222,20 +225,65 @@ export class LspClient {
222
225
)
223
226
}
224
227
}
228
+
225
229
/**
226
- * Activates the language server, this will start LSP server running over IPC protocol.
227
- * It will create a output channel named Amazon Q Language Server.
228
- * This function assumes the LSP server has already been downloaded.
230
+ * Checks that we can actually run the `node` executable and execute code with it.
231
+ */
232
+ async function validateNodeExe ( nodePath : string , lsp : string , args : string [ ] ) {
233
+ // Check that we can start `node` by itself.
234
+ const proc = new ChildProcess ( nodePath , [ '-e' , 'console.log("ok " + process.version)' ] , { logging : 'no' } )
235
+ const r = await proc . run ( )
236
+ const ok = r . exitCode === 0 && r . stdout . includes ( 'ok' )
237
+ if ( ! ok ) {
238
+ const msg = `failed to run basic "node -e" test (exitcode=${ r . exitCode } ): ${ proc } `
239
+ logger . error ( msg )
240
+ throw new ToolkitError ( `amazonqLsp: ${ msg } ` )
241
+ }
242
+
243
+ // Check that we can start `node …/lsp.js --stdio …`.
244
+ const lspProc = new ChildProcess ( nodePath , [ lsp , ...args ] , { logging : 'no' } )
245
+ try {
246
+ // Start asynchronously (it never stops; we need to stop it below).
247
+ lspProc . run ( ) . catch ( ( e ) => logger . error ( 'failed to run: %s' , lspProc ) )
248
+
249
+ const ok2 =
250
+ ! lspProc . stopped &&
251
+ ( await waitUntil (
252
+ async ( ) => {
253
+ return lspProc . pid ( ) !== undefined
254
+ } ,
255
+ {
256
+ timeout : 5000 ,
257
+ interval : 100 ,
258
+ truthy : true ,
259
+ }
260
+ ) )
261
+ const selfExit = await waitUntil ( async ( ) => lspProc . stopped , {
262
+ timeout : 500 ,
263
+ interval : 100 ,
264
+ truthy : true ,
265
+ } )
266
+ if ( ! ok2 || selfExit ) {
267
+ throw new ToolkitError ( `amazonqLsp: failed to run (exitcode=${ lspProc . exitCode ( ) } ): ${ lspProc } ` )
268
+ }
269
+ } finally {
270
+ lspProc . stop ( true )
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Activates the language server (assumes the LSP server has already been downloaded):
276
+ * 1. start LSP server running over IPC protocol.
277
+ * 2. create a output channel named Amazon Q Language Server.
229
278
*/
230
279
export async function activate ( extensionContext : ExtensionContext , resourcePaths : ResourcePaths ) {
231
- LspClient . instance
280
+ LspClient . instance // Tickle the singleton... :/
232
281
const toDispose = extensionContext . subscriptions
233
282
234
283
let rangeFormatting : Disposable | undefined
235
284
// The debug options for the server
236
285
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
237
286
const debugOptions = { execArgv : [ '--nolazy' , '--preserve-symlinks' , '--stdio' ] }
238
-
239
287
const workerThreads = CodeWhispererSettings . instance . getIndexWorkerThreads ( )
240
288
const gpu = CodeWhispererSettings . instance . isLocalIndexGPUEnabled ( )
241
289
@@ -259,6 +307,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
259
307
debug : { module : serverModule , transport : TransportKind . ipc , options : debugOptions } ,
260
308
}
261
309
310
+ // TODO(jmkeyes): this overwrites the above...?
262
311
serverOptions = createServerOptions ( {
263
312
encryptionKey : key ,
264
313
executable : resourcePaths . node ,
@@ -268,6 +317,8 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
268
317
269
318
const documentSelector = [ { scheme : 'file' , language : '*' } ]
270
319
320
+ await validateNodeExe ( resourcePaths . node , resourcePaths . lsp , debugOptions . execArgv )
321
+
271
322
// Options to control the language client
272
323
const clientOptions : LanguageClientOptions = {
273
324
// Register the server for json documents
@@ -359,10 +410,15 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
359
410
} )
360
411
)
361
412
362
- return LspClient . instance . client . onReady ( ) . then ( ( ) => {
363
- const disposableFunc = { dispose : ( ) => rangeFormatting ?. dispose ( ) as void }
364
- toDispose . push ( disposableFunc )
365
- } )
413
+ return LspClient . instance . client . onReady ( ) . then (
414
+ ( ) => {
415
+ const disposableFunc = { dispose : ( ) => rangeFormatting ?. dispose ( ) as void }
416
+ toDispose . push ( disposableFunc )
417
+ } ,
418
+ ( reason ) => {
419
+ logger . error ( 'client.onReady() failed: %O' , reason )
420
+ }
421
+ )
366
422
}
367
423
368
424
export async function deactivate ( ) : Promise < any > {
0 commit comments