diff --git a/extensions/notebook/src/jupyter/jupyterKernel.ts b/extensions/notebook/src/jupyter/jupyterKernel.ts index 5d9f2d8bcb..0f2049a410 100644 --- a/extensions/notebook/src/jupyter/jupyterKernel.ts +++ b/extensions/notebook/src/jupyter/jupyterKernel.ts @@ -109,6 +109,10 @@ export class JupyterKernel implements nb.IKernel { interrupt(): Promise { return this.kernelImpl.interrupt(); } + + restart(): Promise { + return this.kernelImpl.restart(); + } } export class JupyterFuture implements nb.IFuture { diff --git a/extensions/notebook/src/test/common.ts b/extensions/notebook/src/test/common.ts index b89518735e..e778bfb0f3 100644 --- a/extensions/notebook/src/test/common.ts +++ b/extensions/notebook/src/test/common.ts @@ -308,6 +308,9 @@ export class TestKernel implements azdata.nb.IKernel { interrupt(): Thenable { throw new Error('Method not implemented.'); } + restart(): Thenable { + throw new Error('Method not implemented.'); + } } //#endregion diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index c2d9ace749..144010c0eb 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -128,6 +128,24 @@ declare module 'azdata' { * An event that is emitted when a [notebook document](#NotebookDocument) is closed. */ export const onDidCloseNotebookDocument: vscode.Event; + + export interface IKernel { + + /** + * Restart a kernel. + * + * #### Notes + * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/4.x/notebook/services/api/api.yaml#!/kernels). + * + * The promise is fulfilled on a valid response and rejected otherwise. + * + * It is assumed that the API call does not mutate the kernel id or name. + * + * The promise will be rejected if the kernel status is `Dead` or if the + * request fails or the response is invalid. + */ + restart(): Thenable; + } } /** diff --git a/src/sql/workbench/api/browser/mainThreadNotebook.ts b/src/sql/workbench/api/browser/mainThreadNotebook.ts index 537c9eafe7..3a0ec58ab0 100644 --- a/src/sql/workbench/api/browser/mainThreadNotebook.ts +++ b/src/sql/workbench/api/browser/mainThreadNotebook.ts @@ -484,6 +484,10 @@ class KernelWrapper implements azdata.nb.IKernel { interrupt(): Thenable { return this._proxy.ext.$interruptKernel(this.kernelDetails.kernelId); } + + restart(): Thenable { + return this._proxy.ext.$restartKernel(this.kernelDetails.kernelId); + } } diff --git a/src/sql/workbench/api/common/extHostNotebook.ts b/src/sql/workbench/api/common/extHostNotebook.ts index 58641942a2..a7e48dbac7 100644 --- a/src/sql/workbench/api/common/extHostNotebook.ts +++ b/src/sql/workbench/api/common/extHostNotebook.ts @@ -213,6 +213,11 @@ export class ExtHostNotebook implements ExtHostNotebookShape { return kernel.interrupt(); } + $restartKernel(kernelId: number): Thenable { + let kernel = this._getAdapter(kernelId); + return kernel.restart(); + } + $sendInputReply(futureId: number, content: azdata.nb.IInputReply): void { let future = this._getAdapter(futureId); return future.sendInputReply(content); diff --git a/src/sql/workbench/api/common/notebooks/vscodeExecuteProvider.ts b/src/sql/workbench/api/common/notebooks/vscodeExecuteProvider.ts index 2f962dfcf9..9701ab4b9f 100644 --- a/src/sql/workbench/api/common/notebooks/vscodeExecuteProvider.ts +++ b/src/sql/workbench/api/common/notebooks/vscodeExecuteProvider.ts @@ -156,6 +156,10 @@ class VSCodeKernel implements azdata.nb.IKernel { public async interrupt(): Promise { return; } + + public async restart(): Promise { + return; + } } class VSCodeSession implements azdata.nb.ISession { diff --git a/src/sql/workbench/api/common/sqlExtHost.protocol.ts b/src/sql/workbench/api/common/sqlExtHost.protocol.ts index 57272b5ef9..0fd09ddbc8 100644 --- a/src/sql/workbench/api/common/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/common/sqlExtHost.protocol.ts @@ -952,6 +952,7 @@ export interface ExtHostNotebookShape { $requestComplete(kernelId: number, content: azdata.nb.ICompleteRequest): Thenable; $requestExecute(kernelId: number, content: azdata.nb.IExecuteRequest, disposeOnDone?: boolean): Thenable; $interruptKernel(kernelId: number): Thenable; + $restartKernel(kernelId: number): Thenable; // Future APIs $sendInputReply(futureId: number, content: azdata.nb.IInputReply): void; diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts index 0569b0b2cb..6f6c54c7c4 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -239,6 +239,24 @@ CommandsRegistry.registerCommand({ } }); +CommandsRegistry.registerCommand({ + id: 'notebook.restartKernel', + handler: async (accessor: ServicesAccessor) => { + const editorService: IEditorService = accessor.get(IEditorService); + if (editorService.activeEditor instanceof NotebookInput) { + await editorService.activeEditor.notebookModel?.clientSession?.restart(); + } + } +}); + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: 'notebook.restartKernel', + title: localize('restartNotebookKernel', "Restart Notebook Kernel"), + }, + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookEditor.ID)) +}); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_TAB_FOCUS_COMMAND_ID, diff --git a/src/sql/workbench/contrib/notebook/test/emptySessionClasses.ts b/src/sql/workbench/contrib/notebook/test/emptySessionClasses.ts index c2cb747034..edd4c0fdd0 100644 --- a/src/sql/workbench/contrib/notebook/test/emptySessionClasses.ts +++ b/src/sql/workbench/contrib/notebook/test/emptySessionClasses.ts @@ -168,6 +168,10 @@ class EmptyKernel implements nb.IKernel { interrupt(): Thenable { return Promise.resolve(undefined); } + + restart(): Thenable { + return Promise.resolve(undefined); + } } export class EmptyFuture implements FutureInternal { diff --git a/src/sql/workbench/contrib/notebook/test/stubs.ts b/src/sql/workbench/contrib/notebook/test/stubs.ts index 9a94f39dba..c21f0c9011 100644 --- a/src/sql/workbench/contrib/notebook/test/stubs.ts +++ b/src/sql/workbench/contrib/notebook/test/stubs.ts @@ -353,7 +353,7 @@ export class ClientSessionStub implements IClientSession { selectKernel(): Promise { throw new Error('Method not implemented.'); } - restart(): Promise { + restart(): Promise { throw new Error('Method not implemented.'); } setPath(path: string): Promise { @@ -464,6 +464,9 @@ export class KernelStub implements nb.IKernel { interrupt(): Thenable { throw new Error('Method not implemented.'); } + restart(): Thenable { + throw new Error('Method not implemented.'); + } } export class FutureStub implements nb.IFuture { diff --git a/src/sql/workbench/services/notebook/browser/models/clientSession.ts b/src/sql/workbench/services/notebook/browser/models/clientSession.ts index ced8cfd585..e0f26bf11c 100644 --- a/src/sql/workbench/services/notebook/browser/models/clientSession.ts +++ b/src/sql/workbench/services/notebook/browser/models/clientSession.ts @@ -323,16 +323,27 @@ export class ClientSession implements IClientSession { /** * Restart the session. * - * @returns A promise that resolves with whether the kernel has restarted. + * @returns A promise that resolves when the kernel has restarted. * * #### Notes - * If there is a running kernel, present a dialog. - * If there is no kernel, we start a kernel with the last run - * kernel name and resolves with `true`. If no kernel has been started, - * this is a no-op, and resolves with `false`. + * If there is an existing kernel, restart it and resolve. + * If no kernel has been started, this is a no-op, and resolves. + * Reject on error. */ - restart(): Promise { - throw new Error('Not implemented'); + restart(): Promise { + if (!this._session?.kernel) { + // no-op if no kernel is present + return Promise.resolve(); + } + let restartCompleted = new Deferred(); + this._session?.kernel?.restart().then(() => { + this.options.notificationService.info(localize('kernelRestartedSuccessfully', 'Kernel restarted successfully')); + restartCompleted.resolve(); + }, err => { + this.options.notificationService.error(localize('kernelRestartFailed', 'Kernel restart failed: {0}', err)); + restartCompleted.reject(err); + }); + return restartCompleted.promise; } /** diff --git a/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts b/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts index 951a0f8dbf..8c7eddf413 100644 --- a/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts +++ b/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts @@ -179,15 +179,14 @@ export interface IClientSession extends IDisposable { /** * Restart the session. * - * @returns A promise that resolves with whether the kernel has restarted. + * @returns A promise that resolves when the kernel has restarted. * * #### Notes - * If there is a running kernel, present a dialog. - * If there is no kernel, we start a kernel with the last run - * kernel name and resolves with `true`. If no kernel has been started, - * this is a no-op, and resolves with `false`. + * If there is an existing kernel, restart it and resolve. + * If no kernel has been started, this is a no-op, and resolves. + * Reject on error. */ - restart(): Promise; + restart(): Promise; /** * Change the session path. diff --git a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts index 39e5bdb99b..60f893fe83 100644 --- a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts +++ b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts @@ -382,6 +382,10 @@ class SqlKernel extends Disposable implements nb.IKernel { }); } + restart(): Thenable { + return Promise.reject(localize('SqlKernelRestartNotSupported', 'SQL kernel restart not supported')); + } + private addQueryEventListeners(queryRunner: QueryRunner): void { this._register(queryRunner.onQueryEnd(() => { this.queryComplete().catch(error => { diff --git a/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts b/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts index a1966c61a8..1b37e835ee 100644 --- a/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts +++ b/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts @@ -252,6 +252,9 @@ class ExtHostNotebookStub implements ExtHostNotebookShape { $interruptKernel(kernelId: number): Thenable { throw new Error('Method not implemented.'); } + $restartKernel(kernelId: number): Thenable { + throw new Error('Method not implemented.'); + } $sendInputReply(futureId: number, content: azdata.nb.IInputReply): void { throw new Error('Method not implemented.'); }