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
23 changes: 20 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ Example:
}
```

<a name="amazonqLsp-settings">Overrides specifically for the Amazon Q language server can be set using the `aws.dev.amazonqLsp` setting. This is a JSON object consisting of keys/values required to override language server: `manifestUrl`, `supportedVersions`, `id`, and `locationOverride`.</a>
<a name="amazonqLsp-settings">Overrides specifically for the Amazon Q language server</a> can be set using the `aws.dev.amazonqLsp` setting. This is a JSON object consisting of keys/values required to override language server: `manifestUrl`, `supportedVersions`, `id`, and `path`.

Example:

Expand All @@ -436,7 +436,20 @@ Example:
"manifestUrl": "https://custom.url/manifest.json",
"supportedVersions": "4.0.0",
"id": "AmazonQ",
"locationOverride": "/custom/path/to/local/lsp/folder",
"path": "/custom/path/to/local/lsp/folder",
}
```

<a name="amazonqWorkspaceLsp-settings">Overrides specifically for the Amazon Q Workspace Context language server</a> can be set using the `aws.dev.amazonqWorkspaceLsp` setting. This is a JSON object consisting of keys/values required to override language server: `manifestUrl`, `supportedVersions`, `id`, and `path`.

Example:

```json
"aws.dev.amazonqWorkspaceLsp": {
"manifestUrl": "https://custom.url/manifest.json",
"supportedVersions": "4.0.0",
"id": "AmazonQ",
"path": "/custom/path/to/local/lsp/folder",
}
```

Expand Down Expand Up @@ -488,7 +501,11 @@ Unlike the user setting overrides, not all of these environment variables have t
- `__AMAZONQLSP_MANIFEST_URL`: for aws.dev.amazonqLsp.manifestUrl
- `__AMAZONQLSP_SUPPORTED_VERSIONS`: for aws.dev.amazonqLsp.supportedVersions
- `__AMAZONQLSP_ID`: for aws.dev.amazonqLsp.id
- `__AMAZONQLSP_LOCATION_OVERRIDE`: for aws.dev.amazonqLsp.locationOverride
- `__AMAZONQLSP_PATH`: for aws.dev.amazonqWorkspaceLsp.locationOverride
- `__AMAZONQWORKSPACELSP_MANIFEST_URL`: for aws.dev.amazonqWorkspaceLsp.manifestUrl
- `__AMAZONQWORKSPACELSP_SUPPORTED_VERSIONS`: for aws.dev.amazonqWorkspaceLsp.supportedVersions
- `__AMAZONQWORKSPACELSP_ID`: for aws.dev.amazonqWorkspaceLsp.id
- `__AMAZONQWORKSPACELSP_PATH`: for aws.dev.amazonqWorkspaceLsp.locationOverride

#### Lambda

Expand Down
4 changes: 2 additions & 2 deletions packages/amazonq/src/lsp/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

import vscode from 'vscode'
import { startLanguageServer } from './client'
import { AmazonQLSPResolver } from './lspInstaller'
import { AmazonQLspInstaller } from './lspInstaller'
import { Commands, lspSetupStage, ToolkitError } from 'aws-core-vscode/shared'

export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
try {
await lspSetupStage('all', async () => {
const installResult = await new AmazonQLSPResolver().resolve()
const installResult = await new AmazonQLspInstaller().resolve()
await lspSetupStage('launch', async () => await startLanguageServer(ctx, installResult.resourcePaths))
})
ctx.subscriptions.push(
Expand Down
14 changes: 4 additions & 10 deletions packages/amazonq/src/lsp/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,19 @@
*/

import { DevSettings, getServiceEnvVarConfig } from 'aws-core-vscode/shared'
import { LspConfig } from 'aws-core-vscode/amazonq'

export interface AmazonQLspConfig {
manifestUrl: string
supportedVersions: string
id: string
locationOverride?: string
}

export const defaultAmazonQLspConfig: AmazonQLspConfig = {
export const defaultAmazonQLspConfig: LspConfig = {
manifestUrl: 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json',
supportedVersions: '^3.1.1',
id: 'AmazonQ', // used for identification in global storage/local disk location. Do not change.
locationOverride: undefined,
}

export function getAmazonQLspConfig(): AmazonQLspConfig {
export function getAmazonQLspConfig(): LspConfig {
return {
...defaultAmazonQLspConfig,
...(DevSettings.instance.getServiceConfig('amazonqLsp', {}) as AmazonQLspConfig),
...(DevSettings.instance.getServiceConfig('amazonqLsp', {}) as LspConfig),
...getServiceEnvVarConfig('amazonqLsp', Object.keys(defaultAmazonQLspConfig)),
}
}
66 changes: 17 additions & 49 deletions packages/amazonq/src/lsp/lspInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,32 @@
* SPDX-License-Identifier: Apache-2.0
*/

import * as vscode from 'vscode'
import {
ManifestResolver,
LanguageServerResolver,
LspResolver,
fs,
LspResolution,
getNodeExecutableName,
cleanLspDownloads,
getLogger,
} from 'aws-core-vscode/shared'
import { fs, getNodeExecutableName, BaseLspInstaller, ResourcePaths } from 'aws-core-vscode/shared'
import path from 'path'
import { Range } from 'semver'
import { getAmazonQLspConfig } from './config'

const logger = getLogger('amazonqLsp')
export class AmazonQLspInstaller extends BaseLspInstaller {
constructor() {
super(getAmazonQLspConfig(), 'amazonqLsp')
}

protected override async postInstall(assetDirectory: string): Promise<void> {
const resourcePaths = this.resourcePaths(assetDirectory)
await fs.chmod(resourcePaths.node, 0o755)
}

export class AmazonQLSPResolver implements LspResolver {
async resolve(): Promise<LspResolution> {
const { id, manifestUrl, supportedVersions, locationOverride } = getAmazonQLspConfig()
if (locationOverride) {
void vscode.window.showInformationMessage(`Using language server override location: ${locationOverride}`)
protected override resourcePaths(assetDirectory?: string): ResourcePaths {
if (!assetDirectory) {
return {
assetDirectory: locationOverride,
location: 'override',
version: '0.0.0',
resourcePaths: {
lsp: locationOverride,
node: getNodeExecutableName(),
},
lsp: this.config.path ?? '',
node: getNodeExecutableName(),
}
}

// "AmazonQ" is shared across toolkits to provide a common access point, don't change it
const manifest = await new ManifestResolver(manifestUrl, id).resolve()
const installationResult = await new LanguageServerResolver(
manifest,
id,
new Range(supportedVersions, {
includePrerelease: true,
})
).resolve()

const nodePath = path.join(installationResult.assetDirectory, `servers/${getNodeExecutableName()}`)
await fs.chmod(nodePath, 0o755)

const deletedVersions = await cleanLspDownloads(
manifest.versions,
path.dirname(installationResult.assetDirectory)
)
logger.debug(`Cleaned up ${deletedVersions.length} old versions`)

const nodePath = path.join(assetDirectory, `servers/${getNodeExecutableName()}`)
return {
...installationResult,
resourcePaths: {
lsp: path.join(installationResult.assetDirectory, 'servers/aws-lsp-codewhisperer.js'),
node: nodePath,
},
lsp: path.join(assetDirectory, 'servers/aws-lsp-codewhisperer.js'),
node: nodePath,
}
}
}
12 changes: 6 additions & 6 deletions packages/amazonq/test/e2e/lsp/lspInstaller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import assert from 'assert'
import sinon from 'sinon'
import { AmazonQLSPResolver } from '../../../src/lsp/lspInstaller'
import { AmazonQLspInstaller } from '../../../src/lsp/lspInstaller'
import {
DevSettings,
fs,
Expand All @@ -18,9 +18,9 @@ import {
} from 'aws-core-vscode/shared'
import * as semver from 'semver'
import { assertTelemetry } from 'aws-core-vscode/test'
import { LspController } from 'aws-core-vscode/amazonq'
import { LspConfig, LspController } from 'aws-core-vscode/amazonq'
import { LanguageServerSetup } from 'aws-core-vscode/telemetry'
import { AmazonQLspConfig, getAmazonQLspConfig } from '../../../src/lsp/config'
import { getAmazonQLspConfig } from '../../../src/lsp/config'

function createVersion(version: string) {
return {
Expand All @@ -44,13 +44,13 @@ function createVersion(version: string) {
}

describe('AmazonQLSPInstaller', () => {
let resolver: AmazonQLSPResolver
let resolver: AmazonQLspInstaller
let sandbox: sinon.SinonSandbox
let tempDir: string
// If globalState contains an ETag that is up to date with remote, we won't fetch it resulting in inconsistent behavior.
// Therefore, we clear it temporarily for these tests to ensure consistent behavior.
let manifestStorage: { [key: string]: any }
let lspConfig: AmazonQLspConfig
let lspConfig: LspConfig

before(async () => {
manifestStorage = globals.globalState.get(manifestStorageKey) || {}
Expand All @@ -59,7 +59,7 @@ describe('AmazonQLSPInstaller', () => {

beforeEach(async () => {
sandbox = sinon.createSandbox()
resolver = new AmazonQLSPResolver()
resolver = new AmazonQLspInstaller()
tempDir = await makeTemporaryToolkitFolder()
sandbox.stub(LanguageServerResolver.prototype, 'defaultDownloadFolder').returns(tempDir)
// Called on extension activation and can contaminate telemetry.
Expand Down
141 changes: 86 additions & 55 deletions packages/amazonq/test/unit/amazonq/lsp/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,73 +7,104 @@ import assert from 'assert'
import { DevSettings } from 'aws-core-vscode/shared'
import sinon from 'sinon'
import { defaultAmazonQLspConfig, getAmazonQLspConfig } from '../../../../src/lsp/config'
import { LspConfig, getAmazonQWorkspaceLspConfig, defaultAmazonQWorkspaceLspConfig } from 'aws-core-vscode/amazonq'

describe('getAmazonQLspConfig', () => {
let sandbox: sinon.SinonSandbox
let serviceConfigStub: sinon.SinonStub
const settingConfig = {
manifestUrl: 'https://custom.url/manifest.json',
supportedVersions: '4.0.0',
id: 'AmazonQSetting',
locationOverride: '/custom/path',
}
for (const [name, config, defaultConfig, setEnv, resetEnv] of [
[
'getAmazonQLspConfig',
getAmazonQLspConfig,
defaultAmazonQLspConfig,
(envConfig: LspConfig) => {
process.env.__AMAZONQLSP_MANIFEST_URL = envConfig.manifestUrl
process.env.__AMAZONQLSP_SUPPORTED_VERSIONS = envConfig.supportedVersions
process.env.__AMAZONQLSP_ID = envConfig.id
process.env.__AMAZONQLSP_LOCATION_OVERRIDE = envConfig.locationOverride
},
() => {
delete process.env.__AMAZONQLSP_MANIFEST_URL
delete process.env.__AMAZONQLSP_SUPPORTED_VERSIONS
delete process.env.__AMAZONQLSP_ID
delete process.env.__AMAZONQLSP_LOCATION_OVERRIDE
},
],
[
'getAmazonQWorkspaceLspConfig',
getAmazonQWorkspaceLspConfig,
defaultAmazonQWorkspaceLspConfig,
(envConfig: LspConfig) => {
process.env.__AMAZONQWORKSPACELSP_MANIFEST_URL = envConfig.manifestUrl
process.env.__AMAZONQWORKSPACELSP_SUPPORTED_VERSIONS = envConfig.supportedVersions
process.env.__AMAZONQWORKSPACELSP_ID = envConfig.id
process.env.__AMAZONQWORKSPACELSP_LOCATION_OVERRIDE = envConfig.locationOverride
},
() => {
delete process.env.__AMAZONQWORKSPACELSP_MANIFEST_URL
delete process.env.__AMAZONQWORKSPACELSP_SUPPORTED_VERSIONS
delete process.env.__AMAZONQWORKSPACELSP_ID
delete process.env.__AMAZONQWORKSPACELSP_LOCATION_OVERRIDE
},
],
] as const) {
describe(name, () => {
let sandbox: sinon.SinonSandbox
let serviceConfigStub: sinon.SinonStub
const settingConfig: LspConfig = {
manifestUrl: 'https://custom.url/manifest.json',
supportedVersions: '4.0.0',
id: 'AmazonQSetting',
locationOverride: '/custom/path',
}

beforeEach(() => {
sandbox = sinon.createSandbox()
beforeEach(() => {
sandbox = sinon.createSandbox()

serviceConfigStub = sandbox.stub()
sandbox.stub(DevSettings, 'instance').get(() => ({
getServiceConfig: serviceConfigStub,
}))
})
serviceConfigStub = sandbox.stub()
sandbox.stub(DevSettings, 'instance').get(() => ({
getServiceConfig: serviceConfigStub,
}))
})

afterEach(() => {
sandbox.restore()
})
afterEach(() => {
sandbox.restore()
resetEnv()
})

it('uses default config', () => {
serviceConfigStub.returns({})
const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, defaultAmazonQLspConfig)
})
it('uses default config', () => {
serviceConfigStub.returns({})
assert.deepStrictEqual(config(), defaultConfig)
})

it('overrides location', () => {
const locationOverride = '/custom/path/to/lsp'
serviceConfigStub.returns({ locationOverride })
it('overrides location', () => {
const locationOverride = '/custom/path/to/lsp'
serviceConfigStub.returns({ locationOverride })

const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, {
...defaultAmazonQLspConfig,
locationOverride,
assert.deepStrictEqual(config(), {
...defaultConfig,
locationOverride,
})
})
})

it('overrides default settings', () => {
serviceConfigStub.returns(settingConfig)

const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, settingConfig)
})
it('overrides default settings', () => {
serviceConfigStub.returns(settingConfig)

it('environment variable takes precedence over settings', () => {
const envConfig = {
manifestUrl: 'https://another-custom.url/manifest.json',
supportedVersions: '5.1.1',
id: 'AmazonQEnv',
locationOverride: '/some/new/custom/path',
}
assert.deepStrictEqual(config(), settingConfig)
})

process.env.__AMAZONQLSP_MANIFEST_URL = envConfig.manifestUrl
process.env.__AMAZONQLSP_SUPPORTED_VERSIONS = envConfig.supportedVersions
process.env.__AMAZONQLSP_ID = envConfig.id
process.env.__AMAZONQLSP_LOCATION_OVERRIDE = envConfig.locationOverride
it('environment variable takes precedence over settings', () => {
const envConfig: LspConfig = {
manifestUrl: 'https://another-custom.url/manifest.json',
supportedVersions: '5.1.1',
id: 'AmazonQEnv',
locationOverride: '/some/new/custom/path',
}

serviceConfigStub.returns(settingConfig)
setEnv(envConfig)
serviceConfigStub.returns(settingConfig)

const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, {
...defaultAmazonQLspConfig,
...envConfig,
assert.deepStrictEqual(config(), {
...defaultAmazonQLspConfig,
...envConfig,
})
})
})
})
}
1 change: 1 addition & 0 deletions packages/core/src/amazonq/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export { ExtensionMessage } from '../amazonq/webview/ui/commands'
export { CodeReference } from '../codewhispererChat/view/connector/connector'
export { extractAuthFollowUp } from './util/authUtils'
export { Messenger } from './commons/connector/baseMessenger'
export * from './lsp/config'
import { FeatureContext } from '../shared/featureConfig'

/**
Expand Down
Loading
Loading