Skip to content

Commit

Permalink
Proof of concept language server that provides CodeWhisperer recommen…
Browse files Browse the repository at this point in the history
…dations (#526)

This change adds a concept language server that provides code recommendations from CodeWhisperer. The recommendations are returned through code completions and inline completion.

The sample VS Code extension has been updated to support this language server when editing typescript files. This is accomplished by introducing an inline completion provider, which wraps the concept language server.

At this time, inline completion is not a part of the language server protocol. VS Code has an extensibility API for inline completion, and a proposal is part of the next protocol version 3.18 (microsoft/language-server-protocol#1673). This proposal is modelled after the extensibility API.

A likely implementation of the protocol is proposed in microsoft/vscode-languageserver-node#1190, and portions of this change leverage the shapes defined in that proposal. This way, if or when inline completion is a part of the spec, we could update code to reference `vscode-languageserver` related types with little effort.

This concept uses IAM credentials with CodeWhisperer as a way to get up and iterating quickly. In future work we will want to switch over to using bearer tokens, like the AWS Toolkit for VS Code does.

The CodeWhisperer service client was produced using the AWS Toolkit for VS Code's client generator (https://github.com/aws/aws-toolkit-vscode/blob/master/scripts/build/generateServiceClient.ts)

This language server is being submitted so that we can perform additional experimentation such as:
- verifying browser compatibility
- exploring the UX around surfacing code recommendations
- future bearer token support
  • Loading branch information
awschristou authored Jul 7, 2023
1 parent b4865d2 commit 27fb0bf
Show file tree
Hide file tree
Showing 21 changed files with 1,615 additions and 11 deletions.
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@
},
"preLaunchTask": "npm: compile"
},
{
"name": "CodeWhisperer Server",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceFolder}/client/vscode"],
"outFiles": ["${workspaceFolder}/client/vscode/out/**/*.js"],
"env": {
"LSP_SERVER": "${workspaceFolder}/app/aws-lsp-codewhisperer-binary/out/index.js",
"ENABLE_INLINE_COMPLETION": "true"
},
"preLaunchTask": "npm: compile"
},
{
"name": "S3 Server",
"type": "extensionHost",
Expand Down
19 changes: 19 additions & 0 deletions app/aws-lsp-codewhisperer-binary/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@lsp-placeholder/aws-lsp-codewhisperer-binary",
"version": "0.0.1",
"description": "CodeWhisperer Language Server Binary",
"main": "out/index.js",
"bin": {
"aws-lsp-codewhisperer-binary": "./out/index.js"
},
"scripts": {
"compile": "tsc --build",
"package-x64": "pkg --targets node18-linux-x64,node18-win-x64,node18-macos-x64 --out-path bin --compress GZip ."
},
"dependencies": {
"@lsp-placeholder/aws-lsp-codewhisperer": "^0.0.1"
},
"devDependencies": {
"pkg": "^5.8.1"
}
}
23 changes: 23 additions & 0 deletions app/aws-lsp-codewhisperer-binary/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
CodeWhispererServer,
CodeWhispererServerProps,
CodeWhispererServiceProps,
createCodeWhispererService,
} from '@lsp-placeholder/aws-lsp-codewhisperer'
import { ProposedFeatures, createConnection } from 'vscode-languageserver/node'

const connection = createConnection(ProposedFeatures.all)

const serviceProps: CodeWhispererServiceProps = {
displayName: CodeWhispererServer.serverId,
connection,
}

const service = createCodeWhispererService(serviceProps)

const props: CodeWhispererServerProps = {
connection,
codeWhispererService: service,
}

export const server = new CodeWhispererServer(props)
8 changes: 8 additions & 0 deletions app/aws-lsp-codewhisperer-binary/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.packages.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./out"
},
"include": ["src"]
}
3 changes: 2 additions & 1 deletion client/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
},
"main": "./out/extension.js",
"activationEvents": [
"onLanguage:yaml"
"onLanguage:yaml",
"onLanguage:typescript"
],
"contributes": {
"configuration": {
Expand Down
19 changes: 18 additions & 1 deletion client/vscode/src/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as path from 'path'
import { ExtensionContext, workspace } from 'vscode'

import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'
import { registerInlineCompletion } from './inlineCompletionActivation'

export async function activateDocumentsLanguageServer(extensionContext: ExtensionContext) {
/**
Expand All @@ -28,6 +29,14 @@ export async function activateDocumentsLanguageServer(extensionContext: Extensio
const fallbackPath = path.join(extensionContext.extensionPath, '../../../out/src/server/server.js')
const serverModule = process.env.LSP_SERVER ?? fallbackPath

/**
* If you are iterating with a language server that uses inline completion,
* set the ENABLE_INLINE_COMPLETION environment variable to "true".
* This will set up the extension's inline completion provider to get recommendations
* from the language server.
*/
const enableInlineCompletion = process.env.ENABLE_INLINE_COMPLETION === 'true'

const debugOptions = { execArgv: ['--nolazy', '--inspect=6012', '--preserve-symlinks'] }

// If the extension is launch in debug mode the debug server options are use
Expand All @@ -41,19 +50,27 @@ export async function activateDocumentsLanguageServer(extensionContext: Extensio
const clientOptions: LanguageClientOptions = {
// Register the server for json documents
documentSelector: [
// yaml/json is illustrative of static filetype handling language servers
{ scheme: 'file', language: 'yaml' },
{ scheme: 'untitled', language: 'yaml' },
{ scheme: 'file', language: 'json' },
{ scheme: 'untitled', language: 'json' },
// typescript is illustrative of code-handling language servers
{ scheme: 'file', language: 'typescript' },
{ scheme: 'untitled', language: 'typescript' },
],
synchronize: {
fileEvents: workspace.createFileSystemWatcher('**/*.{json,yml,yaml}'),
fileEvents: workspace.createFileSystemWatcher('**/*.{json,yml,yaml,ts}'),
},
}

// Create the language client and start the client.
const client = new LanguageClient('awsDocuments', 'AWS Documents Language Server', serverOptions, clientOptions)

if (enableInlineCompletion) {
registerInlineCompletion(client)
}

client.start()

return client
Expand Down
45 changes: 45 additions & 0 deletions client/vscode/src/futureTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { InlineCompletionContext, InlineCompletionItem, InlineCompletionList } from 'vscode'
import {
ProtocolRequestType,
StaticRegistrationOptions,
TextDocumentPositionParams,
TextDocumentRegistrationOptions,
WorkDoneProgressOptions,
WorkDoneProgressParams,
} from 'vscode-languageclient'

/**
* Inline completion is not a part of the language server protocol.
* It is being proposed at this time (https://github.com/microsoft/language-server-protocol/pull/1673).
*
* This file contains boilerplate code that goes away if that proposal goes mainline.
* The proposal is being modelled after the VS Code extensibility APIs, so the types
* used from `vscode` are compatible with what is being planned for future
* `vscode-languageclient` types.
*
* See remarks in server\aws-lsp-codewhisperer\src\language-server\codeWhispererServer.ts
* for more details.
*/

type InlineCompletionOptions = WorkDoneProgressOptions

type InlineCompletionRegistrationOptions = InlineCompletionOptions &
TextDocumentRegistrationOptions &
StaticRegistrationOptions

export type InlineCompletionParams = WorkDoneProgressParams &
TextDocumentPositionParams & {
context: InlineCompletionContext
}

/**
* inlineCompletionRequestType defines the custom method that the language client
* requests from the server to provide inline completion recommendations.
*/
export const inlineCompletionRequestType = new ProtocolRequestType<
InlineCompletionParams,
InlineCompletionList | InlineCompletionItem[] | null,
InlineCompletionItem[],
void,
InlineCompletionRegistrationOptions
>('aws/textDocument/inlineCompletion')
43 changes: 43 additions & 0 deletions client/vscode/src/inlineCompletionActivation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
CancellationToken,
InlineCompletionContext,
InlineCompletionItem,
InlineCompletionItemProvider,
InlineCompletionList,
Position,
TextDocument,
languages,
} from 'vscode'
import { LanguageClient } from 'vscode-languageclient/node'
import { InlineCompletionParams, inlineCompletionRequestType } from './futureTypes'

export function registerInlineCompletion(languageClient: LanguageClient) {
const inlineCompletionProvider = new CodeWhispererInlineCompletionItemProvider(languageClient)
languages.registerInlineCompletionItemProvider({ scheme: 'file', language: 'typescript' }, inlineCompletionProvider)
}

class CodeWhispererInlineCompletionItemProvider implements InlineCompletionItemProvider {
constructor(private readonly languageClient: LanguageClient) {}

async provideInlineCompletionItems(
document: TextDocument,
position: Position,
context: InlineCompletionContext,
token: CancellationToken
): Promise<InlineCompletionItem[] | InlineCompletionList> {
const request: InlineCompletionParams = {
textDocument: {
uri: document.uri.toString(),
},
position,
context,
}

const response = await this.languageClient.sendRequest(inlineCompletionRequestType, request, token)

const list: InlineCompletionList = response as InlineCompletionList
this.languageClient.info(`Client: Received ${list.items.length} suggestions`)

return list
}
}
Loading

0 comments on commit 27fb0bf

Please sign in to comment.