Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/amazonq/src/lsp/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import vscode from 'vscode'
import { startLanguageServer } from './client'
import { AmazonQLspInstaller } from './lspInstaller'
import { lspSetupStage, ToolkitError } from 'aws-core-vscode/shared'
import { lspSetupStage, ToolkitError, messages } from 'aws-core-vscode/shared'

export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
try {
Expand All @@ -16,6 +16,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
})
} catch (err) {
const e = err as ToolkitError
void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`)
void messages.showViewLogsMessage(`Failed to launch Amazon Q language server: ${e.message}`)
}
}
2 changes: 1 addition & 1 deletion packages/amazonq/src/lsp/chat/webviewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider {
) {
this.webview = webviewView.webview

const lspDir = Uri.parse(LanguageServerResolver.defaultDir)
const lspDir = Uri.parse(LanguageServerResolver.defaultDir())
webviewView.webview.options = {
enableScripts: true,
enableCommandUris: true,
Expand Down
29 changes: 21 additions & 8 deletions packages/amazonq/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ import { InlineCompletionManager } from '../app/inline/completion'
import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth'
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
import { ConnectionMetadata } from '@aws/language-server-runtimes/protocol'
import { Settings, oidcClientName, createServerOptions, globals, Experiments, Commands } from 'aws-core-vscode/shared'
import {
Settings,
oidcClientName,
createServerOptions,
globals,
Experiments,
Commands,
validateNodeExe,
getLogger,
} from 'aws-core-vscode/shared'
import { activate } from './chat/activation'
import { AmazonQResourcePaths } from './lspInstaller'

const localize = nls.loadMessageBundle()
const logger = getLogger('amazonqLsp.lspClient')

export async function startLanguageServer(
extensionContext: vscode.ExtensionContext,
Expand All @@ -25,24 +35,27 @@ export async function startLanguageServer(

const serverModule = resourcePaths.lsp

const argv = [
'--nolazy',
'--preserve-symlinks',
'--stdio',
'--pre-init-encryption',
'--set-credentials-encryption-key',
]
const serverOptions = createServerOptions({
encryptionKey,
executable: resourcePaths.node,
serverModule,
execArgv: [
'--nolazy',
'--preserve-symlinks',
'--stdio',
'--pre-init-encryption',
'--set-credentials-encryption-key',
],
execArgv: argv,
})

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

const clientId = 'amazonq'
const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`)

await validateNodeExe(resourcePaths.node, resourcePaths.lsp, argv, logger)

// Options to control the language client
const clientOptions: LanguageClientOptions = {
// Register the server for json documents
Expand Down
20 changes: 18 additions & 2 deletions packages/amazonq/test/unit/amazonq/lsp/lspClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
*/
import * as sinon from 'sinon'
import assert from 'assert'
import { globals } from 'aws-core-vscode/shared'
import { LspClient } from 'aws-core-vscode/amazonq'
import { globals, getNodeExecutableName } from 'aws-core-vscode/shared'
import { LspClient, lspClient as lspClientModule } from 'aws-core-vscode/amazonq'

describe('Amazon Q LSP client', function () {
let lspClient: LspClient
Expand Down Expand Up @@ -50,6 +50,22 @@ describe('Amazon Q LSP client', function () {
assert.ok(!encryptedSample.includes('hello'))
})

it('validates node executable + lsp bundle', async () => {
await assert.rejects(async () => {
await lspClientModule.activate(globals.context, {
// Mimic the `LspResolution<ResourcePaths>` type.
node: 'node.bogus.exe',
lsp: 'fake/lsp.js',
})
}, /.*failed to run basic .*node.*exitcode.*node\.bogus\.exe.*/)
await assert.rejects(async () => {
await lspClientModule.activate(globals.context, {
node: getNodeExecutableName(),
lsp: 'fake/lsp.js',
})
}, /.*failed to run .*exitcode.*node.*lsp\.js/)
})

afterEach(() => {
sinon.restore()
})
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/amazonq/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
} from './onboardingPage/walkthrough'
export { LspController } from './lsp/lspController'
export { LspClient } from './lsp/lspClient'
export * as lspClient from './lsp/lspClient'
export { api } from './extApi'
export { AmazonQChatViewProvider } from './webview/webView'
export { init as cwChatAppInit } from '../codewhispererChat/app'
Expand Down
58 changes: 30 additions & 28 deletions packages/core/src/amazonq/lsp/lspClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as jose from 'jose'

import { Disposable, ExtensionContext } from 'vscode'

import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'
import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient'
import {
BuildIndexRequestPayload,
BuildIndexRequestType,
Expand All @@ -39,12 +39,13 @@ import { fs } from '../../shared/fs/fs'
import { getLogger } from '../../shared/logger/logger'
import globals from '../../shared/extensionGlobals'
import { ResourcePaths } from '../../shared/lsp/types'
import { createServerOptions } from '../../shared/lsp/utils/platform'
import { createServerOptions, validateNodeExe } from '../../shared/lsp/utils/platform'
import { waitUntil } from '../../shared/utilities/timeoutUtils'

const localize = nls.loadMessageBundle()

const key = crypto.randomBytes(32)
const logger = getLogger('amazonqWorkspaceLsp')

/**
* LspClient manages the API call between VS Code extension and LSP server
Expand Down Expand Up @@ -80,7 +81,7 @@ export class LspClient {
const resp = await this.client?.sendRequest(BuildIndexRequestType, encryptedRequest)
return resp
} catch (e) {
getLogger().error(`LspClient: buildIndex error: ${e}`)
logger.error(`buildIndex error: ${e}`)
return undefined
}
}
Expand All @@ -95,7 +96,7 @@ export class LspClient {
const resp = await this.client?.sendRequest(QueryVectorIndexRequestType, encryptedRequest)
return resp
} catch (e) {
getLogger().error(`LspClient: queryVectorIndex error: ${e}`)
logger.error(`queryVectorIndex error: ${e}`)
return []
}
}
Expand All @@ -111,7 +112,7 @@ export class LspClient {
const resp: any = await this.client?.sendRequest(QueryInlineProjectContextRequestType, encrypted)
return resp
} catch (e) {
getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`)
logger.error(`queryInlineProjectContext error: ${e}`)
throw e
}
}
Expand All @@ -132,7 +133,7 @@ export class LspClient {
const resp = await this.client?.sendRequest(UpdateIndexV2RequestType, encryptedRequest)
return resp
} catch (e) {
getLogger().error(`LspClient: updateIndex error: ${e}`)
logger.error(`updateIndex error: ${e}`)
return undefined
}
}
Expand All @@ -144,7 +145,7 @@ export class LspClient {
const resp: any = await this.client?.sendRequest(QueryRepomapIndexRequestType, await this.encrypt(request))
return resp
} catch (e) {
getLogger().error(`LspClient: QueryRepomapIndex error: ${e}`)
logger.error(`QueryRepomapIndex error: ${e}`)
throw e
}
}
Expand All @@ -157,7 +158,7 @@ export class LspClient {
)
return resp
} catch (e) {
getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`)
logger.error(`queryInlineProjectContext error: ${e}`)
throw e
}
}
Expand All @@ -174,7 +175,7 @@ export class LspClient {
)
return resp
} catch (e) {
getLogger().error(`LspClient: getContextCommandItems error: ${e}`)
logger.error(`getContextCommandItems error: ${e}`)
throw e
}
}
Expand All @@ -190,7 +191,7 @@ export class LspClient {
)
return resp || []
} catch (e) {
getLogger().error(`LspClient: getContextCommandPrompt error: ${e}`)
logger.error(`getContextCommandPrompt error: ${e}`)
throw e
}
}
Expand All @@ -204,7 +205,7 @@ export class LspClient {
)
return resp
} catch (e) {
getLogger().error(`LspClient: getIndexSequenceNumber error: ${e}`)
logger.error(`getIndexSequenceNumber error: ${e}`)
throw e
}
}
Expand All @@ -222,20 +223,20 @@ export class LspClient {
)
}
}

/**
* Activates the language server, this will start LSP server running over IPC protocol.
* It will create a output channel named Amazon Q Language Server.
* This function assumes the LSP server has already been downloaded.
* Activates the language server (assumes the LSP server has already been downloaded):
* 1. start LSP server running over IPC protocol.
* 2. create a output channel named Amazon Q Language Server.
*/
export async function activate(extensionContext: ExtensionContext, resourcePaths: ResourcePaths) {
LspClient.instance
LspClient.instance // Tickle the singleton... :/
const toDispose = extensionContext.subscriptions

let rangeFormatting: Disposable | undefined
// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
const debugOptions = { execArgv: ['--nolazy', '--preserve-symlinks', '--stdio'] }

const workerThreads = CodeWhispererSettings.instance.getIndexWorkerThreads()
const gpu = CodeWhispererSettings.instance.isLocalIndexGPUEnabled()

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

const serverModule = resourcePaths.lsp

// If the extension is launch in debug mode the debug server options are use
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
}

serverOptions = createServerOptions({
const serverOptions = createServerOptions({
encryptionKey: key,
executable: resourcePaths.node,
serverModule,
// TODO(jmkeyes): we always use the debug options...?
execArgv: debugOptions.execArgv,
})

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

await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv, logger)

// Options to control the language client
const clientOptions: LanguageClientOptions = {
// Register the server for json documents
Expand Down Expand Up @@ -359,10 +356,15 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
})
)

return LspClient.instance.client.onReady().then(() => {
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
toDispose.push(disposableFunc)
})
return LspClient.instance.client.onReady().then(
() => {
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
toDispose.push(disposableFunc)
},
(reason) => {
logger.error('client.onReady() failed: %O', reason)
}
)
}

export async function deactivate(): Promise<any> {
Expand Down
Loading