diff --git a/package.json b/package.json index 7fde3ef3b4..6667e114aa 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,10 @@ "onCommand:PowerShell.UnregisterExternalExtension", "onCommand:PowerShell.GetPowerShellVersionDetails", "onView:PowerShellCommands", - "onNotebookEditor:PowerShellNotebookMode" + "onNotebook:PowerShellNotebookModeDefault", + "onNotebookEditor:PowerShellNotebookModeDefault", + "onNotebook:PowerShellNotebookModeOption", + "onNotebookEditor:PowerShellNotebookModeOption" ], "dependencies": { "node-fetch": "^2.6.0", @@ -113,10 +116,21 @@ }, "notebookProvider": [ { - "viewType": "PowerShellNotebookMode", + "viewType": "PowerShellNotebookModeDefault", "displayName": "Powershell Notebook", "selector": [ { + "filenamePattern": "*.Notebook.ps1" + } + ], + "priority": "default" + }, + { + "viewType": "PowerShellNotebookModeOption", + "displayName": "Powershell Notebook", + "selector": [ + { + "excludeFileNamePattern": "*.Notebook.ps1", "filenamePattern": "*.ps1" } ], diff --git a/src/features/PowerShellNotebooks.ts b/src/features/PowerShellNotebooks.ts index ea161212bc..730b9e66e5 100644 --- a/src/features/PowerShellNotebooks.ts +++ b/src/features/PowerShellNotebooks.ts @@ -34,13 +34,40 @@ export class PowerShellNotebooksFeature extends LanguageClientConsumer { } public registerNotebookProviders() { + const options = { + transientOutputs: true, + transientMetadata: { + inputCollapsed: true, + outputCollapsed: true, + runState: true, + runStartTime: true, + executionOrder: true, + lastRunDuration: true, + statusMessage: true, + }, + }; + + // Until vscode supports using the same view type with different priority, + // we register 2 of the same viewTypes. + // This one is used to open *.Notebook.ps1 files which automatically go straight to Notebook mode. + this.disposables.push(vscode.notebook.registerNotebookKernelProvider({ + viewType: "PowerShellNotebookModeDefault" + }, this.notebookKernel)); + + this.disposables.push(vscode.notebook.registerNotebookContentProvider( + "PowerShellNotebookModeDefault", + this.notebookContentProvider, + options)); + + // This one is used to open *.ps1 files which will be opened in the default text editor first. this.disposables.push(vscode.notebook.registerNotebookKernelProvider({ - viewType: "PowerShellNotebookMode" + viewType: "PowerShellNotebookModeOption" }, this.notebookKernel)); this.disposables.push(vscode.notebook.registerNotebookContentProvider( - "PowerShellNotebookMode", - this.notebookContentProvider)); + "PowerShellNotebookModeOption", + this.notebookContentProvider, + options)); } public dispose() { @@ -55,8 +82,17 @@ export class PowerShellNotebooksFeature extends LanguageClientConsumer { private static async EnableNotebookMode() { const uri = vscode.window.activeTextEditor.document.uri; - await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode"); + + // If the file is an untitled file, then we can't close it. + if (!vscode.window.activeTextEditor.document.isUntitled) { + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + } + + if (uri.fsPath?.endsWith(".Notebook.ps1")) { + await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookModeDefault"); + } else { + await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookModeOption"); + } } private static async DisableNotebookMode() { @@ -95,8 +131,12 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide // load from backup if needed. const actualUri = context.backupId ? vscode.Uri.parse(context.backupId) : uri; this.logger.writeDiagnostic(`Opening Notebook: ${uri.toString()}`); + const isUntitled = uri.scheme !== "file"; - const data = (await vscode.workspace.fs.readFile(actualUri)).toString(); + // If we have an untitled file, get the contents from vscode instead of the file system. + const data: string = isUntitled + ? (await vscode.workspace.openTextDocument(actualUri)).getText() + : (await vscode.workspace.fs.readFile(actualUri)).toString(); let lines: string[]; // store the line ending in the metadata of the document @@ -117,6 +157,7 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide metadata: { custom: { lineEnding, + isUntitled, } } }; @@ -374,6 +415,11 @@ class PowerShellNotebookKernel implements vscode.NotebookKernel, vscode.Notebook await this.languageClient.sendRequest(EvaluateRequestType, { expression: cell.document.getText(), }); + + // Show the integrated console if it isn't already visible and + // scroll terminal to bottom so new output is visible + await vscode.commands.executeCommand("PowerShell.ShowSessionConsole", true); + await vscode.commands.executeCommand("workbench.action.terminal.scrollToBottom"); } // Since executing a cell is a "fire and forget", there's no time for the user to cancel diff --git a/test/features/PowerShellNotebooks.test.ts b/test/features/PowerShellNotebooks.test.ts index cee7fa0c34..38fcc573fe 100644 --- a/test/features/PowerShellNotebooks.test.ts +++ b/test/features/PowerShellNotebooks.test.ts @@ -34,6 +34,8 @@ const notebookSimpleLineComments = vscode.Uri.file( path.join(...notebookDir,"simpleLineComments.ps1")); const notebookSimpleMixedComments = vscode.Uri.file( path.join(...notebookDir,"simpleMixedComments.ps1")); +const notebookSimpleDotNotebook = vscode.Uri.file( + path.join(...notebookDir,"simple.Notebook.ps1")); const notebookTestData = new Map(); @@ -343,7 +345,7 @@ suite("PowerShellNotebooks tests", () => { } // Open an existing notebook ps1. - await vscode.commands.executeCommand("vscode.openWith", notebookSimpleMixedComments, "PowerShellNotebookMode"); + await vscode.commands.executeCommand("vscode.openWith", notebookSimpleMixedComments, "PowerShellNotebookModeOption"); // Allow some time to pass to render the Notebook await utils.sleep(5000); @@ -369,7 +371,7 @@ suite("PowerShellNotebooks tests", () => { } // Open an existing notebook ps1. - await vscode.commands.executeCommand("vscode.openWith", notebookBlockCommentsWithTextOnSameLine, "PowerShellNotebookMode"); + await vscode.commands.executeCommand("vscode.openWith", notebookBlockCommentsWithTextOnSameLine, "PowerShellNotebookModeOption"); // Allow some time to pass to render the Notebook await utils.sleep(5000); @@ -385,4 +387,28 @@ suite("PowerShellNotebooks tests", () => { // Verify that saving does not mutate result. assert.strictEqual(contentOfBackingFileBefore, contentOfBackingFileAfter); }).timeout(20000); + + test("Can open an untitled Notebook", async () => { + const doc = await vscode.workspace.openTextDocument({ + language: "powershell", + content: `# asdf +gci`, + }); + + const notebookData = await notebookContentProvider.openNotebook(doc.uri, {}); + assert.strictEqual(notebookData.cells.length, 2); + assert.strictEqual(notebookData.cells[0].cellKind, vscode.CellKind.Markdown); + assert.strictEqual(notebookData.cells[0].source, "asdf"); + assert.strictEqual(notebookData.cells[1].cellKind, vscode.CellKind.Code); + assert.strictEqual(notebookData.cells[1].source, "gci"); + }).timeout(20000); + + test(".Notebook.ps1 files are opened automatically", async () => { + await vscode.commands.executeCommand("vscode.open", notebookSimpleDotNotebook); + assert.strictEqual(vscode.notebook.activeNotebookEditor.document.cells.length, 2); + assert.strictEqual(vscode.notebook.activeNotebookEditor.document.cells[0].cellKind, vscode.CellKind.Markdown); + assert.strictEqual(vscode.notebook.activeNotebookEditor.document.cells[0].document.getText(), "asdf"); + assert.strictEqual(vscode.notebook.activeNotebookEditor.document.cells[1].cellKind, vscode.CellKind.Code); + assert.strictEqual(vscode.notebook.activeNotebookEditor.document.cells[1].document.getText(), "gci\n"); + }).timeout(20000); }); diff --git a/test/features/testNotebookFiles/simple.Notebook.ps1 b/test/features/testNotebookFiles/simple.Notebook.ps1 new file mode 100644 index 0000000000..b213540649 --- /dev/null +++ b/test/features/testNotebookFiles/simple.Notebook.ps1 @@ -0,0 +1,2 @@ +# asdf +gci