diff --git a/packages/amazonq/src/lsp/activation.ts b/packages/amazonq/src/lsp/activation.ts index 2a68a1f2511..bdf4a59ab1a 100644 --- a/packages/amazonq/src/lsp/activation.ts +++ b/packages/amazonq/src/lsp/activation.ts @@ -7,16 +7,11 @@ import vscode from 'vscode' import { startLanguageServer } from './client' import { AmazonQLSPResolver } from './lspInstaller' import { ToolkitError } from 'aws-core-vscode/shared' -import path from 'path' export async function activate(ctx: vscode.ExtensionContext): Promise { try { const installResult = await new AmazonQLSPResolver().resolve() - const serverLocation = - installResult.location === 'override' - ? installResult.assetDirectory - : path.join(installResult.assetDirectory, 'servers/aws-lsp-codewhisperer.js') - await startLanguageServer(ctx, serverLocation) + await startLanguageServer(ctx, installResult.resourcePaths) } catch (err) { const e = err as ToolkitError void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index cb12e68d900..da09f54bb06 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -12,10 +12,11 @@ import { registerInlineCompletion } from '../inline/completion' import { AmazonQLspAuth, notificationTypes, writeEncryptionInit } from './auth' import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { ConnectionMetadata } from '@aws/language-server-runtimes/protocol' +import { ResourcePaths } from 'aws-core-vscode/shared' const localize = nls.loadMessageBundle() -export function startLanguageServer(extensionContext: vscode.ExtensionContext, serverPath: string) { +export function startLanguageServer(extensionContext: vscode.ExtensionContext, resourcePaths: ResourcePaths) { const toDispose = extensionContext.subscriptions // The debug options for the server @@ -30,6 +31,8 @@ export function startLanguageServer(extensionContext: vscode.ExtensionContext, s ], } + const serverPath = 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 = { @@ -37,7 +40,7 @@ export function startLanguageServer(extensionContext: vscode.ExtensionContext, s debug: { module: serverPath, transport: TransportKind.ipc, options: debugOptions }, } - const child = cp.spawn('node', [serverPath, ...debugOptions.execArgv]) + const child = cp.spawn(resourcePaths.node, [serverPath, ...debugOptions.execArgv]) writeEncryptionInit(child.stdin) serverOptions = () => Promise.resolve(child) diff --git a/packages/amazonq/src/lsp/lspInstaller.ts b/packages/amazonq/src/lsp/lspInstaller.ts index 5148d895466..a087abf395c 100644 --- a/packages/amazonq/src/lsp/lspInstaller.ts +++ b/packages/amazonq/src/lsp/lspInstaller.ts @@ -5,13 +5,21 @@ import * as vscode from 'vscode' import { Range } from 'semver' -import { ManifestResolver, LanguageServerResolver, LspResolver, LspResult } from 'aws-core-vscode/shared' +import { + ManifestResolver, + LanguageServerResolver, + LspResolver, + fs, + LspResolution, + getNodeExecutableName, +} from 'aws-core-vscode/shared' +import path from 'path' const manifestURL = 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json' export const supportedLspServerVersions = '^2.3.0' export class AmazonQLSPResolver implements LspResolver { - async resolve(): Promise { + async resolve(): Promise { const overrideLocation = process.env.AWS_LANGUAGE_SERVER_OVERRIDE if (overrideLocation) { void vscode.window.showInformationMessage(`Using language server override location: ${overrideLocation}`) @@ -19,6 +27,10 @@ export class AmazonQLSPResolver implements LspResolver { assetDirectory: overrideLocation, location: 'override', version: '0.0.0', + resourcePaths: { + lsp: overrideLocation, + node: getNodeExecutableName(), + }, } } @@ -31,7 +43,16 @@ export class AmazonQLSPResolver implements LspResolver { new Range(supportedLspServerVersions) ).resolve() + const nodePath = path.join(installationResult.assetDirectory, `servers/${getNodeExecutableName()}`) + await fs.chmod(nodePath, 0o755) + // TODO Cleanup old versions of language servers - return installationResult + return { + ...installationResult, + resourcePaths: { + lsp: path.join(installationResult.assetDirectory, 'servers/aws-lsp-codewhisperer.js'), + node: nodePath, + }, + } } } diff --git a/packages/core/src/amazonq/lsp/lspClient.ts b/packages/core/src/amazonq/lsp/lspClient.ts index b901dd20d67..f30aa5b681d 100644 --- a/packages/core/src/amazonq/lsp/lspClient.ts +++ b/packages/core/src/amazonq/lsp/lspClient.ts @@ -32,7 +32,7 @@ import { } from './types' import { Writable } from 'stream' import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings' -import { fs, getLogger, globals } from '../../shared' +import { ResourcePaths, fs, getLogger, globals } from '../../shared' const localize = nls.loadMessageBundle() @@ -172,7 +172,7 @@ export class LspClient { * It will create a output channel named Amazon Q Language Server. * This function assumes the LSP server has already been downloaded. */ -export async function activate(extensionContext: ExtensionContext, serverModule: string) { +export async function activate(extensionContext: ExtensionContext, resourcePaths: ResourcePaths) { LspClient.instance const toDispose = extensionContext.subscriptions @@ -195,12 +195,9 @@ export async function activate(extensionContext: ExtensionContext, serverModule: delete process.env.Q_WORKER_THREADS } - const nodename = process.platform === 'win32' ? 'node.exe' : 'node' + const serverModule = resourcePaths.lsp - const child = spawn(extensionContext.asAbsolutePath(path.join('resources', nodename)), [ - serverModule, - ...debugOptions.execArgv, - ]) + const child = spawn(resourcePaths.node, [serverModule, ...debugOptions.execArgv]) // share an encryption key using stdin // follow same practice of DEXP LSP server writeEncryptionInit(child.stdin) diff --git a/packages/core/src/amazonq/lsp/lspController.ts b/packages/core/src/amazonq/lsp/lspController.ts index bae7cfbd443..3b38d9a17a2 100644 --- a/packages/core/src/amazonq/lsp/lspController.ts +++ b/packages/core/src/amazonq/lsp/lspController.ts @@ -161,7 +161,7 @@ export class LspController { setImmediate(async () => { try { const installResult = await new WorkspaceLSPResolver().resolve() - await activateLsp(context, path.join(installResult.assetDirectory, 'resources/qserver/lspServer.js')) + await activateLsp(context, installResult.resourcePaths) getLogger().info('LspController: LSP activated') void LspController.instance.buildIndex(buildIndexConfig) // log the LSP server CPU and Memory usage per 30 minutes. diff --git a/packages/core/src/amazonq/lsp/workspaceInstaller.ts b/packages/core/src/amazonq/lsp/workspaceInstaller.ts index fda843fa063..f1aec614a4d 100644 --- a/packages/core/src/amazonq/lsp/workspaceInstaller.ts +++ b/packages/core/src/amazonq/lsp/workspaceInstaller.ts @@ -3,17 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { LspResolver, LspResult } from '../../shared/languageServer/types' +import path from 'path' +import { LspResolution, LspResolver } from '../../shared/languageServer/types' import { ManifestResolver } from '../../shared/languageServer/manifestResolver' import { LanguageServerResolver } from '../../shared/languageServer/lspResolver' import { Range } from 'semver' +import { getNodeExecutableName } from '../../shared/languageServer/utils/platform' +import { fs } from '../../shared/fs/fs' const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json' // this LSP client in Q extension is only going to work with these LSP server versions const supportedLspServerVersions = '0.1.32' export class WorkspaceLSPResolver implements LspResolver { - async resolve(): Promise { + async resolve(): Promise { const name = 'AmazonQ-Workspace' const manifest = await new ManifestResolver(manifestUrl, name).resolve() const installationResult = await new LanguageServerResolver( @@ -22,7 +25,21 @@ export class WorkspaceLSPResolver implements LspResolver { new Range(supportedLspServerVersions) ).resolve() + const nodeName = + process.platform === 'win32' ? getNodeExecutableName() : `node-${process.platform}-${process.arch}` + const nodePath = path.join(installationResult.assetDirectory, nodeName) + await fs.chmod(nodePath, 0o755) + // TODO Cleanup old versions of language servers - return installationResult + return { + ...installationResult, + resourcePaths: { + lsp: path.join( + installationResult.assetDirectory, + `qserver-${process.platform}-${process.arch}/qserver/lspServer.js` + ), + node: nodePath, + }, + } } } diff --git a/packages/core/src/shared/index.ts b/packages/core/src/shared/index.ts index d081bfc2bde..01f398e058c 100644 --- a/packages/core/src/shared/index.ts +++ b/packages/core/src/shared/index.ts @@ -63,3 +63,4 @@ export * from './languageServer/manifestResolver' export * from './languageServer/lspResolver' export * from './languageServer/types' export { default as request } from './request' +export * from './languageServer/utils/platform' diff --git a/packages/core/src/shared/languageServer/types.ts b/packages/core/src/shared/languageServer/types.ts index ec365424eaa..4f5a3cf1c87 100644 --- a/packages/core/src/shared/languageServer/types.ts +++ b/packages/core/src/shared/languageServer/types.ts @@ -15,8 +15,16 @@ export interface LspResult { assetDirectory: string } +export interface ResourcePaths { + lsp: string + node: string +} +export interface LspResolution extends LspResult { + resourcePaths: ResourcePaths +} + export interface LspResolver { - resolve(): Promise + resolve(): Promise } export interface TargetContent { diff --git a/packages/core/src/shared/languageServer/utils/platform.ts b/packages/core/src/shared/languageServer/utils/platform.ts new file mode 100644 index 00000000000..f313b66186f --- /dev/null +++ b/packages/core/src/shared/languageServer/utils/platform.ts @@ -0,0 +1,8 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export function getNodeExecutableName(): string { + return process.platform === 'win32' ? 'node.exe' : 'node' +}