Skip to content

Commit ea36f11

Browse files
authored
Merge branch 'feature/amazonqLSP-auth' into feature/amazonqLSP-auth
2 parents 98d0f08 + f672c51 commit ea36f11

File tree

16 files changed

+245
-101
lines changed

16 files changed

+245
-101
lines changed

packages/amazonq/src/lsp/activation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ export async function activate(ctx: vscode.ExtensionContext) {
2020
await AuthUtil.instance.restore()
2121
} catch (err) {
2222
const e = err as ToolkitError
23-
void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`)
23+
void messages.showViewLogsMessage(`Failed to launch Amazon Q language server: ${e.message}`)
2424
}
2525
}

packages/amazonq/src/lsp/chat/webviewProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider {
5050
) {
5151
this.webview = webviewView.webview
5252

53-
const lspDir = Uri.parse(LanguageServerResolver.defaultDir)
53+
const lspDir = Uri.parse(LanguageServerResolver.defaultDir())
5454
webviewView.webview.options = {
5555
enableScripts: true,
5656
enableCommandUris: true,

packages/amazonq/src/lsp/client.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ import {
3030
Experiments,
3131
Commands,
3232
openUrl,
33+
validateNodeExe,
3334
getLogger,
3435
} from 'aws-core-vscode/shared'
3536
import { activate } from './chat/activation'
3637
import { AmazonQResourcePaths } from './lspInstaller'
3738
import { auth2 } from 'aws-core-vscode/auth'
3839

3940
const localize = nls.loadMessageBundle()
41+
const logger = getLogger('amazonqLsp.lspClient')
4042

4143
export const clientId = 'amazonq'
4244
export const clientName = oidcClientName()
@@ -50,22 +52,25 @@ export async function startLanguageServer(
5052

5153
const serverModule = resourcePaths.lsp
5254

55+
const argv = [
56+
'--nolazy',
57+
'--preserve-symlinks',
58+
'--stdio',
59+
'--pre-init-encryption',
60+
'--set-credentials-encryption-key',
61+
]
5362
const serverOptions = createServerOptions({
5463
encryptionKey,
5564
executable: resourcePaths.node,
5665
serverModule,
57-
execArgv: [
58-
'--nolazy',
59-
'--preserve-symlinks',
60-
'--stdio',
61-
'--pre-init-encryption',
62-
'--set-credentials-encryption-key',
63-
],
66+
execArgv: argv,
6467
})
6568

6669
const documentSelector = [{ scheme: 'file', language: '*' }]
6770
const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`)
6871

72+
await validateNodeExe(resourcePaths.node, resourcePaths.lsp, argv, logger)
73+
6974
// Options to control the language client
7075
const clientOptions: LanguageClientOptions = {
7176
// Register the server for json documents

packages/amazonq/test/unit/amazonq/lsp/lspClient.test.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
*/
55
import * as sinon from 'sinon'
66
import assert from 'assert'
7-
import { globals } from 'aws-core-vscode/shared'
8-
import { LspClient } from 'aws-core-vscode/amazonq'
7+
import { globals, getNodeExecutableName } from 'aws-core-vscode/shared'
8+
import { LspClient, lspClient as lspClientModule } from 'aws-core-vscode/amazonq'
99

1010
describe('Amazon Q LSP client', function () {
1111
let lspClient: LspClient
@@ -50,6 +50,22 @@ describe('Amazon Q LSP client', function () {
5050
assert.ok(!encryptedSample.includes('hello'))
5151
})
5252

53+
it('validates node executable + lsp bundle', async () => {
54+
await assert.rejects(async () => {
55+
await lspClientModule.activate(globals.context, {
56+
// Mimic the `LspResolution<ResourcePaths>` type.
57+
node: 'node.bogus.exe',
58+
lsp: 'fake/lsp.js',
59+
})
60+
}, /.*failed to run basic .*node.*exitcode.*node\.bogus\.exe.*/)
61+
await assert.rejects(async () => {
62+
await lspClientModule.activate(globals.context, {
63+
node: getNodeExecutableName(),
64+
lsp: 'fake/lsp.js',
65+
})
66+
}, /.*failed to run .*exitcode.*node.*lsp\.js/)
67+
})
68+
5369
afterEach(() => {
5470
sinon.restore()
5571
})

packages/core/src/amazonq/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export {
1717
} from './onboardingPage/walkthrough'
1818
export { LspController } from './lsp/lspController'
1919
export { LspClient } from './lsp/lspClient'
20+
export * as lspClient from './lsp/lspClient'
2021
export { api } from './extApi'
2122
export { AmazonQChatViewProvider } from './webview/webView'
2223
export { init as cwChatAppInit } from '../codewhispererChat/app'

packages/core/src/amazonq/lsp/lspClient.ts

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import * as jose from 'jose'
1515

1616
import { Disposable, ExtensionContext } from 'vscode'
1717

18-
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'
18+
import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient'
1919
import {
2020
BuildIndexRequestPayload,
2121
BuildIndexRequestType,
@@ -39,12 +39,13 @@ import { fs } from '../../shared/fs/fs'
3939
import { getLogger } from '../../shared/logger/logger'
4040
import globals from '../../shared/extensionGlobals'
4141
import { ResourcePaths } from '../../shared/lsp/types'
42-
import { createServerOptions } from '../../shared/lsp/utils/platform'
42+
import { createServerOptions, validateNodeExe } from '../../shared/lsp/utils/platform'
4343
import { waitUntil } from '../../shared/utilities/timeoutUtils'
4444

4545
const localize = nls.loadMessageBundle()
4646

4747
const key = crypto.randomBytes(32)
48+
const logger = getLogger('amazonqWorkspaceLsp')
4849

4950
/**
5051
* LspClient manages the API call between VS Code extension and LSP server
@@ -80,7 +81,7 @@ export class LspClient {
8081
const resp = await this.client?.sendRequest(BuildIndexRequestType, encryptedRequest)
8182
return resp
8283
} catch (e) {
83-
getLogger().error(`LspClient: buildIndex error: ${e}`)
84+
logger.error(`buildIndex error: ${e}`)
8485
return undefined
8586
}
8687
}
@@ -95,7 +96,7 @@ export class LspClient {
9596
const resp = await this.client?.sendRequest(QueryVectorIndexRequestType, encryptedRequest)
9697
return resp
9798
} catch (e) {
98-
getLogger().error(`LspClient: queryVectorIndex error: ${e}`)
99+
logger.error(`queryVectorIndex error: ${e}`)
99100
return []
100101
}
101102
}
@@ -111,7 +112,7 @@ export class LspClient {
111112
const resp: any = await this.client?.sendRequest(QueryInlineProjectContextRequestType, encrypted)
112113
return resp
113114
} catch (e) {
114-
getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`)
115+
logger.error(`queryInlineProjectContext error: ${e}`)
115116
throw e
116117
}
117118
}
@@ -132,7 +133,7 @@ export class LspClient {
132133
const resp = await this.client?.sendRequest(UpdateIndexV2RequestType, encryptedRequest)
133134
return resp
134135
} catch (e) {
135-
getLogger().error(`LspClient: updateIndex error: ${e}`)
136+
logger.error(`updateIndex error: ${e}`)
136137
return undefined
137138
}
138139
}
@@ -144,7 +145,7 @@ export class LspClient {
144145
const resp: any = await this.client?.sendRequest(QueryRepomapIndexRequestType, await this.encrypt(request))
145146
return resp
146147
} catch (e) {
147-
getLogger().error(`LspClient: QueryRepomapIndex error: ${e}`)
148+
logger.error(`QueryRepomapIndex error: ${e}`)
148149
throw e
149150
}
150151
}
@@ -157,7 +158,7 @@ export class LspClient {
157158
)
158159
return resp
159160
} catch (e) {
160-
getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`)
161+
logger.error(`queryInlineProjectContext error: ${e}`)
161162
throw e
162163
}
163164
}
@@ -174,7 +175,7 @@ export class LspClient {
174175
)
175176
return resp
176177
} catch (e) {
177-
getLogger().error(`LspClient: getContextCommandItems error: ${e}`)
178+
logger.error(`getContextCommandItems error: ${e}`)
178179
throw e
179180
}
180181
}
@@ -190,7 +191,7 @@ export class LspClient {
190191
)
191192
return resp || []
192193
} catch (e) {
193-
getLogger().error(`LspClient: getContextCommandPrompt error: ${e}`)
194+
logger.error(`getContextCommandPrompt error: ${e}`)
194195
throw e
195196
}
196197
}
@@ -204,7 +205,7 @@ export class LspClient {
204205
)
205206
return resp
206207
} catch (e) {
207-
getLogger().error(`LspClient: getIndexSequenceNumber error: ${e}`)
208+
logger.error(`getIndexSequenceNumber error: ${e}`)
208209
throw e
209210
}
210211
}
@@ -222,20 +223,20 @@ export class LspClient {
222223
)
223224
}
224225
}
226+
225227
/**
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.
228+
* Activates the language server (assumes the LSP server has already been downloaded):
229+
* 1. start LSP server running over IPC protocol.
230+
* 2. create a output channel named Amazon Q Language Server.
229231
*/
230232
export async function activate(extensionContext: ExtensionContext, resourcePaths: ResourcePaths) {
231-
LspClient.instance
233+
LspClient.instance // Tickle the singleton... :/
232234
const toDispose = extensionContext.subscriptions
233235

234236
let rangeFormatting: Disposable | undefined
235237
// The debug options for the server
236238
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
237239
const debugOptions = { execArgv: ['--nolazy', '--preserve-symlinks', '--stdio'] }
238-
239240
const workerThreads = CodeWhispererSettings.instance.getIndexWorkerThreads()
240241
const gpu = CodeWhispererSettings.instance.isLocalIndexGPUEnabled()
241242

@@ -252,22 +253,18 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
252253

253254
const serverModule = resourcePaths.lsp
254255

255-
// If the extension is launch in debug mode the debug server options are use
256-
// Otherwise the run options are used
257-
let serverOptions: ServerOptions = {
258-
run: { module: serverModule, transport: TransportKind.ipc },
259-
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
260-
}
261-
262-
serverOptions = createServerOptions({
256+
const serverOptions = createServerOptions({
263257
encryptionKey: key,
264258
executable: resourcePaths.node,
265259
serverModule,
260+
// TODO(jmkeyes): we always use the debug options...?
266261
execArgv: debugOptions.execArgv,
267262
})
268263

269264
const documentSelector = [{ scheme: 'file', language: '*' }]
270265

266+
await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv, logger)
267+
271268
// Options to control the language client
272269
const clientOptions: LanguageClientOptions = {
273270
// Register the server for json documents
@@ -359,10 +356,15 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
359356
})
360357
)
361358

362-
return LspClient.instance.client.onReady().then(() => {
363-
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
364-
toDispose.push(disposableFunc)
365-
})
359+
return LspClient.instance.client.onReady().then(
360+
() => {
361+
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
362+
toDispose.push(disposableFunc)
363+
},
364+
(reason) => {
365+
logger.error('client.onReady() failed: %O', reason)
366+
}
367+
)
366368
}
367369

368370
export async function deactivate(): Promise<any> {

packages/core/src/amazonq/lsp/lspController.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ export class LspController {
9898
}
9999

100100
async buildIndex(buildIndexConfig: BuildIndexConfig) {
101-
this.logger.info(`LspController: Starting to build index of project`)
101+
this.logger.info(`Starting to build index of project`)
102102
const start = performance.now()
103103
const projPaths = (vscode.workspace.workspaceFolders ?? []).map((folder) => folder.uri.fsPath)
104104
if (projPaths.length === 0) {
105-
this.logger.info(`LspController: Skipping building index. No projects found in workspace`)
105+
this.logger.info(`Skipping building index. No projects found in workspace`)
106106
return
107107
}
108108
projPaths.sort()
@@ -119,12 +119,12 @@ export class LspController {
119119
(accumulator, currentFile) => accumulator + currentFile.fileSizeBytes,
120120
0
121121
)
122-
this.logger.info(`LspController: Found ${files.length} files in current project ${projPaths}`)
122+
this.logger.info(`Found ${files.length} files in current project ${projPaths}`)
123123
const config = buildIndexConfig.isVectorIndexEnabled ? 'all' : 'default'
124124
const r = files.map((f) => f.fileUri.fsPath)
125125
const resp = await LspClient.instance.buildIndex(r, projRoot, config)
126126
if (resp) {
127-
this.logger.debug(`LspController: Finish building index of project`)
127+
this.logger.debug(`Finish building index of project`)
128128
const usage = await LspClient.instance.getLspServerUsage()
129129
telemetry.amazonq_indexWorkspace.emit({
130130
duration: performance.now() - start,
@@ -137,7 +137,7 @@ export class LspController {
137137
credentialStartUrl: buildIndexConfig.startUrl,
138138
})
139139
} else {
140-
this.logger.error(`LspController: Failed to build index of project`)
140+
this.logger.error(`Failed to build index of project`)
141141
telemetry.amazonq_indexWorkspace.emit({
142142
duration: performance.now() - start,
143143
result: 'Failed',
@@ -149,7 +149,7 @@ export class LspController {
149149
}
150150
} catch (error) {
151151
// TODO: use telemetry.run()
152-
this.logger.error(`LspController: Failed to build index of project`)
152+
this.logger.error(`Failed to build index of project`)
153153
telemetry.amazonq_indexWorkspace.emit({
154154
duration: performance.now() - start,
155155
result: 'Failed',
@@ -166,7 +166,7 @@ export class LspController {
166166

167167
async trySetupLsp(context: vscode.ExtensionContext, buildIndexConfig: BuildIndexConfig) {
168168
if (isCloud9() || isWeb() || isAmazonInternalOs()) {
169-
this.logger.warn('LspController: Skipping LSP setup. LSP is not compatible with the current environment. ')
169+
this.logger.warn('Skipping LSP setup. LSP is not compatible with the current environment. ')
170170
// do not do anything if in Cloud9 or Web mode or in AL2 (AL2 does not support node v18+)
171171
return
172172
}
@@ -181,7 +181,7 @@ export class LspController {
181181
const usage = await LspClient.instance.getLspServerUsage()
182182
if (usage) {
183183
this.logger.info(
184-
`LspController: LSP server CPU ${usage.cpuUsage}%, LSP server Memory ${
184+
`LSP server CPU ${usage.cpuUsage}%, LSP server Memory ${
185185
usage.memoryUsage / (1024 * 1024)
186186
}MB `
187187
)
@@ -190,7 +190,7 @@ export class LspController {
190190
30 * 60 * 1000
191191
)
192192
} catch (e) {
193-
this.logger.error(`LspController: LSP failed to activate ${e}`)
193+
this.logger.error(`LSP failed to activate ${e}`)
194194
}
195195
})
196196
}
@@ -207,7 +207,7 @@ export class LspController {
207207
return
208208
}
209209
this._contextCommandSymbolsUpdated = true
210-
getLogger().debug(`LspController: Start adding symbols to context picker menu`)
210+
getLogger().debug(`Start adding symbols to context picker menu`)
211211
try {
212212
const indexSeqNum = await LspClient.instance.getIndexSequenceNumber()
213213
await LspClient.instance.updateIndex([], 'context_command_symbol_update')
@@ -223,15 +223,15 @@ export class LspController {
223223
{ interval: 1000, timeout: 60_000, truthy: true }
224224
)
225225
} catch (err) {
226-
getLogger().error(`LspController: Failed to find symbols`)
226+
this.logger.error(`Failed to find symbols`)
227227
}
228228
}
229229

230230
private async setupLsp(context: vscode.ExtensionContext) {
231231
await lspSetupStage('all', async () => {
232232
const installResult = await new WorkspaceLspInstaller().resolve()
233233
await lspSetupStage('launch', async () => activateLsp(context, installResult.resourcePaths))
234-
this.logger.info('LspController: LSP activated')
234+
this.logger.info('LSP activated')
235235
})
236236
}
237237
}

0 commit comments

Comments
 (0)