Path: blob/main/src/vs/workbench/api/browser/mainThreadNotebook.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 { VSBuffer } from '../../../base/common/buffer.js';6import { CancellationToken } from '../../../base/common/cancellation.js';7import { Emitter } from '../../../base/common/event.js';8import { DisposableStore, dispose, IDisposable } from '../../../base/common/lifecycle.js';9import { StopWatch } from '../../../base/common/stopwatch.js';10import { assertType } from '../../../base/common/types.js';11import { URI } from '../../../base/common/uri.js';12import { CommandsRegistry } from '../../../platform/commands/common/commands.js';13import { ILogService } from '../../../platform/log/common/log.js';14import { NotebookDto } from './mainThreadNotebookDto.js';15import { INotebookCellStatusBarService } from '../../contrib/notebook/common/notebookCellStatusBarService.js';16import { INotebookCellStatusBarItemProvider, INotebookContributionData, INotebookExclusiveDocumentFilter, NotebookData, NotebookExtensionDescription, TransientOptions } from '../../contrib/notebook/common/notebookCommon.js';17import { INotebookService, SimpleNotebookProviderInfo } from '../../contrib/notebook/common/notebookService.js';18import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';19import { SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js';20import { ExtHostContext, ExtHostNotebookShape, MainContext, MainThreadNotebookShape } from '../common/extHost.protocol.js';21import { IRelativePattern } from '../../../base/common/glob.js';22import { revive } from '../../../base/common/marshalling.js';23import { INotebookFileMatchNoModel } from '../../contrib/search/common/searchNotebookHelpers.js';24import { NotebookPriorityInfo } from '../../contrib/search/common/search.js';25import { coalesce } from '../../../base/common/arrays.js';26import { FileOperationError } from '../../../platform/files/common/files.js';2728@extHostNamedCustomer(MainContext.MainThreadNotebook)29export class MainThreadNotebooks implements MainThreadNotebookShape {3031private readonly _disposables = new DisposableStore();3233private readonly _proxy: ExtHostNotebookShape;34private readonly _notebookSerializer = new Map<number, IDisposable>();35private readonly _notebookCellStatusBarRegistrations = new Map<number, IDisposable>();3637constructor(38extHostContext: IExtHostContext,39@INotebookService private readonly _notebookService: INotebookService,40@INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService,41@ILogService private readonly _logService: ILogService,42) {43this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);44}4546dispose(): void {47this._disposables.dispose();48dispose(this._notebookSerializer.values());49}5051$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions, data: INotebookContributionData | undefined): void {52const disposables = new DisposableStore();5354disposables.add(this._notebookService.registerNotebookSerializer(viewType, extension, {55options,56dataToNotebook: async (data: VSBuffer): Promise<NotebookData> => {57const sw = new StopWatch();58let result: NotebookData;59if (data.byteLength === 0 && viewType === 'interactive') {60// we don't want any starting cells for an empty interactive window.61result = NotebookDto.fromNotebookDataDto({ cells: [], metadata: {} });62} else {63const dto = await this._proxy.$dataToNotebook(handle, data, CancellationToken.None);64result = NotebookDto.fromNotebookDataDto(dto.value);65}66this._logService.trace(`[NotebookSerializer] dataToNotebook DONE after ${sw.elapsed()}ms`, {67viewType,68extensionId: extension.id.value,69});70return result;71},72notebookToData: (data: NotebookData): Promise<VSBuffer> => {73const sw = new StopWatch();74const result = this._proxy.$notebookToData(handle, new SerializableObjectWithBuffers(NotebookDto.toNotebookDataDto(data)), CancellationToken.None);75this._logService.trace(`[NotebookSerializer] notebookToData DONE after ${sw.elapsed()}`, {76viewType,77extensionId: extension.id.value,78});79return result;80},81save: async (uri, versionId, options, token) => {82const stat = await this._proxy.$saveNotebook(handle, uri, versionId, options, token);83if (isFileOperationError(stat)) {84throw new FileOperationError(stat.message, stat.fileOperationResult, stat.options);85}86return {87...stat,88children: undefined,89resource: uri90};91},92searchInNotebooks: async (textQuery, token, allPriorityInfo): Promise<{ results: INotebookFileMatchNoModel<URI>[]; limitHit: boolean }> => {93const contributedType = this._notebookService.getContributedNotebookType(viewType);94if (!contributedType) {95return { results: [], limitHit: false };96}97const fileNames = contributedType.selectors;9899const includes = fileNames.map((selector) => {100const globPattern = (selector as INotebookExclusiveDocumentFilter).include || selector as IRelativePattern | string;101return globPattern.toString();102});103104if (!includes.length) {105return {106results: [], limitHit: false107};108}109110const thisPriorityInfo = coalesce<NotebookPriorityInfo>([{ isFromSettings: false, filenamePatterns: includes }, ...allPriorityInfo.get(viewType) ?? []]);111const otherEditorsPriorityInfo = Array.from(allPriorityInfo.keys())112.flatMap(key => {113if (key !== viewType) {114return allPriorityInfo.get(key) ?? [];115}116return [];117});118119const searchComplete = await this._proxy.$searchInNotebooks(handle, textQuery, thisPriorityInfo, otherEditorsPriorityInfo, token);120const revivedResults: INotebookFileMatchNoModel<URI>[] = searchComplete.results.map(result => {121const resource = URI.revive(result.resource);122return {123resource,124cellResults: result.cellResults.map(e => revive(e))125};126});127return { results: revivedResults, limitHit: searchComplete.limitHit };128}129}));130131if (data) {132disposables.add(this._notebookService.registerContributedNotebookType(viewType, data));133}134this._notebookSerializer.set(handle, disposables);135136this._logService.trace('[NotebookSerializer] registered notebook serializer', {137viewType,138extensionId: extension.id.value,139});140}141142$unregisterNotebookSerializer(handle: number): void {143this._notebookSerializer.get(handle)?.dispose();144this._notebookSerializer.delete(handle);145}146147$emitCellStatusBarEvent(eventHandle: number): void {148const emitter = this._notebookCellStatusBarRegistrations.get(eventHandle);149if (emitter instanceof Emitter) {150emitter.fire(undefined);151}152}153154async $registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, viewType: string): Promise<void> {155const that = this;156const provider: INotebookCellStatusBarItemProvider = {157async provideCellStatusBarItems(uri: URI, index: number, token: CancellationToken) {158const result = await that._proxy.$provideNotebookCellStatusBarItems(handle, uri, index, token);159return {160items: result?.items ?? [],161dispose() {162if (result) {163that._proxy.$releaseNotebookCellStatusBarItems(result.cacheId);164}165}166};167},168viewType169};170171if (typeof eventHandle === 'number') {172const emitter = new Emitter<void>();173this._notebookCellStatusBarRegistrations.set(eventHandle, emitter);174provider.onDidChangeStatusBarItems = emitter.event;175}176177const disposable = this._cellStatusBarService.registerCellStatusBarItemProvider(provider);178this._notebookCellStatusBarRegistrations.set(handle, disposable);179}180181async $unregisterNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined): Promise<void> {182const unregisterThing = (handle: number) => {183const entry = this._notebookCellStatusBarRegistrations.get(handle);184if (entry) {185this._notebookCellStatusBarRegistrations.get(handle)?.dispose();186this._notebookCellStatusBarRegistrations.delete(handle);187}188};189unregisterThing(handle);190if (typeof eventHandle === 'number') {191unregisterThing(eventHandle);192}193}194}195196CommandsRegistry.registerCommand('_executeDataToNotebook', async (accessor, ...args) => {197198const [notebookType, bytes] = args;199assertType(typeof notebookType === 'string', 'string');200assertType(bytes instanceof VSBuffer, 'VSBuffer');201202const notebookService = accessor.get(INotebookService);203const info = await notebookService.withNotebookDataProvider(notebookType);204if (!(info instanceof SimpleNotebookProviderInfo)) {205return;206}207208const dto = await info.serializer.dataToNotebook(bytes);209return new SerializableObjectWithBuffers(NotebookDto.toNotebookDataDto(dto));210});211212CommandsRegistry.registerCommand('_executeNotebookToData', async (accessor, ...args) => {213214const [notebookType, dto] = args;215assertType(typeof notebookType === 'string', 'string');216assertType(typeof dto === 'object');217218const notebookService = accessor.get(INotebookService);219const info = await notebookService.withNotebookDataProvider(notebookType);220if (!(info instanceof SimpleNotebookProviderInfo)) {221return;222}223224const data = NotebookDto.fromNotebookDataDto(dto.value);225const bytes = await info.serializer.notebookToData(data);226return bytes;227});228229function isFileOperationError(error: any): error is FileOperationError {230return typeof error.fileOperationResult === 'number' && typeof error.message === 'string';231}232233234