From 20e37d0ac0ca21aca1575c9e66da360c8001b45c Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Apr 2020 13:10:28 -0700 Subject: [PATCH 01/35] initial changes --- .gitignore | 2 + package-lock.json | 64 +++++++++++++++++++++++ package.json | 23 +++++++-- src/controls/animatedStatusBar.ts | 7 +-- src/features/GetCommands.ts | 3 +- src/features/PowerShellNotebooks.ts | 80 +++++++++++++++++++++++++++++ src/main.ts | 8 ++- tsconfig.json | 5 +- 8 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 src/features/PowerShellNotebooks.ts diff --git a/.gitignore b/.gitignore index fab6d5d7bd..48b55d8802 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ npm-debug.log .vscode-test/ *.DS_Store test-results.xml +vscode.d.ts +vscode.proposed.d.ts diff --git a/package-lock.json b/package-lock.json index 60a479ec69..e58d620ed3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1207,6 +1207,12 @@ "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, + "kleur": { + "version": "3.0.3", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha1-p5yezIbuHOP6YgbRIWxQHxR/wH4=", + "dev": true + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -1583,6 +1589,16 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "prompts": { + "version": "2.3.2", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha1-SAVy2J7POVZtK9P+LJ/Mt8TAsGg=", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.4" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -1773,6 +1789,12 @@ } } }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha1-E01oEpd1ZDfMBcoBNw06elcQde0=", + "dev": true + }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -2149,6 +2171,48 @@ } } }, + "vscode-dts": { + "version": "0.3.1", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/vscode-dts/-/vscode-dts-0.3.1.tgz", + "integrity": "sha1-BwVUp/yar16FRmoFGb8yBnMIzfQ=", + "dev": true, + "requires": { + "minimist": "^1.2.0", + "prompts": "^2.1.0", + "rimraf": "^3.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/glob/-/glob-7.1.6.tgz", + "integrity": "sha1-FB8zuBp8JJLhJVlDB0gMRmeSeKY=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha1-8aVAK6YiCtUswSgrrBrjqkn9Bho=", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "vscode-extension-telemetry": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.6.tgz", diff --git a/package.json b/package.json index 9178125be0..a2a6e2b4a2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "publisher": "ms-vscode", "description": "(Preview) Develop PowerShell scripts in Visual Studio Code!", "engines": { - "vscode": "^1.43.0" + "vscode": "^1.44.0" }, "license": "SEE LICENSE IN LICENSE.txt", "homepage": "https://github.com/PowerShell/vscode-powershell/blob/master/README.md", @@ -62,7 +62,7 @@ "@types/semver": "~7.2.0", "@types/sinon": "~9.0.4", "@types/uuid": "^8.0.0", - "@types/vscode": "1.43.0", + "@types/vscode": "1.44.0", "mocha": "~5.2.0", "mocha-junit-reporter": "~2.0.0", "mocha-multi-reporters": "~1.1.7", @@ -72,7 +72,8 @@ "tslint": "~6.1.2", "typescript": "~3.9.3", "vsce": "~1.77.0", - "vscode-test": "~1.4.0" + "vscode-test": "~1.4.0", + "vscode-dts": "^0.3.1" }, "extensionDependencies": [ "vscode.powershell" @@ -80,7 +81,10 @@ "scripts": { "compile": "tsc -v && tsc -p ./ && tslint -p ./", "compile-watch": "tsc -watch -p ./", - "test": "node ./out/test/runTests.js" + "test": "node ./out/test/runTests.js", + "download-api": "vscode-dts dev", + "postdownload-api": "vscode-dts master", + "postinstall": "npm run download-api" }, "contributes": { "breakpoints": [ @@ -106,6 +110,17 @@ } ] }, + "notebookProvider": [ + { + "viewType": "psnb", + "displayName": "Powershell Notebook", + "selector": [ + { + "filenamePattern": "*.ps1" + } + ] + } + ], "keybindings": [ { "command": "PowerShell.ShowHelp", diff --git a/src/controls/animatedStatusBar.ts b/src/controls/animatedStatusBar.ts index dc4709a56d..a76ca302b6 100644 --- a/src/controls/animatedStatusBar.ts +++ b/src/controls/animatedStatusBar.ts @@ -7,7 +7,8 @@ import { StatusBarAlignment, StatusBarItem, ThemeColor, - window} from "vscode"; + window, + Command} from "vscode"; export function showAnimatedStatusBarMessage(text: string, hideWhenDone: Thenable): Disposable { const animatedStatusBarItem: AnimatedStatusBarItem = new AnimatedStatusBarItem(text); @@ -58,11 +59,11 @@ class AnimatedStatusBarItem implements StatusBarItem { this.statusBarItem.color = value; } - public get command(): string { + public get command(): string | Command { return this.statusBarItem.command; } - public set command(value: string) { + public set command(value: string | Command) { this.statusBarItem.command = value; } diff --git a/src/features/GetCommands.ts b/src/features/GetCommands.ts index 12fff1d453..f28b66c611 100644 --- a/src/features/GetCommands.ts +++ b/src/features/GetCommands.ts @@ -93,7 +93,8 @@ class CommandsExplorerProvider implements vscode.TreeDataProvider { } public refresh(): void { - this.didChangeTreeData.fire(); + // we pass in null because no event data in needed. + this.didChangeTreeData.fire(null); } public getTreeItem(element: Command): vscode.TreeItem { diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts new file mode 100644 index 0000000000..bd1b08e415 --- /dev/null +++ b/src/features/PowerShellNotebooks.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { IFeature, LanguageClient } from '../feature'; +import { EvaluateRequestType } from './Console'; + +export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFeature { + + private languageClient: LanguageClient; + + public dispose() { + // asedf + } + + public setLanguageClient(languageClient: LanguageClient) { + this.languageClient = languageClient; + } + + async resolveNotebook(editor: vscode.NotebookEditor): Promise { + editor.document.languages = ['powershell']; + const uri = editor.document.uri; + const data = (await vscode.workspace.fs.readFile(uri)).toString(); + const lines = data.split(/\r|\n|\r\n/g); + + await editor.edit(editBuilder => { + let curr: string[] = []; + let cellKind: vscode.CellKind | undefined; + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < lines.length; i++) { + const kind = /^\#/.exec(lines[i]) === null ? vscode.CellKind.Code : vscode.CellKind.Markdown; + + if (kind === cellKind) { + curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); + } else { + // new cell + if (cellKind !== undefined) { + editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + } + + curr = []; + cellKind = kind; + curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#/, '') : lines[i]); + } + } + + if (curr.length) { + editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + } + }); + return; + } + + async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise { + await this.languageClient.sendRequest(EvaluateRequestType, { + expression: cell.source, + }); + } + + async save(document: vscode.NotebookDocument): Promise { + const uri = document.uri; + const retArr: string[] = []; + document.cells.forEach(cell => { + if (cell.cellKind === vscode.CellKind.Code) { + retArr.push(...cell.source.split(/\r|\n|\r\n/)); + } else { + retArr.push(...cell.source.split(/\r|\n|\r\n/).map(line => `# ${line}`)); + } + }); + + try { + await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(retArr.join('\n'))) + } catch (e) { + return false; + } + + return true; + } +} diff --git a/src/main.ts b/src/main.ts index b8c681973b..b1faccabfa 100644 --- a/src/main.ts +++ b/src/main.ts @@ -34,6 +34,8 @@ import { Logger, LogLevel } from "./logging"; import { SessionManager } from "./session"; import Settings = require("./settings"); import { PowerShellLanguageId } from "./utils"; +import utils = require("./utils"); +import { PowerShellNotebooksFeature } from "./features/PowerShellNotebooks"; // The most reliable way to get the name and version of the current extension. // tslint:disable-next-line: no-var-requires @@ -133,6 +135,9 @@ export function activate(context: vscode.ExtensionContext): void { PackageJSON.version, telemetryReporter); + const powerShellNotebooksFeature = new PowerShellNotebooksFeature(); + context.subscriptions.push(vscode.notebook.registerNotebookProvider('psnb', powerShellNotebooksFeature)); + // Create features extensionFeatures = [ new ConsoleFeature(logger), @@ -156,7 +161,8 @@ export function activate(context: vscode.ExtensionContext): void { new HelpCompletionFeature(logger), new CustomViewsFeature(), new PickRunspaceFeature(), - new ExternalApiFeature(sessionManager, logger) + new ExternalApiFeature(sessionManager, logger), + powerShellNotebooksFeature ]; sessionManager.setExtensionFeatures(extensionFeatures); diff --git a/tsconfig.json b/tsconfig.json index eeb3c3222e..c2fa369ef5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,10 @@ "module": "commonjs", "target": "es6", "outDir": "out", - "lib": ["es6"], + "lib": [ + "es2017", + "DOM" + ], "sourceMap": true }, "exclude": [ From 899b40692eb528e7f67d85ab7bb98d417024550f Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 14 Apr 2020 17:33:28 -0700 Subject: [PATCH 02/35] add activation event --- package.json | 5 +++-- src/main.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a2a6e2b4a2..165c122c81 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "onCommand:PowerShell.RegisterExternalExtension", "onCommand:PowerShell.UnregisterExternalExtension", "onCommand:PowerShell.GetPowerShellVersionDetails", - "onView:PowerShellCommands" + "onView:PowerShellCommands", + "onNotebookEditor:PowerShellNotebookMode" ], "dependencies": { "node-fetch": "^2.6.0", @@ -112,7 +113,7 @@ }, "notebookProvider": [ { - "viewType": "psnb", + "viewType": "PowerShellNotebookMode", "displayName": "Powershell Notebook", "selector": [ { diff --git a/src/main.ts b/src/main.ts index b1faccabfa..b88b5cb650 100644 --- a/src/main.ts +++ b/src/main.ts @@ -136,7 +136,7 @@ export function activate(context: vscode.ExtensionContext): void { telemetryReporter); const powerShellNotebooksFeature = new PowerShellNotebooksFeature(); - context.subscriptions.push(vscode.notebook.registerNotebookProvider('psnb', powerShellNotebooksFeature)); + context.subscriptions.push(vscode.notebook.registerNotebookProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); // Create features extensionFeatures = [ From 09a18566e8f9e7d3266425973866df4d249d6483 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Sun, 26 Apr 2020 12:56:24 -0700 Subject: [PATCH 03/35] add block comment support --- package.json | 34 ++++++++++++ resources/dark/book.svg | 3 ++ resources/dark/file-code.svg | 3 ++ resources/light/book.svg | 3 ++ resources/light/file-code.svg | 3 ++ src/features/PowerShellNotebooks.ts | 82 ++++++++++++++++++++++++++--- src/main.ts | 7 ++- 7 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 resources/dark/book.svg create mode 100644 resources/dark/file-code.svg create mode 100644 resources/light/book.svg create mode 100644 resources/light/file-code.svg diff --git a/package.json b/package.json index 165c122c81..66f89f081e 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "url": "https://github.com/PowerShell/vscode-powershell.git" }, "main": "./out/src/main", + "enableProposedApi": true, "activationEvents": [ "onDebugInitialConfigurations", "onDebugResolve:PowerShell", @@ -217,6 +218,24 @@ "dark": "resources/dark/play.svg" } }, + { + "command": "PowerShell.ShowNotebookMode", + "title": "(Preview) Show Notebook Mode", + "category": "PowerShell", + "icon": { + "light": "resources/light/book.svg", + "dark": "resources/dark/book.svg" + } + }, + { + "command": "PowerShell.HideNotebookMode", + "title": "Show Text Editor", + "category": "PowerShell", + "icon": { + "light": "resources/light/file-code.svg", + "dark": "resources/dark/file-code.svg" + } + }, { "command": "PowerShell.RestartSession", "title": "Restart Current Session", @@ -392,6 +411,16 @@ "when": "editorLangId == powershell && config.powershell.buttons.showRunButtons", "command": "PowerShell.RunSelection", "group": "navigation@101" + }, + { + "when": "editorLangId == powershell && config.powershell.notebooks.showToggleButton", + "command": "PowerShell.ShowNotebookMode", + "group": "navigation@102" + }, + { + "when": "resourceLangId == powershell && notebookEditorFocused", + "command": "PowerShell.HideNotebookMode", + "group": "navigation@102" } ], "editor/title/context": [ @@ -897,6 +926,11 @@ "type": "boolean", "default": false, "description": "Show buttons in the editor titlebar for moving the panel around." + }, + "powershell.notebooks.showToggleButton": { + "type":"boolean", + "default": false, + "description": "Controls whether we show or hide the buttons to toggle Notebook mode in the top right." } } }, diff --git a/resources/dark/book.svg b/resources/dark/book.svg new file mode 100644 index 0000000000..4d433d56e3 --- /dev/null +++ b/resources/dark/book.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/dark/file-code.svg b/resources/dark/file-code.svg new file mode 100644 index 0000000000..d2a7c4eab0 --- /dev/null +++ b/resources/dark/file-code.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/book.svg b/resources/light/book.svg new file mode 100644 index 0000000000..95a115b2e0 --- /dev/null +++ b/resources/light/book.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/file-code.svg b/resources/light/file-code.svg new file mode 100644 index 0000000000..cb42220622 --- /dev/null +++ b/resources/light/file-code.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index bd1b08e415..5b7f1793b7 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -8,10 +8,36 @@ import { EvaluateRequestType } from './Console'; export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFeature { + private readonly showNotebookModeCommand: vscode.Disposable; + private readonly hideNotebookModeCommand: vscode.Disposable; private languageClient: LanguageClient; + private blockCommentCells: Set; + + constructor() { + const editorAssociations = vscode.workspace.getConfiguration("workbench").get("editorAssociations"); + + if(!editorAssociations.some((value) => + value.filenamePattern === "*.ps1" && value.viewType === "default")) { + editorAssociations.push({ + viewType: "default", + filenamePattern: "*.ps1" + }); + } + + vscode.workspace.getConfiguration("workbench").update("editorAssociations", editorAssociations, true); + + this.showNotebookModeCommand = vscode.commands.registerCommand("PowerShell.ShowNotebookMode", async () => { + await vscode.commands.executeCommand("vscode.openWith", vscode.window.activeTextEditor.document.uri, "PowerShellNotebookMode"); + }); + + this.hideNotebookModeCommand = vscode.commands.registerCommand("PowerShell.HideNotebookMode", async () => { + await vscode.commands.executeCommand("vscode.openWith", vscode.notebook.activeNotebookDocument.uri, "default"); + }); + } public dispose() { - // asedf + this.showNotebookModeCommand.dispose(); + this.hideNotebookModeCommand.dispose(); } public setLanguageClient(languageClient: LanguageClient) { @@ -26,27 +52,63 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea await editor.edit(editBuilder => { let curr: string[] = []; + this.blockCommentCells = new Set(); + let currentCellIndex: number = 0; let cellKind: vscode.CellKind | undefined; + let insideBlockComment: boolean = false; + // tslint:disable-next-line: prefer-for-of for (let i = 0; i < lines.length; i++) { - const kind = /^\#/.exec(lines[i]) === null ? vscode.CellKind.Code : vscode.CellKind.Markdown; + // Handle block comments + if (insideBlockComment) { + if (lines[i] === "#>") { + insideBlockComment = false; + editBuilder.insert(0, curr, 'markdown', vscode.CellKind.Markdown, [], undefined) + this.blockCommentCells.add(currentCellIndex); + currentCellIndex++; + curr = []; + cellKind = undefined; + continue; + } else { + curr.push(lines[i]); + continue; + } + } else if (lines[i] === "<#") { + // Insert what we saw leading up to this. + editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + currentCellIndex++; + + // reset state because we're starting a new Markdown cell. + curr = []; + cellKind = vscode.CellKind.Markdown; + insideBlockComment = true; + continue; + } + + // Handle everything else (regular comments and code) + // If a line starts with # it's a comment + const kind: vscode.CellKind = lines[i].startsWith("#") ? vscode.CellKind.Markdown : vscode.CellKind.Code; + // If this line is a continuation of the previous cell type, then add this line to curr. if (kind === cellKind) { - curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); + curr.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, '') : lines[i]); } else { - // new cell + // If cellKind is not undifined, then we can add the cell we've just computed to the editBuilder. if (cellKind !== undefined) { editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + currentCellIndex++; } + // set initial new cell state curr = []; cellKind = kind; - curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#/, '') : lines[i]); + curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); } } if (curr.length) { editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + currentCellIndex++; } }); return; @@ -61,11 +123,17 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea async save(document: vscode.NotebookDocument): Promise { const uri = document.uri; const retArr: string[] = []; - document.cells.forEach(cell => { + document.cells.forEach((cell, index) => { if (cell.cellKind === vscode.CellKind.Code) { retArr.push(...cell.source.split(/\r|\n|\r\n/)); } else { - retArr.push(...cell.source.split(/\r|\n|\r\n/).map(line => `# ${line}`)); + if (this.blockCommentCells.has(index)) { + retArr.push("<#"); + retArr.push(...cell.source.split(/\r|\n|\r\n/)); + retArr.push("#>"); + } else { + retArr.push(...cell.source.split(/\r|\n|\r\n/).map(line => `# ${line}`)); + } } }); diff --git a/src/main.ts b/src/main.ts index b88b5cb650..b016309d29 100644 --- a/src/main.ts +++ b/src/main.ts @@ -136,7 +136,12 @@ export function activate(context: vscode.ExtensionContext): void { telemetryReporter); const powerShellNotebooksFeature = new PowerShellNotebooksFeature(); - context.subscriptions.push(vscode.notebook.registerNotebookProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); + + try { + context.subscriptions.push(vscode.notebook.registerNotebookProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); + } catch (e) { + // This would happen in VS Code changes their API. + } // Create features extensionFeatures = [ From 0d1faa115c49efbba7aed67f9f3c58538ce0ec1f Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Sun, 26 Apr 2020 19:25:52 -0700 Subject: [PATCH 04/35] add saveMarkdownCellsAs setting --- package.json | 9 +++++++++ src/features/HelpCompletion.ts | 4 ++-- src/features/PowerShellNotebooks.ts | 25 +++++++++++++++++++++---- src/settings.ts | 15 +++++++++++++-- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 66f89f081e..8d5064e2ba 100644 --- a/package.json +++ b/package.json @@ -931,6 +931,15 @@ "type":"boolean", "default": false, "description": "Controls whether we show or hide the buttons to toggle Notebook mode in the top right." + }, + "powershell.notebooks.saveMarkdownCellsAs": { + "type":"string", + "enum": [ + "BlockComment", + "LineComment" + ], + "default": "BlockComment", + "description": "Controls what new markdown cells in Notebook Mode get saved as in the PowerShell file." } } }, diff --git a/src/features/HelpCompletion.ts b/src/features/HelpCompletion.ts index 97255bfc67..8395ad058c 100644 --- a/src/features/HelpCompletion.ts +++ b/src/features/HelpCompletion.ts @@ -33,7 +33,7 @@ export class HelpCompletionFeature implements IFeature { constructor(private log: Logger) { this.settings = Settings.load(); - if (this.settings.helpCompletion !== Settings.HelpCompletion.Disabled) { + if (this.settings.helpCompletion !== Settings.CommentType.Disabled) { this.helpCompletionProvider = new HelpCompletionProvider(); const subscriptions = []; workspace.onDidChangeTextDocument(this.onEvent, this, subscriptions); @@ -166,7 +166,7 @@ class HelpCompletionProvider { const result = await this.langClient.sendRequest(CommentHelpRequestType, { documentUri: doc.uri.toString(), triggerPosition: triggerStartPos, - blockComment: this.settings.helpCompletion === Settings.HelpCompletion.BlockComment, + blockComment: this.settings.helpCompletion === Settings.CommentType.BlockComment, }); if (!(result && result.content)) { diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 5b7f1793b7..25d4099cbd 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -3,15 +3,17 @@ *--------------------------------------------------------*/ import * as vscode from 'vscode'; +import { CommentType } from '../settings'; import { IFeature, LanguageClient } from '../feature'; import { EvaluateRequestType } from './Console'; +import Settings = require("../settings"); export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFeature { private readonly showNotebookModeCommand: vscode.Disposable; private readonly hideNotebookModeCommand: vscode.Disposable; private languageClient: LanguageClient; - private blockCommentCells: Set; + private blockCommentCells: Map; constructor() { const editorAssociations = vscode.workspace.getConfiguration("workbench").get("editorAssociations"); @@ -52,7 +54,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea await editor.edit(editBuilder => { let curr: string[] = []; - this.blockCommentCells = new Set(); + this.blockCommentCells = new Map(); let currentCellIndex: number = 0; let cellKind: vscode.CellKind | undefined; let insideBlockComment: boolean = false; @@ -64,7 +66,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea if (lines[i] === "#>") { insideBlockComment = false; editBuilder.insert(0, curr, 'markdown', vscode.CellKind.Markdown, [], undefined) - this.blockCommentCells.add(currentCellIndex); + this.blockCommentCells.set(currentCellIndex, CommentType.BlockComment); currentCellIndex++; curr = []; cellKind = undefined; @@ -76,6 +78,9 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea } else if (lines[i] === "<#") { // Insert what we saw leading up to this. editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + if (cellKind === vscode.CellKind.Markdown) { + this.blockCommentCells.set(currentCellIndex, CommentType.LineComment); + } currentCellIndex++; // reset state because we're starting a new Markdown cell. @@ -96,6 +101,9 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea // If cellKind is not undifined, then we can add the cell we've just computed to the editBuilder. if (cellKind !== undefined) { editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + if (cellKind === vscode.CellKind.Markdown) { + this.blockCommentCells.set(currentCellIndex, CommentType.LineComment); + } currentCellIndex++; } @@ -108,6 +116,9 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea if (curr.length) { editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) + if (cellKind === vscode.CellKind.Markdown) { + this.blockCommentCells.set(currentCellIndex, insideBlockComment ? CommentType.BlockComment : CommentType.LineComment); + } currentCellIndex++; } }); @@ -127,7 +138,13 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea if (cell.cellKind === vscode.CellKind.Code) { retArr.push(...cell.source.split(/\r|\n|\r\n/)); } else { - if (this.blockCommentCells.has(index)) { + // First honor the comment type of the cell if it already has one. + // If not, use the user setting. + const commentKind = this.blockCommentCells.has(index) + ? this.blockCommentCells.get(index) + : Settings.load().notebooks.saveMarkdownCellsAs; + + if (commentKind === CommentType.BlockComment) { retArr.push("<#"); retArr.push(...cell.source.split(/\r|\n|\r\n/)); retArr.push("#>"); diff --git a/src/settings.ts b/src/settings.ts index 9cd5883084..97c6ff963c 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -21,7 +21,7 @@ enum PipelineIndentationStyle { None, } -export enum HelpCompletion { +export enum CommentType { Disabled = "Disabled", BlockComment = "BlockComment", LineComment = "LineComment", @@ -102,6 +102,7 @@ export interface ISettings { pester?: IPesterSettings; buttons?: IButtonSettings; cwd?: string; + notebooks?: INotebooksSettings; } export interface IStartAsLoginShellSettings { @@ -132,6 +133,10 @@ export interface IButtonSettings { showPanelMovementButtons?: boolean; } +export interface INotebooksSettings { + saveMarkdownCellsAs?: CommentType; +} + export function load(): ISettings { const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration( @@ -210,6 +215,10 @@ export function load(): ISettings { debugOutputVerbosity: "Diagnostic", }; + const defaultNotebooksSettings: INotebooksSettings = { + saveMarkdownCellsAs: CommentType.BlockComment, + } + return { startAutomatically: configuration.get("startAutomatically", true), @@ -230,7 +239,7 @@ export function load(): ISettings { enableProfileLoading: configuration.get("enableProfileLoading", false), helpCompletion: - configuration.get("helpCompletion", HelpCompletion.BlockComment), + configuration.get("helpCompletion", CommentType.BlockComment), scriptAnalysis: configuration.get("scriptAnalysis", defaultScriptAnalysisSettings), debugging: @@ -251,6 +260,8 @@ export function load(): ISettings { configuration.get("pester", defaultPesterSettings), buttons: configuration.get("buttons", defaultButtonSettings), + notebooks: + configuration.get("notebooks", defaultNotebooksSettings), startAsLoginShell: // tslint:disable-next-line // We follow the same convention as VS Code - https://github.com/microsoft/vscode/blob/ff00badd955d6cfcb8eab5f25f3edc86b762f49f/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts#L105-L107 From 21ff7647bcc24c8ca1ca7cccbbe08182a4944e63 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 28 Apr 2020 08:20:24 -0700 Subject: [PATCH 05/35] set metadata in new cells on save --- package.json | 2 +- src/features/PowerShellNotebooks.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 8d5064e2ba..bc72e1ca17 100644 --- a/package.json +++ b/package.json @@ -933,7 +933,7 @@ "description": "Controls whether we show or hide the buttons to toggle Notebook mode in the top right." }, "powershell.notebooks.saveMarkdownCellsAs": { - "type":"string", + "type": "string", "enum": [ "BlockComment", "LineComment" diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 25d4099cbd..bfb7c4a888 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -140,9 +140,14 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea } else { // First honor the comment type of the cell if it already has one. // If not, use the user setting. - const commentKind = this.blockCommentCells.has(index) - ? this.blockCommentCells.get(index) - : Settings.load().notebooks.saveMarkdownCellsAs; + let commentKind: CommentType; + if (this.blockCommentCells.has(index)) { + commentKind = this.blockCommentCells.get(index); + } else { + commentKind = Settings.load().notebooks.saveMarkdownCellsAs; + // We need to update the metadata for new cells. + this.blockCommentCells.set(index, commentKind); + } if (commentKind === CommentType.BlockComment) { retArr.push("<#"); From 8f8d9aa4f99a5a4adb5936b1a405b871a81086e2 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 28 Apr 2020 08:49:03 -0700 Subject: [PATCH 06/35] actually work across files --- src/features/PowerShellNotebooks.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index bfb7c4a888..f904a1fa80 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -13,7 +13,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea private readonly showNotebookModeCommand: vscode.Disposable; private readonly hideNotebookModeCommand: vscode.Disposable; private languageClient: LanguageClient; - private blockCommentCells: Map; + private cellCommentTypes: Map> = new Map>(); constructor() { const editorAssociations = vscode.workspace.getConfiguration("workbench").get("editorAssociations"); @@ -49,12 +49,18 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea async resolveNotebook(editor: vscode.NotebookEditor): Promise { editor.document.languages = ['powershell']; const uri = editor.document.uri; + + if (!this.cellCommentTypes.has(uri)) { + this.cellCommentTypes.set(uri, new Map()); + } + const notebookCommentTypes = this.cellCommentTypes.get(uri); + + const data = (await vscode.workspace.fs.readFile(uri)).toString(); const lines = data.split(/\r|\n|\r\n/g); await editor.edit(editBuilder => { let curr: string[] = []; - this.blockCommentCells = new Map(); let currentCellIndex: number = 0; let cellKind: vscode.CellKind | undefined; let insideBlockComment: boolean = false; @@ -66,7 +72,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea if (lines[i] === "#>") { insideBlockComment = false; editBuilder.insert(0, curr, 'markdown', vscode.CellKind.Markdown, [], undefined) - this.blockCommentCells.set(currentCellIndex, CommentType.BlockComment); + notebookCommentTypes.set(currentCellIndex, CommentType.BlockComment); currentCellIndex++; curr = []; cellKind = undefined; @@ -79,7 +85,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea // Insert what we saw leading up to this. editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) if (cellKind === vscode.CellKind.Markdown) { - this.blockCommentCells.set(currentCellIndex, CommentType.LineComment); + notebookCommentTypes.set(currentCellIndex, CommentType.LineComment); } currentCellIndex++; @@ -102,7 +108,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea if (cellKind !== undefined) { editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) if (cellKind === vscode.CellKind.Markdown) { - this.blockCommentCells.set(currentCellIndex, CommentType.LineComment); + notebookCommentTypes.set(currentCellIndex, CommentType.LineComment); } currentCellIndex++; } @@ -117,7 +123,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea if (curr.length) { editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) if (cellKind === vscode.CellKind.Markdown) { - this.blockCommentCells.set(currentCellIndex, insideBlockComment ? CommentType.BlockComment : CommentType.LineComment); + notebookCommentTypes.set(currentCellIndex, insideBlockComment ? CommentType.BlockComment : CommentType.LineComment); } currentCellIndex++; } @@ -133,6 +139,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea async save(document: vscode.NotebookDocument): Promise { const uri = document.uri; + const notebookCommentTypes = this.cellCommentTypes.get(uri); const retArr: string[] = []; document.cells.forEach((cell, index) => { if (cell.cellKind === vscode.CellKind.Code) { @@ -141,12 +148,12 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea // First honor the comment type of the cell if it already has one. // If not, use the user setting. let commentKind: CommentType; - if (this.blockCommentCells.has(index)) { - commentKind = this.blockCommentCells.get(index); + if (notebookCommentTypes.has(index)) { + commentKind = notebookCommentTypes.get(index); } else { commentKind = Settings.load().notebooks.saveMarkdownCellsAs; // We need to update the metadata for new cells. - this.blockCommentCells.set(index, commentKind); + notebookCommentTypes.set(index, commentKind); } if (commentKind === CommentType.BlockComment) { From 92a56d89b9f8728bf777d65b8abac5e456fcb97b Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 15 May 2020 14:23:22 -0700 Subject: [PATCH 07/35] updated API with metadata --- package-lock.json | 12 +- package.json | 2 +- src/features/PowerShellNotebooks.ts | 217 +++++++++++++++------------- src/main.ts | 2 +- 4 files changed, 124 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index e58d620ed3..521999475b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,9 +115,9 @@ } }, "@types/node": { - "version": "12.12.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.39.tgz", - "integrity": "sha512-pADGfwnDkr6zagDwEiCVE4yQrv7XDkoeVa4OfA9Ju/zRTk6YNDLGtQbkdL4/56mCQQCs4AhNrBIag6jrp7ZuOg==", + "version": "14.0.1", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/@types/node/-/@types/node-14.0.1.tgz", + "integrity": "sha1-XZPgoJnNCs1e89W948CG4fSf9ow=", "dev": true }, "@types/node-fetch": { @@ -167,9 +167,9 @@ "dev": true }, "@types/vscode": { - "version": "1.43.0", - "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/@types/vscode/-/@types/vscode-1.43.0.tgz", - "integrity": "sha1-IiduYANMaTszEX8QaP+qwOiVIts=", + "version": "1.44.0", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/@types/vscode/-/@types/vscode-1.44.0.tgz", + "integrity": "sha1-Yuz+PQ44lC/OVWV02lTuEBPHdbc=", "dev": true }, "acorn": { diff --git a/package.json b/package.json index bc72e1ca17..90ed213334 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@types/glob": "^7.1.2", "@types/mocha": "~7.0.2", "@types/mock-fs": "~4.10.0", - "@types/node": "~12.12.39", + "@types/node": "~14.0.1", "@types/node-fetch": "~2.5.7", "@types/rewire": "~2.5.28", "@types/semver": "~7.2.0", diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index f904a1fa80..5458caa8bb 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -8,12 +8,11 @@ import { IFeature, LanguageClient } from '../feature'; import { EvaluateRequestType } from './Console'; import Settings = require("../settings"); -export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFeature { +export class PowerShellNotebooksFeature implements vscode.NotebookContentProvider, IFeature { private readonly showNotebookModeCommand: vscode.Disposable; private readonly hideNotebookModeCommand: vscode.Disposable; private languageClient: LanguageClient; - private cellCommentTypes: Map> = new Map>(); constructor() { const editorAssociations = vscode.workspace.getConfiguration("workbench").get("editorAssociations"); @@ -37,109 +36,116 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea }); } - public dispose() { - this.showNotebookModeCommand.dispose(); - this.hideNotebookModeCommand.dispose(); - } - - public setLanguageClient(languageClient: LanguageClient) { - this.languageClient = languageClient; - } - - async resolveNotebook(editor: vscode.NotebookEditor): Promise { - editor.document.languages = ['powershell']; - const uri = editor.document.uri; - - if (!this.cellCommentTypes.has(uri)) { - this.cellCommentTypes.set(uri, new Map()); - } - const notebookCommentTypes = this.cellCommentTypes.get(uri); - - + async openNotebook(uri: vscode.Uri): Promise { const data = (await vscode.workspace.fs.readFile(uri)).toString(); const lines = data.split(/\r|\n|\r\n/g); - await editor.edit(editBuilder => { - let curr: string[] = []; - let currentCellIndex: number = 0; - let cellKind: vscode.CellKind | undefined; - let insideBlockComment: boolean = false; - - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < lines.length; i++) { - // Handle block comments - if (insideBlockComment) { - if (lines[i] === "#>") { - insideBlockComment = false; - editBuilder.insert(0, curr, 'markdown', vscode.CellKind.Markdown, [], undefined) - notebookCommentTypes.set(currentCellIndex, CommentType.BlockComment); - currentCellIndex++; - curr = []; - cellKind = undefined; - continue; - } else { - curr.push(lines[i]); - continue; - } - } else if (lines[i] === "<#") { - // Insert what we saw leading up to this. - editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) - if (cellKind === vscode.CellKind.Markdown) { - notebookCommentTypes.set(currentCellIndex, CommentType.LineComment); - } - currentCellIndex++; + const notebookData: vscode.NotebookData = { + languages: ["powershell"], + cells: [], + metadata: {} + } + + let curr: string[] = []; + let cellKind: vscode.CellKind | undefined; + let insideBlockComment: boolean = false; + + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < lines.length; i++) { + // Handle block comments + if (insideBlockComment) { + if (lines[i] === "#>") { + insideBlockComment = false; + + notebookData.cells.push({ + cellKind: vscode.CellKind.Markdown, + language: "markdown", + outputs: [], + source: curr.join("\n"), + metadata: { + custom: { + commentType: CommentType.BlockComment + } + } + }); - // reset state because we're starting a new Markdown cell. curr = []; - cellKind = vscode.CellKind.Markdown; - insideBlockComment = true; + cellKind = undefined; continue; - } - - // Handle everything else (regular comments and code) - // If a line starts with # it's a comment - const kind: vscode.CellKind = lines[i].startsWith("#") ? vscode.CellKind.Markdown : vscode.CellKind.Code; - - // If this line is a continuation of the previous cell type, then add this line to curr. - if (kind === cellKind) { - curr.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, '') : lines[i]); } else { - // If cellKind is not undifined, then we can add the cell we've just computed to the editBuilder. - if (cellKind !== undefined) { - editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) - if (cellKind === vscode.CellKind.Markdown) { - notebookCommentTypes.set(currentCellIndex, CommentType.LineComment); + curr.push(lines[i]); + continue; + } + } else if (lines[i] === "<#") { + // Insert what we saw leading up to this. + notebookData.cells.push({ + cellKind: cellKind!, + language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', + outputs: [], + source: curr.join("\n"), + metadata: { + custom: { + commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, } - currentCellIndex++; } + }); - // set initial new cell state - curr = []; - cellKind = kind; - curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); - } + // reset state because we're starting a new Markdown cell. + curr = []; + cellKind = vscode.CellKind.Markdown; + insideBlockComment = true; + continue; } - if (curr.length) { - editBuilder.insert(0, curr, cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', cellKind!, [], undefined) - if (cellKind === vscode.CellKind.Markdown) { - notebookCommentTypes.set(currentCellIndex, insideBlockComment ? CommentType.BlockComment : CommentType.LineComment); + // Handle everything else (regular comments and code) + // If a line starts with # it's a comment + const kind: vscode.CellKind = lines[i].startsWith("#") ? vscode.CellKind.Markdown : vscode.CellKind.Code; + + // If this line is a continuation of the previous cell type, then add this line to curr. + if (kind === cellKind) { + curr.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, '') : lines[i]); + } else { + // If cellKind is not undifined, then we can add the cell we've just computed to the editBuilder. + if (cellKind !== undefined) { + notebookData.cells.push({ + cellKind: cellKind!, + language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', + outputs: [], + source: curr.join("\n"), + metadata: { + custom: { + commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, + } + } + }); } - currentCellIndex++; + + // set initial new cell state + curr = []; + cellKind = kind; + curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); } - }); - return; - } + } - async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise { - await this.languageClient.sendRequest(EvaluateRequestType, { - expression: cell.source, - }); + if (curr.length) { + notebookData.cells.push({ + cellKind: cellKind!, + language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', + outputs: [], + source: curr.join("\n"), + metadata: { + custom: { + commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, + } + } + }); + } + + return notebookData; } - async save(document: vscode.NotebookDocument): Promise { + async saveNotebook(document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { const uri = document.uri; - const notebookCommentTypes = this.cellCommentTypes.get(uri); const retArr: string[] = []; document.cells.forEach((cell, index) => { if (cell.cellKind === vscode.CellKind.Code) { @@ -147,14 +153,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea } else { // First honor the comment type of the cell if it already has one. // If not, use the user setting. - let commentKind: CommentType; - if (notebookCommentTypes.has(index)) { - commentKind = notebookCommentTypes.get(index); - } else { - commentKind = Settings.load().notebooks.saveMarkdownCellsAs; - // We need to update the metadata for new cells. - notebookCommentTypes.set(index, commentKind); - } + const commentKind = cell.metadata.custom?.commentType || Settings.load().notebooks.saveMarkdownCellsAs; if (commentKind === CommentType.BlockComment) { retArr.push("<#"); @@ -166,12 +165,28 @@ export class PowerShellNotebooksFeature implements vscode.NotebookProvider, IFea } }); - try { - await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(retArr.join('\n'))) - } catch (e) { - return false; - } + await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(retArr.join('\n'))); + } + + saveNotebookAs(targetResource: vscode.Uri, document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { + throw new Error("Method not implemented."); + } + + onDidChangeNotebook: vscode.Event; + kernel?: vscode.NotebookKernel; + + public dispose() { + this.showNotebookModeCommand.dispose(); + this.hideNotebookModeCommand.dispose(); + } - return true; + public setLanguageClient(languageClient: LanguageClient) { + this.languageClient = languageClient; + } + + async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise { + await this.languageClient.sendRequest(EvaluateRequestType, { + expression: cell.source, + }); } } diff --git a/src/main.ts b/src/main.ts index b016309d29..373ed38a26 100644 --- a/src/main.ts +++ b/src/main.ts @@ -138,7 +138,7 @@ export function activate(context: vscode.ExtensionContext): void { const powerShellNotebooksFeature = new PowerShellNotebooksFeature(); try { - context.subscriptions.push(vscode.notebook.registerNotebookProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); + context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); } catch (e) { // This would happen in VS Code changes their API. } From 4de2e622328c9378715808769e2355c5da132693 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 29 Jun 2020 20:40:20 -0700 Subject: [PATCH 08/35] refactor based on option and new Kernel type --- package.json | 5 +- src/features/PowerShellNotebooks.ts | 104 ++++++++++++++++++---------- 2 files changed, 69 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 90ed213334..5ee5ee6251 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,8 @@ { "filenamePattern": "*.ps1" } - ] + ], + "priority": "option" } ], "keybindings": [ @@ -928,7 +929,7 @@ "description": "Show buttons in the editor titlebar for moving the panel around." }, "powershell.notebooks.showToggleButton": { - "type":"boolean", + "type": "boolean", "default": false, "description": "Controls whether we show or hide the buttons to toggle Notebook mode in the top right." }, diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 5458caa8bb..df67e05965 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -8,36 +8,39 @@ import { IFeature, LanguageClient } from '../feature'; import { EvaluateRequestType } from './Console'; import Settings = require("../settings"); -export class PowerShellNotebooksFeature implements vscode.NotebookContentProvider, IFeature { +export class PowerShellNotebooksFeature implements vscode.NotebookContentProvider, vscode.NotebookKernel, IFeature { private readonly showNotebookModeCommand: vscode.Disposable; private readonly hideNotebookModeCommand: vscode.Disposable; private languageClient: LanguageClient; - constructor() { - const editorAssociations = vscode.workspace.getConfiguration("workbench").get("editorAssociations"); - - if(!editorAssociations.some((value) => - value.filenamePattern === "*.ps1" && value.viewType === "default")) { - editorAssociations.push({ - viewType: "default", - filenamePattern: "*.ps1" - }); - } - - vscode.workspace.getConfiguration("workbench").update("editorAssociations", editorAssociations, true); + private _onDidChangeNotebook = new vscode.EventEmitter(); + onDidChangeNotebook: vscode.Event = this._onDidChangeNotebook.event; + kernel?: vscode.NotebookKernel; + constructor() { + this.kernel = this; this.showNotebookModeCommand = vscode.commands.registerCommand("PowerShell.ShowNotebookMode", async () => { - await vscode.commands.executeCommand("vscode.openWith", vscode.window.activeTextEditor.document.uri, "PowerShellNotebookMode"); + const uri = vscode.window.activeTextEditor.document.uri; + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode"); }); this.hideNotebookModeCommand = vscode.commands.registerCommand("PowerShell.HideNotebookMode", async () => { - await vscode.commands.executeCommand("vscode.openWith", vscode.notebook.activeNotebookDocument.uri, "default"); + const uri = vscode.notebook.activeNotebookEditor.document.uri; + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await vscode.commands.executeCommand("vscode.openWith", uri, "default"); }); } - async openNotebook(uri: vscode.Uri): Promise { - const data = (await vscode.workspace.fs.readFile(uri)).toString(); + label: string = 'PowerShell'; + preloads?: vscode.Uri[]; + + async openNotebook(uri: vscode.Uri, context: vscode.NotebookDocumentOpenContext): Promise { + // load from backup if needed. + const actualUri = context.backupId ? vscode.Uri.parse(context.backupId) : uri; + + const data = (await vscode.workspace.fs.readFile(actualUri)).toString(); const lines = data.split(/\r|\n|\r\n/g); const notebookData: vscode.NotebookData = { @@ -144,12 +147,43 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide return notebookData; } - async saveNotebook(document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { - const uri = document.uri; + resolveNotebook(document: vscode.NotebookDocument, webview: { readonly onDidReceiveMessage: vscode.Event; postMessage(message: any): Thenable; asWebviewUri(localResource: vscode.Uri): vscode.Uri; }): Promise { + return; + } + + saveNotebook(document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { + return this._save(document, document.uri, cancellation); + } + + saveNotebookAs(targetResource: vscode.Uri, document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { + return this._save(document, targetResource, cancellation); + } + + async backupNotebook(document: vscode.NotebookDocument, context: vscode.NotebookDocumentBackupContext, cancellation: vscode.CancellationToken): Promise { + await this._save(document, context.destination, cancellation); + + return { + id: context.destination.toString(), + delete: () => { + vscode.workspace.fs.delete(context.destination); + } + }; + } + + public dispose() { + this.showNotebookModeCommand.dispose(); + this.hideNotebookModeCommand.dispose(); + } + + public setLanguageClient(languageClient: LanguageClient) { + this.languageClient = languageClient; + } + + async _save(document: vscode.NotebookDocument, targetResource: vscode.Uri, _token: vscode.CancellationToken): Promise { const retArr: string[] = []; document.cells.forEach((cell, index) => { if (cell.cellKind === vscode.CellKind.Code) { - retArr.push(...cell.source.split(/\r|\n|\r\n/)); + retArr.push(...cell.document.getText().split(/\r|\n|\r\n/)); } else { // First honor the comment type of the cell if it already has one. // If not, use the user setting. @@ -157,36 +191,30 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide if (commentKind === CommentType.BlockComment) { retArr.push("<#"); - retArr.push(...cell.source.split(/\r|\n|\r\n/)); + retArr.push(...cell.document.getText().split(/\r|\n|\r\n/)); retArr.push("#>"); } else { - retArr.push(...cell.source.split(/\r|\n|\r\n/).map(line => `# ${line}`)); + retArr.push(...cell.document.getText().split(/\r|\n|\r\n/).map(line => `# ${line}`)); } } }); - await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(retArr.join('\n'))); - } - - saveNotebookAs(targetResource: vscode.Uri, document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { - throw new Error("Method not implemented."); - } - - onDidChangeNotebook: vscode.Event; - kernel?: vscode.NotebookKernel; - - public dispose() { - this.showNotebookModeCommand.dispose(); - this.hideNotebookModeCommand.dispose(); + await vscode.workspace.fs.writeFile(targetResource, new TextEncoder().encode(retArr.join('\n'))); } - public setLanguageClient(languageClient: LanguageClient) { - this.languageClient = languageClient; + async executeAllCells(document: vscode.NotebookDocument, token: vscode.CancellationToken): Promise { + for (const cell of document.cells) { + await this.executeCell(document, cell, token); + } } async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise { + if (token.isCancellationRequested) { + return; + } + await this.languageClient.sendRequest(EvaluateRequestType, { - expression: cell.source, + expression: cell.document.getText(), }); } } From 79e67b5c0ade4fcda5806e388ac0fd0a4f5ba23c Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:21:05 -0700 Subject: [PATCH 09/35] add github action and proposed --- .github/workflows/updateNotebookApi.yml | 90 ++++ .gitignore | 1 - package.json | 4 +- src/main.ts | 1 + vscode.proposed.d.ts | 539 ++++++++++++++++++++++++ 5 files changed, 631 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/updateNotebookApi.yml create mode 100644 vscode.proposed.d.ts diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml new file mode 100644 index 0000000000..83819fa6b0 --- /dev/null +++ b/.github/workflows/updateNotebookApi.yml @@ -0,0 +1,90 @@ +name: "Update Notebook API" + +on: + push: + pull_request: + schedule: + - cron: '0 22 * * *' + +jobs: + Update-Notebook-Api: + + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Setup Node.js environment + uses: actions/setup-node@v2.1.0 + + # Runs a single command using the runners shell + - name: Run a one-line script + run: mv ./vscode.proposed.d.ts ./old.vscode.proposed.d.ts + + # Runs a single command using the runners shell + - name: Run a one-line script + run: npm install + + # Runs a single command using the runners shell + - name: Run a one-line script + run: npm run download-api + + # Runs a set of commands using the runners shell + - name: Run a multi-line script + run: | + $fullFile = [System.Collections.Generic.List[string]]@() + $dts = Get-Content ./vscode.proposed.d.ts + + $index = 0 + while ($dts[$index] -notmatch "declare module 'vscode' {") { + $fullFile += $dts[$index] + $index++ + } + + $fullFile += $dts[$index] + + for ( $i = $index; $i -lt $dts.Length; $i++) { + if($dts[$i] -match '//#region @rebornix: Notebook') { + $index = $i + break + } + } + + while ($dts[$index] -notmatch "//#endregion") { + $fullFile += $dts[$index] + $index++ + } + + $fullFile += $dts[$index] + $fullFile += '}' + + $fullFile | Set-Content ./vscode.proposed.d.ts + + $oldFile = Get-Content ./old.vscode.proposed.d.ts -Raw + $newFile = Get-Content ./vscode.proposed.d.ts -Raw + if ($oldFile -eq $newFile) { + $env:SKIP_PR = '1' + } + + - name: Run + run: npm run compile + + - name: Create Pull Request + if: env.SKIP_PR != '1' + id: cpr + uses: peter-evans/create-pull-request@v2 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + commit-message: "Update Notebook dts" + committer: GitHub + author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + title: "Update Notebook dts" + assignees: TylerLeonhardt + reviewers: TylerLeonhardt + base: notebook-ui-support + draft: false + branch: powershell-notebook-patch-${{ env.BRANCH_POSTFIX }} + labels: Created_by_Action diff --git a/.gitignore b/.gitignore index 48b55d8802..a49ef7fae2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,3 @@ npm-debug.log *.DS_Store test-results.xml vscode.d.ts -vscode.proposed.d.ts diff --git a/package.json b/package.json index 5ee5ee6251..e683f88672 100644 --- a/package.json +++ b/package.json @@ -84,9 +84,7 @@ "compile": "tsc -v && tsc -p ./ && tslint -p ./", "compile-watch": "tsc -watch -p ./", "test": "node ./out/test/runTests.js", - "download-api": "vscode-dts dev", - "postdownload-api": "vscode-dts master", - "postinstall": "npm run download-api" + "download-api": "vscode-dts dev" }, "contributes": { "breakpoints": [ diff --git a/src/main.ts b/src/main.ts index 373ed38a26..010781f395 100644 --- a/src/main.ts +++ b/src/main.ts @@ -141,6 +141,7 @@ export function activate(context: vscode.ExtensionContext): void { context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); } catch (e) { // This would happen in VS Code changes their API. + logger.writeVerbose("Failed to register NotebookContentProvider", e); } // Create features diff --git a/vscode.proposed.d.ts b/vscode.proposed.d.ts new file mode 100644 index 0000000000..5392658ada --- /dev/null +++ b/vscode.proposed.d.ts @@ -0,0 +1,539 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MarkdownString } from 'vscode'; + +/** + * This is the place for API experiments and proposals. + * These API are NOT stable and subject to change. They are only available in the Insiders + * distribution and CANNOT be used in published extensions. + * + * To test these API in local environment: + * - Use Insiders release of VS Code. + * - Add `"enableProposedApi": true` to your package.json. + * - Copy this file to your project. + */ + +declare module 'vscode' { + //#region @rebornix: Notebook + + export enum CellKind { + Markdown = 1, + Code = 2 + } + + export enum CellOutputKind { + Text = 1, + Error = 2, + Rich = 3 + } + + export interface CellStreamOutput { + outputKind: CellOutputKind.Text; + text: string; + } + + export interface CellErrorOutput { + outputKind: CellOutputKind.Error; + /** + * Exception Name + */ + ename: string; + /** + * Exception Value + */ + evalue: string; + /** + * Exception call stack + */ + traceback: string[]; + } + + export interface NotebookCellOutputMetadata { + /** + * Additional attributes of a cell metadata. + */ + custom?: { [key: string]: any }; + } + + export interface CellDisplayOutput { + outputKind: CellOutputKind.Rich; + /** + * { mime_type: value } + * + * Example: + * ```json + * { + * "outputKind": vscode.CellOutputKind.Rich, + * "data": { + * "text/html": [ + * "

Hello

" + * ], + * "text/plain": [ + * "" + * ] + * } + * } + */ + data: { [key: string]: any; }; + + readonly metadata?: NotebookCellOutputMetadata; + } + + export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput; + + export enum NotebookCellRunState { + Running = 1, + Idle = 2, + Success = 3, + Error = 4 + } + + export interface NotebookCellMetadata { + /** + * Controls if the content of a cell is editable or not. + */ + editable?: boolean; + + /** + * Controls if the cell is executable. + * This metadata is ignored for markdown cell. + */ + runnable?: boolean; + + /** + * Controls if the cell has a margin to support the breakpoint UI. + * This metadata is ignored for markdown cell. + */ + breakpointMargin?: boolean; + + /** + * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. + * Defaults to true. + */ + hasExecutionOrder?: boolean; + + /** + * The order in which this cell was executed. + */ + executionOrder?: number; + + /** + * A status message to be shown in the cell's status bar + */ + statusMessage?: string; + + /** + * The cell's current run state + */ + runState?: NotebookCellRunState; + + /** + * If the cell is running, the time at which the cell started running + */ + runStartTime?: number; + + /** + * The total duration of the cell's last run + */ + lastRunDuration?: number; + + /** + * Additional attributes of a cell metadata. + */ + custom?: { [key: string]: any }; + } + + export interface NotebookCell { + readonly notebook: NotebookDocument; + readonly uri: Uri; + readonly cellKind: CellKind; + readonly document: TextDocument; + language: string; + outputs: CellOutput[]; + metadata: NotebookCellMetadata; + } + + export interface NotebookDocumentMetadata { + /** + * Controls if users can add or delete cells + * Defaults to true + */ + editable?: boolean; + + /** + * Controls whether the full notebook can be run at once. + * Defaults to true + */ + runnable?: boolean; + + /** + * Default value for [cell editable metadata](#NotebookCellMetadata.editable). + * Defaults to true. + */ + cellEditable?: boolean; + + /** + * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). + * Defaults to true. + */ + cellRunnable?: boolean; + + /** + * Default value for [cell hasExecutionOrder metadata](#NotebookCellMetadata.hasExecutionOrder). + * Defaults to true. + */ + cellHasExecutionOrder?: boolean; + + displayOrder?: GlobPattern[]; + + /** + * Additional attributes of the document metadata. + */ + custom?: { [key: string]: any }; + } + + export interface NotebookDocument { + readonly uri: Uri; + readonly fileName: string; + readonly viewType: string; + readonly isDirty: boolean; + readonly cells: NotebookCell[]; + languages: string[]; + displayOrder?: GlobPattern[]; + metadata: NotebookDocumentMetadata; + } + + export interface NotebookConcatTextDocument { + isClosed: boolean; + dispose(): void; + onDidChange: Event; + version: number; + getText(): string; + getText(range: Range): string; + + offsetAt(position: Position): number; + positionAt(offset: number): Position; + validateRange(range: Range): Range; + validatePosition(position: Position): Position; + + locationAt(positionOrRange: Position | Range): Location; + positionAt(location: Location): Position; + contains(uri: Uri): boolean + } + + export interface NotebookEditorCellEdit { + insert(index: number, content: string | string[], language: string, type: CellKind, outputs: CellOutput[], metadata: NotebookCellMetadata | undefined): void; + delete(index: number): void; + } + + export interface NotebookEditor { + /** + * The document associated with this notebook editor. + */ + readonly document: NotebookDocument; + + /** + * The primary selected cell on this notebook editor. + */ + readonly selection?: NotebookCell; + + /** + * The column in which this editor shows. + */ + viewColumn?: ViewColumn; + + /** + * Whether the panel is active (focused by the user). + */ + readonly active: boolean; + + /** + * Whether the panel is visible. + */ + readonly visible: boolean; + + /** + * Fired when the panel is disposed. + */ + readonly onDidDispose: Event; + + /** + * Fired when the output hosting webview posts a message. + */ + readonly onDidReceiveMessage: Event; + /** + * Post a message to the output hosting webview. + * + * Messages are only delivered if the editor is live. + * + * @param message Body of the message. This must be a string or other json serilizable object. + */ + postMessage(message: any): Thenable; + + /** + * Convert a uri for the local file system to one that can be used inside outputs webview. + */ + asWebviewUri(localResource: Uri): Uri; + + edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; + } + + export interface NotebookOutputSelector { + mimeTypes?: string[]; + } + + export interface NotebookRenderRequest { + output: CellDisplayOutput; + mimeType: string; + outputId: string; + } + + export interface NotebookOutputRenderer { + /** + * + * @returns HTML fragment. We can probably return `CellOutput` instead of string ? + * + */ + render(document: NotebookDocument, request: NotebookRenderRequest): string; + + /** + * Call before HTML from the renderer is executed, and will be called for + * every editor associated with notebook documents where the renderer + * is or was used. + * + * The communication object will only send and receive messages to the + * render API, retrieved via `acquireNotebookRendererApi`, acquired with + * this specific renderer's ID. + * + * If you need to keep an association between the communication object + * and the document for use in the `render()` method, you can use a WeakMap. + */ + resolveNotebook?(document: NotebookDocument, communication: NotebookCommunication): void; + + readonly preloads?: Uri[]; + } + + export interface NotebookCellsChangeData { + readonly start: number; + readonly deletedCount: number; + readonly deletedItems: NotebookCell[]; + readonly items: NotebookCell[]; + } + + export interface NotebookCellsChangeEvent { + + /** + * The affected document. + */ + readonly document: NotebookDocument; + readonly changes: ReadonlyArray; + } + + export interface NotebookCellMoveEvent { + + /** + * The affected document. + */ + readonly document: NotebookDocument; + readonly index: number; + readonly newIndex: number; + } + + export interface NotebookCellOutputsChangeEvent { + + /** + * The affected document. + */ + readonly document: NotebookDocument; + readonly cells: NotebookCell[]; + } + + export interface NotebookCellLanguageChangeEvent { + + /** + * The affected document. + */ + readonly document: NotebookDocument; + readonly cell: NotebookCell; + readonly language: string; + } + + export interface NotebookCellData { + readonly cellKind: CellKind; + readonly source: string; + language: string; + outputs: CellOutput[]; + metadata: NotebookCellMetadata; + } + + export interface NotebookData { + readonly cells: NotebookCellData[]; + readonly languages: string[]; + readonly metadata: NotebookDocumentMetadata; + } + + interface NotebookDocumentContentChangeEvent { + + /** + * The document that the edit is for. + */ + readonly document: NotebookDocument; + } + + interface NotebookDocumentEditEvent { + + /** + * The document that the edit is for. + */ + readonly document: NotebookDocument; + + /** + * Undo the edit operation. + * + * This is invoked by VS Code when the user undoes this edit. To implement `undo`, your + * extension should restore the document and editor to the state they were in just before this + * edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`. + */ + undo(): Thenable | void; + + /** + * Redo the edit operation. + * + * This is invoked by VS Code when the user redoes this edit. To implement `redo`, your + * extension should restore the document and editor to the state they were in just after this + * edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`. + */ + redo(): Thenable | void; + + /** + * Display name describing the edit. + * + * This will be shown to users in the UI for undo/redo operations. + */ + readonly label?: string; + } + + interface NotebookDocumentBackup { + /** + * Unique identifier for the backup. + * + * This id is passed back to your extension in `openCustomDocument` when opening a custom editor from a backup. + */ + readonly id: string; + + /** + * Delete the current backup. + * + * This is called by VS Code when it is clear the current backup is no longer needed, such as when a new backup + * is made or when the file is saved. + */ + delete(): void; + } + + interface NotebookDocumentBackupContext { + readonly destination: Uri; + } + + interface NotebookDocumentOpenContext { + readonly backupId?: string; + } + + /** + * Communication object passed to the {@link NotebookContentProvider} and + * {@link NotebookOutputRenderer} to communicate with the webview. + */ + export interface NotebookCommunication { + /** + * ID of the editor this object communicates with. A single notebook + * document can have multiple attached webviews and editors, when the + * notebook is split for instance. The editor ID lets you differentiate + * between them. + */ + readonly editorId: string; + + /** + * Fired when the output hosting webview posts a message. + */ + readonly onDidReceiveMessage: Event; + /** + * Post a message to the output hosting webview. + * + * Messages are only delivered if the editor is live. + * + * @param message Body of the message. This must be a string or other json serilizable object. + */ + postMessage(message: any): Thenable; + + /** + * Convert a uri for the local file system to one that can be used inside outputs webview. + */ + asWebviewUri(localResource: Uri): Uri; + } + + export interface NotebookContentProvider { + openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise; + resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise; + saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; + saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; + readonly onDidChangeNotebook: Event; + backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise; + + kernel?: NotebookKernel; + } + + export interface NotebookKernel { + label: string; + preloads?: Uri[]; + executeCell(document: NotebookDocument, cell: NotebookCell, token: CancellationToken): Promise; + executeAllCells(document: NotebookDocument, token: CancellationToken): Promise; + } + + export namespace notebook { + export function registerNotebookContentProvider( + notebookType: string, + provider: NotebookContentProvider + ): Disposable; + + export function registerNotebookKernel( + id: string, + selectors: GlobPattern[], + kernel: NotebookKernel + ): Disposable; + + export function registerNotebookOutputRenderer( + id: string, + outputSelector: NotebookOutputSelector, + renderer: NotebookOutputRenderer + ): Disposable; + + export const onDidOpenNotebookDocument: Event; + export const onDidCloseNotebookDocument: Event; + + /** + * All currently known notebook documents. + */ + export const notebookDocuments: ReadonlyArray; + + export let visibleNotebookEditors: NotebookEditor[]; + export const onDidChangeVisibleNotebookEditors: Event; + + export let activeNotebookEditor: NotebookEditor | undefined; + export const onDidChangeActiveNotebookEditor: Event; + export const onDidChangeNotebookCells: Event; + export const onDidChangeCellOutputs: Event; + export const onDidChangeCellLanguage: Event; + /** + * Create a document that is the concatenation of all notebook cells. By default all code-cells are included + * but a selector can be provided to narrow to down the set of cells. + * + * @param notebook + * @param selector + */ + export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; + } + + //#endregion +} From 0d51a03e41c23d436c4d923b8fa54fe186673bc9 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:24:34 -0700 Subject: [PATCH 10/35] add pwsh --- .github/workflows/updateNotebookApi.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index 83819fa6b0..af0ae8e35d 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -10,6 +10,9 @@ jobs: Update-Notebook-Api: runs-on: ubuntu-latest + defaults: + run: + shell: pwsh # Steps represent a sequence of tasks that will be executed as part of the job steps: From e4dd7615b8848805aa3655c7385123bea3240a57 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:26:08 -0700 Subject: [PATCH 11/35] remove old --- .github/workflows/updateNotebookApi.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index af0ae8e35d..2765545961 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -71,6 +71,9 @@ jobs: $env:SKIP_PR = '1' } + # Or else tsc will complain. + Remove-Item ./old.vscode.proposed.d.ts -Force + - name: Run run: npm run compile From 1e06c3a7425b6fa96e28be39164b34f4ce8a93fb Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:31:39 -0700 Subject: [PATCH 12/35] trigger PR --- .github/workflows/updateNotebookApi.yml | 5 +---- vscode.proposed.d.ts | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index 2765545961..e0c3711498 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -67,9 +67,6 @@ jobs: $oldFile = Get-Content ./old.vscode.proposed.d.ts -Raw $newFile = Get-Content ./vscode.proposed.d.ts -Raw - if ($oldFile -eq $newFile) { - $env:SKIP_PR = '1' - } # Or else tsc will complain. Remove-Item ./old.vscode.proposed.d.ts -Force @@ -78,7 +75,7 @@ jobs: run: npm run compile - name: Create Pull Request - if: env.SKIP_PR != '1' + if: github.event_name == 'schedule' id: cpr uses: peter-evans/create-pull-request@v2 env: diff --git a/vscode.proposed.d.ts b/vscode.proposed.d.ts index 5392658ada..f534603577 100644 --- a/vscode.proposed.d.ts +++ b/vscode.proposed.d.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MarkdownString } from 'vscode'; - /** * This is the place for API experiments and proposals. * These API are NOT stable and subject to change. They are only available in the Insiders From 00d18e45bdbbad24bceadc1f2ce1372929791fc3 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:34:24 -0700 Subject: [PATCH 13/35] add log --- .github/workflows/updateNotebookApi.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index e0c3711498..22dd0cc8f9 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -68,6 +68,10 @@ jobs: $oldFile = Get-Content ./old.vscode.proposed.d.ts -Raw $newFile = Get-Content ./vscode.proposed.d.ts -Raw + if($oldFile -ne $newFile) { + Write-Host "New changes detected!" + } + # Or else tsc will complain. Remove-Item ./old.vscode.proposed.d.ts -Force From c2d5ee8e7f8ee3a4e4f9a0396ede8441a3ee15a0 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:36:44 -0700 Subject: [PATCH 14/35] force cron to trigger --- .github/workflows/updateNotebookApi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index 22dd0cc8f9..fc376d9108 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -4,7 +4,7 @@ on: push: pull_request: schedule: - - cron: '0 22 * * *' + - cron: '48 18 * * *' jobs: Update-Notebook-Api: From 20b4639478c08b1fdd7def26b00185cd964c5071 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:41:44 -0700 Subject: [PATCH 15/35] time --- .github/workflows/updateNotebookApi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index fc376d9108..d2258304e8 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -4,7 +4,7 @@ on: push: pull_request: schedule: - - cron: '48 18 * * *' + - cron: '42 18 * * *' jobs: Update-Notebook-Api: From 3179eba8e83cae00bbca7fca71f5a4a6f8072530 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:44:08 -0700 Subject: [PATCH 16/35] time --- .github/workflows/updateNotebookApi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index d2258304e8..9914012343 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -4,7 +4,7 @@ on: push: pull_request: schedule: - - cron: '42 18 * * *' + - cron: 45 18 * * * jobs: Update-Notebook-Api: From 1d177cd96651568433c804e1597bd57256084027 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 11:51:50 -0700 Subject: [PATCH 17/35] time --- .github/workflows/updateNotebookApi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index 9914012343..d3215ef209 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -4,7 +4,7 @@ on: push: pull_request: schedule: - - cron: 45 18 * * * + - cron: 53 18 * * * jobs: Update-Notebook-Api: From ba409a7efd9cd145acc02c4dbe1056b71549c7c4 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 12:15:46 -0700 Subject: [PATCH 18/35] better titles --- .github/workflows/updateNotebookApi.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index d3215ef209..4104004205 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -4,7 +4,7 @@ on: push: pull_request: schedule: - - cron: 53 18 * * * + - cron: 0 22 * * * jobs: Update-Notebook-Api: @@ -23,19 +23,19 @@ jobs: uses: actions/setup-node@v2.1.0 # Runs a single command using the runners shell - - name: Run a one-line script - run: mv ./vscode.proposed.d.ts ./old.vscode.proposed.d.ts + - name: Rename proposed dts to old + run: Move-Item ./vscode.proposed.d.ts ./old.vscode.proposed.d.ts # Runs a single command using the runners shell - - name: Run a one-line script + - name: npm install run: npm install # Runs a single command using the runners shell - - name: Run a one-line script + - name: Get latest proposed dts run: npm run download-api # Runs a set of commands using the runners shell - - name: Run a multi-line script + - name: Generate new dts and compare it with the old one run: | $fullFile = [System.Collections.Generic.List[string]]@() $dts = Get-Content ./vscode.proposed.d.ts @@ -75,7 +75,7 @@ jobs: # Or else tsc will complain. Remove-Item ./old.vscode.proposed.d.ts -Force - - name: Run + - name: Compile the TypeScript to check for errors run: npm run compile - name: Create Pull Request From 679e0fc56812c4f0a6fcdb828182ce7c6b85e0d7 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 12:20:01 -0700 Subject: [PATCH 19/35] powershell comments --- .github/workflows/updateNotebookApi.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index 4104004205..e20704fe70 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -37,17 +37,21 @@ jobs: # Runs a set of commands using the runners shell - name: Generate new dts and compare it with the old one run: | + # This will contain the content of our new file $fullFile = [System.Collections.Generic.List[string]]@() $dts = Get-Content ./vscode.proposed.d.ts + # First add everything up to the declare statement $index = 0 while ($dts[$index] -notmatch "declare module 'vscode' {") { $fullFile += $dts[$index] $index++ } + # Add the declare statement $fullFile += $dts[$index] + # Find the Notebook region start index for ( $i = $index; $i -lt $dts.Length; $i++) { if($dts[$i] -match '//#region @rebornix: Notebook') { $index = $i @@ -55,24 +59,29 @@ jobs: } } + # Add everything until the endregion to the new file while ($dts[$index] -notmatch "//#endregion") { $fullFile += $dts[$index] $index++ } + # Add the endregion line and ending brace line $fullFile += $dts[$index] $fullFile += '}' + # Overwrite the file with the new content $fullFile | Set-Content ./vscode.proposed.d.ts + # Get the old and new files' raw text $oldFile = Get-Content ./old.vscode.proposed.d.ts -Raw $newFile = Get-Content ./vscode.proposed.d.ts -Raw + # Compare them and log if they are different if($oldFile -ne $newFile) { Write-Host "New changes detected!" } - # Or else tsc will complain. + # 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 From 7ca3cca4288461cde286b23cefefa3431167e5b6 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 12:22:31 -0700 Subject: [PATCH 20/35] remove not needed comments --- .github/workflows/updateNotebookApi.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index e20704fe70..6cf1b24bf7 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -14,7 +14,6 @@ jobs: run: shell: pwsh - # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 @@ -22,19 +21,15 @@ jobs: - name: Setup Node.js environment uses: actions/setup-node@v2.1.0 - # Runs a single command using the runners shell - name: Rename proposed dts to old run: Move-Item ./vscode.proposed.d.ts ./old.vscode.proposed.d.ts - # Runs a single command using the runners shell - name: npm install run: npm install - # Runs a single command using the runners shell - name: Get latest proposed dts run: npm run download-api - # Runs a set of commands using the runners shell - name: Generate new dts and compare it with the old one run: | # This will contain the content of our new file From 6314f6eee9331056ae413f7ffa0ae76763760e7a Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 12:26:30 -0700 Subject: [PATCH 21/35] use githubrunnumber --- .github/workflows/updateNotebookApi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index 6cf1b24bf7..0d9f93fbab 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -97,5 +97,5 @@ jobs: reviewers: TylerLeonhardt base: notebook-ui-support draft: false - branch: powershell-notebook-patch-${{ env.BRANCH_POSTFIX }} + branch: powershell-notebook-patch-${{ env.GITHUB_RUN_NUMBER }} labels: Created_by_Action From be22cf1a4a8885c99ec213d3deb90337208d7b03 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 16:01:20 -0700 Subject: [PATCH 22/35] use run_id --- .github/workflows/updateNotebookApi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index 0d9f93fbab..a7ea47b675 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -97,5 +97,5 @@ jobs: reviewers: TylerLeonhardt base: notebook-ui-support draft: false - branch: powershell-notebook-patch-${{ env.GITHUB_RUN_NUMBER }} + branch: powershell-notebook-patch-${{ github.run_id }} labels: Created_by_Action From b832aa996b8254c40da45fecc51e02adeacc77d8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2020 15:56:19 -0700 Subject: [PATCH 23/35] Update Notebook dts (#3) Co-authored-by: TylerLeonhardt --- vscode.proposed.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vscode.proposed.d.ts b/vscode.proposed.d.ts index f534603577..5392658ada 100644 --- a/vscode.proposed.d.ts +++ b/vscode.proposed.d.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { MarkdownString } from 'vscode'; + /** * This is the place for API experiments and proposals. * These API are NOT stable and subject to change. They are only available in the Insiders From f33ca0326f996eadfbfdff36e7b7a0852bb284a9 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 1 Jul 2020 16:46:36 -0700 Subject: [PATCH 24/35] better check for registering --- src/features/PowerShellNotebooks.ts | 2 +- src/main.ts | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index df67e05965..2cbceeb9f4 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -181,7 +181,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide async _save(document: vscode.NotebookDocument, targetResource: vscode.Uri, _token: vscode.CancellationToken): Promise { const retArr: string[] = []; - document.cells.forEach((cell, index) => { + document.cells.forEach((cell) => { if (cell.cellKind === vscode.CellKind.Code) { retArr.push(...cell.document.getText().split(/\r|\n|\r\n/)); } else { diff --git a/src/main.ts b/src/main.ts index 010781f395..5c91ebfdd9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -135,15 +135,6 @@ export function activate(context: vscode.ExtensionContext): void { PackageJSON.version, telemetryReporter); - const powerShellNotebooksFeature = new PowerShellNotebooksFeature(); - - try { - context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); - } catch (e) { - // This would happen in VS Code changes their API. - logger.writeVerbose("Failed to register NotebookContentProvider", e); - } - // Create features extensionFeatures = [ new ConsoleFeature(logger), @@ -167,10 +158,23 @@ export function activate(context: vscode.ExtensionContext): void { new HelpCompletionFeature(logger), new CustomViewsFeature(), new PickRunspaceFeature(), - new ExternalApiFeature(sessionManager, logger), - powerShellNotebooksFeature + new ExternalApiFeature(sessionManager, logger) ]; + // Notebook UI is only supported in VS Code Insiders. + if(vscode.env.uriScheme === "vscode-insiders") { + const powerShellNotebooksFeature = new PowerShellNotebooksFeature(); + + try { + context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); + extensionFeatures.push(powerShellNotebooksFeature); + } catch (e) { + // This would happen in VS Code changes their API. + powerShellNotebooksFeature.dispose(); + logger.writeVerbose("Failed to register NotebookContentProvider", e); + } + } + sessionManager.setExtensionFeatures(extensionFeatures); if (extensionSettings.startAutomatically) { From 2bf03403e296dfa6d614c2801412721734d37e7f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2020 15:21:34 -0700 Subject: [PATCH 25/35] Update Notebook dts (#4) Co-authored-by: TylerLeonhardt --- vscode.proposed.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vscode.proposed.d.ts b/vscode.proposed.d.ts index 5392658ada..561c4fb08f 100644 --- a/vscode.proposed.d.ts +++ b/vscode.proposed.d.ts @@ -474,6 +474,10 @@ declare module 'vscode' { } export interface NotebookContentProvider { + /** + * Content providers should always use [file system providers](#FileSystemProvider) to + * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. + */ openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise; resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; From 4a0b2d784594485fc7e1892c1ccc0cceccd67836 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Jul 2020 11:32:59 -0700 Subject: [PATCH 26/35] rob's feedback --- package-lock.json | 6 +- package.json | 4 +- src/features/GetCommands.ts | 3 +- src/features/PowerShellNotebooks.ts | 109 +++++++++++++++++----------- src/main.ts | 2 +- 5 files changed, 73 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 521999475b..f0332b3285 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,9 +115,9 @@ } }, "@types/node": { - "version": "14.0.1", - "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/@types/node/-/@types/node-14.0.1.tgz", - "integrity": "sha1-XZPgoJnNCs1e89W948CG4fSf9ow=", + "version": "14.0.22", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/@types/node/-/@types/node-14.0.22.tgz", + "integrity": "sha1-I+pNiBic7H1Y+ea2b3hrIV62G9w=", "dev": true }, "@types/node-fetch": { diff --git a/package.json b/package.json index e683f88672..c43b88cb87 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@types/glob": "^7.1.2", "@types/mocha": "~7.0.2", "@types/mock-fs": "~4.10.0", - "@types/node": "~14.0.1", + "@types/node": "~14.0.22", "@types/node-fetch": "~2.5.7", "@types/rewire": "~2.5.28", "@types/semver": "~7.2.0", @@ -75,7 +75,7 @@ "typescript": "~3.9.3", "vsce": "~1.77.0", "vscode-test": "~1.4.0", - "vscode-dts": "^0.3.1" + "vscode-dts": "~0.3.1" }, "extensionDependencies": [ "vscode.powershell" diff --git a/src/features/GetCommands.ts b/src/features/GetCommands.ts index f28b66c611..12fff1d453 100644 --- a/src/features/GetCommands.ts +++ b/src/features/GetCommands.ts @@ -93,8 +93,7 @@ class CommandsExplorerProvider implements vscode.TreeDataProvider { } public refresh(): void { - // we pass in null because no event data in needed. - this.didChangeTreeData.fire(null); + this.didChangeTreeData.fire(); } public getTreeItem(element: Command): vscode.TreeItem { diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 2cbceeb9f4..f35ad796aa 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -15,28 +15,26 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide private languageClient: LanguageClient; private _onDidChangeNotebook = new vscode.EventEmitter(); - onDidChangeNotebook: vscode.Event = this._onDidChangeNotebook.event; - kernel?: vscode.NotebookKernel; + public onDidChangeNotebook: vscode.Event = this._onDidChangeNotebook.event; + public kernel?: vscode.NotebookKernel; - constructor() { + public label: string = 'PowerShell'; + public preloads?: vscode.Uri[]; + + public constructor() { + // VS Code Notebook API uses this property for handling cell execution. this.kernel = this; - this.showNotebookModeCommand = vscode.commands.registerCommand("PowerShell.ShowNotebookMode", async () => { - const uri = vscode.window.activeTextEditor.document.uri; - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode"); - }); - this.hideNotebookModeCommand = vscode.commands.registerCommand("PowerShell.HideNotebookMode", async () => { - const uri = vscode.notebook.activeNotebookEditor.document.uri; - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await vscode.commands.executeCommand("vscode.openWith", uri, "default"); - }); - } + this.showNotebookModeCommand = vscode.commands.registerCommand( + "PowerShell.ShowNotebookMode", + PowerShellNotebooksFeature.showNotebookMode); - label: string = 'PowerShell'; - preloads?: vscode.Uri[]; + this.hideNotebookModeCommand = vscode.commands.registerCommand( + "PowerShell.HideNotebookMode", + PowerShellNotebooksFeature.hideNotebookMode); + } - async openNotebook(uri: vscode.Uri, context: vscode.NotebookDocumentOpenContext): Promise { + 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; @@ -49,22 +47,26 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide metadata: {} } - let curr: string[] = []; + let currentCellSource: string[] = []; let cellKind: vscode.CellKind | undefined; let insideBlockComment: boolean = false; + // Iterate through all lines in a document (aka ps1 file) and group the lines + // into cells (markdown or code) that will be rendered in Notebook mode. // tslint:disable-next-line: prefer-for-of for (let i = 0; i < lines.length; i++) { // Handle block comments if (insideBlockComment) { if (lines[i] === "#>") { + // We've reached the end of a block comment, + // push a markdown cell. insideBlockComment = false; notebookData.cells.push({ cellKind: vscode.CellKind.Markdown, language: "markdown", outputs: [], - source: curr.join("\n"), + source: currentCellSource.join("\n"), metadata: { custom: { commentType: CommentType.BlockComment @@ -72,20 +74,22 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide } }); - curr = []; + currentCellSource = []; cellKind = undefined; continue; - } else { - curr.push(lines[i]); - continue; } + + // If we're still in a block comment, push the line and continue. + currentCellSource.push(lines[i]); + continue; } else if (lines[i] === "<#") { - // Insert what we saw leading up to this. + // If we found the start of a block comment, + // insert what we saw leading up to this. notebookData.cells.push({ cellKind: cellKind!, language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', outputs: [], - source: curr.join("\n"), + source: currentCellSource.join("\n"), metadata: { custom: { commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, @@ -94,7 +98,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide }); // reset state because we're starting a new Markdown cell. - curr = []; + currentCellSource = []; cellKind = vscode.CellKind.Markdown; insideBlockComment = true; continue; @@ -104,17 +108,17 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide // If a line starts with # it's a comment const kind: vscode.CellKind = lines[i].startsWith("#") ? vscode.CellKind.Markdown : vscode.CellKind.Code; - // If this line is a continuation of the previous cell type, then add this line to curr. + // If this line is a continuation of the previous cell type, then add this line to the current cell source. if (kind === cellKind) { - curr.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, '') : lines[i]); + currentCellSource.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, '') : lines[i]); } else { - // If cellKind is not undifined, then we can add the cell we've just computed to the editBuilder. + // If cellKind is not undifined, then we can add the cell we've just computed. if (cellKind !== undefined) { notebookData.cells.push({ cellKind: cellKind!, language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', outputs: [], - source: curr.join("\n"), + source: currentCellSource.join("\n"), metadata: { custom: { commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, @@ -124,18 +128,21 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide } // set initial new cell state - curr = []; + currentCellSource = []; cellKind = kind; - curr.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); + currentCellSource.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); } } - if (curr.length) { + // If we have some leftover lines that have not been added (for example, + // when there is only the _start_ of a block comment but not an _end_.) + // add the appropriate cell. + if (currentCellSource.length) { notebookData.cells.push({ cellKind: cellKind!, language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', outputs: [], - source: curr.join("\n"), + source: currentCellSource.join("\n"), metadata: { custom: { commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, @@ -147,19 +154,20 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide return notebookData; } - resolveNotebook(document: vscode.NotebookDocument, webview: { readonly onDidReceiveMessage: vscode.Event; postMessage(message: any): Thenable; asWebviewUri(localResource: vscode.Uri): vscode.Uri; }): Promise { + public resolveNotebook(document: vscode.NotebookDocument, webview: { readonly onDidReceiveMessage: vscode.Event; postMessage(message: any): Thenable; asWebviewUri(localResource: vscode.Uri): vscode.Uri; }): Promise { + // We don't need to do anything here because our Notebooks are backed by files. return; } - saveNotebook(document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { + public saveNotebook(document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { return this._save(document, document.uri, cancellation); } - saveNotebookAs(targetResource: vscode.Uri, document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { + public saveNotebookAs(targetResource: vscode.Uri, document: vscode.NotebookDocument, cancellation: vscode.CancellationToken): Promise { return this._save(document, targetResource, cancellation); } - async backupNotebook(document: vscode.NotebookDocument, context: vscode.NotebookDocumentBackupContext, cancellation: vscode.CancellationToken): Promise { + public async backupNotebook(document: vscode.NotebookDocument, context: vscode.NotebookDocumentBackupContext, cancellation: vscode.CancellationToken): Promise { await this._save(document, context.destination, cancellation); return { @@ -179,9 +187,9 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide this.languageClient = languageClient; } - async _save(document: vscode.NotebookDocument, targetResource: vscode.Uri, _token: vscode.CancellationToken): Promise { + private async _save(document: vscode.NotebookDocument, targetResource: vscode.Uri, _token: vscode.CancellationToken): Promise { const retArr: string[] = []; - document.cells.forEach((cell) => { + for (const cell of document.cells) { if (cell.cellKind === vscode.CellKind.Code) { retArr.push(...cell.document.getText().split(/\r|\n|\r\n/)); } else { @@ -197,18 +205,33 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide retArr.push(...cell.document.getText().split(/\r|\n|\r\n/).map(line => `# ${line}`)); } } - }); + } await vscode.workspace.fs.writeFile(targetResource, new TextEncoder().encode(retArr.join('\n'))); } - async executeAllCells(document: vscode.NotebookDocument, token: vscode.CancellationToken): Promise { + 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"); + } + + 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"); + } + + /* + `vscode.NotebookKernel` implementations + */ + public async executeAllCells(document: vscode.NotebookDocument, token: vscode.CancellationToken): Promise { for (const cell of document.cells) { await this.executeCell(document, cell, token); } } - async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise { + public async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise { if (token.isCancellationRequested) { return; } diff --git a/src/main.ts b/src/main.ts index 5c91ebfdd9..a1fd710625 100644 --- a/src/main.ts +++ b/src/main.ts @@ -169,7 +169,7 @@ export function activate(context: vscode.ExtensionContext): void { context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); extensionFeatures.push(powerShellNotebooksFeature); } catch (e) { - // This would happen in VS Code changes their API. + // This would happen if VS Code changes their API. powerShellNotebooksFeature.dispose(); logger.writeVerbose("Failed to register NotebookContentProvider", e); } From b5d080f1d89b538783ba4ef6b0c8b629a58d2163 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Jul 2020 16:03:44 -0700 Subject: [PATCH 27/35] Add a few tests --- src/features/PowerShellNotebooks.ts | 24 +-- test/features/PowerShellNotebooks.test.ts | 144 ++++++++++++++++++ test/features/testNotebookFiles/onlyCode.ps1 | 1 + .../testNotebookFiles/onlyMarkdown.ps1 | 3 + .../testNotebookFiles/simpleBlockComments.ps1 | 11 ++ .../testNotebookFiles/simpleLineComments.ps1 | 7 + .../testNotebookFiles/simpleMixedComments.ps1 | 9 ++ 7 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 test/features/PowerShellNotebooks.test.ts create mode 100644 test/features/testNotebookFiles/onlyCode.ps1 create mode 100644 test/features/testNotebookFiles/onlyMarkdown.ps1 create mode 100644 test/features/testNotebookFiles/simpleBlockComments.ps1 create mode 100644 test/features/testNotebookFiles/simpleLineComments.ps1 create mode 100644 test/features/testNotebookFiles/simpleMixedComments.ps1 diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index f35ad796aa..820e70549b 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -85,17 +85,21 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide } else if (lines[i] === "<#") { // If we found the start of a block comment, // insert what we saw leading up to this. - notebookData.cells.push({ - cellKind: cellKind!, - language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', - outputs: [], - source: currentCellSource.join("\n"), - metadata: { - custom: { - commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, + // If cellKind is undefined, that means we + // are starting the file with a BlockComment. + if (cellKind) { + notebookData.cells.push({ + cellKind, + language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', + outputs: [], + source: currentCellSource.join("\n"), + metadata: { + custom: { + commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled, + } } - } - }); + }); + } // reset state because we're starting a new Markdown cell. currentCellSource = []; diff --git a/test/features/PowerShellNotebooks.test.ts b/test/features/PowerShellNotebooks.test.ts new file mode 100644 index 0000000000..28219f428b --- /dev/null +++ b/test/features/PowerShellNotebooks.test.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import * as assert from "assert"; +import * as path from "path"; +import * as vscode from "vscode"; +import { PowerShellNotebooksFeature } from "../../src/features/PowerShellNotebooks"; +import { before } from "mocha"; +import os = require("os"); +import { readFileSync } from "fs"; + +const notebookDir = [ + __dirname, + "..", + "..", + "..", + "test", + "features", + "testNotebookFiles" +]; + +const notebookOnlyCode = vscode.Uri.file( + path.join(...notebookDir, "onlyCode.ps1")); +const notebookOnlyMarkdown = vscode.Uri.file( + path.join(...notebookDir,"onlyMarkdown.ps1")); +const notebookSimpleBlockComments = vscode.Uri.file( + path.join(...notebookDir,"simpleBlockComments.ps1")); +const notebookSimpleLineComments = vscode.Uri.file( + path.join(...notebookDir,"simpleLineComments.ps1")); +const notebookSimpleMixedComments = vscode.Uri.file( + path.join(...notebookDir,"simpleMixedComments.ps1")); + +const notebookTestData = new Map(); + +suite("PowerShellNotebooks tests", () => { + notebookTestData.set(notebookOnlyCode, [ + { + cellKind: vscode.CellKind.Code, + language: "powershell", + source: readBackingFile(notebookOnlyCode), + outputs: [], + metadata: {} + } + ]); + + notebookTestData.set(notebookOnlyMarkdown, [ + { + cellKind: vscode.CellKind.Markdown, + language: "markdown", + source: readBackingFile(notebookOnlyMarkdown), + outputs: [], + metadata: {} + } + ]); + + let content = readBackingFile(notebookSimpleBlockComments).split(os.EOL); + notebookTestData.set(notebookSimpleBlockComments, [ + { + cellKind: vscode.CellKind.Markdown, + language: "markdown", + source: content.slice(0, 5).join(os.EOL), + outputs: [], + metadata: {} + }, + { + cellKind: vscode.CellKind.Code, + language: "powershell", + source: content.slice(5, 6).join(os.EOL), + outputs: [], + metadata: {} + }, + { + cellKind: vscode.CellKind.Markdown, + language: "markdown", + source: content.slice(6, 11).join(os.EOL), + outputs: [], + metadata: {} + }, + ]); + + content = readBackingFile(notebookSimpleLineComments).split(os.EOL); + notebookTestData.set(notebookSimpleLineComments, [ + { + cellKind: vscode.CellKind.Markdown, + language: "markdown", + source: content.slice(0, 3).join(os.EOL), + outputs: [], + metadata: {} + }, + { + cellKind: vscode.CellKind.Code, + language: "powershell", + source: content.slice(3, 4).join(os.EOL), + outputs: [], + metadata: {} + }, + { + cellKind: vscode.CellKind.Markdown, + language: "markdown", + source: content.slice(4, 7).join(os.EOL), + outputs: [], + metadata: {} + }, + ]); + + content = readBackingFile(notebookSimpleMixedComments).split(os.EOL); + notebookTestData.set(notebookSimpleMixedComments, [ + { + cellKind: vscode.CellKind.Markdown, + language: "markdown", + source: content.slice(0, 3).join(os.EOL), + outputs: [], + metadata: {} + }, + { + cellKind: vscode.CellKind.Code, + language: "powershell", + source: content.slice(3, 4).join(os.EOL), + outputs: [], + metadata: {} + }, + { + cellKind: vscode.CellKind.Markdown, + language: "markdown", + source: content.slice(4, 9).join(os.EOL), + outputs: [], + metadata: {} + }, + ]); + + const feature = new PowerShellNotebooksFeature(); + + for (const [uri, cells] of notebookTestData) { + test(`Can open a notebook with expected cells - ${uri.fsPath}`, async () => { + const notebookData = await feature.openNotebook(uri, {}); + assert.deepStrictEqual(notebookData.cells.length, cells.length); + }); + } +}); + +function readBackingFile(uri: vscode.Uri): string { + return readFileSync(uri.fsPath).toString(); +} diff --git a/test/features/testNotebookFiles/onlyCode.ps1 b/test/features/testNotebookFiles/onlyCode.ps1 new file mode 100644 index 0000000000..916d38265f --- /dev/null +++ b/test/features/testNotebookFiles/onlyCode.ps1 @@ -0,0 +1 @@ +Get-ChildItem diff --git a/test/features/testNotebookFiles/onlyMarkdown.ps1 b/test/features/testNotebookFiles/onlyMarkdown.ps1 new file mode 100644 index 0000000000..fa2d22dd81 --- /dev/null +++ b/test/features/testNotebookFiles/onlyMarkdown.ps1 @@ -0,0 +1,3 @@ +# # h1 +# **bold** +# text \ No newline at end of file diff --git a/test/features/testNotebookFiles/simpleBlockComments.ps1 b/test/features/testNotebookFiles/simpleBlockComments.ps1 new file mode 100644 index 0000000000..5183f4e135 --- /dev/null +++ b/test/features/testNotebookFiles/simpleBlockComments.ps1 @@ -0,0 +1,11 @@ +<# +Foo +bar +baz +#> +Get-ChildItem +<# +Foo +bar +baz +#> \ No newline at end of file diff --git a/test/features/testNotebookFiles/simpleLineComments.ps1 b/test/features/testNotebookFiles/simpleLineComments.ps1 new file mode 100644 index 0000000000..39943e3e04 --- /dev/null +++ b/test/features/testNotebookFiles/simpleLineComments.ps1 @@ -0,0 +1,7 @@ +# Foo +# bar +# baz +Get-ChildItem +# Foo +# bar +# baz \ No newline at end of file diff --git a/test/features/testNotebookFiles/simpleMixedComments.ps1 b/test/features/testNotebookFiles/simpleMixedComments.ps1 new file mode 100644 index 0000000000..48a855d9bc --- /dev/null +++ b/test/features/testNotebookFiles/simpleMixedComments.ps1 @@ -0,0 +1,9 @@ +# Foo +# bar +# baz +Get-ChildItem +<# +Foo +bar +baz +#> \ No newline at end of file From ec109bd9b95cd94366331aa9229534153e867ce1 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Sun, 12 Jul 2020 20:43:28 -0700 Subject: [PATCH 28/35] added save test --- src/features/PowerShellNotebooks.ts | 16 +-- test/features/PowerShellNotebooks.test.ts | 117 +++++++++++++++++++--- test/runTests.ts | 7 +- 3 files changed, 117 insertions(+), 23 deletions(-) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 820e70549b..bd9b913445 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -21,17 +21,19 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide public label: string = 'PowerShell'; public preloads?: vscode.Uri[]; - public constructor() { + public constructor(skipRegisteringCommands?: boolean) { // VS Code Notebook API uses this property for handling cell execution. this.kernel = this; - this.showNotebookModeCommand = vscode.commands.registerCommand( - "PowerShell.ShowNotebookMode", - PowerShellNotebooksFeature.showNotebookMode); + if(!skipRegisteringCommands) { + this.showNotebookModeCommand = vscode.commands.registerCommand( + "PowerShell.ShowNotebookMode", + PowerShellNotebooksFeature.showNotebookMode); - this.hideNotebookModeCommand = vscode.commands.registerCommand( - "PowerShell.HideNotebookMode", - PowerShellNotebooksFeature.hideNotebookMode); + this.hideNotebookModeCommand = vscode.commands.registerCommand( + "PowerShell.HideNotebookMode", + PowerShellNotebooksFeature.hideNotebookMode); + } } public async openNotebook(uri: vscode.Uri, context: vscode.NotebookDocumentOpenContext): Promise { diff --git a/test/features/PowerShellNotebooks.test.ts b/test/features/PowerShellNotebooks.test.ts index 28219f428b..0a31cd4c38 100644 --- a/test/features/PowerShellNotebooks.test.ts +++ b/test/features/PowerShellNotebooks.test.ts @@ -9,6 +9,7 @@ import { PowerShellNotebooksFeature } from "../../src/features/PowerShellNoteboo import { before } from "mocha"; import os = require("os"); import { readFileSync } from "fs"; +import { CommentType } from "../../src/settings"; const notebookDir = [ __dirname, @@ -40,7 +41,11 @@ suite("PowerShellNotebooks tests", () => { language: "powershell", source: readBackingFile(notebookOnlyCode), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.Disabled, + } + } } ]); @@ -50,7 +55,11 @@ suite("PowerShellNotebooks tests", () => { language: "markdown", source: readBackingFile(notebookOnlyMarkdown), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.LineComment, + } + } } ]); @@ -61,21 +70,33 @@ suite("PowerShellNotebooks tests", () => { language: "markdown", source: content.slice(0, 5).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.BlockComment, + } + } }, { cellKind: vscode.CellKind.Code, language: "powershell", source: content.slice(5, 6).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.Disabled, + } + } }, { cellKind: vscode.CellKind.Markdown, language: "markdown", source: content.slice(6, 11).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.BlockComment, + } + } }, ]); @@ -86,21 +107,33 @@ suite("PowerShellNotebooks tests", () => { language: "markdown", source: content.slice(0, 3).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.LineComment, + } + } }, { cellKind: vscode.CellKind.Code, language: "powershell", source: content.slice(3, 4).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.Disabled, + } + } }, { cellKind: vscode.CellKind.Markdown, language: "markdown", source: content.slice(4, 7).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.LineComment, + } + } }, ]); @@ -111,34 +144,88 @@ suite("PowerShellNotebooks tests", () => { language: "markdown", source: content.slice(0, 3).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.LineComment, + } + } }, { cellKind: vscode.CellKind.Code, language: "powershell", source: content.slice(3, 4).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.Disabled, + } + } }, { cellKind: vscode.CellKind.Markdown, language: "markdown", source: content.slice(4, 9).join(os.EOL), outputs: [], - metadata: {} + metadata: { + custom: { + commentType: CommentType.BlockComment, + } + } }, ]); - const feature = new PowerShellNotebooksFeature(); + const feature = new PowerShellNotebooksFeature(true); - for (const [uri, cells] of notebookTestData) { + for (const [uri, expectedCells] of notebookTestData) { test(`Can open a notebook with expected cells - ${uri.fsPath}`, async () => { - const notebookData = await feature.openNotebook(uri, {}); - assert.deepStrictEqual(notebookData.cells.length, cells.length); + const actualNotebookData = await feature.openNotebook(uri, {}); + compareCells(actualNotebookData.cells, expectedCells); }); } + + test("Can save a new notebook with expected cells and metadata", async () => { + const uri = vscode.Uri.file(path.join(__dirname, "testFile.ps1")); + try { + await vscode.workspace.fs.delete(uri); + } catch { + // If the file doesn't exist that's fine. + } + + // Open an existing notebook ps1. + await vscode.commands.executeCommand("vscode.openWith", notebookSimpleMixedComments, "PowerShellNotebookMode"); + + // Allow some time to pass to render the Notebook + await sleep(5000); + assert.strictEqual( + vscode.notebook.activeNotebookEditor.document.uri.toString(), + 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, {}); + + // Compare that saving as a file results in the same cell data as the existing one. + const expectedCells = notebookTestData.get(notebookSimpleMixedComments); + compareCells(newNotebook.cells, expectedCells); + }).timeout(20000); }); function readBackingFile(uri: vscode.Uri): string { return readFileSync(uri.fsPath).toString(); } + +function compareCells(actualCells: vscode.NotebookCellData[], expectedCells: vscode.NotebookCellData[]) : void { + assert.deepStrictEqual(actualCells.length, expectedCells.length); + + // Compare cell metadata + for (let i = 0; i < actualCells.length; i++) { + assert.deepStrictEqual( + actualCells[i].metadata.custom, + expectedCells[i].metadata.custom + ); + } +} + +function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/test/runTests.ts b/test/runTests.ts index 34d5f724bd..4a2ce44ca5 100644 --- a/test/runTests.ts +++ b/test/runTests.ts @@ -17,7 +17,12 @@ async function main() { const extensionTestsPath = path.resolve(__dirname, "./testRunner"); // Download VS Code, unzip it and run the integration test from the local directory. - await runTests({ extensionDevelopmentPath, extensionTestsPath, launchArgs: ["."] }); + await runTests({ + extensionDevelopmentPath, + extensionTestsPath, + launchArgs: ["--disable-extensions", "--enable-proposed-api", "ms-vscode.powershell-preview", "."], + version: 'insiders' + }); } catch (err) { // tslint:disable-next-line:no-console console.error(err); From a0a1c2c8736d6039415dec89ad0ee7c61864fe03 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 13 Jul 2020 10:32:18 -0700 Subject: [PATCH 29/35] move to utils.sleep --- test/features/PowerShellNotebooks.test.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/features/PowerShellNotebooks.test.ts b/test/features/PowerShellNotebooks.test.ts index 0a31cd4c38..8e4e31d04f 100644 --- a/test/features/PowerShellNotebooks.test.ts +++ b/test/features/PowerShellNotebooks.test.ts @@ -10,6 +10,7 @@ import { before } from "mocha"; import os = require("os"); import { readFileSync } from "fs"; import { CommentType } from "../../src/settings"; +import * as utils from "../../src/utils"; const notebookDir = [ __dirname, @@ -195,7 +196,7 @@ suite("PowerShellNotebooks tests", () => { await vscode.commands.executeCommand("vscode.openWith", notebookSimpleMixedComments, "PowerShellNotebookMode"); // Allow some time to pass to render the Notebook - await sleep(5000); + await utils.sleep(5000); assert.strictEqual( vscode.notebook.activeNotebookEditor.document.uri.toString(), notebookSimpleMixedComments.toString()); @@ -225,7 +226,3 @@ function compareCells(actualCells: vscode.NotebookCellData[], expectedCells: vsc ); } } - -function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} From 33cdaee81a68aaa01d0c61b46a82728c4dcfebbe Mon Sep 17 00:00:00 2001 From: "Tyler Leonhardt (POWERSHELL)" Date: Mon, 13 Jul 2020 13:16:16 -0700 Subject: [PATCH 30/35] fix regex --- src/features/PowerShellNotebooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index bd9b913445..50e1558dd7 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -41,7 +41,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide const actualUri = context.backupId ? vscode.Uri.parse(context.backupId) : uri; const data = (await vscode.workspace.fs.readFile(actualUri)).toString(); - const lines = data.split(/\r|\n|\r\n/g); + const lines = data.split(/\r\n|\r|\n/g); const notebookData: vscode.NotebookData = { languages: ["powershell"], From 41aa71afbcf57b824e3b34d10abc67138bba36db Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 13 Jul 2020 14:34:16 -0700 Subject: [PATCH 31/35] add logger --- src/features/PowerShellNotebooks.ts | 6 +++++- src/main.ts | 2 +- test/features/PowerShellNotebooks.test.ts | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 50e1558dd7..39e32033a2 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -7,6 +7,7 @@ import { CommentType } from '../settings'; import { IFeature, LanguageClient } from '../feature'; import { EvaluateRequestType } from './Console'; import Settings = require("../settings"); +import { ILogger } from '../logging'; export class PowerShellNotebooksFeature implements vscode.NotebookContentProvider, vscode.NotebookKernel, IFeature { @@ -21,7 +22,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide public label: string = 'PowerShell'; public preloads?: vscode.Uri[]; - public constructor(skipRegisteringCommands?: boolean) { + public constructor(private logger: ILogger, skipRegisteringCommands?: boolean) { // VS Code Notebook API uses this property for handling cell execution. this.kernel = this; @@ -39,6 +40,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide 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; + this.logger.writeDiagnostic(`Opening Notebook: ${uri.toString()}`); const data = (await vscode.workspace.fs.readFile(actualUri)).toString(); const lines = data.split(/\r\n|\r|\n/g); @@ -194,6 +196,8 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide } private async _save(document: vscode.NotebookDocument, targetResource: vscode.Uri, _token: vscode.CancellationToken): Promise { + this.logger.writeDiagnostic(`Saving Notebook: ${targetResource.toString()}`); + const retArr: string[] = []; for (const cell of document.cells) { if (cell.cellKind === vscode.CellKind.Code) { diff --git a/src/main.ts b/src/main.ts index a1fd710625..df8f9a8ddd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -163,7 +163,7 @@ export function activate(context: vscode.ExtensionContext): void { // Notebook UI is only supported in VS Code Insiders. if(vscode.env.uriScheme === "vscode-insiders") { - const powerShellNotebooksFeature = new PowerShellNotebooksFeature(); + const powerShellNotebooksFeature = new PowerShellNotebooksFeature(logger); try { context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); diff --git a/test/features/PowerShellNotebooks.test.ts b/test/features/PowerShellNotebooks.test.ts index 8e4e31d04f..4e319233f5 100644 --- a/test/features/PowerShellNotebooks.test.ts +++ b/test/features/PowerShellNotebooks.test.ts @@ -6,11 +6,11 @@ import * as assert from "assert"; import * as path from "path"; import * as vscode from "vscode"; import { PowerShellNotebooksFeature } from "../../src/features/PowerShellNotebooks"; -import { before } from "mocha"; import os = require("os"); import { readFileSync } from "fs"; import { CommentType } from "../../src/settings"; import * as utils from "../../src/utils"; +import { MockLogger } from "../test_utils"; const notebookDir = [ __dirname, @@ -175,7 +175,7 @@ suite("PowerShellNotebooks tests", () => { }, ]); - const feature = new PowerShellNotebooksFeature(true); + const feature = new PowerShellNotebooksFeature(new MockLogger(), true); for (const [uri, expectedCells] of notebookTestData) { test(`Can open a notebook with expected cells - ${uri.fsPath}`, async () => { From 85e69269ada70830fcdecad000ac4b28ad8e3b97 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 13 Jul 2020 14:48:28 -0700 Subject: [PATCH 32/35] Codacy part 1 --- .vscode/launch.json | 6 ++++- src/features/PowerShellNotebooks.ts | 32 +++++++++++------------ src/main.ts | 2 +- src/settings.ts | 2 +- test/features/PowerShellNotebooks.test.ts | 32 +++++++++++------------ test/runTests.ts | 8 ++++-- 6 files changed, 45 insertions(+), 37 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d821aa8b5..2e8ab9f8a0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,8 +29,12 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": [ + "--disable-extensions", + "--enable-proposed-api", "ms-vscode.powershell-preview", "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/testRunner.js" ], + "--extensionTestsPath=${workspaceFolder}/out/test/testRunner.js", + "${workspaceFolder}/test" + ], "outFiles": ["${workspaceFolder}/out/**/*.js"], "preLaunchTask": "Build" } diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 39e32033a2..025bc31ceb 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -2,12 +2,12 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { CommentType } from '../settings'; -import { IFeature, LanguageClient } from '../feature'; -import { EvaluateRequestType } from './Console'; +import * as vscode from "vscode"; +import { CommentType } from "../settings"; +import { IFeature, LanguageClient } from "../feature"; +import { EvaluateRequestType } from "./Console"; import Settings = require("../settings"); -import { ILogger } from '../logging'; +import { ILogger } from "../logging"; export class PowerShellNotebooksFeature implements vscode.NotebookContentProvider, vscode.NotebookKernel, IFeature { @@ -19,7 +19,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide public onDidChangeNotebook: vscode.Event = this._onDidChangeNotebook.event; public kernel?: vscode.NotebookKernel; - public label: string = 'PowerShell'; + public label: string = "PowerShell"; public preloads?: vscode.Uri[]; public constructor(private logger: ILogger, skipRegisteringCommands?: boolean) { @@ -49,7 +49,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide languages: ["powershell"], cells: [], metadata: {} - } + }; let currentCellSource: string[] = []; let cellKind: vscode.CellKind | undefined; @@ -94,7 +94,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide if (cellKind) { notebookData.cells.push({ cellKind, - language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', + language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell", outputs: [], source: currentCellSource.join("\n"), metadata: { @@ -118,13 +118,13 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide // If this line is a continuation of the previous cell type, then add this line to the current cell source. if (kind === cellKind) { - currentCellSource.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, '') : lines[i]); + currentCellSource.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, "") : lines[i]); } else { // If cellKind is not undifined, then we can add the cell we've just computed. if (cellKind !== undefined) { notebookData.cells.push({ cellKind: cellKind!, - language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', + language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell", outputs: [], source: currentCellSource.join("\n"), metadata: { @@ -138,7 +138,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide // set initial new cell state currentCellSource = []; cellKind = kind; - currentCellSource.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, '') : lines[i]); + currentCellSource.push(kind === vscode.CellKind.Markdown ? lines[i].replace(/^\#\s*/, "") : lines[i]); } } @@ -148,7 +148,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide if (currentCellSource.length) { notebookData.cells.push({ cellKind: cellKind!, - language: cellKind === vscode.CellKind.Markdown ? 'markdown' : 'powershell', + language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell", outputs: [], source: currentCellSource.join("\n"), metadata: { @@ -212,23 +212,23 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide retArr.push(...cell.document.getText().split(/\r|\n|\r\n/)); retArr.push("#>"); } else { - retArr.push(...cell.document.getText().split(/\r|\n|\r\n/).map(line => `# ${line}`)); + retArr.push(...cell.document.getText().split(/\r|\n|\r\n/).map((line) => `# ${line}`)); } } } - await vscode.workspace.fs.writeFile(targetResource, new TextEncoder().encode(retArr.join('\n'))); + 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("workbench.action.closeActiveEditor"); await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode"); } private static async hideNotebookMode() { const uri = vscode.notebook.activeNotebookEditor.document.uri; - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); await vscode.commands.executeCommand("vscode.openWith", uri, "default"); } diff --git a/src/main.ts b/src/main.ts index df8f9a8ddd..d4da95a233 100644 --- a/src/main.ts +++ b/src/main.ts @@ -166,7 +166,7 @@ export function activate(context: vscode.ExtensionContext): void { const powerShellNotebooksFeature = new PowerShellNotebooksFeature(logger); try { - context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('PowerShellNotebookMode', powerShellNotebooksFeature)); + context.subscriptions.push(vscode.notebook.registerNotebookContentProvider("PowerShellNotebookMode", powerShellNotebooksFeature)); extensionFeatures.push(powerShellNotebooksFeature); } catch (e) { // This would happen if VS Code changes their API. diff --git a/src/settings.ts b/src/settings.ts index 97c6ff963c..6271eb1274 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -217,7 +217,7 @@ export function load(): ISettings { const defaultNotebooksSettings: INotebooksSettings = { saveMarkdownCellsAs: CommentType.BlockComment, - } + }; return { startAutomatically: diff --git a/test/features/PowerShellNotebooks.test.ts b/test/features/PowerShellNotebooks.test.ts index 4e319233f5..800b97fb39 100644 --- a/test/features/PowerShellNotebooks.test.ts +++ b/test/features/PowerShellNotebooks.test.ts @@ -35,6 +35,22 @@ const notebookSimpleMixedComments = vscode.Uri.file( const notebookTestData = new Map(); +function readBackingFile(uri: vscode.Uri): string { + return readFileSync(uri.fsPath).toString(); +} + +function compareCells(actualCells: vscode.NotebookCellData[], expectedCells: vscode.NotebookCellData[]) : void { + assert.deepStrictEqual(actualCells.length, expectedCells.length); + + // Compare cell metadata + for (let i = 0; i < actualCells.length; i++) { + assert.deepStrictEqual( + actualCells[i].metadata.custom, + expectedCells[i].metadata.custom + ); + } +} + suite("PowerShellNotebooks tests", () => { notebookTestData.set(notebookOnlyCode, [ { @@ -210,19 +226,3 @@ suite("PowerShellNotebooks tests", () => { compareCells(newNotebook.cells, expectedCells); }).timeout(20000); }); - -function readBackingFile(uri: vscode.Uri): string { - return readFileSync(uri.fsPath).toString(); -} - -function compareCells(actualCells: vscode.NotebookCellData[], expectedCells: vscode.NotebookCellData[]) : void { - assert.deepStrictEqual(actualCells.length, expectedCells.length); - - // Compare cell metadata - for (let i = 0; i < actualCells.length; i++) { - assert.deepStrictEqual( - actualCells[i].metadata.custom, - expectedCells[i].metadata.custom - ); - } -} diff --git a/test/runTests.ts b/test/runTests.ts index 4a2ce44ca5..283ba84f46 100644 --- a/test/runTests.ts +++ b/test/runTests.ts @@ -20,8 +20,12 @@ async function main() { await runTests({ extensionDevelopmentPath, extensionTestsPath, - launchArgs: ["--disable-extensions", "--enable-proposed-api", "ms-vscode.powershell-preview", "."], - version: 'insiders' + launchArgs: [ + "--disable-extensions", + "--enable-proposed-api", "ms-vscode.powershell-preview", + "./test" + ], + version: "insiders" }); } catch (err) { // tslint:disable-next-line:no-console From ffe92d23abce9f1b1d4f742450929e104bcf3925 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 13 Jul 2020 15:08:59 -0700 Subject: [PATCH 33/35] Codacy part 2 --- src/features/PowerShellNotebooks.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index 025bc31ceb..36fc7281cb 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -79,7 +79,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide }); currentCellSource = []; - cellKind = undefined; + cellKind = null; continue; } @@ -89,7 +89,7 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide } else if (lines[i] === "<#") { // If we found the start of a block comment, // insert what we saw leading up to this. - // If cellKind is undefined, that means we + // If cellKind is null/undefined, that means we // are starting the file with a BlockComment. if (cellKind) { notebookData.cells.push({ @@ -120,8 +120,8 @@ export class PowerShellNotebooksFeature implements vscode.NotebookContentProvide if (kind === cellKind) { currentCellSource.push(kind === vscode.CellKind.Markdown && !insideBlockComment ? lines[i].replace(/^\#\s*/, "") : lines[i]); } else { - // If cellKind is not undifined, then we can add the cell we've just computed. - if (cellKind !== undefined) { + // If cellKind has a value, then we can add the cell we've just computed. + if (cellKind) { notebookData.cells.push({ cellKind: cellKind!, language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell", From 41498dd6c0a1f51ea55ac92daaf9145a974af617 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2020 15:10:42 -0700 Subject: [PATCH 34/35] Update Notebook dts (#5) Co-authored-by: TylerLeonhardt --- vscode.proposed.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/vscode.proposed.d.ts b/vscode.proposed.d.ts index 561c4fb08f..84b8b36b9c 100644 --- a/vscode.proposed.d.ts +++ b/vscode.proposed.d.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MarkdownString } from 'vscode'; - /** * This is the place for API experiments and proposals. * These API are NOT stable and subject to change. They are only available in the Insiders From ea67540fa48c925d6c81737c63c1fb31f9edf80a Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 13 Jul 2020 17:22:01 -0700 Subject: [PATCH 35/35] move GitHub Action to use master --- .github/workflows/updateNotebookApi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateNotebookApi.yml b/.github/workflows/updateNotebookApi.yml index a7ea47b675..b4919e0741 100644 --- a/.github/workflows/updateNotebookApi.yml +++ b/.github/workflows/updateNotebookApi.yml @@ -95,7 +95,7 @@ jobs: title: "Update Notebook dts" assignees: TylerLeonhardt reviewers: TylerLeonhardt - base: notebook-ui-support + base: master draft: false branch: powershell-notebook-patch-${{ github.run_id }} labels: Created_by_Action