@@ -41,10 +41,13 @@ import globals from '../../shared/extensionGlobals'
4141import { ResourcePaths } from '../../shared/lsp/types'
4242import { createServerOptions } from '../../shared/lsp/utils/platform'
4343import { waitUntil } from '../../shared/utilities/timeoutUtils'
44+ import { ToolkitError } from '../../shared/errors'
45+ import { ChildProcess } from '../../shared/utilities/processUtils'
4446
4547const localize = nls . loadMessageBundle ( )
4648
4749const key = crypto . randomBytes ( 32 )
50+ const logger = getLogger ( 'amazonqLsp.lspClient' )
4851
4952/**
5053 * LspClient manages the API call between VS Code extension and LSP server
@@ -80,7 +83,7 @@ export class LspClient {
8083 const resp = await this . client ?. sendRequest ( BuildIndexRequestType , encryptedRequest )
8184 return resp
8285 } catch ( e ) {
83- getLogger ( ) . error ( `LspClient: buildIndex error: ${ e } ` )
86+ logger . error ( `buildIndex error: ${ e } ` )
8487 return undefined
8588 }
8689 }
@@ -95,7 +98,7 @@ export class LspClient {
9598 const resp = await this . client ?. sendRequest ( QueryVectorIndexRequestType , encryptedRequest )
9699 return resp
97100 } catch ( e ) {
98- getLogger ( ) . error ( `LspClient: queryVectorIndex error: ${ e } ` )
101+ logger . error ( `queryVectorIndex error: ${ e } ` )
99102 return [ ]
100103 }
101104 }
@@ -111,7 +114,7 @@ export class LspClient {
111114 const resp : any = await this . client ?. sendRequest ( QueryInlineProjectContextRequestType , encrypted )
112115 return resp
113116 } catch ( e ) {
114- getLogger ( ) . error ( `LspClient: queryInlineProjectContext error: ${ e } ` )
117+ logger . error ( `queryInlineProjectContext error: ${ e } ` )
115118 throw e
116119 }
117120 }
@@ -132,7 +135,7 @@ export class LspClient {
132135 const resp = await this . client ?. sendRequest ( UpdateIndexV2RequestType , encryptedRequest )
133136 return resp
134137 } catch ( e ) {
135- getLogger ( ) . error ( `LspClient: updateIndex error: ${ e } ` )
138+ logger . error ( `updateIndex error: ${ e } ` )
136139 return undefined
137140 }
138141 }
@@ -144,7 +147,7 @@ export class LspClient {
144147 const resp : any = await this . client ?. sendRequest ( QueryRepomapIndexRequestType , await this . encrypt ( request ) )
145148 return resp
146149 } catch ( e ) {
147- getLogger ( ) . error ( `LspClient: QueryRepomapIndex error: ${ e } ` )
150+ logger . error ( `QueryRepomapIndex error: ${ e } ` )
148151 throw e
149152 }
150153 }
@@ -157,7 +160,7 @@ export class LspClient {
157160 )
158161 return resp
159162 } catch ( e ) {
160- getLogger ( ) . error ( `LspClient: queryInlineProjectContext error: ${ e } ` )
163+ logger . error ( `queryInlineProjectContext error: ${ e } ` )
161164 throw e
162165 }
163166 }
@@ -174,7 +177,7 @@ export class LspClient {
174177 )
175178 return resp
176179 } catch ( e ) {
177- getLogger ( ) . error ( `LspClient: getContextCommandItems error: ${ e } ` )
180+ logger . error ( `getContextCommandItems error: ${ e } ` )
178181 throw e
179182 }
180183 }
@@ -190,7 +193,7 @@ export class LspClient {
190193 )
191194 return resp || [ ]
192195 } catch ( e ) {
193- getLogger ( ) . error ( `LspClient: getContextCommandPrompt error: ${ e } ` )
196+ logger . error ( `getContextCommandPrompt error: ${ e } ` )
194197 throw e
195198 }
196199 }
@@ -204,7 +207,7 @@ export class LspClient {
204207 )
205208 return resp
206209 } catch ( e ) {
207- getLogger ( ) . error ( `LspClient: getIndexSequenceNumber error: ${ e } ` )
210+ logger . error ( `getIndexSequenceNumber error: ${ e } ` )
208211 throw e
209212 }
210213 }
@@ -222,20 +225,65 @@ export class LspClient {
222225 )
223226 }
224227}
228+
225229/**
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.
229278 */
230279export async function activate ( extensionContext : ExtensionContext , resourcePaths : ResourcePaths ) {
231- LspClient . instance
280+ LspClient . instance // Tickle the singleton... :/
232281 const toDispose = extensionContext . subscriptions
233282
234283 let rangeFormatting : Disposable | undefined
235284 // The debug options for the server
236285 // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
237286 const debugOptions = { execArgv : [ '--nolazy' , '--preserve-symlinks' , '--stdio' ] }
238-
239287 const workerThreads = CodeWhispererSettings . instance . getIndexWorkerThreads ( )
240288 const gpu = CodeWhispererSettings . instance . isLocalIndexGPUEnabled ( )
241289
@@ -259,6 +307,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
259307 debug : { module : serverModule , transport : TransportKind . ipc , options : debugOptions } ,
260308 }
261309
310+ // TODO(jmkeyes): this overwrites the above...?
262311 serverOptions = createServerOptions ( {
263312 encryptionKey : key ,
264313 executable : resourcePaths . node ,
@@ -268,6 +317,8 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
268317
269318 const documentSelector = [ { scheme : 'file' , language : '*' } ]
270319
320+ await validateNodeExe ( resourcePaths . node , resourcePaths . lsp , debugOptions . execArgv )
321+
271322 // Options to control the language client
272323 const clientOptions : LanguageClientOptions = {
273324 // Register the server for json documents
@@ -359,10 +410,15 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
359410 } )
360411 )
361412
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+ )
366422}
367423
368424export async function deactivate ( ) : Promise < any > {
0 commit comments