From 89fbc31bff9f22a91d225c90b16511676d79bfe8 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 23 Jul 2024 11:07:40 +0200 Subject: [PATCH] feat: copilot new api VSCODE-550 (#757) * feat: convert copilot POC to feature utilising latest API VSCODE-550 * refactor: use prompts in code * fix: use uri path * refactor: pass participant to playground controller --- package.json | 33 ++ src/commands/index.ts | 4 + src/editors/playgroundController.ts | 26 ++ src/mdbExtensionController.ts | 42 +++ src/participant/participant.ts | 332 ++++++++++++++++++ src/templates/playgroundBasicTextTemplate.ts | 16 + .../editors/playgroundController.test.ts | 9 +- ...aygroundSelectedCodeActionProvider.test.ts | 27 ++ .../language/languageServerController.test.ts | 6 + 9 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 src/participant/participant.ts create mode 100644 src/templates/playgroundBasicTextTemplate.ts diff --git a/package.json b/package.json index dd40df552..f60d77464 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,9 @@ "color": "#3D4F58", "theme": "dark" }, + "enabledApiProposals": [ + "chatVariableResolver" + ], "license": "SEE LICENSE IN LICENSE.txt", "main": "./dist/extension.js", "scripts": { @@ -71,11 +74,25 @@ }, "activationEvents": [ "onView:mongoDB", + "onStartupFinished", "onLanguage:json", "onLanguage:javascript", "onLanguage:plaintext" ], "contributes": { + "chatParticipants": [ + { + "id": "mongodb.participant", + "name": "MongoDB", + "description": "Ask anything about MongoDB, from writing queries to questions about your cluster.", + "commands": [ + { + "name": "query", + "description": "Ask how to write MongoDB queries or pipelines. For example, you can ask: \"Show me all the documents where the address contains the word street\"." + } + ] + } + ], "viewsContainers": { "activitybar": [ { @@ -142,6 +159,14 @@ } ], "commands": [ + { + "command": "mdb.runParticipantQuery", + "title": "Run Content Generated by the Chat Participant" + }, + { + "command": "mdb.openParticipantQueryInPlayground", + "title": "Open Generated by the Chat Participant Content In Playground" + }, { "command": "mdb.connect", "title": "MongoDB: Connect" @@ -688,6 +713,14 @@ } ], "commandPalette": [ + { + "command": "mdb.openParticipantQueryInPlayground", + "when": "false" + }, + { + "command": "mdb.runParticipantQuery", + "when": "false" + }, { "command": "mdb.disconnect", "when": "mdb.connectedToMongoDB == true" diff --git a/src/commands/index.ts b/src/commands/index.ts index ebef4dec1..61513dc2c 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -73,6 +73,10 @@ enum EXTENSION_COMMANDS { MDB_START_STREAM_PROCESSOR = 'mdb.startStreamProcessor', MDB_STOP_STREAM_PROCESSOR = 'mdb.stopStreamProcessor', MDB_DROP_STREAM_PROCESSOR = 'mdb.dropStreamProcessor', + + // Chat participant. + OPEN_PARTICIPANT_QUERY_IN_PLAYGROUND = 'mdb.openParticipantQueryInPlayground', + RUN_PARTICIPANT_QUERY = 'mdb.runParticipantQuery', } export default EXTENSION_COMMANDS; diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 64b2594a3..187f320fa 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -16,6 +16,7 @@ import { DatabaseTreeItem } from '../explorer'; import type ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; import formatError from '../utils/formatError'; import type { LanguageServerController } from '../language'; +import playgroundBasicTextTemplate from '../templates/playgroundBasicTextTemplate'; import playgroundCreateIndexTemplate from '../templates/playgroundCreateIndexTemplate'; import playgroundCreateCollectionTemplate from '../templates/playgroundCreateCollectionTemplate'; import playgroundCloneDocumentTemplate from '../templates/playgroundCloneDocumentTemplate'; @@ -44,6 +45,7 @@ import { isPlayground, getPlaygroundExtensionForTelemetry, } from '../utils/playground'; +import type { ParticipantController } from '../participant/participant'; const log = createLogger('playground controller'); @@ -132,6 +134,7 @@ export default class PlaygroundController { private _playgroundResultTextDocument?: vscode.TextDocument; private _statusView: StatusView; private _playgroundResultViewProvider: PlaygroundResultProvider; + private _participantController: ParticipantController; private _codeToEvaluate = ''; @@ -144,6 +147,7 @@ export default class PlaygroundController { activeConnectionCodeLensProvider, exportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider, + participantController, }: { connectionController: ConnectionController; languageServerController: LanguageServerController; @@ -153,6 +157,7 @@ export default class PlaygroundController { activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider; + participantController: ParticipantController; }) { this._connectionController = connectionController; this._activeTextEditor = vscode.window.activeTextEditor; @@ -164,6 +169,7 @@ export default class PlaygroundController { this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider; this._playgroundSelectedCodeActionProvider = playgroundSelectedCodeActionProvider; + this._participantController = participantController; this._connectionController.addEventListener( DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED, @@ -382,6 +388,21 @@ export default class PlaygroundController { return this._createPlaygroundFileWithContent(content); } + createPlaygroundFromParticipantQuery({ + text, + }: { + text: string; + }): Promise { + const useDefaultTemplate = !!vscode.workspace + .getConfiguration('mdb') + .get('useDefaultTemplateForPlayground'); + const content = useDefaultTemplate + ? playgroundBasicTextTemplate.replace('PLAYGROUND_CONTENT', text) + : text; + this._telemetryService.trackPlaygroundCreated('agent'); + return this._createPlaygroundFileWithContent(content); + } + createPlaygroundForCloneDocument( documentContents: string, databaseName: string, @@ -802,6 +823,11 @@ export default class PlaygroundController { return { namespace, expression }; } + async evaluateParticipantQuery({ text }: { text: string }): Promise { + this._codeToEvaluate = text; + return this._evaluatePlayground(); + } + async _transpile(): Promise { const { selectedText, importStatements, driverSyntax, builders, language } = this._exportToLanguageCodeLensProvider._exportToLanguageAddons; diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 791d85287..579757dac 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -40,6 +40,7 @@ import WebviewController from './views/webviewController'; import { createIdFactory, generateId } from './utils/objectIdHelper'; import { ConnectionStorage } from './storage/connectionStorage'; import type StreamProcessorTreeItem from './explorer/streamProcessorTreeItem'; +import { ParticipantController } from './participant/participant'; // This class is the top-level controller for our extension. // Commands which the extensions handles are defined in the function `activate`. @@ -63,6 +64,7 @@ export default class MDBExtensionController implements vscode.Disposable { _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; _editDocumentCodeLensProvider: EditDocumentCodeLensProvider; _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; + _participantController: ParticipantController; constructor( context: vscode.ExtensionContext, @@ -105,6 +107,9 @@ export default class MDBExtensionController implements vscode.Disposable { new PlaygroundSelectedCodeActionProvider(); this._playgroundDiagnosticsCodeActionProvider = new PlaygroundDiagnosticsCodeActionProvider(); + this._participantController = new ParticipantController({ + connectionController: this._connectionController, + }); this._playgroundController = new PlaygroundController({ connectionController: this._connectionController, languageServerController: this._languageServerController, @@ -115,6 +120,7 @@ export default class MDBExtensionController implements vscode.Disposable { exportToLanguageCodeLensProvider: this._exportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: this._playgroundSelectedCodeActionProvider, + participantController: this._participantController, }); this._editorsController = new EditorsController({ context, @@ -265,6 +271,42 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerEditorCommands(); this.registerTreeViewCommands(); + + // ------ CHAT PARTICIPANT ------ // + this.registerParticipantCommand( + EXTENSION_COMMANDS.OPEN_PARTICIPANT_QUERY_IN_PLAYGROUND, + () => { + return this._playgroundController.createPlaygroundFromParticipantQuery({ + text: + this._participantController._chatResult.metadata.queryContent || '', + }); + } + ); + this.registerParticipantCommand( + EXTENSION_COMMANDS.RUN_PARTICIPANT_QUERY, + () => { + return this._playgroundController.evaluateParticipantQuery({ + text: + this._participantController._chatResult.metadata.queryContent || '', + }); + } + ); + }; + + registerParticipantCommand = ( + command: string, + commandHandler: (...args: any[]) => Promise + ): void => { + const commandHandlerWithTelemetry = (args: any[]): Promise => { + this._telemetryService.trackCommandRun(command); + + return commandHandler(args); + }; + + this._context.subscriptions.push( + this._participantController.getParticipant(this._context), + vscode.commands.registerCommand(command, commandHandlerWithTelemetry) + ); }; registerCommand = ( diff --git a/src/participant/participant.ts b/src/participant/participant.ts new file mode 100644 index 000000000..d49e58cf0 --- /dev/null +++ b/src/participant/participant.ts @@ -0,0 +1,332 @@ +import * as vscode from 'vscode'; + +import { createLogger } from '../logging'; +import type ConnectionController from '../connectionController'; +import EXTENSION_COMMANDS from '../commands'; + +const log = createLogger('participant'); + +interface ChatResult extends vscode.ChatResult { + metadata: { + command: string; + databaseName?: string; + collectionName?: string; + queryContent?: string; + description?: string; + }; + stream?: vscode.ChatResponseStream; +} + +export const CHAT_PARTICIPANT_ID = 'mongodb.participant'; +export const CHAT_PARTICIPANT_MODEL = 'gpt-4'; + +function handleEmptyQueryRequest(participantId?: string): ChatResult { + log.info('Chat request participant id', participantId); + + return { + metadata: { + command: '', + }, + errorDetails: { + message: + 'Please specify a question when using this command. Usage: @MongoDB /query find documents where "name" contains "database".', + }, + }; +} + +export function getRunnableContentFromString(responseContent: string) { + const matchedJSQueryContent = responseContent.match( + /```javascript((.|\n)*)```/ + ); + log.info('matchedJSQueryContent', matchedJSQueryContent); + + const queryContent = + matchedJSQueryContent && matchedJSQueryContent.length > 1 + ? matchedJSQueryContent[1] + : ''; + log.info('queryContent', queryContent); + return queryContent; +} + +export class ParticipantController { + _participant?: vscode.ChatParticipant; + _chatResult: ChatResult; + _connectionController: ConnectionController; + + constructor({ + connectionController, + }: { + connectionController: ConnectionController; + }) { + this._chatResult = { metadata: { command: '' } }; + this._connectionController = connectionController; + } + + createParticipant(context: vscode.ExtensionContext) { + // Chat participants appear as top-level options in the chat input + // when you type `@`, and can contribute sub-commands in the chat input + // that appear when you type `/`. + this._participant = vscode.chat.createChatParticipant( + CHAT_PARTICIPANT_ID, + this.chatHandler.bind(this) + ); + this._participant.iconPath = vscode.Uri.joinPath( + vscode.Uri.parse(context.extensionPath), + 'images', + 'mongodb.png' + ); + return this._participant; + } + + getParticipant(context: vscode.ExtensionContext) { + return this._participant || this.createParticipant(context); + } + + handleError(err: any, stream: vscode.ChatResponseStream): void { + // Making the chat request might fail because + // - model does not exist + // - user consent not given + // - quote limits exceeded + if (err instanceof vscode.LanguageModelError) { + log.error(err.message, err.code, err.cause); + if ( + err.cause instanceof Error && + err.cause.message.includes('off_topic') + ) { + stream.markdown( + vscode.l10n.t( + "I'm sorry, I can only explain computer science concepts.\n\n" + ) + ); + } + } + } + + async getChatResponseContent({ + messages, + stream, + token, + }: { + messages: vscode.LanguageModelChatMessage[]; + stream: vscode.ChatResponseStream; + token: vscode.CancellationToken; + }): Promise { + let responseContent = ''; + try { + const [model] = await vscode.lm.selectChatModels({ + vendor: 'copilot', + family: CHAT_PARTICIPANT_MODEL, + }); + if (model) { + const chatResponse = await model.sendRequest(messages, {}, token); + for await (const fragment of chatResponse.text) { + responseContent += fragment; + stream.markdown(fragment); + } + stream.markdown('\n\n'); + } + } catch (err) { + this.handleError(err, stream); + } + + return responseContent; + } + + // @MongoDB what is mongodb? + async handleGenericRequest({ + request, + context, + stream, + token, + }: { + request: vscode.ChatRequest; + context: vscode.ChatContext; + stream: vscode.ChatResponseStream; + token: vscode.CancellationToken; + }) { + const messages = [ + // eslint-disable-next-line new-cap + vscode.LanguageModelChatMessage.Assistant(`You are a MongoDB expert! + You create MongoDB queries and aggregation pipelines, + and you are very good at it. The user will provide the basis for the query. + Keep your response concise. Respond with markdown, code snippets are possible with '''javascript. + You can imagine the schema, collection, and database name. + Respond in MongoDB shell syntax using the '''javascript code style.'.`), + ]; + + context.history.map((historyItem) => { + if ( + historyItem.participant === CHAT_PARTICIPANT_ID && + historyItem instanceof vscode.ChatRequestTurn + ) { + // eslint-disable-next-line new-cap + messages.push(vscode.LanguageModelChatMessage.User(historyItem.prompt)); + } + + if ( + historyItem.participant === CHAT_PARTICIPANT_ID && + historyItem instanceof vscode.ChatResponseTurn + ) { + let res = ''; + for (const fragment of historyItem.response) { + res += fragment; + } + // eslint-disable-next-line new-cap + messages.push(vscode.LanguageModelChatMessage.Assistant(res)); + } + }); + + // eslint-disable-next-line new-cap + messages.push(vscode.LanguageModelChatMessage.User(request.prompt)); + + const abortController = new AbortController(); + token.onCancellationRequested(() => { + abortController.abort(); + }); + + const responseContent = await this.getChatResponseContent({ + messages, + stream, + token, + }); + const queryContent = getRunnableContentFromString(responseContent); + + if (!queryContent || queryContent.trim().length === 0) { + return { metadata: { command: '' } }; + } + + stream.button({ + command: EXTENSION_COMMANDS.RUN_PARTICIPANT_QUERY, + title: vscode.l10n.t('▶️ Run'), + }); + + stream.button({ + command: EXTENSION_COMMANDS.OPEN_PARTICIPANT_QUERY_IN_PLAYGROUND, + title: vscode.l10n.t('Open in playground'), + }); + + return { + metadata: { + command: '', + stream, + queryContent, + }, + }; + } + + // @MongoDB /query find all documents where the "address" has the word Broadway in it. + async handleQueryRequest({ + request, + stream, + token, + }: { + request: vscode.ChatRequest; + context?: vscode.ChatContext; + stream: vscode.ChatResponseStream; + token: vscode.CancellationToken; + }) { + if (!request.prompt || request.prompt.trim().length === 0) { + return handleEmptyQueryRequest(this._participant?.id); + } + + let dataService = this._connectionController.getActiveDataService(); + if (!dataService) { + stream.markdown( + "Looks like you aren't currently connected, first let's get you connected to the cluster we'd like to create this query to run against.\n\n" + ); + // We add a delay so the user can read the message. + // TODO: maybe there is better way to handle this. + // stream.button() does not awaits so we can't use it here. + // Followups do not support input so we can't use that either. + await new Promise((resolve) => setTimeout(resolve, 1000)); + const successfullyConnected = + await this._connectionController.changeActiveConnection(); + dataService = this._connectionController.getActiveDataService(); + + if (!dataService || !successfullyConnected) { + stream.markdown( + 'No connection for command provided. Please use a valid connection for running commands.\n\n' + ); + return { metadata: { command: '' } }; + } + + stream.markdown( + `Connected to "${this._connectionController.getActiveConnectionName()}".\n\n` + ); + } + + const abortController = new AbortController(); + token.onCancellationRequested(() => { + abortController.abort(); + }); + + const messages = [ + // eslint-disable-next-line new-cap + vscode.LanguageModelChatMessage.Assistant(`You are a MongoDB expert! + You create MongoDB queries and aggregation pipelines, + and you are very good at it. The user will provide the basis for the query. + Keep your response concise. Respond with markdown, code snippets are possible with '''javascript. + You can imagine the schema, collection, and database name. + Respond in MongoDB shell syntax using the '''javascript code style.`), + // eslint-disable-next-line new-cap + vscode.LanguageModelChatMessage.User(request.prompt), + ]; + const responseContent = await this.getChatResponseContent({ + messages, + stream, + token, + }); + const queryContent = getRunnableContentFromString(responseContent); + + if (!queryContent || queryContent.trim().length === 0) { + return { metadata: { command: '' } }; + } + + stream.button({ + command: EXTENSION_COMMANDS.RUN_PARTICIPANT_QUERY, + title: vscode.l10n.t('▶️ Run'), + }); + stream.button({ + command: EXTENSION_COMMANDS.OPEN_PARTICIPANT_QUERY_IN_PLAYGROUND, + title: vscode.l10n.t('Open in playground'), + }); + + return { + metadata: { + command: '', + stream, + queryContent, + }, + }; + } + + async chatHandler( + request: vscode.ChatRequest, + context: vscode.ChatContext, + stream: vscode.ChatResponseStream, + token: vscode.CancellationToken + ): Promise { + if (request.command === 'query') { + this._chatResult = await this.handleQueryRequest({ + request, + context, + stream, + token, + }); + return this._chatResult; + } else if (request.command === 'docs') { + // TODO: Implement this. + } else if (request.command === 'schema') { + // TODO: Implement this. + } else if (request.command === 'logs') { + // TODO: Implement this. + } + + return await this.handleGenericRequest({ + request, + context, + stream, + token, + }); + } +} diff --git a/src/templates/playgroundBasicTextTemplate.ts b/src/templates/playgroundBasicTextTemplate.ts new file mode 100644 index 000000000..2fe3e8186 --- /dev/null +++ b/src/templates/playgroundBasicTextTemplate.ts @@ -0,0 +1,16 @@ +const template = `/* global use, db */ +// MongoDB Playground +// To disable this template go to Settings | MongoDB | Use Default Template For Playground. +// Make sure you are connected to enable completions and to be able to run a playground. +// Use Ctrl+Space inside a snippet or a string literal to trigger completions. +// The result of the last command run in a playground is shown on the results panel. +// By default the first 20 documents will be returned with a cursor. +// Use 'console.log()' to print to the debug output. +// For more documentation on playgrounds please refer to +// https://www.mongodb.com/docs/mongodb-vscode/playgrounds/ +// Select the database to use. +use('CURRENT_DATABASE'); +PLAYGROUND_CONTENT +`; + +export default template; diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 14df23617..66af1b06b 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -22,6 +22,7 @@ import { StorageController } from '../../../storage'; import TelemetryService from '../../../telemetry/telemetryService'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import { ExtensionContextStub, LanguageServerControllerStub } from '../stubs'; +import { ParticipantController } from '../../../participant/participant'; const expect = chai.expect; @@ -57,6 +58,7 @@ suite('Playground Controller Test Suite', function () { let testPlaygroundController: PlaygroundController; let showErrorMessageStub: SinonStub; let sandbox: sinon.SinonSandbox; + let testParticipantController: ParticipantController; beforeEach(() => { sandbox = sinon.createSandbox(); @@ -84,11 +86,13 @@ suite('Playground Controller Test Suite', function () { testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); - languageServerControllerStub = new LanguageServerControllerStub( extensionContextStub, testStorageController ); + testParticipantController = new ParticipantController({ + connectionController: testConnectionController, + }); testPlaygroundController = new PlaygroundController({ connectionController: testConnectionController, languageServerController: languageServerControllerStub, @@ -98,6 +102,7 @@ suite('Playground Controller Test Suite', function () { activeConnectionCodeLensProvider: testActiveDBCodeLensProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, + participantController: testParticipantController, }); showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage'); sandbox.stub(testTelemetryService, 'trackNewConnection'); @@ -354,6 +359,7 @@ suite('Playground Controller Test Suite', function () { exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, + participantController: testParticipantController, }); expect(playgroundController._activeTextEditor).to.deep.equal( @@ -372,6 +378,7 @@ suite('Playground Controller Test Suite', function () { exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, + participantController: testParticipantController, }); const textFromEditor = 'var x = { name: qwerty }'; const selection = { diff --git a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts index 1dea18fbd..74f143249 100644 --- a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts +++ b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts @@ -13,6 +13,11 @@ import type { PlaygroundResult } from '../../../types/playgroundType'; import { ExportToLanguageMode } from '../../../types/playgroundType'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import { ExtensionContextStub } from '../stubs'; +import { ParticipantController } from '../../../participant/participant'; +import ConnectionController from '../../../connectionController'; +import StatusView from '../../../views/statusView'; +import StorageController from '../../../storage/storageController'; +import TelemetryService from '../../../telemetry/telemetryService'; // eslint-disable-next-line @typescript-eslint/no-var-requires const { version } = require('../../../../package.json'); @@ -33,8 +38,29 @@ suite('Playground Selected CodeAction Provider Test Suite', function () { suite('the MongoDB playground in JS', () => { const testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); const sandbox = sinon.createSandbox(); + let testStorageController: StorageController; + let testTelemetryService: TelemetryService; + let testStatusView: StatusView; + let testConnectionController: ConnectionController; + let testParticipantController: ParticipantController; beforeEach(async () => { + testStorageController = new StorageController(extensionContextStub); + testTelemetryService = new TelemetryService( + testStorageController, + extensionContextStub + ); + testStatusView = new StatusView(extensionContextStub); + testConnectionController = new ConnectionController({ + statusView: testStatusView, + storageController: testStorageController, + telemetryService: testTelemetryService, + }); + + testParticipantController = new ParticipantController({ + connectionController: testConnectionController, + }); + sandbox.replace( mdbTestExtension.testExtensionController, '_languageServerController', @@ -73,6 +99,7 @@ suite('Playground Selected CodeAction Provider Test Suite', function () { exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, + participantController: testParticipantController, }); const fakeOpenPlaygroundResult = sandbox.fake(); diff --git a/src/test/suite/language/languageServerController.test.ts b/src/test/suite/language/languageServerController.test.ts index b34944301..52a660a37 100644 --- a/src/test/suite/language/languageServerController.test.ts +++ b/src/test/suite/language/languageServerController.test.ts @@ -22,6 +22,7 @@ import { StorageController } from '../../../storage'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import TelemetryService from '../../../telemetry/telemetryService'; import { ExtensionContextStub } from '../stubs'; +import { ParticipantController } from '../../../participant/participant'; const expect = chai.expect; @@ -60,6 +61,7 @@ suite('Language Server Controller Test Suite', () => { let languageServerControllerStub: LanguageServerController; let testPlaygroundController: PlaygroundController; + let testParticipantController: ParticipantController; const sandbox = sinon.createSandbox(); @@ -67,6 +69,9 @@ suite('Language Server Controller Test Suite', () => { languageServerControllerStub = new LanguageServerController( extensionContextStub ); + testParticipantController = new ParticipantController({ + connectionController: testConnectionController, + }); testPlaygroundController = new PlaygroundController({ connectionController: testConnectionController, languageServerController: languageServerControllerStub, @@ -76,6 +81,7 @@ suite('Language Server Controller Test Suite', () => { activeConnectionCodeLensProvider: testActiveDBCodeLensProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, + participantController: testParticipantController, }); await languageServerControllerStub.startLanguageServer(); await testPlaygroundController._activeConnectionChanged();