diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5623fae8254..a647a562840 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1791,7 +1791,12 @@ declare module 'vscode' { * Controls if a meetadata property change will trigger notebook document content change and if it will be used in the diff editor * Default to false. If the content provider doesn't persisit a metadata property in the file document, it should be set to true. */ - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean } + transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; + + /** + * Not ready for production or development use yet. + */ + viewOptions?: { displayName: string; filenamePattern: GlobPattern | { include: GlobPattern; exclude: GlobPattern; }; exclusive?: boolean; }; } ): Disposable; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 0691d5d3cc0..728d07e9630 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -6,6 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; +import { IRelativePattern } from 'vs/base/common/glob'; import { combinedDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; @@ -18,7 +19,7 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, NotebookCellOutputsSplice, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, NotebookCellOutputsSplice, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -443,10 +444,15 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo // } } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise { + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { + transientOutputs: boolean; + transientMetadata: TransientMetadata; + viewOptions?: { displayName: string; filenamePattern: string | IRelativePattern | INotebookExclusiveDocumentFilter; exclusive: boolean; }; + }): Promise { const controller: IMainNotebookController = { supportBackup, options, + viewOptions: options.viewOptions, reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => { const data = await this._proxy.$resolveNotebookData(viewType, mainthreadTextModel.uri); mainthreadTextModel.updateLanguages(data.languages); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2252959b6cc..8fc8c4501cf 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -735,7 +735,11 @@ export enum NotebookEditorRevealType { export type INotebookCellStatusBarEntryDto = Dto; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { + transientOutputs: boolean; + transientMetadata: TransientMetadata; + viewOptions?: { displayName: string; filenamePattern: string | IRelativePattern | INotebookExclusiveDocumentFilter; exclusive: boolean; }; + }): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; $unregisterNotebookKernelProvider(handle: number): Promise; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 9be0944abe3..aaeb430bef7 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -304,6 +304,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN options?: { transientOutputs: boolean; transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; + viewOptions?: { displayName: string; filenamePattern: vscode.GlobPattern | { include: vscode.GlobPattern; exclude: vscode.GlobPattern }; exclusive?: boolean; }; } ): vscode.Disposable { @@ -332,7 +333,14 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const supportBackup = !!provider.backupNotebook; - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, { transientOutputs: options?.transientOutputs || false, transientMetadata: options?.transientMetadata || {} }); + const viewOptionsFilenamePattern = typeConverters.NotebookExclusiveDocumentPattern.from(options?.viewOptions?.filenamePattern); + console.warn(`Notebook content provider view options file name pattern is valid ${options?.viewOptions?.filenamePattern}`); + + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, { + transientOutputs: options?.transientOutputs || false, + transientMetadata: options?.transientMetadata || {}, + viewOptions: options?.viewOptions && viewOptionsFilenamePattern ? { displayName: options.viewOptions.displayName, filenamePattern: viewOptionsFilenamePattern, exclusive: options.viewOptions.exclusive || false } : undefined + }); return new extHostTypes.Disposable(() => { listener.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 00758180339..593022037f4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -344,7 +344,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri } const infos = this.notebookService.getContributedNotebookProviders(notebookUri); - let info = infos.find(info => !id || info.id === id); + let info = infos.find(info => (!id || info.id === id) && info.exclusive) || infos.find(info => !id || info.id === id); if (!info && id !== undefined) { info = this.notebookService.getContributedNotebookProvider(id); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index b7c97df0df7..00db1427e49 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -121,7 +121,9 @@ export class NotebookProviderInfoStore extends Disposable { providerExtensionId: extension.description.identifier.value, providerDescription: extension.description.description, providerDisplayName: extension.description.isBuiltin ? nls.localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, - providerExtensionLocation: extension.description.extensionLocation + providerExtensionLocation: extension.description.extensionLocation, + dynamicContribution: false, + exclusive: false })); } } @@ -175,6 +177,10 @@ export class NotebookProviderInfoStore extends Disposable { return; } this._contributedEditors.set(info.id, info); + + const mementoObject = this._memento.getMemento(StorageScope.GLOBAL); + mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); + this._memento.saveMemento(); } getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] { @@ -550,6 +556,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu if (!this._notebookProviders.has(viewType)) { await this._extensionService.whenInstalledExtensionsRegistered(); // notebook providers/kernels/renderers might use `*` as activation event. + // TODO, only activate by `*` if this._notebookProviders.get(viewType).dynamicContribution === true await this._extensionService.activateByEvent(`*`); // this awaits full activation of all matching extensions await this._extensionService.activateByEvent(`onNotebook:${viewType}`); @@ -562,6 +569,23 @@ export class NotebookService extends Disposable implements INotebookService, ICu registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable { this._notebookProviders.set(viewType, { extensionData, controller }); + + if (controller.viewOptions && !this.notebookProviderInfoStore.get(viewType)) { + // register this content provider to the static contribution, if it does not exist + this.notebookProviderInfoStore.add(new NotebookProviderInfo({ + displayName: controller.viewOptions.displayName, + id: viewType, + priority: NotebookEditorPriority.default, + selector: [{ filenamePattern: controller.viewOptions.filenamePattern }], + providerExtensionId: extensionData.id.value, + providerDescription: extensionData.description, + providerDisplayName: extensionData.id.value, + providerExtensionLocation: URI.revive(extensionData.location), + dynamicContribution: true, + exclusive: controller.viewOptions.exclusive + })); + } + this._onDidChangeViewTypes.fire(); return toDisposable(() => { this._notebookProviders.delete(viewType); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index ce10e2148a1..b48010c800d 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -778,7 +778,7 @@ export interface INotebookDocumentFilter { //TODO@rebornix test -function isDocumentExcludePattern(filenamePattern: string | glob.IRelativePattern | INotebookExclusiveDocumentFilter): filenamePattern is { include: string | glob.IRelativePattern; exclude: string | glob.IRelativePattern; } { +export function isDocumentExcludePattern(filenamePattern: string | glob.IRelativePattern | INotebookExclusiveDocumentFilter): filenamePattern is { include: string | glob.IRelativePattern; exclude: string | glob.IRelativePattern; } { const arg = filenamePattern as INotebookExclusiveDocumentFilter; if ((typeof arg.include === 'string' || glob.isRelativePattern(arg.include)) diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index fa256db854c..d68e4142faf 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -64,8 +64,15 @@ export class NotebookModelResolverService implements INotebookEditorModelResolve const existingViewType = this._notebookService.getNotebookTextModel(resource)?.viewType; if (!viewType) { - viewType = existingViewType ?? this._notebookService.getContributedNotebookProviders(resource)[0]?.id; + if (existingViewType) { + viewType = existingViewType; + } else { + const providers = this._notebookService.getContributedNotebookProviders(resource); + const exclusiveProvider = providers.find(provider => provider.exclusive); + viewType = exclusiveProvider?.id || providers[0]?.id; + } } + if (!viewType) { throw new Error(`Missing viewType for '${resource}'`); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index 3649b54141c..7465b9e1be5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -6,10 +6,10 @@ import * as glob from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { basename } from 'vs/base/common/path'; -import { NotebookEditorPriority } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExclusiveDocumentFilter, isDocumentExcludePattern, NotebookEditorPriority } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export interface NotebookSelector { - readonly filenamePattern?: string; + readonly filenamePattern?: string | glob.IRelativePattern | INotebookExclusiveDocumentFilter; readonly excludeFileNamePattern?: string; } @@ -22,6 +22,8 @@ export interface NotebookEditorDescriptor { readonly providerDescription?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; + readonly dynamicContribution: boolean; + readonly exclusive: boolean; } export class NotebookProviderInfo implements NotebookEditorDescriptor { @@ -35,6 +37,8 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { readonly providerDescription?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; + readonly dynamicContribution: boolean; + readonly exclusive: boolean; constructor(descriptor: NotebookEditorDescriptor) { this.id = descriptor.id; @@ -45,6 +49,8 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { this.providerDescription = descriptor.providerDescription; this.providerDisplayName = descriptor.providerDisplayName; this.providerExtensionLocation = descriptor.providerExtensionLocation; + this.dynamicContribution = descriptor.dynamicContribution; + this.exclusive = descriptor.exclusive; } matches(resource: URI): boolean { @@ -52,7 +58,11 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { } static selectorMatches(selector: NotebookSelector, resource: URI): boolean { - if (selector.filenamePattern) { + if (!selector.filenamePattern) { + return false; + } + + if (typeof selector.filenamePattern === 'string') { if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource.fsPath).toLowerCase())) { if (selector.excludeFileNamePattern) { if (glob.match(selector.excludeFileNamePattern.toLowerCase(), basename(resource.fsPath).toLowerCase())) { @@ -64,6 +74,19 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { return true; } } + + let filenamePattern = isDocumentExcludePattern(selector.filenamePattern) ? selector.filenamePattern.include : (selector.filenamePattern as string | glob.IRelativePattern); + let excludeFilenamePattern = isDocumentExcludePattern(selector.filenamePattern) ? selector.filenamePattern.exclude : undefined; + + if (glob.match(filenamePattern, basename(resource.fsPath).toLowerCase())) { + if (excludeFilenamePattern) { + if (glob.match(excludeFilenamePattern, basename(resource.fsPath).toLowerCase())) { + return false; + } + } + return true; + } + return false; } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index b05db598a5d..aa00ccd018d 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -10,19 +10,21 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { Event } from 'vs/base/common/event'; import { INotebookTextModel, INotebookRendererInfo, - IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions + IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; +import { IRelativePattern } from 'vs/base/common/glob'; export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { supportBackup: boolean; + viewOptions?: { displayName: string; filenamePattern: string | IRelativePattern | INotebookExclusiveDocumentFilter; exclusive: boolean; }; options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; resolveNotebookDocument(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions }>; reloadNotebook(mainthreadTextModel: NotebookTextModel): Promise;