Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 10 additions & 6 deletions packages/amazonq/src/lsp/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
*/

import vscode from 'vscode'
import path from 'path'
import { AmazonQLSPDownloader } from './download'
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<void> {
const serverPath = ctx.asAbsolutePath('resources/qdeveloperserver')
const clientPath = ctx.asAbsolutePath('resources/qdeveloperclient')
await new AmazonQLSPDownloader(serverPath, clientPath).tryInstallLsp()
await startLanguageServer(ctx, path.join(serverPath, 'aws-lsp-codewhisperer.js'))
try {
const installResult = await new AmazonQLSPResolver().resolve()
await startLanguageServer(ctx, path.join(installResult.assetDirectory, 'servers/aws-lsp-codewhisperer.js'))
} catch (err) {
const e = err as ToolkitError
void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`)
}
}
77 changes: 0 additions & 77 deletions packages/amazonq/src/lsp/download.ts

This file was deleted.

37 changes: 37 additions & 0 deletions packages/amazonq/src/lsp/lspInstaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as vscode from 'vscode'
import { Range } from 'semver'
import { ManifestResolver, LanguageServerResolver, LspResolver, LspResult } from 'aws-core-vscode/shared'

const manifestURL = 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json'
const supportedLspServerVersions = '^2.3.1'

export class AmazonQLSPResolver implements LspResolver {
async resolve(): Promise<LspResult> {
const overrideLocation = process.env.AWS_LANGUAGE_SERVER_OVERRIDE
if (overrideLocation) {
void vscode.window.showInformationMessage(`Using language server override location: ${overrideLocation}`)
return {
assetDirectory: overrideLocation,
location: 'override',
version: '0.0.0',
}
}

// "AmazonQ" is shared across toolkits to provide a common access point, don't change it
const name = 'AmazonQ'
const manifest = await new ManifestResolver(manifestURL, name).resolve()
const installationResult = await new LanguageServerResolver(
manifest,
name,
new Range(supportedLspServerVersions)
).resolve()

// TODO Cleanup old versions of language servers
return installationResult
}
}
51 changes: 0 additions & 51 deletions packages/amazonq/test/unit/amazonq/lsp/lspController.test.ts

This file was deleted.

4 changes: 1 addition & 3 deletions packages/core/src/amazonq/lsp/lspClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,11 @@ 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) {
export async function activate(extensionContext: ExtensionContext, serverModule: string) {
LspClient.instance
const toDispose = extensionContext.subscriptions

let rangeFormatting: Disposable | undefined
// The server is implemented in node
const serverModule = path.join(extensionContext.extensionPath, 'resources/qserver/lspServer.js')
// 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'] }
Expand Down
76 changes: 4 additions & 72 deletions packages/core/src/amazonq/lsp/lspController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import { telemetry } from '../../shared/telemetry'
import { isCloud9 } from '../../shared/extensionUtilities'
import globals, { isWeb } from '../../shared/extensionGlobals'
import { isAmazonInternalOs } from '../../shared/vscode/env'
import { LspDownloader, Manifest } from '../../shared/fetchLsp'
import { fs } from '../../shared/fs/fs'
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities'
import { WorkspaceLSPResolver } from './workspaceInstaller'

export interface Chunk {
readonly filePath: string
Expand All @@ -25,13 +23,6 @@ export interface Chunk {
readonly relativePath?: string
readonly programmingLanguage?: string
}

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']

const nodeBinName = process.platform === 'win32' ? 'node.exe' : 'node'

export interface BuildIndexConfig {
startUrl?: string
maxIndexSize: number
Expand All @@ -49,22 +40,14 @@ export interface BuildIndexConfig {
* Pre-process the input to Index Files API
* Post-process the output from Query API
*/
export class LspController extends LspDownloader {
export class LspController {
static #instance: LspController
private _isIndexingInProgress = false
private serverPath: string
private nodePath: string

public static get instance() {
return (this.#instance ??= new this())
}

constructor() {
super(manifestUrl, supportedLspServerVersions)
this.serverPath = globals.context.asAbsolutePath(path.join('resources', 'qserver'))
this.nodePath = globals.context.asAbsolutePath(path.join('resources', nodeBinName))
}

isIndexingInProgress() {
return this._isIndexingInProgress
}
Expand Down Expand Up @@ -170,67 +153,16 @@ export class LspController extends LspDownloader {
}
}

async isLspInstalled(): Promise<boolean> {
return (await fs.exists(this.serverPath)) && (await fs.exists(this.nodePath))
}

async cleanup(): Promise<boolean> {
if (await fs.exists(this.serverPath)) {
await tryRemoveFolder(this.serverPath)
}

if (await fs.exists(this.nodePath)) {
await fs.delete(this.nodePath)
}

return true
}

async install(manifest: Manifest) {
const server = this.getDependency(manifest, 'qserver')
const runtime = this.getDependency(manifest, 'node')
if (!server || !runtime) {
getLogger('lsp').info(`Did not find LSP URL for ${process.platform} ${process.arch}`)
return false
}

let tempFolder = undefined

try {
tempFolder = await makeTemporaryToolkitFolder()
await this.downloadAndExtractServer({
content: server,
installLocation: this.serverPath,
name: 'qserver',
tempFolder,
extractToTempFolder: true,
})

const runtimeTempPath = path.join(tempFolder, nodeBinName)
await this.installRuntime(runtime, this.nodePath, runtimeTempPath)
} finally {
// clean up temp folder
if (tempFolder) {
await tryRemoveFolder(tempFolder)
}
}

return true
}

async trySetupLsp(context: vscode.ExtensionContext, buildIndexConfig: BuildIndexConfig) {
if (isCloud9() || isWeb() || isAmazonInternalOs()) {
getLogger().warn('LspController: Skipping LSP setup. LSP is not compatible with the current environment. ')
// do not do anything if in Cloud9 or Web mode or in AL2 (AL2 does not support node v18+)
return
}
setImmediate(async () => {
const ok = await LspController.instance.tryInstallLsp()
if (!ok) {
return
}
try {
await activateLsp(context)
const installResult = await new WorkspaceLSPResolver().resolve()
await activateLsp(context, path.join(installResult.assetDirectory, 'resources/qserver/lspServer.js'))
getLogger().info('LspController: LSP activated')
void LspController.instance.buildIndex(buildIndexConfig)
// log the LSP server CPU and Memory usage per 30 minutes.
Expand Down
28 changes: 28 additions & 0 deletions packages/core/src/amazonq/lsp/workspaceInstaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { LspResolver, LspResult } from '../../shared/languageServer/types'
import { ManifestResolver } from '../../shared/languageServer/manifestResolver'
import { LanguageServerResolver } from '../../shared/languageServer/lspResolver'
import { Range } from 'semver'

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<LspResult> {
const name = 'AmazonQ-Workspace'
const manifest = await new ManifestResolver(manifestUrl, name).resolve()
const installationResult = await new LanguageServerResolver(
manifest,
name,
new Range(supportedLspServerVersions)
).resolve()

// TODO Cleanup old versions of language servers
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling this out explicitly so we don't forget

return installationResult
}
}
15 changes: 13 additions & 2 deletions packages/core/src/shared/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@
import { isWeb } from './extensionGlobals'

export function randomUUID(): `${string}-${string}-${string}-${string}-${string}` {
return getCrypto().randomUUID()
}

function getCrypto() {
if (isWeb()) {
return globalThis.crypto.randomUUID()
return globalThis.crypto
}

return require('crypto').randomUUID()
return require('crypto')
}

/**
Expand All @@ -54,3 +58,10 @@ export function truncateUuid(uuid: string) {
const cleanedUUID = uuid.replace(/-/g, '')
return `${cleanedUUID.substring(0, 4)}...${cleanedUUID.substring(cleanedUUID.length - 4)}`
}

export function createHash(algorithm: string, contents: string | Buffer): string {
const crypto = getCrypto()
const hash = crypto.createHash(algorithm)
hash.update(contents)
return `${algorithm}:${hash.digest('hex')}`
}
Loading
Loading