Path: blob/main/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { localize } from '../../../../../nls.js';6import { toAction } from '../../../../../base/common/actions.js';7import { createErrorWithActions } from '../../../../../base/common/errorMessage.js';8import { Emitter, Event } from '../../../../../base/common/event.js';9import * as glob from '../../../../../base/common/glob.js';10import { Iterable } from '../../../../../base/common/iterator.js';11import { Lazy } from '../../../../../base/common/lazy.js';12import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';13import { ResourceMap } from '../../../../../base/common/map.js';14import { Schemas } from '../../../../../base/common/network.js';15import { basename, isEqual } from '../../../../../base/common/resources.js';16import { isDefined } from '../../../../../base/common/types.js';17import { URI } from '../../../../../base/common/uri.js';18import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';19import { ConfigurationTarget, IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';20import { IResourceEditorInput } from '../../../../../platform/editor/common/editor.js';21import { IFileService } from '../../../../../platform/files/common/files.js';22import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';23import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';24import { Memento, MementoObject } from '../../../../common/memento.js';25import { INotebookEditorContribution, notebookPreloadExtensionPoint, notebookRendererExtensionPoint, notebooksExtensionPoint } from '../notebookExtensionPoint.js';26import { INotebookEditorOptions } from '../notebookBrowser.js';27import { NotebookDiffEditorInput } from '../../common/notebookDiffEditorInput.js';28import { NotebookCellTextModel } from '../../common/model/notebookCellTextModel.js';29import { NotebookTextModel } from '../../common/model/notebookTextModel.js';30import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, NotebookExtensionDescription, INotebookStaticPreloadInfo, NotebookData } from '../../common/notebookCommon.js';31import { NotebookEditorInput } from '../../common/notebookEditorInput.js';32import { INotebookEditorModelResolverService } from '../../common/notebookEditorModelResolverService.js';33import { NotebookOutputRendererInfo, NotebookStaticPreloadInfo as NotebookStaticPreloadInfo } from '../../common/notebookOutputRenderer.js';34import { NotebookEditorDescriptor, NotebookProviderInfo } from '../../common/notebookProvider.js';35import { INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from '../../common/notebookService.js';36import { DiffEditorInputFactoryFunction, EditorInputFactoryFunction, EditorInputFactoryObject, IEditorResolverService, IEditorType, RegisteredEditorInfo, RegisteredEditorPriority, UntitledEditorInputFactoryFunction, type MergeEditorInputFactoryFunction } from '../../../../services/editor/common/editorResolverService.js';37import { IExtensionService, isProposedApiEnabled } from '../../../../services/extensions/common/extensions.js';38import { IExtensionPointUser } from '../../../../services/extensions/common/extensionsRegistry.js';39import { InstallRecommendedExtensionAction } from '../../../extensions/browser/extensionsActions.js';40import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js';41import { INotebookDocument, INotebookDocumentService } from '../../../../services/notebook/common/notebookDocumentService.js';42import { MergeEditorInput } from '../../../mergeEditor/browser/mergeEditorInput.js';43import type { EditorInputWithOptions, IResourceDiffEditorInput, IResourceMergeEditorInput } from '../../../../common/editor.js';44import { bufferToStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from '../../../../../base/common/buffer.js';45import type { IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js';46import { NotebookMultiDiffEditorInput } from '../diff/notebookMultiDiffEditorInput.js';47import { SnapshotContext } from '../../../../services/workingCopy/common/fileWorkingCopy.js';48import { CancellationToken } from '../../../../../base/common/cancellation.js';49import { CancellationError } from '../../../../../base/common/errors.js';50import { ICellRange } from '../../common/notebookRange.js';5152export class NotebookProviderInfoStore extends Disposable {5354private static readonly CUSTOM_EDITORS_STORAGE_ID = 'notebookEditors';55private static readonly CUSTOM_EDITORS_ENTRY_ID = 'editors';5657private readonly _memento: Memento;58private _handled: boolean = false;5960private readonly _contributedEditors = new Map<string, NotebookProviderInfo>();61private readonly _contributedEditorDisposables = this._register(new DisposableStore());6263constructor(64@IStorageService storageService: IStorageService,65@IExtensionService extensionService: IExtensionService,66@IEditorResolverService private readonly _editorResolverService: IEditorResolverService,67@IConfigurationService private readonly _configurationService: IConfigurationService,68@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,69@IInstantiationService private readonly _instantiationService: IInstantiationService,70@IFileService private readonly _fileService: IFileService,71@INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService,72@IUriIdentityService private readonly uriIdentService: IUriIdentityService,73) {74super();7576this._memento = new Memento(NotebookProviderInfoStore.CUSTOM_EDITORS_STORAGE_ID, storageService);7778const mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE);79// Process the notebook contributions but buffer changes from the resolver80this._editorResolverService.bufferChangeEvents(() => {81for (const info of (mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] || []) as NotebookEditorDescriptor[]) {82this.add(new NotebookProviderInfo(info), false);83}84});8586this._register(extensionService.onDidRegisterExtensions(() => {87if (!this._handled) {88// there is no extension point registered for notebook content provider89// clear the memento and cache90this._clear();91mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = [];92this._memento.saveMemento();93}94}));9596notebooksExtensionPoint.setHandler(extensions => this._setupHandler(extensions));97}9899override dispose(): void {100this._clear();101super.dispose();102}103104private _setupHandler(extensions: readonly IExtensionPointUser<INotebookEditorContribution[]>[]) {105this._handled = true;106const builtins: NotebookProviderInfo[] = [...this._contributedEditors.values()].filter(info => !info.extension);107this._clear();108109const builtinProvidersFromCache: Map<string, IDisposable> = new Map();110builtins.forEach(builtin => {111builtinProvidersFromCache.set(builtin.id, this.add(builtin));112});113114for (const extension of extensions) {115for (const notebookContribution of extension.value) {116117if (!notebookContribution.type) {118extension.collector.error(`Notebook does not specify type-property`);119continue;120}121122const existing = this.get(notebookContribution.type);123124if (existing) {125if (!existing.extension && extension.description.isBuiltin && builtins.find(builtin => builtin.id === notebookContribution.type)) {126// we are registering an extension which is using the same view type which is already cached127builtinProvidersFromCache.get(notebookContribution.type)?.dispose();128} else {129extension.collector.error(`Notebook type '${notebookContribution.type}' already used`);130continue;131}132}133134this.add(new NotebookProviderInfo({135extension: extension.description.identifier,136id: notebookContribution.type,137displayName: notebookContribution.displayName,138selectors: notebookContribution.selector || [],139priority: this._convertPriority(notebookContribution.priority),140providerDisplayName: extension.description.displayName ?? extension.description.identifier.value,141}));142}143}144145const mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE);146mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values());147this._memento.saveMemento();148}149150clearEditorCache() {151const mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE);152mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = [];153this._memento.saveMemento();154}155156private _convertPriority(priority?: string) {157if (!priority) {158return RegisteredEditorPriority.default;159}160161if (priority === NotebookEditorPriority.default) {162return RegisteredEditorPriority.default;163}164165return RegisteredEditorPriority.option;166167}168169private _registerContributionPoint(notebookProviderInfo: NotebookProviderInfo): IDisposable {170171const disposables = new DisposableStore();172173for (const selector of notebookProviderInfo.selectors) {174const globPattern = (selector as INotebookExclusiveDocumentFilter).include || selector as glob.IRelativePattern | string;175const notebookEditorInfo: RegisteredEditorInfo = {176id: notebookProviderInfo.id,177label: notebookProviderInfo.displayName,178detail: notebookProviderInfo.providerDisplayName,179priority: notebookProviderInfo.priority,180};181const notebookEditorOptions = {182canHandleDiff: () => !!this._configurationService.getValue(NotebookSetting.textDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized(),183canSupportResource: (resource: URI) => {184if (resource.scheme === Schemas.vscodeNotebookCellOutput) {185const params = new URLSearchParams(resource.query);186return params.get('openIn') === 'notebook';187}188return resource.scheme === Schemas.untitled || resource.scheme === Schemas.vscodeNotebookCell || this._fileService.hasProvider(resource);189}190};191const notebookEditorInputFactory: EditorInputFactoryFunction = async ({ resource, options }) => {192let data;193if (resource.scheme === Schemas.vscodeNotebookCellOutput) {194const outputUriData = CellUri.parseCellOutputUri(resource);195if (!outputUriData || !outputUriData.notebook || outputUriData.cellHandle === undefined) {196throw new Error('Invalid cell output uri');197}198199data = {200notebook: outputUriData.notebook,201handle: outputUriData.cellHandle202};203204} else {205data = CellUri.parse(resource);206}207208let notebookUri: URI;209210let cellOptions: IResourceEditorInput | undefined;211212if (data) {213// resource is a notebook cell214notebookUri = this.uriIdentService.asCanonicalUri(data.notebook);215cellOptions = { resource, options };216} else {217notebookUri = this.uriIdentService.asCanonicalUri(resource);218}219220if (!cellOptions) {221cellOptions = (options as INotebookEditorOptions | undefined)?.cellOptions;222}223224let notebookOptions: INotebookEditorOptions;225226if (resource.scheme === Schemas.vscodeNotebookCellOutput) {227if (data?.handle === undefined || !data?.notebook) {228throw new Error('Invalid cell handle');229}230231const cellUri = CellUri.generate(data.notebook, data.handle);232233cellOptions = { resource: cellUri, options };234235const cellIndex = await this._notebookEditorModelResolverService.resolve(notebookUri)236.then(model => model.object.notebook.cells.findIndex(cell => cell.handle === data?.handle))237.then(index => index >= 0 ? index : 0);238239const cellIndexesToRanges: ICellRange[] = [{ start: cellIndex, end: cellIndex + 1 }];240241notebookOptions = {242...options,243cellOptions,244viewState: undefined,245cellSelections: cellIndexesToRanges246};247} else {248notebookOptions = {249...options,250cellOptions,251viewState: undefined,252};253}254const preferredResourceParam = cellOptions?.resource;255const editor = NotebookEditorInput.getOrCreate(this._instantiationService, notebookUri, preferredResourceParam, notebookProviderInfo.id);256return { editor, options: notebookOptions };257};258259const notebookUntitledEditorFactory: UntitledEditorInputFactoryFunction = async ({ resource, options }) => {260const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: resource }, notebookProviderInfo.id);261262// untitled notebooks are disposed when they get saved. we should not hold a reference263// to such a disposed notebook and therefore dispose the reference as well264Event.once(ref.object.notebook.onWillDispose)(() => {265ref.dispose();266});267268return { editor: NotebookEditorInput.getOrCreate(this._instantiationService, ref.object.resource, undefined, notebookProviderInfo.id), options };269};270const notebookDiffEditorInputFactory: DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEditorInput, group: IEditorGroup) => {271const { modified, original, label, description } = diffEditorInput;272273if (this._configurationService.getValue('notebook.experimental.enableNewDiffEditor')) {274return { editor: NotebookMultiDiffEditorInput.create(this._instantiationService, modified.resource!, label, description, original.resource!, notebookProviderInfo.id) };275}276return { editor: NotebookDiffEditorInput.create(this._instantiationService, modified.resource!, label, description, original.resource!, notebookProviderInfo.id) };277};278const mergeEditorInputFactory: MergeEditorInputFactoryFunction = (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => {279return {280editor: this._instantiationService.createInstance(281MergeEditorInput,282mergeEditor.base.resource,283{284uri: mergeEditor.input1.resource,285title: mergeEditor.input1.label ?? basename(mergeEditor.input1.resource),286description: mergeEditor.input1.description ?? '',287detail: mergeEditor.input1.detail288},289{290uri: mergeEditor.input2.resource,291title: mergeEditor.input2.label ?? basename(mergeEditor.input2.resource),292description: mergeEditor.input2.description ?? '',293detail: mergeEditor.input2.detail294},295mergeEditor.result.resource296)297};298};299300const notebookFactoryObject: EditorInputFactoryObject = {301createEditorInput: notebookEditorInputFactory,302createDiffEditorInput: notebookDiffEditorInputFactory,303createUntitledEditorInput: notebookUntitledEditorFactory,304createMergeEditorInput: mergeEditorInputFactory305};306const notebookCellFactoryObject: EditorInputFactoryObject = {307createEditorInput: notebookEditorInputFactory,308createDiffEditorInput: notebookDiffEditorInputFactory,309};310311// TODO @lramos15 find a better way to toggle handling diff editors than needing these listeners for every registration312// This is a lot of event listeners especially if there are many notebooks313disposables.add(this._configurationService.onDidChangeConfiguration(e => {314if (e.affectsConfiguration(NotebookSetting.textDiffEditorPreview)) {315const canHandleDiff = !!this._configurationService.getValue(NotebookSetting.textDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized();316if (canHandleDiff) {317notebookFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory;318notebookCellFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory;319} else {320notebookFactoryObject.createDiffEditorInput = undefined;321notebookCellFactoryObject.createDiffEditorInput = undefined;322}323}324}));325326disposables.add(this._accessibilityService.onDidChangeScreenReaderOptimized(() => {327const canHandleDiff = !!this._configurationService.getValue(NotebookSetting.textDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized();328if (canHandleDiff) {329notebookFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory;330notebookCellFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory;331} else {332notebookFactoryObject.createDiffEditorInput = undefined;333notebookCellFactoryObject.createDiffEditorInput = undefined;334}335}));336337// Register the notebook editor338disposables.add(this._editorResolverService.registerEditor(339globPattern,340notebookEditorInfo,341notebookEditorOptions,342notebookFactoryObject,343));344// Then register the schema handler as exclusive for that notebook345disposables.add(this._editorResolverService.registerEditor(346`${Schemas.vscodeNotebookCell}:/**/${globPattern}`,347{ ...notebookEditorInfo, priority: RegisteredEditorPriority.exclusive },348notebookEditorOptions,349notebookCellFactoryObject350));351}352353return disposables;354}355356357private _clear(): void {358this._contributedEditors.clear();359this._contributedEditorDisposables.clear();360}361362get(viewType: string): NotebookProviderInfo | undefined {363return this._contributedEditors.get(viewType);364}365366add(info: NotebookProviderInfo, saveMemento = true): IDisposable {367if (this._contributedEditors.has(info.id)) {368throw new Error(`notebook type '${info.id}' ALREADY EXISTS`);369}370this._contributedEditors.set(info.id, info);371let editorRegistration: IDisposable | undefined;372373// built-in notebook providers contribute their own editors374if (info.extension) {375editorRegistration = this._registerContributionPoint(info);376this._contributedEditorDisposables.add(editorRegistration);377}378379if (saveMemento) {380const mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE);381mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values());382this._memento.saveMemento();383}384385return this._register(toDisposable(() => {386const mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE);387mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values());388this._memento.saveMemento();389editorRegistration?.dispose();390this._contributedEditors.delete(info.id);391}));392}393394getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] {395const result: NotebookProviderInfo[] = [];396for (const info of this._contributedEditors.values()) {397if (info.matches(resource)) {398result.push(info);399}400}401if (result.length === 0 && resource.scheme === Schemas.untitled) {402// untitled resource and no path-specific match => all providers apply403return Array.from(this._contributedEditors.values());404}405return result;406}407408[Symbol.iterator](): Iterator<NotebookProviderInfo> {409return this._contributedEditors.values();410}411}412413export class NotebookOutputRendererInfoStore {414private readonly contributedRenderers = new Map</* rendererId */ string, NotebookOutputRendererInfo>();415private readonly preferredMimetypeMemento: Memento;416private readonly preferredMimetype = new Lazy<{ [notebookType: string]: { [mimeType: string]: /* rendererId */ string } }>(417() => this.preferredMimetypeMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE));418419constructor(420@IStorageService storageService: IStorageService,421) {422this.preferredMimetypeMemento = new Memento('workbench.editor.notebook.preferredRenderer2', storageService);423}424425clear() {426this.contributedRenderers.clear();427}428429get(rendererId: string): NotebookOutputRendererInfo | undefined {430return this.contributedRenderers.get(rendererId);431}432433getAll(): NotebookOutputRendererInfo[] {434return Array.from(this.contributedRenderers.values());435}436437add(info: NotebookOutputRendererInfo): void {438if (this.contributedRenderers.has(info.id)) {439return;440}441this.contributedRenderers.set(info.id, info);442}443444/** Update and remember the preferred renderer for the given mimetype in this workspace */445setPreferred(notebookProviderInfo: NotebookProviderInfo, mimeType: string, rendererId: string) {446const mementoObj = this.preferredMimetype.value;447const forNotebook = mementoObj[notebookProviderInfo.id];448if (forNotebook) {449forNotebook[mimeType] = rendererId;450} else {451mementoObj[notebookProviderInfo.id] = { [mimeType]: rendererId };452}453454this.preferredMimetypeMemento.saveMemento();455}456457findBestRenderers(notebookProviderInfo: NotebookProviderInfo | undefined, mimeType: string, kernelProvides: readonly string[] | undefined): IOrderedMimeType[] {458459const enum ReuseOrder {460PreviouslySelected = 1 << 8,461SameExtensionAsNotebook = 2 << 8,462OtherRenderer = 3 << 8,463BuiltIn = 4 << 8,464}465466const preferred = notebookProviderInfo && this.preferredMimetype.value[notebookProviderInfo.id]?.[mimeType];467const notebookExtId = notebookProviderInfo?.extension?.value;468const notebookId = notebookProviderInfo?.id;469const renderers: { ordered: IOrderedMimeType; score: number }[] = Array.from(this.contributedRenderers.values())470.map(renderer => {471const ownScore = kernelProvides === undefined472? renderer.matchesWithoutKernel(mimeType)473: renderer.matches(mimeType, kernelProvides);474475if (ownScore === NotebookRendererMatch.Never) {476return undefined;477}478479const rendererExtId = renderer.extensionId.value;480const reuseScore = preferred === renderer.id481? ReuseOrder.PreviouslySelected482: rendererExtId === notebookExtId || RENDERER_EQUIVALENT_EXTENSIONS.get(rendererExtId)?.has(notebookId!)483? ReuseOrder.SameExtensionAsNotebook484: renderer.isBuiltin ? ReuseOrder.BuiltIn : ReuseOrder.OtherRenderer;485return {486ordered: { mimeType, rendererId: renderer.id, isTrusted: true },487score: reuseScore | ownScore,488};489}).filter(isDefined);490491if (renderers.length === 0) {492return [{ mimeType, rendererId: RENDERER_NOT_AVAILABLE, isTrusted: true }];493}494495return renderers.sort((a, b) => a.score - b.score).map(r => r.ordered);496}497}498499class ModelData implements IDisposable, INotebookDocument {500private readonly _modelEventListeners = new DisposableStore();501get uri() { return this.model.uri; }502503constructor(504readonly model: NotebookTextModel,505onWillDispose: (model: INotebookTextModel) => void506) {507this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model)));508}509510getCellIndex(cellUri: URI): number | undefined {511return this.model.cells.findIndex(cell => isEqual(cell.uri, cellUri));512}513514dispose(): void {515this._modelEventListeners.dispose();516}517}518519export class NotebookService extends Disposable implements INotebookService {520521declare readonly _serviceBrand: undefined;522private static _storageNotebookViewTypeProvider = 'notebook.viewTypeProvider';523private readonly _memento: Memento;524private readonly _viewTypeCache: MementoObject;525526private readonly _notebookProviders;527private _notebookProviderInfoStore: NotebookProviderInfoStore | undefined;528private get notebookProviderInfoStore(): NotebookProviderInfoStore {529if (!this._notebookProviderInfoStore) {530this._notebookProviderInfoStore = this._register(this._instantiationService.createInstance(NotebookProviderInfoStore));531}532533return this._notebookProviderInfoStore;534}535private readonly _notebookRenderersInfoStore;536private readonly _onDidChangeOutputRenderers;537readonly onDidChangeOutputRenderers;538539private readonly _notebookStaticPreloadInfoStore;540541private readonly _models;542543private readonly _onWillAddNotebookDocument;544private readonly _onDidAddNotebookDocument;545private readonly _onWillRemoveNotebookDocument;546private readonly _onDidRemoveNotebookDocument;547548readonly onWillAddNotebookDocument;549readonly onDidAddNotebookDocument;550readonly onDidRemoveNotebookDocument;551readonly onWillRemoveNotebookDocument;552553private readonly _onAddViewType;554readonly onAddViewType;555556private readonly _onWillRemoveViewType;557readonly onWillRemoveViewType;558559private readonly _onDidChangeEditorTypes;560onDidChangeEditorTypes: Event<void>;561562private _cutItems: NotebookCellTextModel[] | undefined;563private _lastClipboardIsCopy: boolean;564565private _displayOrder!: MimeTypeDisplayOrder;566567constructor(568@IExtensionService private readonly _extensionService: IExtensionService,569@IConfigurationService private readonly _configurationService: IConfigurationService,570@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,571@IInstantiationService private readonly _instantiationService: IInstantiationService,572@IStorageService private readonly _storageService: IStorageService,573@INotebookDocumentService private readonly _notebookDocumentService: INotebookDocumentService574) {575super();576this._notebookProviders = new Map<string, SimpleNotebookProviderInfo>();577this._notebookProviderInfoStore = undefined;578this._notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore);579this._onDidChangeOutputRenderers = this._register(new Emitter<void>());580this.onDidChangeOutputRenderers = this._onDidChangeOutputRenderers.event;581this._notebookStaticPreloadInfoStore = new Set<NotebookStaticPreloadInfo>();582this._models = new ResourceMap<ModelData>();583this._onWillAddNotebookDocument = this._register(new Emitter<NotebookTextModel>());584this._onDidAddNotebookDocument = this._register(new Emitter<NotebookTextModel>());585this._onWillRemoveNotebookDocument = this._register(new Emitter<NotebookTextModel>());586this._onDidRemoveNotebookDocument = this._register(new Emitter<NotebookTextModel>());587this.onWillAddNotebookDocument = this._onWillAddNotebookDocument.event;588this.onDidAddNotebookDocument = this._onDidAddNotebookDocument.event;589this.onDidRemoveNotebookDocument = this._onDidRemoveNotebookDocument.event;590this.onWillRemoveNotebookDocument = this._onWillRemoveNotebookDocument.event;591this._onAddViewType = this._register(new Emitter<string>());592this.onAddViewType = this._onAddViewType.event;593this._onWillRemoveViewType = this._register(new Emitter<string>());594this.onWillRemoveViewType = this._onWillRemoveViewType.event;595this._onDidChangeEditorTypes = this._register(new Emitter<void>());596this.onDidChangeEditorTypes = this._onDidChangeEditorTypes.event;597this._lastClipboardIsCopy = true;598599notebookRendererExtensionPoint.setHandler((renderers) => {600this._notebookRenderersInfoStore.clear();601602for (const extension of renderers) {603for (const notebookContribution of extension.value) {604if (!notebookContribution.entrypoint) { // avoid crashing605extension.collector.error(`Notebook renderer does not specify entry point`);606continue;607}608609const id = notebookContribution.id;610if (!id) {611extension.collector.error(`Notebook renderer does not specify id-property`);612continue;613}614615this._notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({616id,617extension: extension.description,618entrypoint: notebookContribution.entrypoint,619displayName: notebookContribution.displayName,620mimeTypes: notebookContribution.mimeTypes || [],621dependencies: notebookContribution.dependencies,622optionalDependencies: notebookContribution.optionalDependencies,623requiresMessaging: notebookContribution.requiresMessaging,624}));625}626}627628this._onDidChangeOutputRenderers.fire();629});630631notebookPreloadExtensionPoint.setHandler(extensions => {632this._notebookStaticPreloadInfoStore.clear();633634for (const extension of extensions) {635if (!isProposedApiEnabled(extension.description, 'contribNotebookStaticPreloads')) {636continue;637}638639for (const notebookContribution of extension.value) {640if (!notebookContribution.entrypoint) { // avoid crashing641extension.collector.error(`Notebook preload does not specify entry point`);642continue;643}644645const type = notebookContribution.type;646if (!type) {647extension.collector.error(`Notebook preload does not specify type-property`);648continue;649}650651this._notebookStaticPreloadInfoStore.add(new NotebookStaticPreloadInfo({652type,653extension: extension.description,654entrypoint: notebookContribution.entrypoint,655localResourceRoots: notebookContribution.localResourceRoots ?? [],656}));657}658}659});660661const updateOrder = () => {662this._displayOrder = new MimeTypeDisplayOrder(663this._configurationService.getValue<string[]>(NotebookSetting.displayOrder) || [],664this._accessibilityService.isScreenReaderOptimized()665? ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER666: NOTEBOOK_DISPLAY_ORDER,667);668};669670updateOrder();671672this._register(this._configurationService.onDidChangeConfiguration(e => {673if (e.affectsConfiguration(NotebookSetting.displayOrder)) {674updateOrder();675}676}));677678this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => {679updateOrder();680}));681682this._memento = new Memento(NotebookService._storageNotebookViewTypeProvider, this._storageService);683this._viewTypeCache = this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);684}685686687getEditorTypes(): IEditorType[] {688return [...this.notebookProviderInfoStore].map(info => ({689id: info.id,690displayName: info.displayName,691providerDisplayName: info.providerDisplayName692}));693}694695clearEditorCache(): void {696this.notebookProviderInfoStore.clearEditorCache();697}698699private _postDocumentOpenActivation(viewType: string) {700// send out activations on notebook text model creation701this._extensionService.activateByEvent(`onNotebook:${viewType}`);702this._extensionService.activateByEvent(`onNotebook:*`);703}704705async canResolve(viewType: string): Promise<boolean> {706if (this._notebookProviders.has(viewType)) {707return true;708}709710await this._extensionService.whenInstalledExtensionsRegistered();711await this._extensionService.activateByEvent(`onNotebookSerializer:${viewType}`);712713return this._notebookProviders.has(viewType);714}715716registerContributedNotebookType(viewType: string, data: INotebookContributionData): IDisposable {717718const info = new NotebookProviderInfo({719extension: data.extension,720id: viewType,721displayName: data.displayName,722providerDisplayName: data.providerDisplayName,723priority: data.priority || RegisteredEditorPriority.default,724selectors: []725});726727info.update({ selectors: data.filenamePattern });728729const reg = this.notebookProviderInfoStore.add(info);730this._onDidChangeEditorTypes.fire();731732return toDisposable(() => {733reg.dispose();734this._onDidChangeEditorTypes.fire();735});736}737738private _registerProviderData(viewType: string, data: SimpleNotebookProviderInfo): IDisposable {739if (this._notebookProviders.has(viewType)) {740throw new Error(`notebook provider for viewtype '${viewType}' already exists`);741}742this._notebookProviders.set(viewType, data);743this._onAddViewType.fire(viewType);744return toDisposable(() => {745this._onWillRemoveViewType.fire(viewType);746this._notebookProviders.delete(viewType);747});748}749750registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable {751this.notebookProviderInfoStore.get(viewType)?.update({ options: serializer.options });752this._viewTypeCache[viewType] = extensionData.id.value;753this._persistMementos();754return this._registerProviderData(viewType, new SimpleNotebookProviderInfo(viewType, serializer, extensionData));755}756757async withNotebookDataProvider(viewType: string): Promise<SimpleNotebookProviderInfo> {758const selected = this.notebookProviderInfoStore.get(viewType);759if (!selected) {760const knownProvider = this.getViewTypeProvider(viewType);761762const actions = knownProvider ? [763toAction({764id: 'workbench.notebook.action.installMissingViewType', label: localize('notebookOpenInstallMissingViewType', "Install extension for '{0}'", viewType), run: async () => {765await this._instantiationService.createInstance(InstallRecommendedExtensionAction, knownProvider).run();766}767})768] : [];769770throw createErrorWithActions(`UNKNOWN notebook type '${viewType}'`, actions);771}772await this.canResolve(selected.id);773const result = this._notebookProviders.get(selected.id);774if (!result) {775throw new Error(`NO provider registered for view type: '${selected.id}'`);776}777return result;778}779780tryGetDataProviderSync(viewType: string): SimpleNotebookProviderInfo | undefined {781const selected = this.notebookProviderInfoStore.get(viewType);782if (!selected) {783return undefined;784}785return this._notebookProviders.get(selected.id);786}787788789private _persistMementos(): void {790this._memento.saveMemento();791}792793getViewTypeProvider(viewType: string): string | undefined {794return this._viewTypeCache[viewType];795}796797getRendererInfo(rendererId: string): INotebookRendererInfo | undefined {798return this._notebookRenderersInfoStore.get(rendererId);799}800801updateMimePreferredRenderer(viewType: string, mimeType: string, rendererId: string, otherMimetypes: readonly string[]): void {802const info = this.notebookProviderInfoStore.get(viewType);803if (info) {804this._notebookRenderersInfoStore.setPreferred(info, mimeType, rendererId);805}806807this._displayOrder.prioritize(mimeType, otherMimetypes);808}809810saveMimeDisplayOrder(target: ConfigurationTarget) {811this._configurationService.updateValue(NotebookSetting.displayOrder, this._displayOrder.toArray(), target);812}813814getRenderers(): INotebookRendererInfo[] {815return this._notebookRenderersInfoStore.getAll();816}817818*getStaticPreloads(viewType: string): Iterable<INotebookStaticPreloadInfo> {819for (const preload of this._notebookStaticPreloadInfoStore) {820if (preload.type === viewType) {821yield preload;822}823}824}825826// --- notebook documents: create, destory, retrieve, enumerate827828async createNotebookTextModel(viewType: string, uri: URI, stream?: VSBufferReadableStream): Promise<NotebookTextModel> {829if (this._models.has(uri)) {830throw new Error(`notebook for ${uri} already exists`);831}832833const info = await this.withNotebookDataProvider(viewType);834if (!(info instanceof SimpleNotebookProviderInfo)) {835throw new Error('CANNOT open file notebook with this provider');836}837838839const bytes = stream ? await streamToBuffer(stream) : VSBuffer.fromByteArray([]);840const data = await info.serializer.dataToNotebook(bytes);841842843const notebookModel = this._instantiationService.createInstance(NotebookTextModel, info.viewType, uri, data.cells, data.metadata, info.serializer.options);844const modelData = new ModelData(notebookModel, this._onWillDisposeDocument.bind(this));845this._models.set(uri, modelData);846this._notebookDocumentService.addNotebookDocument(modelData);847this._onWillAddNotebookDocument.fire(notebookModel);848this._onDidAddNotebookDocument.fire(notebookModel);849this._postDocumentOpenActivation(info.viewType);850return notebookModel;851}852853async createNotebookTextDocumentSnapshot(uri: URI, context: SnapshotContext, token: CancellationToken): Promise<VSBufferReadableStream> {854const model = this.getNotebookTextModel(uri);855856if (!model) {857throw new Error(`notebook for ${uri} doesn't exist`);858}859860const info = await this.withNotebookDataProvider(model.viewType);861862if (!(info instanceof SimpleNotebookProviderInfo)) {863throw new Error('CANNOT open file notebook with this provider');864}865866const serializer = info.serializer;867const outputSizeLimit = this._configurationService.getValue<number>(NotebookSetting.outputBackupSizeLimit) * 1024;868const data: NotebookData = model.createSnapshot({ context: context, outputSizeLimit: outputSizeLimit, transientOptions: serializer.options });869const indentAmount = model.metadata.indentAmount;870if (typeof indentAmount === 'string' && indentAmount) {871// This is required for ipynb serializer to preserve the whitespace in the notebook.872data.metadata.indentAmount = indentAmount;873}874const bytes = await serializer.notebookToData(data);875876if (token.isCancellationRequested) {877throw new CancellationError();878}879return bufferToStream(bytes);880}881882async restoreNotebookTextModelFromSnapshot(uri: URI, viewType: string, snapshot: VSBufferReadableStream): Promise<NotebookTextModel> {883const model = this.getNotebookTextModel(uri);884885if (!model) {886throw new Error(`notebook for ${uri} doesn't exist`);887}888889const info = await this.withNotebookDataProvider(model.viewType);890891if (!(info instanceof SimpleNotebookProviderInfo)) {892throw new Error('CANNOT open file notebook with this provider');893}894895const serializer = info.serializer;896897const bytes = await streamToBuffer(snapshot);898const data = await info.serializer.dataToNotebook(bytes);899model.restoreSnapshot(data, serializer.options);900901return model;902}903904getNotebookTextModel(uri: URI): NotebookTextModel | undefined {905return this._models.get(uri)?.model;906}907908getNotebookTextModels(): Iterable<NotebookTextModel> {909return Iterable.map(this._models.values(), data => data.model);910}911912listNotebookDocuments(): NotebookTextModel[] {913return [...this._models].map(e => e[1].model);914}915916private _onWillDisposeDocument(model: INotebookTextModel): void {917const modelData = this._models.get(model.uri);918if (modelData) {919this._onWillRemoveNotebookDocument.fire(modelData.model);920this._models.delete(model.uri);921this._notebookDocumentService.removeNotebookDocument(modelData);922modelData.dispose();923this._onDidRemoveNotebookDocument.fire(modelData.model);924}925}926927getOutputMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[] {928const sorted = this._displayOrder.sort(new Set<string>(output.outputs.map(op => op.mime)));929const notebookProviderInfo = this.notebookProviderInfoStore.get(textModel.viewType);930931return sorted932.flatMap(mimeType => this._notebookRenderersInfoStore.findBestRenderers(notebookProviderInfo, mimeType, kernelProvides))933.sort((a, b) => (a.rendererId === RENDERER_NOT_AVAILABLE ? 1 : 0) - (b.rendererId === RENDERER_NOT_AVAILABLE ? 1 : 0));934}935936getContributedNotebookTypes(resource?: URI): readonly NotebookProviderInfo[] {937if (resource) {938return this.notebookProviderInfoStore.getContributedNotebook(resource);939}940941return [...this.notebookProviderInfoStore];942}943944hasSupportedNotebooks(resource: URI): boolean {945if (this._models.has(resource)) {946// it might be untitled947return true;948}949950const contribution = this.notebookProviderInfoStore.getContributedNotebook(resource);951if (!contribution.length) {952return false;953}954return contribution.some(info => info.matches(resource) &&955(info.priority === RegisteredEditorPriority.default || info.priority === RegisteredEditorPriority.exclusive)956);957}958959getContributedNotebookType(viewType: string): NotebookProviderInfo | undefined {960return this.notebookProviderInfoStore.get(viewType);961}962963getNotebookProviderResourceRoots(): URI[] {964const ret: URI[] = [];965this._notebookProviders.forEach(val => {966if (val.extensionData.location) {967ret.push(URI.revive(val.extensionData.location));968}969});970971return ret;972}973974// --- copy & paste975976setToCopy(items: NotebookCellTextModel[], isCopy: boolean) {977this._cutItems = items;978this._lastClipboardIsCopy = isCopy;979}980981getToCopy(): { items: NotebookCellTextModel[]; isCopy: boolean } | undefined {982if (this._cutItems) {983return { items: this._cutItems, isCopy: this._lastClipboardIsCopy };984}985986return undefined;987}988989}990991992