/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter, Event } from 'vs/base/common/event'; import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { Iterable } from 'vs/base/common/iterator'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IEditorService, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; function MODEL_ID(resource: URI): string { return resource.toString(); } export class NotebookProviderInfoStore { private readonly contributedEditors = new Map(); clear() { this.contributedEditors.clear(); } get(viewType: string): NotebookProviderInfo | undefined { return this.contributedEditors.get(viewType); } add(info: NotebookProviderInfo): void { if (this.contributedEditors.has(info.id)) { console.log(`Custom editor with id '${info.id}' already registered`); return; } this.contributedEditors.set(info.id, info); } getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] { return [...Iterable.filter(this.contributedEditors.values(), customEditor => customEditor.matches(resource))]; } public [Symbol.iterator](): Iterator { return this.contributedEditors.values(); } } export class NotebookOutputRendererInfoStore { private readonly contributedRenderers = new Map(); clear() { this.contributedRenderers.clear(); } get(viewType: string): NotebookOutputRendererInfo | undefined { return this.contributedRenderers.get(viewType); } add(info: NotebookOutputRendererInfo): void { if (this.contributedRenderers.has(info.id)) { console.log(`Custom notebook output renderer with id '${info.id}' already registered`); return; } this.contributedRenderers.set(info.id, info); } getContributedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { return Array.from(this.contributedRenderers.values()).filter(customEditor => customEditor.matches(mimeType)); } } class ModelData implements IDisposable { private readonly _modelEventListeners = new DisposableStore(); constructor( public model: NotebookTextModel, onWillDispose: (model: INotebookTextModel) => void ) { this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model))); } dispose(): void { this._modelEventListeners.dispose(); } } export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { _serviceBrand: undefined; private readonly _notebookProviders = new Map(); private readonly _notebookRenderers = new Map(); notebookProviderInfoStore: NotebookProviderInfoStore = new NotebookProviderInfoStore(); notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); private readonly _models: { [modelId: string]: ModelData; }; private _onDidChangeActiveEditor = new Emitter<{ viewType: string, uri: URI }>(); onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }> = this._onDidChangeActiveEditor.event; private readonly _onDidChangeViewTypes = new Emitter(); onDidChangeViewTypes: Event = this._onDidChangeViewTypes.event; private cutItems: NotebookCellTextModel[] | undefined; modelManager: NotebookEditorModelManager; constructor( @IExtensionService private readonly extensionService: IExtensionService, @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this._models = {}; this.modelManager = this.instantiationService.createInstance(NotebookEditorModelManager); notebookProviderExtensionPoint.setHandler((extensions) => { this.notebookProviderInfoStore.clear(); for (const extension of extensions) { for (const notebookContribution of extension.value) { this.notebookProviderInfoStore.add(new NotebookProviderInfo({ id: notebookContribution.viewType, displayName: notebookContribution.displayName, selector: notebookContribution.selector || [], providerDisplayName: extension.description.isBuiltin ? nls.localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, })); } } // console.log(this._notebookProviderInfoStore); }); notebookRendererExtensionPoint.setHandler((renderers) => { this.notebookRenderersInfoStore.clear(); for (const extension of renderers) { for (const notebookContribution of extension.value) { this.notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({ id: notebookContribution.viewType, displayName: notebookContribution.displayName, mimeTypes: notebookContribution.mimeTypes || [] })); } } // console.log(this.notebookRenderersInfoStore); }); this.editorService.registerCustomEditorViewTypesHandler('Notebook', this); } getViewTypes(): ICustomEditorInfo[] { return [...this.notebookProviderInfoStore].map(info => ({ id: info.id, displayName: info.displayName, providerDisplayName: info.providerDisplayName })); } async canResolve(viewType: string): Promise { if (!this._notebookProviders.has(viewType)) { // this awaits full activation of all matching extensions await this.extensionService.activateByEvent(`onNotebookEditor:${viewType}`); } return this._notebookProviders.has(viewType); } registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) { this._notebookProviders.set(viewType, { extensionData, controller }); this._onDidChangeViewTypes.fire(); } unregisterNotebookProvider(viewType: string): void { this._notebookProviders.delete(viewType); this._onDidChangeViewTypes.fire(); } registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]) { this._notebookRenderers.set(handle, { extensionData, type, selectors, preloads }); } unregisterNotebookRenderer(handle: number) { this._notebookRenderers.delete(handle); } getRendererInfo(handle: number): INotebookRendererInfo | undefined { const renderer = this._notebookRenderers.get(handle); if (renderer) { return { id: renderer.extensionData.id, extensionLocation: URI.revive(renderer.extensionData.location), preloads: renderer.preloads }; } return; } async resolveNotebook(viewType: string, uri: URI): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; } const notebookModel = await provider.controller.resolveNotebook(viewType, uri); if (!notebookModel) { return undefined; } // new notebook model created const modelId = MODEL_ID(uri); const modelData = new ModelData( notebookModel, (model) => this._onWillDispose(model), ); this._models[modelId] = modelData; return modelData.model; } async executeNotebook(viewType: string, uri: URI): Promise { let provider = this._notebookProviders.get(viewType); if (provider) { return provider.controller.executeNotebook(viewType, uri, new CancellationTokenSource().token); // Cancellation for notebooks - TODO } return; } async executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise { const provider = this._notebookProviders.get(viewType); if (provider) { await provider.controller.executeNotebookCell(uri, handle, token); } } getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[] { return this.notebookProviderInfoStore.getContributedNotebook(resource); } getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined { return this.notebookProviderInfoStore.get(viewType); } getContributedNotebookOutputRenderers(mimeType: string): readonly NotebookOutputRendererInfo[] { return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); } getNotebookProviderResourceRoots(): URI[] { let ret: URI[] = []; this._notebookProviders.forEach(val => { ret.push(URI.revive(val.extensionData.location)); }); return ret; } destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void { let provider = this._notebookProviders.get(viewType); if (provider) { provider.controller.destoryNotebookDocument(notebook); } } updateActiveNotebookDocument(viewType: string, resource: URI): void { this._onDidChangeActiveEditor.fire({ viewType, uri: resource }); } setToCopy(items: NotebookCellTextModel[]) { this.cutItems = items; } getToCopy(): NotebookCellTextModel[] | undefined { return this.cutItems; } async save(viewType: string, resource: URI): Promise { let provider = this._notebookProviders.get(viewType); if (provider) { return provider.controller.save(resource); } return false; } onDidReceiveMessage(viewType: string, uri: URI, message: any): void { let provider = this._notebookProviders.get(viewType); if (provider) { return provider.controller.onDidReceiveMessage(uri, message); } } private _onWillDispose(model: INotebookTextModel): void { let modelId = MODEL_ID(model.uri); let modelData = this._models[modelId]; delete this._models[modelId]; modelData?.dispose(); // this._onModelRemoved.fire(model); } }