diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index d512d5e0ff0..106e05af0ed 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -345,3 +345,15 @@ suite('regression', () => { assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); }); }); + +suite('webview resource uri', () => { + test('asWebviewUri', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await waitFor(500); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + const uri = vscode.notebook.activeNotebookEditor!.asWebviewUri(vscode.Uri.parse('./hello.png')); + assert.equal(uri.scheme, 'vscode-resource'); + }); +}); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5d9dd2da33e..9f0c6ee5723 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1732,6 +1732,11 @@ declare module 'vscode' { */ 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; } @@ -1794,6 +1799,8 @@ declare module 'vscode' { // revert?(document: NotebookDocument, cancellation: CancellationToken): Thenable; // backup?(document: NotebookDocument, cancellation: CancellationToken): Thenable; + kernel?: NotebookKernel; + /** * Responsible for filling in outputs for the cell */ diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 2ead740a60d..2a8ebb3be5c 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -17,6 +17,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IRelativePattern } from 'vs/base/common/glob'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { generateUuid } from 'vs/base/common/uuid'; export class MainThreadNotebookDocument extends Disposable { private _textModel: NotebookTextModel; @@ -29,10 +30,11 @@ export class MainThreadNotebookDocument extends Disposable { private readonly _proxy: ExtHostNotebookShape, public handle: number, public viewType: string, - public uri: URI + public uri: URI, + public webviewId: string, ) { super(); - this._textModel = new NotebookTextModel(handle, viewType, uri); + this._textModel = new NotebookTextModel(handle, viewType, uri, webviewId); this._register(this._textModel.onDidModelChange(e => { this._proxy.$acceptModelChanged(this.uri, e); })); @@ -121,8 +123,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.unregisterNotebookRenderer(handle); } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType); + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, hasKernelSupport: boolean): Promise { + let controller = new MainThreadNotebookController(this._proxy, this, viewType, hasKernelSupport); this._notebookProviders.set(viewType, controller); this._notebookService.registerNotebookController(viewType, extension, controller); return; @@ -176,8 +178,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); } - async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise { - return this._proxy.$executeNotebook(viewType, uri, undefined, token); + async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { + return this._proxy.$executeNotebook(viewType, uri, undefined, useAttachedKernel, token); } async $postMessage(handle: number, value: any): Promise { @@ -203,7 +205,8 @@ export class MainThreadNotebookController implements IMainNotebookController { constructor( private readonly _proxy: ExtHostNotebookShape, private _mainThreadNotebook: MainThreadNotebooks, - private _viewType: string + private _viewType: string, + readonly hasKernelSupport: boolean ) { } @@ -227,7 +230,7 @@ export class MainThreadNotebookController implements IMainNotebookController { return mainthreadNotebook.textModel; } - let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri); + let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri, generateUuid()); await this.createNotebookDocument(document); if (forBackup) { @@ -272,8 +275,8 @@ export class MainThreadNotebookController implements IMainNotebookController { mainthreadNotebook?.textModel.$spliceNotebookCellOutputs(cellHandle, splices); } - async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise { - return this._mainThreadNotebook.executeNotebook(viewType, uri, token); + async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { + return this._mainThreadNotebook.executeNotebook(viewType, uri, useAttachedKernel, token); } onDidReceiveMessage(uri: UriComponents, message: any): void { @@ -287,6 +290,7 @@ export class MainThreadNotebookController implements IMainNotebookController { addedDocuments: [{ viewType: document.viewType, handle: document.handle, + webviewId: document.webviewId, uri: document.uri, metadata: document.textModel.metadata }] @@ -327,8 +331,8 @@ export class MainThreadNotebookController implements IMainNotebookController { document?.textModel.updateRenderers(renderers); } - async executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise { - return this._proxy.$executeNotebook(this._viewType, uri, handle, token); + async executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise { + return this._proxy.$executeNotebook(this._viewType, uri, handle, useAttachedKernel, token); } async save(uri: URI, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 59926ad7adf..82c79c2b59c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -134,7 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); - const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors)); + const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment)); const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index aa5543d5f0a..739d5d69e3d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -689,7 +689,7 @@ export type NotebookCellOutputsSplice = [ ]; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, hasKernelSupport: boolean): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(handle: number): Promise; @@ -1545,6 +1545,7 @@ export interface INotebookEditorPropertiesChangeData { export interface INotebookModelAddedData { uri: UriComponents; handle: number; + webviewId: string; // versionId: number; viewType: string; metadata?: NotebookDocumentMetadata; @@ -1560,7 +1561,7 @@ export interface INotebookDocumentsAndEditorsDelta { export interface ExtHostNotebookShape { $resolveNotebookData(viewType: string, uri: UriComponents): Promise; - $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; + $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise; $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 4403c2a169c..2cb22e8baaa 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -19,6 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { NotImplementedProxy } from 'vs/base/common/types'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; +import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; interface IObservable { proxy: T; @@ -498,6 +499,8 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook public uri: URI, private _proxy: MainThreadNotebookShape, private _onDidReceiveMessage: Emitter, + private _webviewId: string, + private _webviewInitData: WebviewInitData, public document: ExtHostNotebookDocument, private _documentsAndEditors: ExtHostDocumentsAndEditors ) { @@ -585,6 +588,9 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook return this._proxy.$postMessage(this.document.handle, message); } + asWebviewUri(localResource: vscode.Uri): vscode.Uri { + return asWebviewUri(this._webviewInitData, this._webviewId, localResource); + } } export class ExtHostNotebookOutputRenderer { @@ -656,7 +662,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _onDidCloseNotebookDocument = new Emitter(); onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; - constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors) { + constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _webviewInitData: WebviewInitData) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); commands.registerArgumentProcessor({ @@ -716,7 +722,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } this._notebookContentProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, !!provider.kernel); return new VSCodeDisposable(() => { this._notebookContentProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); @@ -833,7 +839,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }; } - async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise { + async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise { let document = this._documents.get(URI.revive(uri).toString()); if (!document) { @@ -841,9 +847,18 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } if (this._notebookContentProviders.has(viewType)) { - let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; + const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; + const provider = this._notebookContentProviders.get(viewType)!.provider; - return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token); + if (provider.kernel && useAttachedKernel) { + if (cell) { + return provider.kernel.executeCell(document, cell, token); + } else { + return provider.kernel.executeAllCells(document, token); + } + } else { + return provider.executeCell(document, cell, token); + } } } @@ -1004,6 +1019,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN revivedUri, this._proxy, onDidReceiveMessage, + modelData.webviewId, + this._webviewInitData, document, this._documentsAndEditors ); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index 19cac62ba1a..600e707f1f4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -3,76 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotebookEditor, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditor, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_HAS_MULTIPLE_KERNELS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickInputService, QuickPickInput, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import * as nls from 'vs/nls'; -import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { NOTEBOOK_ACTIONS_CATEGORY, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -// export class NotebookEditorStatus extends Disposable implements IWorkbenchContribution { -// private _localStore: DisposableStore = new DisposableStore(); -// private kernelInfoElement = this._register(new MutableDisposable()); - -// constructor( -// @IEditorService private readonly editorService: IEditorService, -// @IStatusbarService private readonly statusbarService: IStatusbarService, -// ) { -// super(); -// this.registerListeners(); -// } - -// private registerListeners(): void { -// this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); -// this.updateStatusBar(); -// } - -// private async updateStatusBar(): Promise { -// this._localStore.clear(); - -// const activeEditorPane = this.editorService.activeEditorPane as any | undefined; -// if (!activeEditorPane?.isNotebookEditor) { -// this.kernelInfoElement.clear(); -// return; -// } -// const editor = activeEditorPane.getControl() as INotebookEditor; -// this._localStore.add(editor.onDidChangeKernel(() => { -// this.updateKernelInfo(editor.activeKernel); -// })); - -// this.updateKernelInfo(editor.activeKernel); -// } - -// private updateKernelInfo(kernelInfo: INotebookKernelInfo | undefined) { -// if (!kernelInfo) { -// this.kernelInfoElement.clear(); -// return; -// } - -// const props: IStatusbarEntry = { -// text: kernelInfo.label, -// ariaLabel: kernelInfo.label, -// tooltip: nls.localize('selectKernel', "Select Notebook Kernel"), -// command: 'notebook.selectKernel' -// }; - -// this.updateElement(this.kernelInfoElement, props, 'status.notebook.kernel', nls.localize('selectKernel', "Select Notebook Kernel"), StatusbarAlignment.RIGHT, 50); -// } - -// private updateElement(element: MutableDisposable, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number) { -// if (!element.value) { -// element.value = this.statusbarService.addEntry(props, id, name, alignment, priority); -// } else { -// element.value.update(props); -// } -// } -// } - -// Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NotebookEditorStatus, LifecyclePhase.Eventually); - registerAction2(class extends Action2 { constructor() { @@ -82,12 +22,12 @@ registerAction2(class extends Action2 { title: nls.localize('notebookActions.selectKernel', "Select Notebook Kernel"), precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_FOCUSED), icon: { id: 'codicon/server-environment' }, - // menu: { - // id: MenuId.EditorTitle, - // // when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey), - // group: 'navigation', - // order: -2, - // }, + menu: { + id: MenuId.EditorTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_MULTIPLE_KERNELS), + group: 'navigation', + order: -2, + }, f1: true }); } @@ -119,6 +59,22 @@ registerAction2(class extends Action2 { }; }); + const provider = notebookService.getContributedNotebookProviders(editor.viewModel!.uri)[0]; + + if (provider.hasKernelSupport) { + picks.unshift({ + id: provider.id, + label: provider.displayName, + picked: !activeKernel, // no active kernel, the builtin kernel of the provider is used + description: activeKernel === undefined + ? nls.localize('currentActiveBuiltinKernel', " (Currently Active)") + : '', + run: () => { + editor.activeKernel = undefined; + } + }); + } + const action = await quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); return action?.run(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 7f5062358ee..6ab152226ba 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -47,6 +47,10 @@ export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey('note export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey('notebookCellRunState', undefined); // idle, running export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); // bool +// Kernels + +export const NOTEBOOK_HAS_MULTIPLE_KERNELS = new RawContextKey('notebookHasMultipleKernels', false); + export interface NotebookLayoutInfo { width: number; height: number; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 983be6092bd..a5439cf29de 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -30,7 +30,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, IEditableCellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, IEditableCellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; @@ -87,6 +87,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private editorEditable: IContextKey | null = null; private editorRunnable: IContextKey | null = null; private editorExecutingNotebook: IContextKey | null = null; + private notebookHasMultipleKernels: IContextKey | null = null; private outputRenderer: OutputRenderer; protected readonly _contributions: { [key: string]: INotebookEditorContribution; }; private scrollBeyondLastLine: boolean; @@ -98,7 +99,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor @IStorageService storageService: IStorageService, @INotebookService private notebookService: INotebookService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService readonly contextKeyService: IContextKeyService, @ILayoutService private readonly _layoutService: ILayoutService ) { super(); @@ -204,6 +205,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.editorRunnable = NOTEBOOK_EDITOR_RUNNABLE.bindTo(this.contextKeyService); this.editorRunnable.set(true); this.editorExecutingNotebook = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService); + this.notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(this.contextKeyService); + this.notebookHasMultipleKernels.set(false); const contributions = NotebookEditorExtensionsRegistry.getEditorContributions(); @@ -284,15 +287,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor ); dndController.setList(this.list); - this.webview = this.instantiationService.createInstance(BackLayerWebView, this); - this.webview.webview.onDidBlur(() => this.updateEditorFocus()); - this.webview.webview.onDidFocus(() => this.updateEditorFocus()); - this._register(this.webview.onMessage(message => { - if (this.viewModel) { - this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message); - } - })); - this.list.rowsContainer.appendChild(this.webview.element); + // create Webview this._register(this.list); this._register(combinedDisposable(...renders)); @@ -355,13 +350,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this.attachModel(textModel, viewState); } - const availableKernels = this.notebookService.getContributedNotebookKernels(textModel.viewType, textModel.uri); - this.activeKernel = availableKernels[0]; + this._setKernels(textModel); this.localStore.add(this.notebookService.onDidChangeKernels(() => { if (this.activeKernel === undefined) { - const availableKernels = this.notebookService.getContributedNotebookKernels(textModel.viewType, textModel.uri); - this.activeKernel = availableKernels[0]; + this._setKernels(textModel); } })); @@ -396,11 +389,35 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.viewModel?.dispose(); // avoid event this.notebookViewModel = undefined; - this.webview?.clearInsets(); - this.webview?.clearPreloadsCache(); + // this.webview?.clearInsets(); + // this.webview?.clearPreloadsCache(); + this.webview?.dispose(); + this.webview?.element.remove(); + this.webview = null; this.list?.clear(); } + private _setKernels(textModel: NotebookTextModel) { + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + const availableKernels = this.notebookService.getContributedNotebookKernels(textModel.viewType, textModel.uri); + + if (provider.hasKernelSupport && availableKernels.length > 0) { + this.notebookHasMultipleKernels!.set(true); + } else if (availableKernels.length > 1) { + this.notebookHasMultipleKernels!.set(true); + } else { + this.notebookHasMultipleKernels!.set(false); + } + + if (provider && provider.hasKernelSupport) { + // it has a builtin kernel, don't automatically choose a kernel + return; + } + + // the provider doesn't have a builtin kernel, choose a kernel + this.activeKernel = availableKernels[0]; + } + private updateForMetadata(): void { this.editorEditable?.set(!!this.viewModel!.metadata?.editable); this.editorRunnable?.set(!!this.viewModel!.metadata?.runnable); @@ -408,13 +425,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor DOM.toggleClass(this.getDomNode(), 'notebook-editor-editable', !!this.viewModel!.metadata?.editable); } - private async attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) { - if (!this.webview) { - this.webview = this.instantiationService.createInstance(BackLayerWebView, this); - this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element); - } + private createWebview(id: string) { + this.webview = this.instantiationService.createInstance(BackLayerWebView, this, id); + this.webview.webview.onDidBlur(() => this.updateEditorFocus()); + this.webview.webview.onDidFocus(() => this.updateEditorFocus()); + this.localStore.add(this.webview.onMessage(message => { + if (this.viewModel) { + this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message); + } + })); + this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview.element); + } - await this.webview.waitForInitialization(); + private async attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) { + this.createWebview(textModel.webviewId); + await this.webview!.waitForInitialization(); this.eventDispatcher = new NotebookEventDispatcher(); this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this.eventDispatcher, this.getLayoutInfo()); @@ -532,8 +557,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - getEditorViewState() { - const state = this.notebookViewModel!.getEditorViewState(); + getEditorViewState(): INotebookEditorViewState { + const state = this.notebookViewModel?.getEditorViewState(); + if (!state) { + return { + editingCells: {}, + editorViewStates: {} + }; + } + if (this.list) { state.scrollPosition = { left: this.list.scrollLeft, top: this.list.scrollTop }; let cellHeights: { [key: number]: number } = {}; @@ -1021,15 +1053,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor try { this.editorExecutingNotebook!.set(true); this.notebookViewModel!.currentTokenSource = tokenSource; + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + if (provider) { + const viewType = provider.id; + const notebookUri = this.notebookViewModel!.uri; - if (this._activeKernel) { - await this.notebookService.executeNotebook2(this.notebookViewModel!.viewType, this.notebookViewModel!.uri, this._activeKernel.id, tokenSource.token); - } else { - const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - if (provider) { - const viewType = provider.id; - const notebookUri = this.notebookViewModel!.uri; - return await this.notebookService.executeNotebook(viewType, notebookUri, tokenSource.token); + if (this._activeKernel) { + await this.notebookService.executeNotebook2(this.notebookViewModel!.viewType, this.notebookViewModel!.uri, this._activeKernel.id, tokenSource.token); + } else if (provider.hasKernelSupport) { + return await this.notebookService.executeNotebook(viewType, notebookUri, true, tokenSource.token); + } else { + return await this.notebookService.executeNotebook(viewType, notebookUri, false, tokenSource.token); } } @@ -1070,10 +1104,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (provider) { const viewType = provider.id; const notebookUri = this.notebookViewModel!.uri; + if (this._activeKernel) { return await this.notebookService.executeNotebookCell2(viewType, notebookUri, cell.handle, this._activeKernel.id, tokenSource.token); + } else if (provider.hasKernelSupport) { + return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, true, tokenSource.token); } else { - return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, tokenSource.token); + return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, false, tokenSource.token); } } } finally { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 9fef43d48ef..d603e23e988 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -178,6 +178,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) { this._notebookProviders.set(viewType, { extensionData, controller }); + this.notebookProviderInfoStore.get(viewType)!.hasKernelSupport = controller.hasKernelSupport; this._onDidChangeViewTypes.fire(); } @@ -312,20 +313,20 @@ export class NotebookService extends Disposable implements INotebookService, ICu return modelData.model; } - async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise { + async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { let provider = this._notebookProviders.get(viewType); if (provider) { - return provider.controller.executeNotebook(viewType, uri, token); + return provider.controller.executeNotebook(viewType, uri, useAttachedKernel, token); } return; } - async executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise { + async executeNotebookCell(viewType: string, uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise { const provider = this._notebookProviders.get(viewType); if (provider) { - await provider.controller.executeNotebookCell(uri, handle, token); + await provider.controller.executeNotebookCell(uri, handle, useAttachedKernel, token); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index d00e478f35c..d15640ea9b5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -136,9 +136,11 @@ export class BackLayerWebView extends Disposable { private readonly _onMessage = this._register(new Emitter()); public readonly onMessage: Event = this._onMessage.event; private _initalized: Promise; + private _disposed = false; constructor( public notebookEditor: INotebookEditor, + public id: string, @IWebviewService readonly webviewService: IWebviewService, @IOpenerService readonly openerService: IOpenerService, @INotebookService private readonly notebookService: INotebookService, @@ -473,7 +475,7 @@ ${loaderJs} return { cell: this.insetMapping.get(output)!.cell, output }; } - initialize(content: string) { + async initialize(content: string) { this.webview = this._createInset(this.webviewService, content); this.webview.mountTo(this.element); @@ -558,7 +560,7 @@ ${loaderJs} private _createInset(webviewService: IWebviewService, content: string) { const rootPath = URI.file(path.dirname(getPathFromAmdModule(require, ''))); this.localResourceRootsCache = [...this.notebookService.getNotebookProviderResourceRoots(), rootPath]; - const webview = webviewService.createWebviewElement('' + UUID.generateUuid(), { + const webview = webviewService.createWebviewElement(this.id, { enableFindWidget: false, }, { allowMultipleAPIAcquire: true, @@ -570,6 +572,10 @@ ${loaderJs} } shouldUpdateInset(cell: CodeCellViewModel, output: IOutput, cellTop: number) { + if (this._disposed) { + return; + } + let outputCache = this.insetMapping.get(output)!; let outputIndex = cell.outputs.indexOf(output); let outputOffset = cellTop + cell.getOutputOffset(outputIndex); @@ -586,6 +592,10 @@ ${loaderJs} } updateViewScrollTop(top: number, items: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[]) { + if (this._disposed) { + return; + } + let widgets: IContentWidgetTopRequest[] = items.map(item => { let outputCache = this.insetMapping.get(item.output)!; let id = outputCache.outputId; @@ -613,6 +623,10 @@ ${loaderJs} } createInset(cell: CodeCellViewModel, output: IOutput, cellTop: number, offset: number, shadowContent: string, preloads: Set) { + if (this._disposed) { + return; + } + this.updateRendererPreloads(preloads); let initialTop = cellTop + offset; @@ -648,6 +662,10 @@ ${loaderJs} } removeInset(output: IOutput) { + if (this._disposed) { + return; + } + let outputCache = this.insetMapping.get(output); if (!outputCache) { return; @@ -664,6 +682,10 @@ ${loaderJs} } hideInset(output: IOutput) { + if (this._disposed) { + return; + } + let outputCache = this.insetMapping.get(output); if (!outputCache) { return; @@ -679,6 +701,10 @@ ${loaderJs} } clearInsets() { + if (this._disposed) { + return; + } + this.webview.sendMessage({ type: 'clear' }); @@ -688,6 +714,10 @@ ${loaderJs} } focusOutput(cellId: string) { + if (this._disposed) { + return; + } + this.webview.focus(); setTimeout(() => { // Need this, or focus decoration is not shown. No clue. this.webview.sendMessage({ @@ -698,6 +728,10 @@ ${loaderJs} } updateRendererPreloads(preloads: ReadonlySet) { + if (this._disposed) { + return; + } + let resources: string[] = []; let extensionLocations: URI[] = []; preloads.forEach(preload => { @@ -741,4 +775,9 @@ ${loaderJs} clearPreloadsCache() { this.preloadsCache.clear(); } + + dispose() { + this._disposed = true; + super.dispose(); + } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 5b1fc5f5935..d7ed741fb38 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -104,7 +104,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel constructor( public handle: number, public viewType: string, - public uri: URI + public uri: URI, + public webviewId: string ) { super(); this.cells = []; diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index a1f4ca196a4..958245d133a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -18,6 +18,7 @@ export class NotebookProviderInfo { readonly displayName: string; readonly selector: readonly NotebookSelector[]; readonly providerDisplayName: string; + hasKernelSupport: boolean = false; constructor(descriptor: { readonly id: string; diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 74a4bd3d1a9..b1688f178dd 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -17,10 +17,11 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { + hasKernelSupport: boolean; createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise; - executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; + executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise; onDidReceiveMessage(uri: URI, message: any): void; - executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; + executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise; removeNotebookDocument(notebook: INotebookTextModel): Promise; save(uri: URI, token: CancellationToken): Promise; saveAs(uri: URI, target: URI, token: CancellationToken): Promise; @@ -42,8 +43,8 @@ export interface INotebookService { getRendererInfo(handle: number): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise; createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise; - executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; - executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; + executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise; + executeNotebookCell(viewType: string, uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise; executeNotebook2(viewType: string, uri: URI, kernelId: string, token: CancellationToken): Promise; executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string, token: CancellationToken): Promise; getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index 0d57383c533..37fa644debb 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -15,6 +15,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { reduceCellRanges, ICellRange } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { generateUuid } from 'vs/base/common/uuid'; suite('NotebookViewModel', () => { const instantiationService = new TestInstantiationService(); @@ -23,7 +24,7 @@ suite('NotebookViewModel', () => { instantiationService.spy(IUndoRedoService, 'pushElement'); test('ctor', function () { - const notebook = new NotebookTextModel(0, 'notebook', URI.parse('test')); + const notebook = new NotebookTextModel(0, 'notebook', URI.parse('test'), generateUuid()); const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 0eaf6aeed5e..1020fd08268 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -22,6 +22,8 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CellKind, CellUri, INotebookEditorModel, IOutput, NotebookCellMetadata, INotebookKernelInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; +import { generateUuid } from 'vs/base/common/uuid'; + export class TestCell extends NotebookCellTextModel { constructor( public viewType: string, @@ -266,7 +268,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { const viewType = 'notebook'; const editor = new TestNotebookEditor(); - const notebook = new NotebookTextModel(0, viewType, URI.parse('test')); + const notebook = new NotebookTextModel(0, viewType, URI.parse('test'), generateUuid()); notebook.cells = cells.map((cell, index) => { return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]); }); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 7695ee2df5b..c6a17bc044c 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -44,7 +44,7 @@ suite('NotebookConcatDocument', function () { }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); - extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors); + extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }); let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock() { // async openNotebook() { } }); @@ -52,7 +52,8 @@ suite('NotebookConcatDocument', function () { addedDocuments: [{ handle: 0, uri: notebookUri, - viewType: 'test' + viewType: 'test', + webviewId: 'testid' }] }); extHostNotebooks.$acceptModelChanged(notebookUri, {