From e00bbc156bd24661cefb70e6f2ebbe346e8ff281 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 22 Jul 2020 16:43:17 -0700 Subject: [PATCH 1/4] address Notebook breaking changes --- .github/workflows/updateNotebookApi.yml | 3 - package.json | 12 +- src/features/PowerShellNotebooks.ts | 134 +++++++++++++++------- src/main.ts | 6 +- test/.vscode/settings.json | 3 + test/features/PowerShellNotebooks.test.ts | 8 +- vscode.proposed.d.ts | 16 ++- 7 files changed, 123 insertions(+), 59 deletions(-) create mode 100644 test/.vscode/settings.json diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index f7fd1fdd89..303be5ca6c 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -79,9 +79,6 @@ jobs: # Remove the old file so it doesn't get picked up by tsc Remove-Item ./old.vscode.proposed.d.ts -Force - - name: Compile the TypeScript to check for errors - run: npm run compile - - name: Create Pull Request if: github.event_name == 'schedule' id: cpr diff --git a/package.json b/package.json index b903535ed5..1e08d5f5b0 100644 --- a/package.json +++ b/package.json @@ -218,8 +218,8 @@ } }, { - "command": "PowerShell.ShowNotebookMode", - "title": "(Preview) Show Notebook Mode", + "command": "PowerShell.EnableNotebookMode", + "title": "(Preview) Enable Notebook Mode", "category": "PowerShell", "icon": { "light": "resources/light/book.svg", @@ -227,8 +227,8 @@ } }, { - "command": "PowerShell.HideNotebookMode", - "title": "Show Text Editor", + "command": "PowerShell.DisableNotebookMode", + "title": "(Preview) Disable Notebook Mode", "category": "PowerShell", "icon": { "light": "resources/light/file-code.svg", @@ -413,12 +413,12 @@ }, { "when": "editorLangId == powershell && config.powershell.notebooks.showToggleButton", - "command": "PowerShell.ShowNotebookMode", + "command": "PowerShell.EnableNotebookMode", "group": "navigation@102" }, { "when": "resourceLangId == powershell && notebookEditorFocused", - "command": "PowerShell.HideNotebookMode", + "command": "PowerShell.DisableNotebookMode", "group": "navigation@102" } ], diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 6915d85e55..d1d9064220 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -9,34 +9,69 @@ import { LanguageClientConsumer } from "../languageClientConsumer"; import Settings = require("../settings"); import { ILogger } from "../logging"; -export class PowerShellNotebooksFeature extends LanguageClientConsumer implements vscode.NotebookContentProvider, vscode.NotebookKernel { +export class PowerShellNotebooksFeature extends LanguageClientConsumer { - private readonly showNotebookModeCommand: vscode.Disposable; - private readonly hideNotebookModeCommand: vscode.Disposable; + private readonly disposables: vscode.Disposable[]; + private readonly notebookContentProvider: vscode.NotebookContentProvider; + private readonly notebookKernel: PowerShellNotebookKernel; - private _onDidChangeNotebook = new vscode.EventEmitter(); - public onDidChangeNotebook: vscode.Event = this._onDidChangeNotebook.event; - public kernel?: vscode.NotebookKernel; + public constructor(logger: ILogger, skipRegisteringCommands?: boolean) { + super(); + this.disposables = []; + if(!skipRegisteringCommands) { + this.disposables.push(vscode.commands.registerCommand( + "PowerShell.EnableNotebookMode", + PowerShellNotebooksFeature.EnableNotebookMode)); - public label: string = "PowerShell"; - public preloads?: vscode.Uri[]; + this.disposables.push(vscode.commands.registerCommand( + "PowerShell.DisableNotebookMode", + PowerShellNotebooksFeature.DisableNotebookMode)); + } - public constructor(private logger: ILogger, skipRegisteringCommands?: boolean) { - super(); - // VS Code Notebook API uses this property for handling cell execution. - this.kernel = this; + this.notebookContentProvider = new PowerShellNotebookContentProvider(logger); + this.notebookKernel = new PowerShellNotebookKernel(); + } - if(!skipRegisteringCommands) { - this.showNotebookModeCommand = vscode.commands.registerCommand( - "PowerShell.ShowNotebookMode", - PowerShellNotebooksFeature.showNotebookMode); + public registerNotebookProviders() { + this.disposables.push(vscode.notebook.registerNotebookKernelProvider({ + viewType: "PowerShellNotebookMode" + }, this.notebookKernel)); + + this.disposables.push(vscode.notebook.registerNotebookContentProvider( + "PowerShellNotebookMode", + this.notebookContentProvider)); + } - this.hideNotebookModeCommand = vscode.commands.registerCommand( - "PowerShell.HideNotebookMode", - PowerShellNotebooksFeature.hideNotebookMode); + public dispose() { + for (const disposable of this.disposables) { + disposable.dispose(); } } + public setLanguageClient(languageClient: LanguageClient) { + this.notebookKernel.setLanguageClient(languageClient); + } + + private static async EnableNotebookMode() { + const uri = vscode.window.activeTextEditor.document.uri; + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode"); + } + + private static async DisableNotebookMode() { + const uri = vscode.notebook.activeNotebookEditor.document.uri; + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + await vscode.commands.executeCommand("vscode.openWith", uri, "default"); + } +} + +class PowerShellNotebookContentProvider implements vscode.NotebookContentProvider { + private _onDidChangeNotebook = new vscode.EventEmitter(); + public onDidChangeNotebook: vscode.Event = this._onDidChangeNotebook.event; + + public constructor(private logger: ILogger) { + } + public async openNotebook(uri: vscode.Uri, context: vscode.NotebookDocumentOpenContext): Promise { // load from backup if needed. const actualUri = context.backupId ? vscode.Uri.parse(context.backupId) : uri; @@ -186,11 +221,6 @@ export class PowerShellNotebooksFeature extends LanguageClientConsumer implement }; } - public dispose() { - this.showNotebookModeCommand.dispose(); - this.hideNotebookModeCommand.dispose(); - } - private async _save(document: vscode.NotebookDocument, targetResource: vscode.Uri, _token: vscode.CancellationToken): Promise { this.logger.writeDiagnostic(`Saving Notebook: ${targetResource.toString()}`); @@ -215,35 +245,51 @@ export class PowerShellNotebooksFeature extends LanguageClientConsumer implement await vscode.workspace.fs.writeFile(targetResource, new TextEncoder().encode(retArr.join("\n"))); } +} - private static async showNotebookMode() { - const uri = vscode.window.activeTextEditor.document.uri; - await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode"); - } +class PowerShellNotebookKernel implements vscode.NotebookKernel, vscode.NotebookKernelProvider { + public id?: string; + public label: string = "PowerShell"; + public description?: string = "The PowerShell Notebook Mode kernel that runs commands in the PowerShell Integrated Console."; + public isPreferred?: boolean; + public preloads?: vscode.Uri[]; - private static async hideNotebookMode() { - const uri = vscode.notebook.activeNotebookEditor.document.uri; - await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - await vscode.commands.executeCommand("vscode.openWith", uri, "default"); - } + private languageClient: LanguageClient; - /* - `vscode.NotebookKernel` implementations - */ - public async executeAllCells(document: vscode.NotebookDocument, token: vscode.CancellationToken): Promise { + public async executeAllCells(document: vscode.NotebookDocument): Promise { for (const cell of document.cells) { - await this.executeCell(document, cell, token); + if (cell.cellKind === vscode.CellKind.Code) { + await this.executeCell(document, cell); + } } } - public async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise { - if (token.isCancellationRequested) { - return; - } - + public async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined): Promise { await this.languageClient.sendRequest(EvaluateRequestType, { expression: cell.document.getText(), }); } + + // Since executing a cell is a "fire and forget", there's no time for the user to cancel + // any of the executing cells. We can bring this in after PSES has a better API for executing code. + public cancelCellExecution(document: vscode.NotebookDocument, cell: vscode.NotebookCell): void { + return; + } + + // Since executing a cell is a "fire and forget", there's no time for the user to cancel + // any of the executing cells. We can bring this in after PSES has a better API for executing code. + public cancelAllCellsExecution(document: vscode.NotebookDocument): void { + return; + } + + public setLanguageClient(languageClient: LanguageClient) { + this.languageClient = languageClient; + } + + /* + vscode.NotebookKernelProvider implementation + */ + public provideKernels(document: vscode.NotebookDocument, token: vscode.CancellationToken): vscode.ProviderResult { + return [this]; + } } diff --git a/src/main.ts b/src/main.ts index c3ffecefab..7901e48646 100644 --- a/src/main.ts +++ b/src/main.ts @@ -170,13 +170,17 @@ export function activate(context: vscode.ExtensionContext): void { const powerShellNotebooksFeature = new PowerShellNotebooksFeature(logger); try { - context.subscriptions.push(vscode.notebook.registerNotebookContentProvider("PowerShellNotebookMode", powerShellNotebooksFeature)); + powerShellNotebooksFeature.registerNotebookProviders(); languageClientConsumers.push(powerShellNotebooksFeature); } catch (e) { // This would happen if VS Code changes their API. powerShellNotebooksFeature.dispose(); logger.writeVerbose("Failed to register NotebookContentProvider", e); } + } else { + vscode.commands.registerCommand( + "PowerShell.EnableNotebookMode", + () => vscode.window.showWarningMessage("Notebook Mode only works in Visual Studio Code Insiders. To get it, go to: aka.ms/vscode-insiders")); } sessionManager.setLanguageClientConsumers(languageClientConsumers); diff --git a/test/.vscode/settings.json b/test/.vscode/settings.json new file mode 100644 index 0000000000..b74406eac8 --- /dev/null +++ b/test/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "powershell.helpCompletion": "LineComment" +} \ No newline at end of file diff --git a/test/features/PowerShellNotebooks.test.ts b/test/features/PowerShellNotebooks.test.ts index 800b97fb39..361e8e0ea5 100644 --- a/test/features/PowerShellNotebooks.test.ts +++ b/test/features/PowerShellNotebooks.test.ts @@ -192,10 +192,12 @@ suite("PowerShellNotebooks tests", () => { ]); const feature = new PowerShellNotebooksFeature(new MockLogger(), true); + // `notebookContentProvider` is a private property so cast the feature as `any` so we can access it. + const notebookContentProvider: vscode.NotebookContentProvider = (feature as any).notebookContentProvider; for (const [uri, expectedCells] of notebookTestData) { test(`Can open a notebook with expected cells - ${uri.fsPath}`, async () => { - const actualNotebookData = await feature.openNotebook(uri, {}); + const actualNotebookData = await notebookContentProvider.openNotebook(uri, {}); compareCells(actualNotebookData.cells, expectedCells); }); } @@ -218,8 +220,8 @@ suite("PowerShellNotebooks tests", () => { notebookSimpleMixedComments.toString()); // Save it as testFile.ps1 and reopen it using the feature. - await feature.saveNotebookAs(uri, vscode.notebook.activeNotebookEditor.document, null); - const newNotebook = await feature.openNotebook(uri, {}); + await notebookContentProvider.saveNotebookAs(uri, vscode.notebook.activeNotebookEditor.document, null); + const newNotebook = await notebookContentProvider.openNotebook(uri, {}); // Compare that saving as a file results in the same cell data as the existing one. const expectedCells = notebookTestData.get(notebookSimpleMixedComments); diff --git a/vscode.proposed.d.ts b/vscode.proposed.d.ts index 162b10011b..abce892ee3 100644 --- a/vscode.proposed.d.ts +++ b/vscode.proposed.d.ts @@ -89,6 +89,11 @@ declare module 'vscode' { Error = 4 } + export enum NotebookRunState { + Running = 1, + Idle = 2 + } + export interface NotebookCellMetadata { /** * Controls if the content of a cell is editable or not. @@ -191,6 +196,11 @@ declare module 'vscode' { * Additional attributes of the document metadata. */ custom?: { [key: string]: any }; + + /** + * The document's current run state + */ + runState?: NotebookRunState; } export interface NotebookDocument { @@ -498,8 +508,10 @@ declare module 'vscode' { description?: string; isPreferred?: boolean; preloads?: Uri[]; - executeCell(document: NotebookDocument, cell: NotebookCell, token: CancellationToken): Promise; - executeAllCells(document: NotebookDocument, token: CancellationToken): Promise; + executeCell(document: NotebookDocument, cell: NotebookCell): void; + cancelCellExecution(document: NotebookDocument, cell: NotebookCell): void; + executeAllCells(document: NotebookDocument): void; + cancelAllCellsExecution(document: NotebookDocument): void; } export interface NotebookDocumentFilter { From 45dd6b763ff60917e4682c1419e538db1be743f7 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 22 Jul 2020 16:49:03 -0700 Subject: [PATCH 2/4] remove settings.json --- .gitignore | 1 + test/.vscode/settings.json | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 test/.vscode/settings.json diff --git a/.gitignore b/.gitignore index a49ef7fae2..ad235ecf73 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ npm-debug.log *.DS_Store test-results.xml vscode.d.ts +test/.vscode/settings.json diff --git a/test/.vscode/settings.json b/test/.vscode/settings.json deleted file mode 100644 index b74406eac8..0000000000 --- a/test/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "powershell.helpCompletion": "LineComment" -} \ No newline at end of file From 3474b306768c7337dde9de548d5080e33395d8b7 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 22 Jul 2020 19:21:03 -0700 Subject: [PATCH 3/4] protect languageClient --- src/features/PowerShellNotebooks.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index d1d9064220..24e0ad2480 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -248,13 +248,26 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide } class PowerShellNotebookKernel implements vscode.NotebookKernel, vscode.NotebookKernelProvider { + private static informationMessage = "PowerShell extension has not finished starting up yet. Please try again in a few moments."; + public id?: string; public label: string = "PowerShell"; public description?: string = "The PowerShell Notebook Mode kernel that runs commands in the PowerShell Integrated Console."; public isPreferred?: boolean; public preloads?: vscode.Uri[]; - private languageClient: LanguageClient; + private _languageClient: LanguageClient; + private get languageClient(): LanguageClient { + if (!this._languageClient) { + vscode.window.showInformationMessage( + PowerShellNotebookKernel.informationMessage); + } + return this._languageClient; + } + + private set languageClient(value: LanguageClient) { + this._languageClient = value; + } public async executeAllCells(document: vscode.NotebookDocument): Promise { for (const cell of document.cells) { From 4fc638921971c06ff60bbe740f0eb06f9954554f Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Thu, 23 Jul 2020 09:03:10 -0700 Subject: [PATCH 4/4] add LanguageClient --- src/features/PowerShellNotebooks.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 24e0ad2480..f03150b2ef 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -8,6 +8,7 @@ import { EvaluateRequestType } from "./Console"; import { LanguageClientConsumer } from "../languageClientConsumer"; import Settings = require("../settings"); import { ILogger } from "../logging"; +import { LanguageClient } from "vscode-languageclient"; export class PowerShellNotebooksFeature extends LanguageClientConsumer {