diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts index bb9da17b9b..891d0bbe96 100644 --- a/src/features/ExternalApi.ts +++ b/src/features/ExternalApi.ts @@ -3,7 +3,6 @@ *--------------------------------------------------------*/ import * as vscode from "vscode"; import { v4 as uuidv4 } from 'uuid'; -import { LanguageClient } from "vscode-languageclient"; import { LanguageClientConsumer } from "../languageClientConsumer"; import { Logger } from "../logging"; import { SessionManager } from "../session"; @@ -15,71 +14,44 @@ export interface IExternalPowerShellDetails { architecture: string; } -export class ExternalApiFeature extends LanguageClientConsumer { - private commands: vscode.Disposable[]; +export interface IPowerShellExtensionClient { + registerExternalExtension(id: string, apiVersion?: string): string; + unregisterExternalExtension(uuid: string): boolean; + getPowerShellVersionDetails(uuid: string): Promise; +} + +/* +In order to use this in a Visual Studio Code extension, you can do the following: + +const powershellExtension = vscode.extensions.getExtension("ms-vscode.PowerShell-Preview"); +const powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; + +NOTE: At some point, we should release a helper npm package that wraps the API and does: +* Discovery of what extension they have installed: PowerShell or PowerShell Preview +* Manages session id for you + +*/ +export class ExternalApiFeature extends LanguageClientConsumer implements IPowerShellExtensionClient { private static readonly registeredExternalExtension: Map = new Map(); constructor(private sessionManager: SessionManager, private log: Logger) { super(); - this.commands = [ - /* - DESCRIPTION: - Registers your extension to allow usage of the external API. The returns - a session UUID that will need to be passed in to subsequent API calls. - - USAGE: - vscode.commands.executeCommand( - "PowerShell.RegisterExternalExtension", - "ms-vscode.PesterTestExplorer" // the name of the extension using us - "v1"); // API Version. - - RETURNS: - string session uuid - */ - vscode.commands.registerCommand("PowerShell.RegisterExternalExtension", (id: string, apiVersion: string = 'v1'): string => - this.registerExternalExtension(id, apiVersion)), - - /* - DESCRIPTION: - Unregisters a session that an extension has. This returns - true if it succeeds or throws if it fails. - - USAGE: - vscode.commands.executeCommand( - "PowerShell.UnregisterExternalExtension", - "uuid"); // the uuid from above for tracking purposes - - RETURNS: - true if it worked, otherwise throws an error. - */ - vscode.commands.registerCommand("PowerShell.UnregisterExternalExtension", (uuid: string = ""): boolean => - this.unregisterExternalExtension(uuid)), - - /* - DESCRIPTION: - This will fetch the version details of the PowerShell used to start - PowerShell Editor Services in the PowerShell extension. - - USAGE: - vscode.commands.executeCommand( - "PowerShell.GetPowerShellVersionDetails", - "uuid"); // the uuid from above for tracking purposes - - RETURNS: - An IPowerShellVersionDetails which consists of: - { - version: string; - displayVersion: string; - edition: string; - architecture: string; - } - */ - vscode.commands.registerCommand("PowerShell.GetPowerShellVersionDetails", (uuid: string = ""): Promise => - this.getPowerShellVersionDetails(uuid)), - ] } - private registerExternalExtension(id: string, apiVersion: string = 'v1'): string { + /* + DESCRIPTION: + Registers your extension to allow usage of the external API. The returns + a session UUID that will need to be passed in to subsequent API calls. + + USAGE: + powerShellExtensionClient.registerExternalExtension( + "ms-vscode.PesterTestExplorer" // the name of the extension using us + "v1"); // API Version. + + RETURNS: + string session uuid + */ + public registerExternalExtension(id: string, apiVersion: string = 'v1'): string { this.log.writeDiagnostic(`Registering extension '${id}' for use with API version '${apiVersion}'.`); for (const [_, externalExtension] of ExternalApiFeature.registeredExternalExtension) { @@ -107,7 +79,19 @@ export class ExternalApiFeature extends LanguageClientConsumer { return uuid; } - private unregisterExternalExtension(uuid: string = ""): boolean { + /* + DESCRIPTION: + Unregisters a session that an extension has. This returns + true if it succeeds or throws if it fails. + + USAGE: + powerShellExtensionClient.unregisterExternalExtension( + "uuid"); // the uuid from above for tracking purposes + + RETURNS: + true if it worked, otherwise throws an error. + */ + public unregisterExternalExtension(uuid: string = ""): boolean { this.log.writeDiagnostic(`Unregistering extension with session UUID: ${uuid}`); if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) { throw new Error(`No extension registered with session UUID: ${uuid}`); @@ -115,10 +99,28 @@ export class ExternalApiFeature extends LanguageClientConsumer { return true; } - private async getPowerShellVersionDetails(uuid: string = ""): Promise { + /* + DESCRIPTION: + This will fetch the version details of the PowerShell used to start + PowerShell Editor Services in the PowerShell extension. + + USAGE: + powerShellExtensionClient.getPowerShellVersionDetails( + "uuid"); // the uuid from above for tracking purposes + + RETURNS: + An IPowerShellVersionDetails which consists of: + { + version: string; + displayVersion: string; + edition: string; + architecture: string; + } + */ + public async getPowerShellVersionDetails(uuid: string = ""): Promise { if (!ExternalApiFeature.registeredExternalExtension.has(uuid)) { throw new Error( - "UUID provided was invalid, make sure you execute the 'PowerShell.GetPowerShellVersionDetails' command and pass in the UUID that it returns to subsequent command executions."); + "UUID provided was invalid, make sure you ran the 'powershellExtensionClient.registerExternalExtension(extensionId)' method and pass in the UUID that it returns to subsequent methods."); } // TODO: When we have more than one API version, make sure to include a check here. @@ -137,9 +139,7 @@ export class ExternalApiFeature extends LanguageClientConsumer { } public dispose() { - for (const command of this.commands) { - command.dispose(); - } + // Nothing to dispose. } } diff --git a/src/main.ts b/src/main.ts index 7901e48646..254714e713 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,7 +15,7 @@ import { DebugSessionFeature } from "./features/DebugSession"; import { ExamplesFeature } from "./features/Examples"; import { ExpandAliasFeature } from "./features/ExpandAlias"; import { ExtensionCommandsFeature } from "./features/ExtensionCommands"; -import { ExternalApiFeature } from "./features/ExternalApi"; +import { ExternalApiFeature, IPowerShellExtensionClient } from "./features/ExternalApi"; import { FindModuleFeature } from "./features/FindModule"; import { GenerateBugReportFeature } from "./features/GenerateBugReport"; import { GetCommandsFeature } from "./features/GetCommands"; @@ -54,7 +54,7 @@ const documentSelector: DocumentSelector = [ { language: "powershell", scheme: "untitled" }, ]; -export function activate(context: vscode.ExtensionContext): void { +export function activate(context: vscode.ExtensionContext): IPowerShellExtensionClient { // create telemetry reporter on extension activation telemetryReporter = new TelemetryReporter(PackageJSON.name, PackageJSON.version, AI_KEY); @@ -147,6 +147,8 @@ export function activate(context: vscode.ExtensionContext): void { new SpecifyScriptArgsFeature(context), ] + const externalApi = new ExternalApiFeature(sessionManager, logger); + // Features and command registrations that require language client languageClientConsumers = [ new ConsoleFeature(logger), @@ -162,7 +164,7 @@ export function activate(context: vscode.ExtensionContext): void { new HelpCompletionFeature(logger), new CustomViewsFeature(), new PickRunspaceFeature(), - new ExternalApiFeature(sessionManager, logger) + externalApi ]; // Notebook UI is only supported in VS Code Insiders. @@ -188,6 +190,12 @@ export function activate(context: vscode.ExtensionContext): void { if (extensionSettings.startAutomatically) { sessionManager.start(); } + + return { + registerExternalExtension: (id: string, apiVersion: string = 'v1') => externalApi.registerExternalExtension(id, apiVersion), + unregisterExternalExtension: uuid => externalApi.unregisterExternalExtension(uuid), + getPowerShellVersionDetails: uuid => externalApi.getPowerShellVersionDetails(uuid), + }; } function checkForUpdatedVersion(context: vscode.ExtensionContext, version: string) { diff --git a/test/features/ExternalApi.test.ts b/test/features/ExternalApi.test.ts index 085377174e..0e7d895307 100644 --- a/test/features/ExternalApi.test.ts +++ b/test/features/ExternalApi.test.ts @@ -3,27 +3,37 @@ *--------------------------------------------------------*/ import * as assert from "assert"; import * as vscode from "vscode"; -import { beforeEach, afterEach } from "mocha"; -import { IExternalPowerShellDetails } from "../../src/features/ExternalApi"; +import { before, beforeEach, afterEach } from "mocha"; +import { IExternalPowerShellDetails, IPowerShellExtensionClient } from "../../src/features/ExternalApi"; const testExtensionId = "ms-vscode.powershell-preview"; suite("ExternalApi feature - Registration API", () => { - test("It can register and unregister an extension", async () => { - const sessionId: string = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", testExtensionId); + let powerShellExtensionClient: IPowerShellExtensionClient; + before(async () => { + const powershellExtension = vscode.extensions.getExtension(testExtensionId); + if (!powershellExtension.isActive) { + powerShellExtensionClient = await powershellExtension.activate(); + return; + } + powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; + }); + + test("It can register and unregister an extension", () => { + const sessionId: string = powerShellExtensionClient.registerExternalExtension(testExtensionId); assert.notStrictEqual(sessionId , ""); assert.notStrictEqual(sessionId , null); assert.strictEqual( - await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId), + powerShellExtensionClient.unregisterExternalExtension(sessionId), true); }); - test("It can register and unregister an extension with a version", async () => { - const sessionId: string = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", "ms-vscode.powershell-preview", "v2"); + test("It can register and unregister an extension with a version", () => { + const sessionId: string = powerShellExtensionClient.registerExternalExtension(testExtensionId, "v2"); assert.notStrictEqual(sessionId , ""); assert.notStrictEqual(sessionId , null); assert.strictEqual( - await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId), + powerShellExtensionClient.unregisterExternalExtension(sessionId), true); }); @@ -32,41 +42,55 @@ suite("ExternalApi feature - Registration API", () => { */ test("API fails if not registered", async () => { assert.rejects( - async () => await vscode.commands.executeCommand("PowerShell.GetPowerShellVersionDetails"), - "UUID provided was invalid, make sure you execute the 'PowerShell.RegisterExternalExtension' command and pass in the UUID that it returns to subsequent command executions."); + async () => await powerShellExtensionClient.getPowerShellVersionDetails(""), + "UUID provided was invalid, make sure you ran the 'powershellExtensionClient.registerExternalExtension(extensionId)' method and pass in the UUID that it returns to subsequent methods."); }); test("It can't register the same extension twice", async () => { - const sessionId: string = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", testExtensionId); + const sessionId: string = powerShellExtensionClient.registerExternalExtension(testExtensionId); try { - assert.rejects( - async () => await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", testExtensionId), - `The extension '${testExtensionId}' is already registered.`); + assert.throws( + () => powerShellExtensionClient.registerExternalExtension(testExtensionId), + { + message: `The extension '${testExtensionId}' is already registered.` + }); } finally { - await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId); + powerShellExtensionClient.unregisterExternalExtension(sessionId); } }); test("It can't unregister an extension that isn't registered", async () => { - assert.rejects( - async () => await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", "not-real"), - `No extension registered with session UUID: not-real`); - }); + assert.throws( + () => powerShellExtensionClient.unregisterExternalExtension("not-real"), + { + message: `No extension registered with session UUID: not-real` + }); + }); }); suite("ExternalApi feature - Other APIs", () => { let sessionId: string; + let powerShellExtensionClient: IPowerShellExtensionClient; + + before(async () => { + const powershellExtension = vscode.extensions.getExtension(testExtensionId); + if (!powershellExtension.isActive) { + powerShellExtensionClient = await powershellExtension.activate(); + return; + } + powerShellExtensionClient = powershellExtension!.exports as IPowerShellExtensionClient; + }); - beforeEach(async () => { - sessionId = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", "ms-vscode.powershell-preview"); + beforeEach(() => { + sessionId = powerShellExtensionClient.registerExternalExtension("ms-vscode.powershell-preview"); }); - afterEach(async () => { - await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId); + afterEach(() => { + powerShellExtensionClient.unregisterExternalExtension(sessionId); }); test("It can get PowerShell version details", async () => { - const versionDetails: IExternalPowerShellDetails = await vscode.commands.executeCommand("PowerShell.GetPowerShellVersionDetails", sessionId); + const versionDetails: IExternalPowerShellDetails = await powerShellExtensionClient.getPowerShellVersionDetails(sessionId); assert.notStrictEqual(versionDetails.architecture, ""); assert.notStrictEqual(versionDetails.architecture, null); diff --git a/test/features/ISECompatibility.test.ts b/test/features/ISECompatibility.test.ts index aa2e32b4a3..ac5592a550 100644 --- a/test/features/ISECompatibility.test.ts +++ b/test/features/ISECompatibility.test.ts @@ -23,7 +23,7 @@ suite("ISECompatibility feature", () => { const currently = vscode.workspace.getConfiguration(iseSetting.path).get(iseSetting.name); assert.notEqual(currently, iseSetting.value); } - }); + }).timeout(10000); test("It leaves Theme after being changed after enabling ISE Mode", async () => { await vscode.commands.executeCommand("PowerShell.EnableISEMode"); assert.equal(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "PowerShell ISE"); @@ -35,5 +35,5 @@ suite("ISECompatibility feature", () => { assert.notEqual(currently, iseSetting.value); } assert.equal(vscode.workspace.getConfiguration("workbench").get("colorTheme"), "Dark+"); - }); + }).timeout(10000); });