diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 71f18271110..2a4a12c8897 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1841,6 +1841,7 @@ declare module 'vscode' { ): Disposable; export function createNotebookEditorDecorationType(options: NotebookDecorationRenderOptions): NotebookEditorDecorationType; + export function openNotebookDocument(uri: Uri, viewType?: string): Promise; export const onDidOpenNotebookDocument: Event; export const onDidCloseNotebookDocument: Event; export const onDidSaveNotebookDocument: Event; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 8795bfa3d09..93215c290b4 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -7,9 +7,10 @@ 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 { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; +import { IExtUri } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -19,9 +20,11 @@ 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, INotebookExclusiveDocumentFilter, 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, INotebookEditorModel, INotebookExclusiveDocumentFilter, NotebookCellOutputsSplice, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; @@ -142,6 +145,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo private _editorEventListenersMapping: Map = new Map(); private _documentEventListenersMapping: ResourceMap = new ResourceMap(); private readonly _cellStatusBarEntries: Map = new Map(); + private readonly _modelReferenceCollection: BoundModelReferenceCollection; constructor( extHostContext: IExtHostContext, @@ -152,9 +156,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo @ILogService private readonly logService: ILogService, @INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, + @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri); + this._register(this._modelReferenceCollection); this.registerListeners(); } @@ -709,4 +717,55 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this.cellStatusBarService.addEntry(statusBarEntry)); } } + + + async $tryOpenDocument(uriComponents: UriComponents, viewType?: string): Promise { + const uri = URI.revive(uriComponents); + const ref = await this._notebookModelResolverService.resolve(uri, viewType); + this._modelReferenceCollection.add(uri, ref); + + return uri; + } +} + + +export class BoundModelReferenceCollection { + + private _data = new Array<{ uri: URI, dispose(): void }>(); + + constructor( + private readonly _extUri: IExtUri, + private readonly _maxAge: number = 1000 * 60 * 3, + ) { + // + } + + dispose(): void { + this._data = dispose(this._data); + } + + remove(uri: URI): void { + for (const entry of [...this._data] /* copy array because dispose will modify it */) { + if (this._extUri.isEqualOrParent(entry.uri, uri)) { + entry.dispose(); + } + } + } + + add(uri: URI, ref: IReference): void { + let handle: any; + let entry: { uri: URI, dispose(): void }; + const dispose = () => { + const idx = this._data.indexOf(entry); + if (idx >= 0) { + ref.dispose(); + clearTimeout(handle); + this._data.splice(idx, 1); + } + }; + handle = setTimeout(dispose, this._maxAge); + entry = { uri, dispose }; + + this._data.push(entry); + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c9fdc2764b3..cf44f735dca 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -967,6 +967,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeNotebookEditorSelection: Event; onDidChangeNotebookEditorVisibleRanges: Event; }) = { + openNotebookDocument: (uriComponents, viewType) => { + checkProposedApiEnabled(extension); + return extHostNotebook.openNotebookDocument(uriComponents, viewType); + }, get onDidOpenNotebookDocument(): Event { checkProposedApiEnabled(extension); return extHostNotebook.onDidOpenNotebookDocument; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 842a1cc47bd..c13066d7be0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -755,6 +755,7 @@ export interface MainThreadNotebookShape extends IDisposable { $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise; $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; + $tryOpenDocument(uriComponents: UriComponents, viewType?: string): Promise; $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise; $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void; $removeNotebookEditorDecorationType(key: string): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 61f8bce094d..0846a2d55bb 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -24,6 +24,7 @@ import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument' import { ExtHostNotebookEditor } from './extHostNotebookEditor'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { IRelativePattern } from 'vs/base/common/glob'; +import { assertIsDefined } from 'vs/base/common/types'; class ExtHostWebviewCommWrapper extends Disposable { private readonly _onDidReceiveDocumentMessage = new Emitter(); @@ -386,6 +387,17 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return new NotebookEditorDecorationType(this._proxy, options); } + async openNotebookDocument(uriComponents: UriComponents, viewType?: string): Promise { + const cached = this._documents.get(URI.revive(uriComponents)); + if (cached) { + return Promise.resolve(cached.notebookDocument); + } + + await this._proxy.$tryOpenDocument(uriComponents, viewType); + const document = this._documents.get(URI.revive(uriComponents)); + return assertIsDefined(document?.notebookDocument); + } + private _withAdapter(handle: number, uri: UriComponents, callback: (adapter: ExtHostNotebookKernelProviderAdapter, document: ExtHostNotebookDocument) => Promise) { const document = this._documents.get(URI.revive(uri));