Skip to content

Commit 54299e2

Browse files
Address Notebook breaking changes (#2827)
* address Notebook breaking changes * remove settings.json * protect languageClient * add LanguageClient
1 parent ea0cf3a commit 54299e2

File tree

7 files changed

+133
-57
lines changed

7 files changed

+133
-57
lines changed

.github/workflows/updateNotebookApi.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ jobs:
7979
# Remove the old file so it doesn't get picked up by tsc
8080
Remove-Item ./old.vscode.proposed.d.ts -Force
8181
82-
- name: Compile the TypeScript to check for errors
83-
run: npm run compile
84-
8582
- name: Create Pull Request
8683
if: github.event_name == 'schedule'
8784
id: cpr

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ npm-debug.log
1515
*.DS_Store
1616
test-results.xml
1717
vscode.d.ts
18+
test/.vscode/settings.json

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,17 @@
218218
}
219219
},
220220
{
221-
"command": "PowerShell.ShowNotebookMode",
222-
"title": "(Preview) Show Notebook Mode",
221+
"command": "PowerShell.EnableNotebookMode",
222+
"title": "(Preview) Enable Notebook Mode",
223223
"category": "PowerShell",
224224
"icon": {
225225
"light": "resources/light/book.svg",
226226
"dark": "resources/dark/book.svg"
227227
}
228228
},
229229
{
230-
"command": "PowerShell.HideNotebookMode",
231-
"title": "Show Text Editor",
230+
"command": "PowerShell.DisableNotebookMode",
231+
"title": "(Preview) Disable Notebook Mode",
232232
"category": "PowerShell",
233233
"icon": {
234234
"light": "resources/light/file-code.svg",
@@ -413,12 +413,12 @@
413413
},
414414
{
415415
"when": "editorLangId == powershell && config.powershell.notebooks.showToggleButton",
416-
"command": "PowerShell.ShowNotebookMode",
416+
"command": "PowerShell.EnableNotebookMode",
417417
"group": "navigation@102"
418418
},
419419
{
420420
"when": "resourceLangId == powershell && notebookEditorFocused",
421-
"command": "PowerShell.HideNotebookMode",
421+
"command": "PowerShell.DisableNotebookMode",
422422
"group": "navigation@102"
423423
}
424424
],

src/features/PowerShellNotebooks.ts

Lines changed: 102 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,71 @@ import { EvaluateRequestType } from "./Console";
88
import { LanguageClientConsumer } from "../languageClientConsumer";
99
import Settings = require("../settings");
1010
import { ILogger } from "../logging";
11+
import { LanguageClient } from "vscode-languageclient";
1112

12-
export class PowerShellNotebooksFeature extends LanguageClientConsumer implements vscode.NotebookContentProvider, vscode.NotebookKernel {
13+
export class PowerShellNotebooksFeature extends LanguageClientConsumer {
1314

14-
private readonly showNotebookModeCommand: vscode.Disposable;
15-
private readonly hideNotebookModeCommand: vscode.Disposable;
15+
private readonly disposables: vscode.Disposable[];
16+
private readonly notebookContentProvider: vscode.NotebookContentProvider;
17+
private readonly notebookKernel: PowerShellNotebookKernel;
1618

17-
private _onDidChangeNotebook = new vscode.EventEmitter<vscode.NotebookDocumentEditEvent>();
18-
public onDidChangeNotebook: vscode.Event<vscode.NotebookDocumentEditEvent> = this._onDidChangeNotebook.event;
19-
public kernel?: vscode.NotebookKernel;
19+
public constructor(logger: ILogger, skipRegisteringCommands?: boolean) {
20+
super();
21+
this.disposables = [];
22+
if(!skipRegisteringCommands) {
23+
this.disposables.push(vscode.commands.registerCommand(
24+
"PowerShell.EnableNotebookMode",
25+
PowerShellNotebooksFeature.EnableNotebookMode));
2026

21-
public label: string = "PowerShell";
22-
public preloads?: vscode.Uri[];
27+
this.disposables.push(vscode.commands.registerCommand(
28+
"PowerShell.DisableNotebookMode",
29+
PowerShellNotebooksFeature.DisableNotebookMode));
30+
}
2331

24-
public constructor(private logger: ILogger, skipRegisteringCommands?: boolean) {
25-
super();
26-
// VS Code Notebook API uses this property for handling cell execution.
27-
this.kernel = this;
32+
this.notebookContentProvider = new PowerShellNotebookContentProvider(logger);
33+
this.notebookKernel = new PowerShellNotebookKernel();
34+
}
2835

29-
if(!skipRegisteringCommands) {
30-
this.showNotebookModeCommand = vscode.commands.registerCommand(
31-
"PowerShell.ShowNotebookMode",
32-
PowerShellNotebooksFeature.showNotebookMode);
36+
public registerNotebookProviders() {
37+
this.disposables.push(vscode.notebook.registerNotebookKernelProvider({
38+
viewType: "PowerShellNotebookMode"
39+
}, this.notebookKernel));
40+
41+
this.disposables.push(vscode.notebook.registerNotebookContentProvider(
42+
"PowerShellNotebookMode",
43+
this.notebookContentProvider));
44+
}
3345

34-
this.hideNotebookModeCommand = vscode.commands.registerCommand(
35-
"PowerShell.HideNotebookMode",
36-
PowerShellNotebooksFeature.hideNotebookMode);
46+
public dispose() {
47+
for (const disposable of this.disposables) {
48+
disposable.dispose();
3749
}
3850
}
3951

52+
public setLanguageClient(languageClient: LanguageClient) {
53+
this.notebookKernel.setLanguageClient(languageClient);
54+
}
55+
56+
private static async EnableNotebookMode() {
57+
const uri = vscode.window.activeTextEditor.document.uri;
58+
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
59+
await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode");
60+
}
61+
62+
private static async DisableNotebookMode() {
63+
const uri = vscode.notebook.activeNotebookEditor.document.uri;
64+
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
65+
await vscode.commands.executeCommand("vscode.openWith", uri, "default");
66+
}
67+
}
68+
69+
class PowerShellNotebookContentProvider implements vscode.NotebookContentProvider {
70+
private _onDidChangeNotebook = new vscode.EventEmitter<vscode.NotebookDocumentEditEvent>();
71+
public onDidChangeNotebook: vscode.Event<vscode.NotebookDocumentEditEvent> = this._onDidChangeNotebook.event;
72+
73+
public constructor(private logger: ILogger) {
74+
}
75+
4076
public async openNotebook(uri: vscode.Uri, context: vscode.NotebookDocumentOpenContext): Promise<vscode.NotebookData> {
4177
// load from backup if needed.
4278
const actualUri = context.backupId ? vscode.Uri.parse(context.backupId) : uri;
@@ -186,11 +222,6 @@ export class PowerShellNotebooksFeature extends LanguageClientConsumer implement
186222
};
187223
}
188224

189-
public dispose() {
190-
this.showNotebookModeCommand.dispose();
191-
this.hideNotebookModeCommand.dispose();
192-
}
193-
194225
private async _save(document: vscode.NotebookDocument, targetResource: vscode.Uri, _token: vscode.CancellationToken): Promise<void> {
195226
this.logger.writeDiagnostic(`Saving Notebook: ${targetResource.toString()}`);
196227

@@ -215,35 +246,64 @@ export class PowerShellNotebooksFeature extends LanguageClientConsumer implement
215246

216247
await vscode.workspace.fs.writeFile(targetResource, new TextEncoder().encode(retArr.join("\n")));
217248
}
249+
}
218250

219-
private static async showNotebookMode() {
220-
const uri = vscode.window.activeTextEditor.document.uri;
221-
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
222-
await vscode.commands.executeCommand("vscode.openWith", uri, "PowerShellNotebookMode");
251+
class PowerShellNotebookKernel implements vscode.NotebookKernel, vscode.NotebookKernelProvider {
252+
private static informationMessage = "PowerShell extension has not finished starting up yet. Please try again in a few moments.";
253+
254+
public id?: string;
255+
public label: string = "PowerShell";
256+
public description?: string = "The PowerShell Notebook Mode kernel that runs commands in the PowerShell Integrated Console.";
257+
public isPreferred?: boolean;
258+
public preloads?: vscode.Uri[];
259+
260+
private _languageClient: LanguageClient;
261+
private get languageClient(): LanguageClient {
262+
if (!this._languageClient) {
263+
vscode.window.showInformationMessage(
264+
PowerShellNotebookKernel.informationMessage);
265+
}
266+
return this._languageClient;
223267
}
224268

225-
private static async hideNotebookMode() {
226-
const uri = vscode.notebook.activeNotebookEditor.document.uri;
227-
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
228-
await vscode.commands.executeCommand("vscode.openWith", uri, "default");
269+
private set languageClient(value: LanguageClient) {
270+
this._languageClient = value;
229271
}
230272

231-
/*
232-
`vscode.NotebookKernel` implementations
233-
*/
234-
public async executeAllCells(document: vscode.NotebookDocument, token: vscode.CancellationToken): Promise<void> {
273+
public async executeAllCells(document: vscode.NotebookDocument): Promise<void> {
235274
for (const cell of document.cells) {
236-
await this.executeCell(document, cell, token);
275+
if (cell.cellKind === vscode.CellKind.Code) {
276+
await this.executeCell(document, cell);
277+
}
237278
}
238279
}
239280

240-
public async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, token: vscode.CancellationToken): Promise<void> {
241-
if (token.isCancellationRequested) {
242-
return;
243-
}
244-
281+
public async executeCell(document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined): Promise<void> {
245282
await this.languageClient.sendRequest(EvaluateRequestType, {
246283
expression: cell.document.getText(),
247284
});
248285
}
286+
287+
// Since executing a cell is a "fire and forget", there's no time for the user to cancel
288+
// any of the executing cells. We can bring this in after PSES has a better API for executing code.
289+
public cancelCellExecution(document: vscode.NotebookDocument, cell: vscode.NotebookCell): void {
290+
return;
291+
}
292+
293+
// Since executing a cell is a "fire and forget", there's no time for the user to cancel
294+
// any of the executing cells. We can bring this in after PSES has a better API for executing code.
295+
public cancelAllCellsExecution(document: vscode.NotebookDocument): void {
296+
return;
297+
}
298+
299+
public setLanguageClient(languageClient: LanguageClient) {
300+
this.languageClient = languageClient;
301+
}
302+
303+
/*
304+
vscode.NotebookKernelProvider implementation
305+
*/
306+
public provideKernels(document: vscode.NotebookDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.NotebookKernel[]> {
307+
return [this];
308+
}
249309
}

src/main.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,17 @@ export function activate(context: vscode.ExtensionContext): void {
170170
const powerShellNotebooksFeature = new PowerShellNotebooksFeature(logger);
171171

172172
try {
173-
context.subscriptions.push(vscode.notebook.registerNotebookContentProvider("PowerShellNotebookMode", powerShellNotebooksFeature));
173+
powerShellNotebooksFeature.registerNotebookProviders();
174174
languageClientConsumers.push(powerShellNotebooksFeature);
175175
} catch (e) {
176176
// This would happen if VS Code changes their API.
177177
powerShellNotebooksFeature.dispose();
178178
logger.writeVerbose("Failed to register NotebookContentProvider", e);
179179
}
180+
} else {
181+
vscode.commands.registerCommand(
182+
"PowerShell.EnableNotebookMode",
183+
() => vscode.window.showWarningMessage("Notebook Mode only works in Visual Studio Code Insiders. To get it, go to: aka.ms/vscode-insiders"));
180184
}
181185

182186
sessionManager.setLanguageClientConsumers(languageClientConsumers);

test/features/PowerShellNotebooks.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,12 @@ suite("PowerShellNotebooks tests", () => {
192192
]);
193193

194194
const feature = new PowerShellNotebooksFeature(new MockLogger(), true);
195+
// `notebookContentProvider` is a private property so cast the feature as `any` so we can access it.
196+
const notebookContentProvider: vscode.NotebookContentProvider = (feature as any).notebookContentProvider;
195197

196198
for (const [uri, expectedCells] of notebookTestData) {
197199
test(`Can open a notebook with expected cells - ${uri.fsPath}`, async () => {
198-
const actualNotebookData = await feature.openNotebook(uri, {});
200+
const actualNotebookData = await notebookContentProvider.openNotebook(uri, {});
199201
compareCells(actualNotebookData.cells, expectedCells);
200202
});
201203
}
@@ -218,8 +220,8 @@ suite("PowerShellNotebooks tests", () => {
218220
notebookSimpleMixedComments.toString());
219221

220222
// Save it as testFile.ps1 and reopen it using the feature.
221-
await feature.saveNotebookAs(uri, vscode.notebook.activeNotebookEditor.document, null);
222-
const newNotebook = await feature.openNotebook(uri, {});
223+
await notebookContentProvider.saveNotebookAs(uri, vscode.notebook.activeNotebookEditor.document, null);
224+
const newNotebook = await notebookContentProvider.openNotebook(uri, {});
223225

224226
// Compare that saving as a file results in the same cell data as the existing one.
225227
const expectedCells = notebookTestData.get(notebookSimpleMixedComments);

vscode.proposed.d.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ declare module 'vscode' {
8989
Error = 4
9090
}
9191

92+
export enum NotebookRunState {
93+
Running = 1,
94+
Idle = 2
95+
}
96+
9297
export interface NotebookCellMetadata {
9398
/**
9499
* Controls if the content of a cell is editable or not.
@@ -191,6 +196,11 @@ declare module 'vscode' {
191196
* Additional attributes of the document metadata.
192197
*/
193198
custom?: { [key: string]: any };
199+
200+
/**
201+
* The document's current run state
202+
*/
203+
runState?: NotebookRunState;
194204
}
195205

196206
export interface NotebookDocument {
@@ -498,8 +508,10 @@ declare module 'vscode' {
498508
description?: string;
499509
isPreferred?: boolean;
500510
preloads?: Uri[];
501-
executeCell(document: NotebookDocument, cell: NotebookCell, token: CancellationToken): Promise<void>;
502-
executeAllCells(document: NotebookDocument, token: CancellationToken): Promise<void>;
511+
executeCell(document: NotebookDocument, cell: NotebookCell): void;
512+
cancelCellExecution(document: NotebookDocument, cell: NotebookCell): void;
513+
executeAllCells(document: NotebookDocument): void;
514+
cancelAllCellsExecution(document: NotebookDocument): void;
503515
}
504516

505517
export interface NotebookDocumentFilter {

0 commit comments

Comments
 (0)